/*******************************************************************************
* Copyright (c) 2012 Association for Decentralized Information Management in
* Industry THTH ry.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.db.layer0.util;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.simantics.db.Session;
import org.simantics.db.exception.DatabaseException;
import org.simantics.db.procedure.Listener;
import org.simantics.db.request.Read;
/**
* A database listener that disposes itself based on a criterion evaluated using
* the request results received by the listener. If the results pass the
* criterion, the listener is disposed and the result is either accepted or
* discarded based on the evaluation.
*
*
* The reason for the existence of this class is that sometimes database
* requests always perform asynchronously, i.e. return empty results at first
* and later get updated to their final results. For example, a the results of a
* web request would take a while to be delivered. This listener makes it a bit
* easier to deal with these situations when results are needed synchronously.
*
*
* {@link #trySyncRequest(Session, Read, Criterion, long, TimeUnit)} provides a
* utility for executing a synchronous read request with this listener so that
* the implementation will wait for a criterion accepted result for the
* specified amount of time. If an accepted result is reached within the time
* limit, it will be returned. If the time limit is reached and no accepted
* result is attained, null
will be returned. If the request
* produces an exception, it will be thrown.
*
* @author Tuukka Lehtonen
*
* @param database request result type
*
* @see EvaluatingListener.Evaluation
* @see EvaluatingListener.Criterion
*/
public class EvaluatingListener implements Listener {
public static enum Evaluation {
/**
* Keep on listening to further results.
*/
IGNORE,
/**
* Dispose listener and discard the results.
*/
DISCARD,
/**
* Dispose listener and return the latest result.
*/
ACCEPT
}
/**
* An evaluable criterion for the result received by
* {@link Listener#execute(Object)} to tell whether to accept the result,
* wait for another result or to consider the listener disposed.
*
* @param the type of the result
*/
public static interface Criterion {
Evaluation evaluate(T result);
}
/**
* The criterion the listener evaluates. When it evaluates to
* {@value Evaluation#DISCARD}, this field is nullified and the listener is
* considered disposed.
*/
private volatile Criterion criterion;
private T result;
private Throwable exception;
private Semaphore wait = new Semaphore(0);
public EvaluatingListener(Criterion criterion) {
if (criterion == null)
throw new NullPointerException("null criterion");
this.criterion = criterion;
}
/**
* @param session
* @param request
* @param criterion
* @param timeout
* @param unit
* @return
* @throws InterruptedException
* @throws DatabaseException
*/
public static T trySyncRequest(Session session, Read request, Criterion criterion, long timeout, TimeUnit unit) throws InterruptedException, DatabaseException {
EvaluatingListener l = new EvaluatingListener(criterion);
session.asyncRequest(request, l);
l.tryWaitForResult(timeout, unit);
// Make sure the listener is disposed.
l.dispose();
l.throwPossibleException();
return l.getResult();
}
public T waitForResult() throws InterruptedException {
wait.acquire();
return getResult();
}
public boolean tryWaitForResult(long timeout, TimeUnit unit) throws InterruptedException {
return wait.tryAcquire(timeout, unit);
}
public T getResult() {
return result;
}
public Throwable getException() {
return exception;
}
public void throwPossibleException() throws DatabaseException {
if (exception != null) {
if (exception instanceof DatabaseException)
throw (DatabaseException) exception;
throw new DatabaseException(exception);
}
}
@Override
public void execute(T result) {
Criterion crit = criterion;
if (crit == null)
return;
EvaluatingListener.Evaluation e = crit.evaluate(result);
switch (e) {
case IGNORE:
ignored(result);
return;
case ACCEPT:
this.result = result;
try {
accepted(result);
} finally {
dispose();
wait.release();
}
return;
case DISCARD:
dispose();
wait.release();
return;
}
}
/**
* Override to process results that were ignored.
*
* @param result
*/
public void ignored(T result) {
}
/**
* Override this to immediately process an accepted result in the listener.
* This method is invoked before the listener is disposed
*
* @param result
*/
public void accepted(T result) {
}
@Override
public void exception(Throwable t) {
this.exception = t;
dispose();
wait.release();
}
private void dispose() {
this.criterion = null;
}
@Override
public boolean isDisposed() {
return criterion == null;
}
}