/** * DataCleaner (community edition) * 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.datacleaner.api; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.metamodel.util.LazyRef; import org.apache.metamodel.util.Ref; import org.apache.metamodel.util.SharedExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default implementation of AnalyzerResultFuture which uses a Ref to get the * actual result. * * @param <R> * the wrapped {@link AnalyzerResult} type. */ public class AnalyzerResultFutureImpl<R extends AnalyzerResult> implements AnalyzerResultFuture<R> { private static final Logger logger = LoggerFactory.getLogger(AnalyzerResultFutureImpl.class); private static final long serialVersionUID = 1L; private final transient CountDownLatch _countDownLatch; private final String _name; private transient volatile List<Listener<? super R>> _listeners; private volatile R _result; private volatile RuntimeException _error; /** * Constructs an {@link AnalyzerResultFutureImpl} * * @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 AnalyzerResultFutureImpl(final String name, final Ref<? extends R> resultRef) { _name = name; _countDownLatch = new CountDownLatch(1); _result = null; _error = null; SharedExecutorService.get().submit(() -> { try { _result = resultRef.get(); if (_result == null && resultRef instanceof LazyRef) { // TODO: workaround - reported as MM bug, remove when // fixed. throw new RuntimeException(((LazyRef<?>) resultRef).getError()); } onSuccess(); } catch (final RuntimeException e) { _error = e; onError(); } finally { _countDownLatch.countDown(); } }); } @Override public synchronized void addListener(final Listener<? super 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); } @Override public synchronized void removeListener(final Listener<R> listener) { if (_listeners == null) { return; } _listeners.remove(listener); } private synchronized void onSuccess() { if (_listeners == null) { return; } try { for (final Listener<? super R> listener : _listeners) { try { listener.onSuccess(_result); } catch (final Exception e) { logger.warn("Unexpected exception while informing listener of success: {}", listener, e); } } } catch (final 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<? super R> listener : _listeners) { try { listener.onError(_error); } catch (final Exception e) { logger.warn("Unexpected exception while informing listener on error: {}", listener, e); } } } catch (final Exception e) { logger.warn("Unexpected exception while iterating listeners on error", e); } finally { _listeners = null; } } @Override public boolean isReady() { if (_countDownLatch == null) { return true; } return _countDownLatch.getCount() == 0; } @Override public R get() { if (_countDownLatch != null) { try { boolean finished = false; int iteration = 0; final int minutesToWaitPerIteration = 2; while (!finished) { iteration++; finished = _countDownLatch.await(minutesToWaitPerIteration, TimeUnit.MINUTES); if (!finished) { logger.info( "Awaited completion for " + (iteration * minutesToWaitPerIteration) + " minutes..."); } } } catch (final InterruptedException e) { throw new IllegalStateException("Awaiting completion of AnalyzerResultFuture was interrupted!", e); } } 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(final ObjectOutputStream out) throws IOException { logger.info("Serialization requested, awaiting reference to load."); get(); out.defaultWriteObject(); logger.info("Serialization finished!"); } }