/** * 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.test.full.scenarios; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import org.datacleaner.api.AnalyzerResult; import org.datacleaner.api.AnalyzerResultFuture; import org.datacleaner.api.AnalyzerResultFuture.Listener; import org.datacleaner.configuration.DataCleanerConfiguration; import org.datacleaner.configuration.DataCleanerConfigurationImpl; import org.datacleaner.configuration.DataCleanerEnvironment; import org.datacleaner.configuration.DataCleanerEnvironmentImpl; import org.datacleaner.connection.Datastore; import org.datacleaner.data.MetaModelInputColumn; import org.datacleaner.job.AnalysisJob; import org.datacleaner.job.ComponentJob; import org.datacleaner.job.builder.AnalysisJobBuilder; import org.datacleaner.job.builder.AnalyzerComponentBuilder; import org.datacleaner.job.concurrent.TaskRunner; import org.datacleaner.job.runner.AnalysisJobMetrics; import org.datacleaner.job.runner.AnalysisListener; import org.datacleaner.job.runner.AnalysisListenerAdaptor; import org.datacleaner.job.runner.AnalysisResultFuture; import org.datacleaner.job.runner.AnalysisRunner; import org.datacleaner.job.runner.AnalysisRunnerImpl; import org.datacleaner.job.runner.RowProcessingMetrics; import org.datacleaner.test.MockAnalyzer; import org.datacleaner.test.MockFutureAnalyzer; import org.datacleaner.test.TestEnvironment; import org.datacleaner.test.TestHelper; import junit.framework.TestCase; /** * A test that verifies that the order of listener methods in * {@link AnalysisListener} is correct also when one of the components is * producing an {@link AnalyzerResultFuture}. A particular concern in this * scenario is that the * {@link AnalysisListener#jobSuccess(org.datacleaner.job.AnalysisJob, org.datacleaner.job.runner.AnalysisJobMetrics)} * method should be invoked only after the future result is available. */ public class AnalyzerResultFutureAndAnalysisListenerTest extends TestCase { private final Datastore datastore = TestHelper.createSampleDatabaseDatastore("orderdb"); private final TaskRunner taskRunner = TestEnvironment.getMultiThreadedTaskRunner(); private final DataCleanerEnvironment environment = new DataCleanerEnvironmentImpl().withTaskRunner(taskRunner); private final DataCleanerConfiguration configuration = new DataCleanerConfigurationImpl().withEnvironment(environment).withDatastores(datastore); public void testScenario() throws Exception { final AnalysisJob job; // build job try (AnalysisJobBuilder ajb = new AnalysisJobBuilder(configuration)) { ajb.setDatastore(datastore); ajb.addSourceColumns("CUSTOMERS.CUSTOMERNAME", "CUSTOMERS.PHONE"); final List<MetaModelInputColumn> sourceColumns = ajb.getSourceColumns(); final AnalyzerComponentBuilder<MockAnalyzer> analyzer1 = ajb.addAnalyzer(MockAnalyzer.class); analyzer1.setName("normal analyzer"); analyzer1.addInputColumn(sourceColumns.get(0)); final AnalyzerComponentBuilder<MockFutureAnalyzer> analyzer2 = ajb.addAnalyzer(MockFutureAnalyzer.class); analyzer2.setName("analyzer with future result"); analyzer2.addInputColumn(sourceColumns.get(1)); job = ajb.toAnalysisJob(); } // make a listener that records the relevant events (for later // assertion) final BlockingQueue<String> messages = new ArrayBlockingQueue<>(100); final AnalysisListener listener = new AnalysisListenerAdaptor() { @Override public void componentSuccess(final AnalysisJob job, final ComponentJob componentJob, final AnalyzerResult result) { messages.add( "componentSuccess(" + componentJob.getName() + "," + result.getClass().getSimpleName() + ")"); if (result instanceof AnalyzerResultFuture) { final AnalyzerResultFuture<?> future = (AnalyzerResultFuture<?>) result; final boolean ready1 = future.isReady(); messages.add("AnalyzerResultFuture.isReady() (1) = " + ready1); // add a couple of listeners future.addListener(new Listener<AnalyzerResult>() { @Override public void onSuccess(final AnalyzerResult result) { messages.add("AnalyzerResultFuture.Listener (1).onSuccess()"); } @Override public void onError(final RuntimeException error) { messages.add("AnalyzerResultFuture.Listener (1).onError(): " + error.getMessage()); } }); future.addListener(new Listener<AnalyzerResult>() { @Override public void onSuccess(final AnalyzerResult result) { messages.add("AnalyzerResultFuture.Listener (2).onSuccess()"); } @Override public void onError(final RuntimeException error) { messages.add("AnalyzerResultFuture.Listener (2).onError(): " + error.getMessage()); } }); } } @Override public void rowProcessingSuccess(final AnalysisJob job, final RowProcessingMetrics metrics) { messages.add("rowProcessingSuccess"); } @Override public void jobSuccess(final AnalysisJob job, final AnalysisJobMetrics metrics) { messages.add("jobSuccess"); } }; // run the job while also adding a few messages from the main thread { final AnalysisRunner runner = new AnalysisRunnerImpl(configuration, listener); final AnalysisResultFuture resultFuture = runner.run(job); messages.add("AnalysisRunner.run(job) returned"); resultFuture.await(); messages.add("AnalysisResultFuture.await() returned"); @SuppressWarnings("rawtypes") final List<? extends AnalyzerResultFuture> futureResults = resultFuture.getResults(AnalyzerResultFuture.class); assertEquals(1, futureResults.size()); final AnalyzerResultFuture<?> futureResult = futureResults.get(0); final boolean ready2 = futureResult.isReady(); messages.add("AnalyzerResultFuture.isReady() (2) = " + ready2); } // make the queue into a list so that we have index positions. final List<String> messagesList = new ArrayList<>(); messages.drainTo(messagesList); // export to DS, dup id in RECORDS final String originalMessagesString = messagesList.toString(); final int indexRowProcessingSuccess = getIndexAndVerifyExists(messagesList, "rowProcessingSuccess"); final int indexNormalAnalyzerSuccess = getIndexAndVerifyExists(messagesList, "componentSuccess(normal analyzer,ListResult)"); final int indexFutureAnalyzerSuccess = getIndexAndVerifyExists(messagesList, "componentSuccess(analyzer with future result,AnalyzerResultFutureImpl)"); assertTrue(originalMessagesString, indexNormalAnalyzerSuccess > indexRowProcessingSuccess); assertTrue(originalMessagesString, indexFutureAnalyzerSuccess > indexRowProcessingSuccess); final int indexAnalyzerResultNotReady = getIndexAndVerifyExists(messagesList, "AnalyzerResultFuture.isReady() (1) = false"); final int indexListener1Success = getIndexAndVerifyExists(messagesList, "AnalyzerResultFuture.Listener (1).onSuccess()"); final int indexListener2Success = getIndexAndVerifyExists(messagesList, "AnalyzerResultFuture.Listener (2).onSuccess()"); assertTrue(originalMessagesString, indexListener1Success > indexAnalyzerResultNotReady); assertTrue(originalMessagesString, indexListener2Success > indexAnalyzerResultNotReady); final int indexAnalysisResultFutureAwaitReturned = getIndexAndVerifyExists(messagesList, "AnalysisResultFuture.await() returned"); assertTrue(originalMessagesString, indexAnalysisResultFutureAwaitReturned > indexListener1Success); assertTrue(originalMessagesString, indexAnalysisResultFutureAwaitReturned > indexListener2Success); final int indexAnalyzerResultReady = getIndexAndVerifyExists(messagesList, "AnalyzerResultFuture.isReady() (2) = true"); assertTrue(originalMessagesString, indexAnalyzerResultReady > indexAnalyzerResultNotReady); final int indexJobSuccess = getIndexAndVerifyExists(messagesList, "jobSuccess"); final int indexRunReturned = getIndexAndVerifyExists(messagesList, "AnalysisRunner.run(job) returned"); assertTrue(originalMessagesString, indexJobSuccess > indexRunReturned); assertTrue(originalMessagesString, indexJobSuccess > indexFutureAnalyzerSuccess); } private int getIndexAndVerifyExists(final List<String> messagesList, final String string) { final int index = messagesList.indexOf(string); if (index == -1) { // use a good message because hunting down the cause of this can be // pretty tough. final String message = "Failed to find '" + string + "' in messages: " + messagesList.toString(); assertTrue(message, index != -1); } return index; } }