/******************************************************************************* * 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; } }