/*
 * Autopsy Forensic Browser
 * 
 * Copyright 2011-2016 Basis Technology Corp.
 * Contact: carrier <at> sleuthkit <dot> org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sleuthkit.autopsy.ingest;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.datamodel.Content;

/**
 * This class manages a sequence of data source level ingest modules for a data
 * source ingest job. It starts the modules, runs data sources through them, and
 * shuts them down when data source level ingest is complete.
 * <p>
 * This class is thread-safe.
 */
final class DataSourceIngestPipeline {

    private static final IngestManager ingestManager = IngestManager.getInstance();
    private static final Logger logger = Logger.getLogger(DataSourceIngestPipeline.class.getName());
    private final DataSourceIngestJob job;
    private final List<PipelineModule> modules = new ArrayList<>();
    private volatile PipelineModule currentModule;

    /**
     * Constructs an object that manages a sequence of data source level ingest
     * modules. It starts the modules, runs data sources through them, and shuts
     * them down when data source level ingest is complete.
     *
     * @param job             The data source ingest job that owns this
     *                        pipeline.
     * @param moduleTemplates Templates for the creating the ingest modules that
     *                        make up this pipeline.
     */
    DataSourceIngestPipeline(DataSourceIngestJob job, List<IngestModuleTemplate> moduleTemplates) {
        this.job = job;
        for (IngestModuleTemplate template : moduleTemplates) {
            if (template.isDataSourceIngestModuleTemplate()) {
                PipelineModule module = new PipelineModule(template.createDataSourceIngestModule(), template.getModuleName());
                modules.add(module);
            }
        }
    }

    /**
     * Indicates whether or not there are any ingest modules in this pipeline.
     *
     * @return True or false.
     */
    boolean isEmpty() {
        return modules.isEmpty();
    }

    /**
     * Starts up the ingest modules in this pipeline.
     *
     * @return A list of ingest module startup errors, possibly empty.
     */
    synchronized List<IngestModuleError> startUp() {
        List<IngestModuleError> errors = new ArrayList<>();
        for (PipelineModule module : modules) {
            try {
                module.startUp(new IngestJobContext(this.job));
            } catch (Throwable ex) { // Catch-all exception firewall
                errors.add(new IngestModuleError(module.getDisplayName(), ex));
            }
        }
        return errors;
    }

    /**
     * Runs a data source through the ingest modules in sequential order.
     *
     * @param task A data source level ingest task containing a data source to
     *             be processed.
     *
     * @return A list of processing errors, possible empty.
     */
    synchronized List<IngestModuleError> process(DataSourceIngestTask task) {
        List<IngestModuleError> errors = new ArrayList<>();
        if (!this.job.isCancelled()) {
            Content dataSource = task.getDataSource();
            for (PipelineModule module : modules) {
                try {
                    this.currentModule = module;
                    String displayName = NbBundle.getMessage(this.getClass(),
                            "IngestJob.progress.dataSourceIngest.displayName",
                            module.getDisplayName(), dataSource.getName());
                    this.job.updateDataSourceIngestProgressBarDisplayName(displayName);
                    this.job.switchDataSourceIngestProgressBarToIndeterminate();
                    DataSourceIngestPipeline.ingestManager.setIngestTaskProgress(task, module.getDisplayName());
                    logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) starting", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getDataSource().getId()}); //NON-NLS
                    module.process(dataSource, new DataSourceIngestModuleProgress(this.job));
                    logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) finished", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getDataSource().getId()}); //NON-NLS
                } catch (Throwable ex) { // Catch-all exception firewall
                    errors.add(new IngestModuleError(module.getDisplayName(), ex));
                    String msg = ex.getMessage();
                    // Jython run-time errors don't seem to have a message, but have details in toString.
                    if (msg == null) {
                        msg = ex.toString();
                    }
                    MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "DataSourceIngestPipeline.moduleError.title.text", module.getDisplayName()), msg);
                }
                if (this.job.isCancelled()) {
                    break;
                } else if (this.job.currentDataSourceIngestModuleIsCancelled()) {
                    this.job.currentDataSourceIngestModuleCancellationCompleted(currentModule.getDisplayName());
                }
            }
        }
        this.currentModule = null;
        ingestManager.setIngestTaskProgressCompleted(task);
        return errors;
    }

    /**
     * Gets the currently running module.
     *
     * @return The module, possibly null if no module is currently running.
     */
    PipelineModule getCurrentlyRunningModule() {
        return this.currentModule;
    }

    /**
     * This class decorates a data source level ingest module with a display
     * name and a processing start time.
     */
    static class PipelineModule implements DataSourceIngestModule {

        private final DataSourceIngestModule module;
        private final String displayName;
        private volatile Date processingStartTime;

        /**
         * Constructs an object that decorates a data source level ingest module
         * with a display name and a processing start time.
         *
         * @param module      The data source level ingest module to be
         *                    decorated.
         * @param displayName The display name.
         */
        PipelineModule(DataSourceIngestModule module, String displayName) {
            this.module = module;
            this.displayName = displayName;
            this.processingStartTime = new Date();
        }

        /**
         * Gets the class name of the decorated ingest module.
         *
         * @return The class name.
         */
        String getClassName() {
            return this.module.getClass().getCanonicalName();
        }

        /**
         * Gets the display of the decorated ingest module.
         *
         * @return The display name.
         */
        String getDisplayName() {
            return this.displayName;
        }

        /**
         * Gets the time the decorated ingest module started processing the data
         * source.
         *
         * @return The start time, will be null if the module has not started
         *         processing the data source yet.
         */
        Date getProcessingStartTime() {
            return this.processingStartTime;
        }

        @Override
        public void startUp(IngestJobContext context) throws IngestModuleException {
            this.module.startUp(context);
        }

        @Override
        public IngestModule.ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
            this.processingStartTime = new Date();
            return this.module.process(dataSource, statusHelper);
        }

    }

}

@@ -4708,32 +4708,16 @@
 his.job.
-getDataSource().
 getId()%7D
@@ -4993,32 +4993,16 @@
 his.job.
-getDataSource().
 getId()%7D

Fix job id logging errors in DataSourceIngestPipeline