/**
* 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.job.runner;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.metamodel.schema.Table;
import org.eobjects.analyzer.beans.api.Analyzer;
import org.eobjects.analyzer.configuration.AnalyzerBeansConfiguration;
import org.eobjects.analyzer.configuration.InjectionManager;
import org.eobjects.analyzer.data.InputColumn;
import org.eobjects.analyzer.data.InputRow;
import org.eobjects.analyzer.job.AnalysisJob;
import org.eobjects.analyzer.job.FilterOutcome;
import org.eobjects.analyzer.job.concurrent.SingleThreadedTaskRunner;
import org.eobjects.analyzer.job.concurrent.TaskListener;
import org.eobjects.analyzer.job.tasks.Task;
import org.eobjects.analyzer.lifecycle.LifeCycleHelper;
import org.eobjects.analyzer.util.SourceColumnFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Object that can handle the task of consuming a number of rows. The
* {@link ConsumeRowHandler} is internally used to execute all necesary
* components for every record, but it can also be used as a utility if
* AnalyzerBeans jobs are being embedded or applied in externally.
*/
public class ConsumeRowHandler {
private static final Logger logger = LoggerFactory.getLogger(ConsumeRowHandler.class);
private final List<RowProcessingConsumer> _consumers;
private final Collection<? extends FilterOutcome> _alwaysSatisfiedOutcomes;
public static class Configuration {
public boolean includeNonDistributedTasks = true;
public AnalysisListener analysisListener = new InfoLoggingAnalysisListener();
public boolean includeAnalyzers = true;
public Collection<? extends FilterOutcome> alwaysSatisfiedOutcomes;
public Table table;
}
/**
* Builds a {@link ConsumeRowHandler} based on a job, and the configuration
* to read the job's consumers
*
* @param job
* @param analyzerBeansConfiguration
* @param configuration
*/
public ConsumeRowHandler(AnalysisJob job, AnalyzerBeansConfiguration analyzerBeansConfiguration,
Configuration configuration) {
_consumers = extractConsumers(job, analyzerBeansConfiguration, configuration);
_alwaysSatisfiedOutcomes = configuration.alwaysSatisfiedOutcomes;
}
/**
* Builds a {@link ConsumeRowHandler} based on a list of consumers.
*
* @param consumers
*/
public ConsumeRowHandler(List<RowProcessingConsumer> consumers) {
this(consumers, null);
}
/**
* Builds a {@link ConsumeRowHandler} based on a list of consumers as well
* as a collection of always-satisfied outcomes.
*
* @param consumers
* @param alwaysSatisfiedOutcomes
*/
public ConsumeRowHandler(List<RowProcessingConsumer> consumers,
Collection<? extends FilterOutcome> alwaysSatisfiedOutcomes) {
_consumers = consumers;
_alwaysSatisfiedOutcomes = alwaysSatisfiedOutcomes;
}
/**
* Gets the {@link RowProcessingConsumer}s that this handler is working on.
*
* @return
*/
public List<RowProcessingConsumer> getConsumers() {
return _consumers;
}
/**
* Gets the output columns produced by all the consumers of this
* {@link ConsumeRowHandler}.
*
* @return
*/
public List<InputColumn<?>> getOutputColumns() {
final List<InputColumn<?>> result = new ArrayList<InputColumn<?>>();
for (final RowProcessingConsumer consumer : _consumers) {
final InputColumn<?>[] outputColumns = consumer.getOutputColumns();
for (final InputColumn<?> outputColumn : outputColumns) {
result.add(outputColumn);
}
}
return result;
}
/**
* @deprecated use {@link #consumeRow(InputRow)} instead
*/
@Deprecated
public List<InputRow> consume(final InputRow row) {
final ConsumeRowResult result = consumeRow(row);
return result.getRows();
}
/**
* Consumes a {@link InputRow} by applying all transformations etc. to it,
* returning a result of transformed rows and their {@link FilterOutcomes}s.
*
* @param row
* @return
*/
public ConsumeRowResult consumeRow(final InputRow row) {
final FilterOutcomes outcomes = new FilterOutcomesImpl(_alwaysSatisfiedOutcomes);
final ConsumeRowHandlerDelegate delegate = new ConsumeRowHandlerDelegate(_consumers, row, 0, outcomes);
final ConsumeRowResult result = delegate.consume();
return result;
}
private List<RowProcessingConsumer> extractConsumers(AnalysisJob analysisJob,
AnalyzerBeansConfiguration analyzerBeansConfiguration, Configuration configuration) {
final InjectionManager injectionManager = analyzerBeansConfiguration.getInjectionManager(analysisJob);
final ReferenceDataActivationManager referenceDataActivationManager = new ReferenceDataActivationManager();
final LifeCycleHelper lifeCycleHelper = new LifeCycleHelper(injectionManager, referenceDataActivationManager,
configuration.includeNonDistributedTasks);
SourceColumnFinder sourceColumnFinder = new SourceColumnFinder();
sourceColumnFinder.addSources(analysisJob);
/**
* Use a single threaded task runner since this handler is invoked in a
* blocking way - the calling code may itself be multithreaded without
* issues.
*/
final SingleThreadedTaskRunner taskRunner = new SingleThreadedTaskRunner();
final AnalysisListener analysisListener = configuration.analysisListener;
final RowProcessingPublishers rowProcessingPublishers = new RowProcessingPublishers(analysisJob,
analysisListener, taskRunner, lifeCycleHelper, sourceColumnFinder);
final RowProcessingPublisher publisher;
if (configuration.table != null) {
publisher = rowProcessingPublishers.getRowProcessingPublisher(configuration.table);
if (publisher == null) {
throw new IllegalArgumentException("Job does not consume records from table: " + configuration.table);
}
} else {
final Collection<RowProcessingPublisher> publisherCollection = rowProcessingPublishers
.getRowProcessingPublishers();
if (publisherCollection.size() > 1) {
throw new IllegalArgumentException(
"Job consumes multiple tables, but ConsumeRowHandler can only handle a single table's components. Please specify a Table constructor argument.");
}
publisher = publisherCollection.iterator().next();
}
final AtomicReference<Throwable> errorReference = new AtomicReference<Throwable>();
publisher.initializeConsumers(new TaskListener() {
@Override
public void onError(Task task, Throwable throwable) {
logger.error("Exception thrown while initializing consumers.", throwable);
errorReference.compareAndSet(null, throwable);
}
@Override
public void onComplete(Task task) {
logger.info("Consumers initialized successfully.");
}
@Override
public void onBegin(Task task) {
logger.info("Beginning the process of initializing consumers.");
}
});
final Throwable throwable = errorReference.get();
if (throwable != null) {
if (throwable instanceof RuntimeException) {
}
}
List<RowProcessingConsumer> consumers = publisher.getConfigurableConsumers();
if (!configuration.includeAnalyzers) {
consumers = removeAnalyzers(consumers);
}
consumers = RowProcessingPublisher.sortConsumers(consumers);
return consumers;
}
private List<RowProcessingConsumer> removeAnalyzers(List<RowProcessingConsumer> consumers) {
final List<RowProcessingConsumer> result = new ArrayList<RowProcessingConsumer>();
for (RowProcessingConsumer rowProcessingConsumer : consumers) {
final Object component = rowProcessingConsumer.getComponent();
if (!(component instanceof Analyzer<?>)) {
result.add(rowProcessingConsumer);
}
}
return result;
}
}