/** * 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.actions; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; import org.apache.metamodel.schema.Table; import org.datacleaner.bootstrap.WindowContext; import org.datacleaner.data.MetaModelInputColumn; import org.datacleaner.descriptors.Descriptors; import org.datacleaner.job.HasFilterOutcomes; import org.datacleaner.job.builder.AnalysisJobBuilder; import org.datacleaner.job.builder.AnalyzerComponentBuilder; import org.datacleaner.job.builder.ComponentBuilder; import org.datacleaner.job.builder.TransformerComponentBuilder; import org.datacleaner.job.runner.AnalysisResultFuture; import org.datacleaner.job.runner.AnalysisRunner; import org.datacleaner.job.runner.AnalysisRunnerImpl; import org.datacleaner.panels.TransformerComponentBuilderPresenter; import org.datacleaner.util.PreviewTransformedDataAnalyzer; import org.datacleaner.util.PreviewUtils; import org.datacleaner.util.SourceColumnFinder; import org.datacleaner.windows.DataSetWindow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActionListener responsible for previewing transformed data in a * {@link DataSetWindow}. */ public final class PreviewTransformedDataActionListener implements ActionListener, Callable<TableModel> { public static class PreviewJob { public final AnalysisJobBuilder analysisJobBuilder; public final AnalyzerComponentBuilder<?> rowCollectorAnalyzer; public final TransformerComponentBuilder<?> previewedTransformer; public PreviewJob(final AnalysisJobBuilder analysisJobBuilder, final AnalyzerComponentBuilder<?> rowCollectorAnalyzer, final TransformerComponentBuilder<?> previewedTransformer) { this.analysisJobBuilder = analysisJobBuilder; this.rowCollectorAnalyzer = rowCollectorAnalyzer; this.previewedTransformer = previewedTransformer; } } public static final int DEFAULT_PREVIEW_ROWS = 200; private static final Logger logger = LoggerFactory.getLogger(PreviewTransformedDataActionListener.class); private final TransformerComponentBuilderPresenter _transformerJobBuilderPresenter; private final TransformerComponentBuilder<?> _transformerJobBuilder; private final WindowContext _windowContext; private final int _previewRows; private DataSetWindow _latestWindow; public PreviewTransformedDataActionListener(final WindowContext windowContext, final TransformerComponentBuilder<?> transformerJobBuilder) { this(windowContext, null, transformerJobBuilder); } public PreviewTransformedDataActionListener(final WindowContext windowContext, final TransformerComponentBuilderPresenter transformerJobBuilderPresenter, final TransformerComponentBuilder<?> transformerJobBuilder) { this(windowContext, transformerJobBuilderPresenter, transformerJobBuilder, DEFAULT_PREVIEW_ROWS); } public PreviewTransformedDataActionListener(final WindowContext windowContext, final TransformerComponentBuilderPresenter transformerJobBuilderPresenter, final TransformerComponentBuilder<?> transformerJobBuilder, final int previewRows) { _windowContext = windowContext; _transformerJobBuilderPresenter = transformerJobBuilderPresenter; _transformerJobBuilder = transformerJobBuilder; _previewRows = previewRows; } @Override public void actionPerformed(final ActionEvent e) { // prevent multiple preview windows from the same preview button final DataSetWindow existingWindow = _latestWindow; if (existingWindow != null) { existingWindow.close(); _latestWindow = null; } final DataSetWindow window = new DataSetWindow("Preview of transformed dataset", this, _windowContext); window.open(); _latestWindow = window; } protected PreviewJob createPreviewJob() { if (_transformerJobBuilderPresenter != null) { _transformerJobBuilderPresenter.applyPropertyValues(); } final String jobBuilderIdentifier = UUID.randomUUID().toString(); final AnalysisJobBuilder originalAnalysisJobBuilder = _transformerJobBuilder.getAnalysisJobBuilder(); // put a marker metadata property on the AnalysisJobBuilder to make // it easy to identify it's equivalent object from the copy later. originalAnalysisJobBuilder.getAnalysisJobMetadata().getProperties() .put(PreviewUtils.METADATA_PROPERTY_MARKER, jobBuilderIdentifier); final AnalysisJobBuilder ajb; try { final AnalysisJobBuilder copyAnalysisJobBuilder = PreviewUtils.copy(originalAnalysisJobBuilder.getRootJobBuilder()); ajb = PreviewUtils.findAnalysisJobBuilder(copyAnalysisJobBuilder, jobBuilderIdentifier); } finally { // remove the marker metadata originalAnalysisJobBuilder.getAnalysisJobMetadata().getProperties() .remove(PreviewUtils.METADATA_PROPERTY_MARKER); } if (ajb == null) { throw new IllegalStateException( "Could not find AnalysisJobBuilder copy which is equivalent to the original"); } final TransformerComponentBuilder<?> tjb = findTransformerComponentBuilder(ajb); PreviewUtils.sanitizeIrrelevantComponents(ajb, tjb); // represents if the transformer is already filtered (also may be transitively) final boolean alreadyFiltered; // remove irrelevant source tables { final SourceColumnFinder sourceColumnFinder = new SourceColumnFinder(); sourceColumnFinder.addSources(ajb); final List<Table> tables = ajb.getSourceTables(); if (tables.size() > 1) { final Table originatingTable = sourceColumnFinder.findOriginatingTable(tjb.getOutputColumns().get(0)); tables.remove(originatingTable); for (final Table otherTable : tables) { ajb.removeSourceTable(otherTable); } } alreadyFiltered = sourceColumnFinder.findAllSourceJobs(tjb).stream().filter(o -> o instanceof HasFilterOutcomes) .findAny().isPresent(); } final List<MetaModelInputColumn> sourceColumns = ajb.getSourceColumns(); if (sourceColumns.isEmpty()) { logger.error("No source columns left after removing irrelevant source tables. Component: {}", _transformerJobBuilder); return null; } // add the result collector (a dummy analyzer) final AnalyzerComponentBuilder<PreviewTransformedDataAnalyzer> rowCollector = ajb.addAnalyzer(Descriptors.ofAnalyzer(PreviewTransformedDataAnalyzer.class)) .addInputColumns(tjb.getInputColumns()).addInputColumns(tjb.getOutputColumns()); if (tjb.getComponentRequirement() != null) { rowCollector.setComponentRequirement(tjb.getComponentRequirement()); } final AnalysisJobBuilder rootJobBuilder = ajb.getRootJobBuilder(); final Collection<? extends ComponentBuilder> componentBuilders; if (alreadyFiltered) { // if there are already filters in place, only apply the max rows filter on the other filters. componentBuilders = rootJobBuilder.getFilterComponentBuilders(); } else { componentBuilders = rootJobBuilder.getComponentBuilders(); } PreviewUtils.limitJobRows(rootJobBuilder, componentBuilders, _previewRows); return new PreviewJob(rootJobBuilder, rowCollector, tjb); } @Override public TableModel call() throws Exception { final PreviewJob previewJob = createPreviewJob(); if (previewJob == null) { return new DefaultTableModel(0, 0); } final AnalyzerComponentBuilder<?> rowCollector = previewJob.rowCollectorAnalyzer; final String[] columnNames = new String[rowCollector.getInputColumns().size()]; for (int i = 0; i < columnNames.length; i++) { columnNames[i] = rowCollector.getInputColumns().get(i).getName(); } final AnalysisRunner runner = new AnalysisRunnerImpl(previewJob.analysisJobBuilder.getConfiguration()); final AnalysisResultFuture resultFuture = runner.run(previewJob.analysisJobBuilder.toAnalysisJob()); resultFuture.await(); if (resultFuture.isErrornous()) { final List<Throwable> errors = resultFuture.getErrors(); final Throwable firstError = errors.get(0); logger.error("Error occurred while running preview data job: {}", firstError.getMessage()); for (final Throwable throwable : errors) { logger.info("Preview data error", throwable); } if (firstError instanceof Exception) { throw (Exception) firstError; } throw new IllegalStateException(firstError); } final List<? extends PreviewTransformedDataAnalyzer> results = resultFuture.getResults(PreviewTransformedDataAnalyzer.class); assert results.size() == 1; final PreviewTransformedDataAnalyzer result = results.get(0); final List<Object[]> rows = result.getList(); final DefaultTableModel tableModel = new DefaultTableModel(columnNames, rows.size()); int rowIndex = 0; for (final Object[] row : rows) { if (row != null) { for (int columnIndex = 0; columnIndex < row.length; columnIndex++) { tableModel.setValueAt(row[columnIndex], rowIndex, columnIndex); } } rowIndex++; } return tableModel; } private TransformerComponentBuilder<?> findTransformerComponentBuilder(final AnalysisJobBuilder ajb) { final AnalysisJobBuilder analysisJobBuilder = _transformerJobBuilder.getAnalysisJobBuilder(); final int transformerIndex = analysisJobBuilder.getTransformerComponentBuilders().indexOf(_transformerJobBuilder); return ajb.getTransformerComponentBuilders().get(transformerIndex); } }