/*
 * Autopsy Forensic Browser
 *
 * Copyright 2014 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.modules.interestingitems;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.ingest.FileIngestModule;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;

/**
 * A file ingest module that generates interesting files set hit artifacts for
 * files that match interesting files set definitions.
 */
final class FilesIdentifierIngestModule implements FileIngestModule {
    @Messages({
        "FilesIdentifierIngestModule.getFilesError=Error getting interesting files sets from file."
    })

    private static final Object sharedResourcesLock = new Object();
    private static final Logger logger = Logger.getLogger(FilesIdentifierIngestModule.class.getName());
    private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
    private static final Map<Long, List<FilesSet>> interestingFileSetsByJob = new ConcurrentHashMap<>();
    private final FilesIdentifierIngestJobSettings settings;
    private IngestJobContext context;
    private Blackboard blackboard;

    /**
     * Construct an interesting files identifier ingest module for an ingest
     * job.
     *
     * @param settings An ingest job settings object for the module.
     */
    FilesIdentifierIngestModule(FilesIdentifierIngestJobSettings settings) {
        this.settings = settings;
    }

    /**
     * @inheritDoc
     */
    @Override
    public void startUp(IngestJobContext context) throws IngestModuleException {
        this.context = context;
        synchronized (FilesIdentifierIngestModule.sharedResourcesLock) {
            if (FilesIdentifierIngestModule.refCounter.incrementAndGet(context.getJobId()) == 1) {
                // Starting up the first instance of this module for this ingest 
                // job, so get the interesting file sets definitions snapshot 
                // for the job. Note that getting this snapshot atomically via a 
                // synchronized definitions manager method eliminates the need 
                // to disable the interesting files set definition UI during ingest.
                List<FilesSet> filesSets = new ArrayList<>();
                try {
                    for (FilesSet set : InterestingItemDefsManager.getInstance().getInterestingFilesSets().values()) {
                        if (settings.interestingFilesSetIsEnabled(set.getName())) {
                            filesSets.add(set);
                        }
                    }
                } catch (InterestingItemDefsManager.InterestingItemDefsManagerException ex) {
                    throw new IngestModuleException(Bundle.FilesIdentifierIngestModule_getFilesError());
                }
                FilesIdentifierIngestModule.interestingFileSetsByJob.put(context.getJobId(), filesSets);
            }
        }
    }

    /**
     * @inheritDoc
     */
    @Override
    public ProcessResult process(AbstractFile file) {
        blackboard = Case.getCurrentCase().getServices().getBlackboard();
        
        // See if the file belongs to any defined interesting files set.
        List<FilesSet> filesSets = FilesIdentifierIngestModule.interestingFileSetsByJob.get(this.context.getJobId());
        for (FilesSet filesSet : filesSets) {
            String ruleSatisfied = filesSet.fileIsMemberOf(file);
            if (ruleSatisfied != null) {
                try {
                    // Post an interesting files set hit artifact to the 
                    // blackboard.
                    String moduleName = InterestingItemsIngestModuleFactory.getModuleName();
                    BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);

                    // Add a set name attribute to the artifact. This adds a 
                    // fair amount of redundant data to the attributes table 
                    // (i.e., rows that differ only in artifact id), but doing
                    // otherwise would requires reworking the interesting files
                    // set hit artifact.
                    BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, moduleName, filesSet.getName());
                    artifact.addAttribute(setNameAttribute);

                    // Add a category attribute to the artifact to record the 
                    // interesting files set membership rule that was satisfied.
                    BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, moduleName, ruleSatisfied);
                    artifact.addAttribute(ruleNameAttribute);
                    
                    try {
                        // index the artifact for keyword search
                        blackboard.indexArtifact(artifact);
                    } catch (Blackboard.BlackboardException ex) {
                        logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS
                        MessageNotifyUtil.Notify.error(
                                NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName());
                    }
                    
                    IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, Collections.singletonList(artifact)));

                } catch (TskCoreException ex) {
                    FilesIdentifierIngestModule.logger.log(Level.SEVERE, "Error posting to the blackboard", ex); //NOI18N NON-NLS
                }
            }
        }
        return ProcessResult.OK;
    }

    /**
     * @inheritDoc
     */
    @Override
    public void shutDown() {
        if (context != null) {
            if (refCounter.decrementAndGet(this.context.getJobId()) == 0) {
                // Shutting down the last instance of this module for this ingest 
                // job, so discard the interesting file sets definitions snapshot 
                // for the job.
                FilesIdentifierIngestModule.interestingFileSetsByJob.remove(this.context.getJobId());
            }
        }
    }
}

@@ -4187,16 +4187,20 @@
 sError()
+, ex
 );%0A     

Update FilesIdentifierIngestModule.java