/**
* AnalyzerBeans
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.eobjects.analyzer.result;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.apache.metamodel.util.HasName;
import org.apache.metamodel.util.Ref;
import org.apache.metamodel.util.SharedExecutorService;
import org.eobjects.analyzer.beans.api.Analyzer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents an {@link AnalyzerResult} that is still being produced.
*
* Usually {@link AnalyzerResult}s are produced immediately by the
* {@link Analyzer#getResult()} method, but in cases where this may take a long
* time, an {@link Analyzer} can instead return a result of this type and
* thereby indicate that some process is still going on, but the rest of the job
* is ready to return.
*
* @param <R>
* the wrapped {@link AnalyzerResult} type.
*/
public class AnalyzerResultFuture<R extends AnalyzerResult> implements AnalyzerResult, HasName, Ref<R> {
/**
* Listener interface for objects that want to be notified when the wrapped
* {@link AnalyzerResult} is ready.
*
* @param <R>
*/
public static interface Listener<R extends AnalyzerResult> {
public void onSuccess(R result);
public void onError(RuntimeException error);
}
private static final Logger logger = LoggerFactory.getLogger(AnalyzerResultFuture.class);
private static final long serialVersionUID = 1L;
private transient final CountDownLatch _countDownLatch;
private transient List<Listener<R>> _listeners;
private final String _name;
private R _result;
private RuntimeException _error;
/**
* Constructs an {@link AnalyzerResultFuture}
*
* @param name
* a name/label to use for presenting and distinguishing this
* result from others.
* @param resultRef
* a reference for the result being processed.
*/
public AnalyzerResultFuture(String name, final Ref<? extends R> resultRef) {
_name = name;
_countDownLatch = new CountDownLatch(1);
_result = null;
_error = null;
SharedExecutorService.get().submit(new Runnable() {
@Override
public void run() {
try {
_result = resultRef.get();
onSuccess();
} catch (RuntimeException e) {
_error = e;
onError();
} finally {
_countDownLatch.countDown();
}
}
});
}
/**
* Adds a {@link Listener} to this {@link AnalyzerResultFuture}.
*
* @param listener
*/
public synchronized void addListener(Listener<R> listener) {
// it might be we add a listener AFTER the result is actually produced,
// in which case we simply inform the listener immediately.
if (isReady()) {
if (_error != null) {
listener.onError(_error);
} else {
listener.onSuccess(_result);
}
return;
}
if (_listeners == null) {
_listeners = new LinkedList<>();
}
_listeners.add(listener);
}
/**
* Removes a {@link Listener} from this {@link AnalyzerResultFuture}.
*
* @param listener
*/
public synchronized void removeListener(Listener<R> listener) {
if (_listeners == null) {
return;
}
_listeners.remove(listener);
}
private synchronized void onSuccess() {
if (_listeners == null) {
return;
}
try {
for (final Listener<R> listener : _listeners) {
try {
listener.onSuccess(_result);
} catch (Exception e) {
logger.warn("Unexpected exception while informing listener of success: {}", listener, e);
}
}
} catch (Exception e) {
logger.warn("Unexpected exception while iterating listeners on success", e);
} finally {
_listeners = null;
}
}
private synchronized void onError() {
if (_listeners == null) {
return;
}
try {
for (final Listener<R> listener : _listeners) {
try {
listener.onError(_error);
} catch (Exception e) {
logger.warn("Unexpected exception while informing listener on error: {}", listener, e);
}
}
} catch (Exception e) {
logger.warn("Unexpected exception while iterating listeners on error", e);
} finally {
_listeners = null;
}
}
/**
* Determines if the wrapped {@link AnalyzerResult} is ready or if
* processing is still going on to produce it.
*
* Once ready, call {@link #get()} to get it.
*
* @return true if the wrapped {@link AnalyzerResult} is ready or false if
* it is not.
*/
public boolean isReady() {
if (_countDownLatch == null) {
return true;
}
return _countDownLatch.getCount() == 0;
}
@Override
public R get() {
if (_countDownLatch != null) {
try {
_countDownLatch.await();
} catch (InterruptedException e) {
// do nothing
}
}
if (_error != null) {
throw _error;
}
return _result;
}
@Override
public String toString() {
return "AnalyzerResultFuture[" + _name + "]";
}
@Override
public String getName() {
return _name;
}
/**
* Method invoked when serialization takes place. Makes sure that we await
* the loading of the result reference before writing any data.
*
* @param out
* @throws IOException
*/
private void writeObject(ObjectOutputStream out) throws IOException {
logger.info("Serialization requested, awaiting reference to load.");
get();
out.defaultWriteObject();
logger.info("Serialization finished!");
}
}