Moved Cross case keyword search into Autopsy

This commit is contained in:
U-BASIS\dsmyda 2019-03-11 12:43:40 -04:00
parent f80dbca870
commit 564d281489
19 changed files with 3979 additions and 0 deletions

View File

@ -49,6 +49,14 @@
<specification-version>1.31.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.swing.outline</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<specification-version>1.34.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.openide.awt</code-name-base>
<build-prerequisite/>

View File

@ -0,0 +1,22 @@
SelectMultiUserCasesPanel.selectAllButton.text=Select All
SelectMultiUserCasesPanel.deselectAllButton.text=Deselect All
SelectMultiUserCasesPanel.jLabel1.text=Select case(s) for keyword search or start typing to search by case name
SelectMultiUserCasesPanel.confirmSelections.text=OK
SelectMultiUserCasesPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchErrorDialog.closeButton.text=Close
MultiCaseKeywordSearchPanel.exactRadioButton.text_1=Exact Match
MultiCaseKeywordSearchPanel.substringRadioButton.text_1=Substring Match
MultiCaseKeywordSearchPanel.regexRadioButton.text_1=Regular Expression
MultiCaseKeywordSearchPanel.keywordTextField.text_1=
MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text=Perform a keyword search on the selected collections to find cases which contain hits. The cases can be opened to examine the results more closely.
MultiCaseKeywordSearchPanel.casesLabel.text_1=Cases
MultiCaseKeywordSearchPanel.resultsLabel.text=Results
MultiCaseKeywordSearchPanel.uncheckButton.text=Uncheck All
MultiCaseKeywordSearchPanel.checkButton.text=Check All
MultiCaseKeywordSearchPanel.searchButton.text=Search
MultiCaseKeywordSearchPanel.viewErrorsButton.text=View Errors
MultiCaseKeywordSearchPanel.warningLabel.text=
MultiCaseKeywordSearchPanel.exportButton.text=Export Results
MultiCaseKeywordSearchPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchPanel.resultsCountLabel.text=
MultiCaseKeywordSearchPanel.pickCasesButton.text_1=Pick Cases

View File

@ -0,0 +1,108 @@
CTL_MultiCaseKeywordSearchOpenAction=Multi-case Keyword Search
CTL_MultiCaseKeywordSearchTopComponent=Multi-case Keyword Search
CTL_MultiCaseKeywordSearchTopComponentAction=Multi-case Keyword Search
MultiCaseKeywordSearchErrorDialog.title.text=Error(s) While Searching
MultiCaseKeywordSearchNode.copyResultAction.text=Copy to clipboard
MultiCaseKeywordSearchNode.OpenCaseAction.text=Open Case
MultiCaseKeywordSearchNode.properties.case=Case
MultiCaseKeywordSearchNode.properties.caseDirectory=Case Directory
MultiCaseKeywordSearchNode.properties.dataSource=Data Source
MultiCaseKeywordSearchNode.properties.path=Keyword Hit Source Path
MultiCaseKeywordSearchNode.properties.source=Keyword Hit Source
MultiCaseKeywordSearchNode.properties.sourceType=Keyword Hit Source Type
MultiCaseKeywordSearchPanel.continueSearch.text=A search is currently being performed. Would you like the search to continue in the background while the search window is closed?
MultiCaseKeywordSearchPanel.continueSearch.title=Closing multi-case search
MultiCaseKeywordSearchPanel.countOfResults.label=Count:
MultiCaseKeywordSearchPanel.emptyNode.waitText=Please Wait...
# {0} - numberOfErrors
MultiCaseKeywordSearchPanel.errorsEncounter.text={0} Error(s) encountered while performing search
MultiCaseKeywordSearchPanel.searchResultsExport.csvExtensionFilterlbl=Comma Separated Values File (csv)
# {0} - file name
MultiCaseKeywordSearchPanel.searchResultsExport.exportMsg=Search results exported to {0}
MultiCaseKeywordSearchPanel.searchResultsExport.failedExportMsg=Export of search results failed
MultiCaseKeywordSearchPanel.searchResultsExport.featureName=Search Results Export
# {0} - file name
MultiCaseKeywordSearchPanel.searchResultsExport.fileExistPrompt=File {0} exists, overwrite?
MultiCaseKeywordSearchPanel.searchThread.cancellingText=Cancelling search
MultiCaseKeywordSearchPanel.warningText.emptySearch=You must enter something to search for in the text field.
MultiCaseKeywordSearchPanel.warningText.noCases=At least one case must be selected to perform a search.
MultiCaseKeywordSearchTopComponent.exceptionMessage.failedToCreatePanel=Failed to create Multi-case Keyword Search panel.
MultiCaseKeywordSearchTopComponent.name.text=Multi-case Keyword Search
MultiCaseSearcher.exceptionMessage.cancelledMessage=Search cancelled
# {0} - connection info
# {1} - case name
# {2} - case directory
MultiCaseSearcher.exceptionMessage.errorLoadingCore=Error connecting to Solr server and loading core (URL: {0}) for case {1} in {2}
# {0} - PostgreSQL server host
# {1} - PostgreSQL server port
# {2} - case database name
# {3} - case directory
MultiCaseSearcher.exceptionMessage.errorOpeningCaseDatabase=Error connecting to PostgreSQL server (Host/Port: [{0}:{1}] and opening case database {2} for case at {3}
# {0} - case directory
MultiCaseSearcher.exceptionMessage.failedToFindCaseMetadata=Failed to find case metadata file in {0}
# {0} - case_name
MultiCaseSearcher.exceptionMessage.failedToGetCaseDatabaseConnectionInfo=Failed to get case database connection info for case {0}
# {0} - case directory path
MultiCaseSearcher.exceptionMessage.failedToGetCaseDirReadlock=Failed to obtain read lock for case directory at {0}
# {0} - case directory
MultiCaseSearcher.exceptionMessage.failedToParseCaseMetadata=Failed to parse case file metadata in {0}
# {0} - host
# {1} - port
MultiCaseSearcher.exceptionMessage.failedToQueryCoordinationServer=Failed to obtain read lock for case directory at {0}:{1}
# {0} - Solr document id
# {1} - case database name
# {2} - case directory
MultiCaseSearcher.exceptionMessage.hitProcessingError=Failed to query case database for processing of Solr object id {0} of case {1} in {2}
# {0} - file name
# {1} - case directory
MultiCaseSearcher.exceptionMessage.missingSolrPropertiesFile=Missing {0} file in {1}
# {0} - list of cases
MultiCaseSearcher.exceptionMessage.noCasesFound=No cases found for: {0}
# {0} - file name
# {1} - case directory
MultiCaseSearcher.exceptionMessage.solrPropertiesFileParseError=Error parsing {0} file in {1}
# {0} - query
# {1} - case_name
MultiCaseSearcher.exceptionMessage.solrQueryError=Failed to execute query "{0}" on case {1}
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.acquiringSharedLockForCase=Acquiring shared lock for "{0}" ({1} of {2} case(s))
MultiCaseSearcher.progressMessage.creatingSolrQuery=Creating search query for Solr server
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.executingSolrQueryForCase=Getting keyword hits for "{0}" ({1} of {2} case(s))
MultiCaseSearcher.progressMessage.findingCases=Finding selected cases
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.loadingSolrCoreForCase=Loading Solr core for "{0}" ({1} of {2} case(s))
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.openingCaseDbForCase=Opening case database for "{0}" ({1} of {2} case(s))
# {0} - total cases
MultiCaseSearcher.progressMessage.startingCaseSearches=Searching {0} case(s)
SelectMultiUserCasesPanel.selectAllButton.text=Select All
SelectMultiUserCasesPanel.deselectAllButton.text=Deselect All
SelectMultiUserCasesPanel.jLabel1.text=Select case(s) for keyword search or start typing to search by case name
SelectMultiUserCasesPanel.confirmSelections.text=OK
SelectMultiUserCasesPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchErrorDialog.closeButton.text=Close
MultiCaseKeywordSearchPanel.exactRadioButton.text_1=Exact Match
MultiCaseKeywordSearchPanel.substringRadioButton.text_1=Substring Match
MultiCaseKeywordSearchPanel.regexRadioButton.text_1=Regular Expression
MultiCaseKeywordSearchPanel.keywordTextField.text_1=
MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text=Perform a keyword search on the selected collections to find cases which contain hits. The cases can be opened to examine the results more closely.
MultiCaseKeywordSearchPanel.casesLabel.text_1=Cases
MultiCaseKeywordSearchPanel.resultsLabel.text=Results
MultiCaseKeywordSearchPanel.uncheckButton.text=Uncheck All
MultiCaseKeywordSearchPanel.checkButton.text=Check All
MultiCaseKeywordSearchPanel.searchButton.text=Search
MultiCaseKeywordSearchPanel.viewErrorsButton.text=View Errors
MultiCaseKeywordSearchPanel.warningLabel.text=
MultiCaseKeywordSearchPanel.exportButton.text=Export Results
MultiCaseKeywordSearchPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchPanel.resultsCountLabel.text=
MultiCaseKeywordSearchPanel.pickCasesButton.text_1=Pick Cases

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="2"/>
</SyntheticProperties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="errorsScrollPane" pref="480" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<Component id="closeButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="errorsScrollPane" pref="196" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="closeButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="14" pref="14" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="errorsScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[470, 175]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextArea" name="errorsTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="40"/>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="5"/>
<Property name="wrapStyleWord" type="boolean" value="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[460, 160]"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JButton" name="closeButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="viking/bethesda/examiner/multicasekeywordsearch/Bundle.properties" key="MultiCaseKeywordSearchErrorDialog.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="closeButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,112 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import javax.swing.JDialog;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
/**
* Dialog to display the errors encounter while perfomring a multi-case keyword search.
*/
final class MultiCaseKeywordSearchErrorDialog extends JDialog {
private static final long serialVersionUID = 1L;
/**
* Creates new MultiCaseKeywordSearchErrorDialog
*/
@Messages({"MultiCaseKeywordSearchErrorDialog.title.text=Error(s) While Searching"})
MultiCaseKeywordSearchErrorDialog(String contents) {
setTitle(Bundle.MultiCaseKeywordSearchErrorDialog_title_text());
initComponents();
errorsTextArea.setText(contents);
this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
pack();
setModal(true);
setResizable(false);
setVisible(true);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
errorsScrollPane = new javax.swing.JScrollPane();
errorsTextArea = new javax.swing.JTextArea();
closeButton = new javax.swing.JButton();
errorsScrollPane.setPreferredSize(new java.awt.Dimension(470, 175));
errorsTextArea.setEditable(false);
errorsTextArea.setColumns(40);
errorsTextArea.setLineWrap(true);
errorsTextArea.setRows(5);
errorsTextArea.setWrapStyleWord(true);
errorsTextArea.setPreferredSize(new java.awt.Dimension(460, 160));
errorsScrollPane.setViewportView(errorsTextArea);
org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchErrorDialog.class, "MultiCaseKeywordSearchErrorDialog.closeButton.text")); // NOI18N
closeButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
closeButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(errorsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 480, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, Short.MAX_VALUE)
.addComponent(closeButton)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(errorsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 196, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(closeButton)
.addGap(14, 14, 14))
);
}// </editor-fold>//GEN-END:initComponents
private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
dispose();
}//GEN-LAST:event_closeButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton closeButton;
private javax.swing.JScrollPane errorsScrollPane;
private javax.swing.JTextArea errorsTextArea;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,291 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionCancelledException;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import static org.sleuthkit.autopsy.casemodule.CaseMetadata.getFileExtension;
import org.sleuthkit.autopsy.casemodule.StartupWindowProvider;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
@NbBundle.Messages({
"MultiCaseKeywordSearchNode.properties.case=Case",
"MultiCaseKeywordSearchNode.properties.caseDirectory=Case Directory",
"MultiCaseKeywordSearchNode.properties.dataSource=Data Source",
"MultiCaseKeywordSearchNode.properties.sourceType=Keyword Hit Source Type",
"MultiCaseKeywordSearchNode.properties.source=Keyword Hit Source",
"MultiCaseKeywordSearchNode.properties.path=Keyword Hit Source Path"
})
/**
* A root node containing child nodes of the results of a multi-case keyword
* Search.
*/
class MultiCaseKeywordSearchNode extends AbstractNode {
private static final Logger LOGGER = Logger.getLogger(MultiCaseKeywordSearchNode.class.getName());
/**
* Construct a new MultiCaseKeywordSearchNode
*
* @param resultList the list of KeywordSearchHits which will be the
* children of this node.
*/
MultiCaseKeywordSearchNode(Collection<SearchHit> resultList) {
super(new MultiCaseKeywordSearchChildren(resultList));
}
/**
* A factory for creating children of the MultiCaseKeywordSearchNode.
*/
static class MultiCaseKeywordSearchChildren extends Children.Keys<SearchHit> {
private final Collection<SearchHit> resultList;
/**
* Construct a new MultiCaseKeywordSearchChildren
*
* @param resultList the list of KeywordSearchHits which will be used to
* construct the children.
*/
MultiCaseKeywordSearchChildren(Collection<SearchHit> resultList) {
this.resultList = resultList;
}
@Override
protected void addNotify() {
super.addNotify();
setKeys(resultList);
}
@Override
protected void removeNotify() {
super.removeNotify();
setKeys(Collections.emptyList());
}
@Override
protected Node[] createNodes(SearchHit t) {
return new Node[]{new SearchHitNode(t)};
}
@Override
public Object clone() {
return super.clone();
}
}
/**
* A leaf node which represents a hit for the multi-case keyword search.
*/
static final class SearchHitNode extends AbstractNode {
private final SearchHit searchHit;
/**
* Construct a new SearchHitNode
*
* @param kwsHit the KeywordSearchHit which will be represented by this
* node.
*/
SearchHitNode(SearchHit kwsHit) {
super(Children.LEAF);
searchHit = kwsHit;
super.setName(searchHit.getCaseDisplayName());
setDisplayName(searchHit.getCaseDisplayName());
}
@Override
public Action getPreferredAction() {
return new OpenCaseAction(getCasePath());
}
/**
* Get the path to the case directory
*
* @return the path to the case directory for the KeywordSearchHit
* represented by this node
*/
private String getCasePath() {
return searchHit.getCaseDirectoryPath();
}
@Override
protected Sheet createSheet() {
Sheet s = super.createSheet();
Sheet.Set ss = s.get(Sheet.PROPERTIES);
if (ss == null) {
ss = Sheet.createPropertiesSet();
s.put(ss);
}
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_case(), Bundle.MultiCaseKeywordSearchNode_properties_case(), Bundle.MultiCaseKeywordSearchNode_properties_case(),
searchHit.getCaseDisplayName()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(), Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(), Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(),
searchHit.getCaseDirectoryPath()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_dataSource(), Bundle.MultiCaseKeywordSearchNode_properties_dataSource(), Bundle.MultiCaseKeywordSearchNode_properties_dataSource(),
searchHit.getDataSourceName()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_path(), Bundle.MultiCaseKeywordSearchNode_properties_path(), Bundle.MultiCaseKeywordSearchNode_properties_path(),
searchHit.getSourcePath()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_sourceType(), Bundle.MultiCaseKeywordSearchNode_properties_sourceType(), Bundle.MultiCaseKeywordSearchNode_properties_sourceType(),
searchHit.getSourceType().getDisplayName()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_source(), Bundle.MultiCaseKeywordSearchNode_properties_source(), Bundle.MultiCaseKeywordSearchNode_properties_source(),
searchHit.getSourceName()));
return s;
}
@Override
public Action[] getActions(boolean context) {
List<Action> actions = new ArrayList<>();
actions.add(new OpenCaseAction(getCasePath()));
actions.add(new CopyResultAction(searchHit));
return actions.toArray(new Action[actions.size()]);
}
}
@NbBundle.Messages({"MultiCaseKeywordSearchNode.copyResultAction.text=Copy to clipboard"})
/**
* Put the contents of the selected row in the clipboard in the same tab
* seperated format as pressing ctrl+c.
*/
private static class CopyResultAction extends AbstractAction {
private static final long serialVersionUID = 1L;
SearchHit result;
/**
* Construct a new CopyResultAction
*/
CopyResultAction(SearchHit selectedResult) {
super(Bundle.MultiCaseKeywordSearchNode_copyResultAction_text());
result = selectedResult;
}
@Override
public void actionPerformed(ActionEvent e) {
StringSelection resultSelection = new StringSelection(result.getCaseDisplayName()+ "\t"
+ result.getCaseDirectoryPath() + "\t"
+ result.getDataSourceName() + "\t"
+ result.getSourceType().getDisplayName() + "\t"
+ result.getSourceName() + "\t"
+ result.getSourcePath() + "\t");
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(resultSelection, resultSelection);
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"MultiCaseKeywordSearchNode.OpenCaseAction.text=Open Case"})
/**
* Action to open the case associated with the selected node.
*/
private static class OpenCaseAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final String caseDirPath;
/**
* Finds the path to the .aut file for the specified case directory.
*
* @param caseDirectory the directory to check for a .aut file
*
* @return the path to the first .aut file found in the directory
*
* @throws CaseActionException if there was an issue finding a .aut file
*/
private static String findAutFile(String caseDirectory) throws CaseActionException {
File caseFolder = Paths.get(caseDirectory).toFile();
if (caseFolder.exists()) {
/*
* Search for '*.aut' files.
*/
File[] fileArray = caseFolder.listFiles();
if (fileArray == null) {
throw new CaseActionException("No files found in case directory");
}
String autFilePath = null;
for (File file : fileArray) {
String name = file.getName().toLowerCase();
if (autFilePath == null && name.endsWith(getFileExtension())) {
return file.getAbsolutePath();
}
}
throw new CaseActionException("No .aut files found in case directory");
}
throw new CaseActionException("Case directory was not found");
}
/**
* Construct a new open case action
*
* @param path the path to the case directory for the case to open
*/
OpenCaseAction(String path) {
super(Bundle.MultiCaseKeywordSearchNode_OpenCaseAction_text());
caseDirPath = path;
}
@Override
public void actionPerformed(ActionEvent e) {
StartupWindowProvider.getInstance().close();
new Thread(
() -> {
try {
Case.openAsCurrentCase(findAutFile(caseDirPath));
} catch (CaseActionException ex) {
if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseDirPath), ex); //NON-NLS
MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage());
}
}
}
).start();
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.core.UserPreferences;
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.experimental.autoingest.MultiCaseKeywordSearchOpenAction")
@ActionReference(path = "Menu/Tools", position = 202)
@ActionRegistration(displayName = "#CTL_MultiCaseKeywordSearchOpenAction", lazy = false)
@Messages({"CTL_MultiCaseKeywordSearchOpenAction=Multi-case Keyword Search"})
/**
* Action to open the top level component for the multi-case keyword search.
*/
public final class MultiCaseKeywordSearchOpenAction extends CallableSystemAction {
private static final String DISPLAY_NAME = Bundle.CTL_MultiCaseKeywordSearchOpenAction();
private static final long serialVersionUID = 1L;
@Override
public boolean isEnabled() {
return UserPreferences.getIsMultiUserModeEnabled();
}
@Override
public void performAction() {
MultiCaseKeywordSearchTopComponent.openTopComponent();
}
@Override
public String getName() {
return DISPLAY_NAME;
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
@Override
public boolean asynchronous() {
return false; // run on edt
}
}

View File

@ -0,0 +1,432 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="searchTypeGroup">
</Component>
</NonVisualComponents>
<Properties>
<Property name="name" type="java.lang.String" value="" noResource="true"/>
<Property name="opaque" type="boolean" value="false"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1000, 442]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="exactRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="substringRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="regexRadioButton" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="keywordTextField" min="-2" pref="570" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="toolDescriptionScrollPane" pref="84" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="casesLabel" min="-2" max="-2" attributes="0"/>
<Component id="casesScrollPane" min="-2" max="-2" attributes="0"/>
<Group type="103" alignment="0" groupAlignment="1" max="-2" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="searchButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pickCasesButton" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="uncheckButton" linkSize="5" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="checkButton" linkSize="5" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="resultsLabel" min="-2" pref="154" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="resultsCountLabel" min="-2" pref="98" max="-2" attributes="0"/>
</Group>
<Component id="resultsScrollPane" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="viewErrorsButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="warningLabel" alignment="0" min="-2" pref="695" max="-2" attributes="0"/>
</Group>
<EmptySpace min="14" max="32767" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="exportButton" max="32767" attributes="0"/>
<Component id="cancelButton" pref="87" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="196" max="-2" attributes="0"/>
<Component id="searchProgressBar" pref="769" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="108" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Group type="102" attributes="0">
<Component id="keywordTextField" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="regexRadioButton" alignment="0" min="-2" pref="23" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="exactRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="substringRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
<Component id="toolDescriptionScrollPane" max="32767" attributes="0"/>
</Group>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="casesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="103" alignment="0" groupAlignment="3" attributes="0">
<Component id="resultsLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="resultsCountLabel" alignment="3" min="-2" pref="14" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="resultsScrollPane" max="32767" attributes="0"/>
<Component id="casesScrollPane" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="uncheckButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="checkButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="warningLabel" min="-2" pref="15" max="-2" attributes="0"/>
<Component id="exportButton" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="searchButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="viewErrorsButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="pickCasesButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="cancelButton" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace pref="433" max="32767" attributes="0"/>
<Component id="searchProgressBar" min="-2" pref="22" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="searchButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.searchButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="substringRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="searchTypeGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.substringRadioButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="substringRadioButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="keywordTextField">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Monospaced" size="14" style="0"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.keywordTextField.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.LineBorderInfo">
<LineBorder roundedCorners="true">
<Color PropertyName="color" blue="c0" green="c0" red="c0" type="rgb"/>
</LineBorder>
</Border>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[2, 25]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[2, 25]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="exactRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="searchTypeGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.exactRadioButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="regexRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="searchTypeGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.regexRadioButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Container class="javax.swing.JScrollPane" name="casesScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[174, 281]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="casesPanel">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[152, 197]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="1"/>
</Layout>
</Container>
</SubComponents>
</Container>
<Component class="javax.swing.JLabel" name="casesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.casesLabel.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="resultsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.resultsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="uncheckButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.uncheckButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[2, 6, 2, 6]"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="uncheckButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="checkButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.checkButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="checkButtonActionPerformed"/>
</Events>
</Component>
<Container class="javax.swing.JScrollPane" name="toolDescriptionScrollPane">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextArea" name="toolDescriptionTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="f0" green="f0" red="f0" type="rgb"/>
</Property>
<Property name="columns" type="int" value="20"/>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Tahoma" size="11" style="0"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="3"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="wrapStyleWord" type="boolean" value="true"/>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="resultsScrollPane">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 40]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 100]"/>
</Property>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JProgressBar" name="searchProgressBar">
</Component>
<Component class="javax.swing.JLabel" name="warningLabel">
<Properties>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="c8" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.warningLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="exportButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.exportButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[2, 2, 2, 2]"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="exportButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="resultsCountLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.resultsCountLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="viewErrorsButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.viewErrorsButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="viewErrorsButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="pickCasesButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.pickCasesButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pickCasesButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,908 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import com.google.common.eventbus.Subscribe;
import com.google.common.eventbus.DeadEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.logging.Level;
import javax.swing.AbstractButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.table.TableColumn;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.TableCellRenderer;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.openide.explorer.ExplorerManager;
import org.netbeans.swing.outline.Outline;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.EmptyNode;
import org.sleuthkit.autopsy.keywordsearch.multicase.MultiCaseSearcher.MultiCaseSearcherException;
import org.sleuthkit.autopsy.keywordsearch.multicase.SearchQuery.QueryType;
/**
* Panel to display the controls and results for the multi-case search.
*/
final class MultiCaseKeywordSearchPanel extends javax.swing.JPanel implements ExplorerManager.Provider {
@Messages({
"MultiCaseKeywordSearchPanel.emptyNode.waitText=Please Wait..."
})
private static final long serialVersionUID = 1L;
private volatile SearchThread searchThread = null;
private final Outline outline;
private final ExplorerManager em;
private final org.openide.explorer.view.OutlineView outlineView;
private static final Logger LOGGER = Logger.getLogger(MultiCaseKeywordSearchPanel.class.getName());
private static final EmptyNode PLEASE_WAIT_NODE = new EmptyNode(Bundle.MultiCaseKeywordSearchPanel_emptyNode_waitText());
private static final MultiCaseKeywordSearchNode NO_RESULTS_NODE = new MultiCaseKeywordSearchNode(new ArrayList<>());
private Collection<SearchHit> allSearchHits = new ArrayList<>();
private Collection<MultiCaseSearcherException> searchExceptions = new ArrayList<>();
private SelectMultiUserCasesDialog caseSelectionDialog = SelectMultiUserCasesDialog.getInstance();
private Node[] currentSelections;
/**
* Creates new form MultiCaseKeywordSearchPanel
*/
MultiCaseKeywordSearchPanel() {
em = new ExplorerManager();
outlineView = new org.openide.explorer.view.OutlineView();
outline = outlineView.getOutline();
outlineView.setPropertyColumns(
Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(), Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(),
Bundle.MultiCaseKeywordSearchNode_properties_dataSource(), Bundle.MultiCaseKeywordSearchNode_properties_dataSource(),
Bundle.MultiCaseKeywordSearchNode_properties_path(), Bundle.MultiCaseKeywordSearchNode_properties_path(),
Bundle.MultiCaseKeywordSearchNode_properties_sourceType(), Bundle.MultiCaseKeywordSearchNode_properties_sourceType(),
Bundle.MultiCaseKeywordSearchNode_properties_source(), Bundle.MultiCaseKeywordSearchNode_properties_source());
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.MultiCaseKeywordSearchNode_properties_case());
initComponents();
outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
outline.setRootVisible(false);
outlineView.setPreferredSize(resultsScrollPane.getPreferredSize());
resultsScrollPane.setViewportView(outlineView);
caseSelectionDialog.subscribeToNewCaseSelections(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
populateCasesList((Node[])e.getSource());
revalidate();
}
});
searchEnabled(true);
outline.setRowSelectionAllowed(false);
searchProgressBar.setVisible(false);
exportButton.setEnabled(false);
outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
setColumnWidths();
}
/**
* If a collection of SearchHits is received update the results shown on the
* panel to include them.
*
* @param hits the collection of SearchHits which was received.
*/
@Messages({"MultiCaseKeywordSearchPanel.countOfResults.label=Count: "})
@Subscribe
void subscribeToResults(Collection<SearchHit> hits) {
allSearchHits.addAll(hits);
if (allSearchHits.size() > 0) {
MultiCaseKeywordSearchNode resultsNode = new MultiCaseKeywordSearchNode(allSearchHits);
SwingUtilities.invokeLater(() -> {
em.setRootContext(resultsNode);
outline.setRowSelectionAllowed(true);
resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + Integer.toString(outline.getRowCount()));
});
} else {
em.setRootContext(NO_RESULTS_NODE);
resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + 0);
}
}
/**
* If a string is received and it matches the
* MultiCaseSearcher.SEARCH_COMPLETE_STRING reset elements of this panel
* to reflect that the search is done.
*
* @param stringRecived the String which was received
*/
@Subscribe
void subscribeToStrings(String stringReceived) {
if (stringReceived.equals(MultiCaseSearcher.getSearchCompleteMessage())) {
searchThread.unregisterWithSearcher(MultiCaseKeywordSearchPanel.this);
searchThread = null;
searchEnabled(true);
if (!searchExceptions.isEmpty()) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_errorsEncounter_text(searchExceptions.size()));
}
if (!em.getRootContext().equals(PLEASE_WAIT_NODE) && !em.getRootContext().equals(NO_RESULTS_NODE)) {
exportButton.setEnabled(true);
SwingUtilities.invokeLater(() -> {
exportButton.setEnabled(true);
setColumnWidths();
});
}
} else {
//If it is not the SEARCH_COMPLETE_STRING log it.
LOGGER.log(Level.INFO, "String posted to MultiCaseKeywordSearchPanel EventBus with value of " + stringReceived);
}
}
/**
* If a InterruptedException is received over the EventBus update the
* warning label.
*
* @param exception the InterruptedException which was received.
*/
@Subscribe
void subscribeToInterruptionExceptions(InterruptedException exception) {
warningLabel.setText(exception.getMessage());
//if we are still displaying please wait force it to update to no results
if (em.getRootContext().equals(PLEASE_WAIT_NODE)) {
em.setRootContext(NO_RESULTS_NODE);
resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + 0);
}
}
/**
* If a MultiCaseSearcherException is received over the EventBus cancel the
* current search and update the warning label.
*
* @param exception the MultiCaseSearcherException which was received.
*/
@Messages({"# {0} - numberOfErrors",
"MultiCaseKeywordSearchPanel.errorsEncounter.text={0} Error(s) encountered while performing search"
})
@Subscribe
void subscribeToMultiCaseSearcherExceptions(MultiCaseSearcherException exception) {
searchExceptions.add(exception);
}
/**
* Log all other events received over the event bus which are not
* specifically covered by another @Subscribe method
*
* @param deadEvent Any object received over the event bus which was not of
* a type otherwise subscribed to
*/
@Subscribe
void subscribeToDeadEvents(DeadEvent deadEvent) {
LOGGER.log(Level.INFO, "Dead Event posted to MultiCaseKeywordSearchPanel EventBus " + deadEvent.toString());
}
private void displaySearchErrors() {
if (!searchExceptions.isEmpty()) {
StringBuilder strBuilder = new StringBuilder("");
searchExceptions.forEach((exception) -> {
strBuilder.append("- ").append(exception.getMessage()).append(System.lineSeparator());
});
SwingUtilities.invokeLater(() -> {
new MultiCaseKeywordSearchErrorDialog(strBuilder.toString());
});
}
}
/**
* Get the list of cases from the Multi user case browser
*/
private void populateCasesList(Node[] selectedNodes) {
Collection<String> disabledCases = getCases(false);
casesPanel.removeAll();
currentSelections = selectedNodes;
int casePanelWidth = casesPanel.getPreferredSize().width;
int heightOfAllRows = 0;
for(Node data : selectedNodes) {
//select all new cases and cases which were previously selected
String multiUserCaseName = data.getName();
boolean isSelected = true;
if (disabledCases.contains(multiUserCaseName)) {
isSelected = false;
}
JCheckBox caseCheckBox = new JCheckBox(multiUserCaseName, isSelected);
caseCheckBox.setBackground(Color.white);
if (casePanelWidth < caseCheckBox.getPreferredSize().width) {
casePanelWidth = caseCheckBox.getPreferredSize().width;
}
heightOfAllRows += caseCheckBox.getPreferredSize().height;
casesPanel.add(caseCheckBox);
}
casesPanel.setPreferredSize(new Dimension(casePanelWidth, heightOfAllRows));
}
@Override
public ExplorerManager getExplorerManager() {
return em;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
searchTypeGroup = new javax.swing.ButtonGroup();
searchButton = new javax.swing.JButton();
substringRadioButton = new javax.swing.JRadioButton();
keywordTextField = new javax.swing.JTextField();
exactRadioButton = new javax.swing.JRadioButton();
regexRadioButton = new javax.swing.JRadioButton();
casesScrollPane = new javax.swing.JScrollPane();
casesPanel = new javax.swing.JPanel();
casesLabel = new javax.swing.JLabel();
resultsLabel = new javax.swing.JLabel();
uncheckButton = new javax.swing.JButton();
checkButton = new javax.swing.JButton();
toolDescriptionScrollPane = new javax.swing.JScrollPane();
toolDescriptionTextArea = new javax.swing.JTextArea();
resultsScrollPane = new javax.swing.JScrollPane();
cancelButton = new javax.swing.JButton();
searchProgressBar = new javax.swing.JProgressBar();
warningLabel = new javax.swing.JLabel();
exportButton = new javax.swing.JButton();
resultsCountLabel = new javax.swing.JLabel();
viewErrorsButton = new javax.swing.JButton();
pickCasesButton = new javax.swing.JButton();
setName(""); // NOI18N
setOpaque(false);
setPreferredSize(new java.awt.Dimension(1000, 442));
org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.searchButton.text")); // NOI18N
searchButton.setMaximumSize(new java.awt.Dimension(84, 23));
searchButton.setMinimumSize(new java.awt.Dimension(84, 23));
searchButton.setPreferredSize(new java.awt.Dimension(84, 23));
searchButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
searchButtonActionPerformed(evt);
}
});
searchTypeGroup.add(substringRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(substringRadioButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.substringRadioButton.text_1")); // NOI18N
substringRadioButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
substringRadioButtonActionPerformed(evt);
}
});
keywordTextField.setFont(new java.awt.Font("Monospaced", 0, 14)); // NOI18N
keywordTextField.setText(org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.keywordTextField.text_1")); // NOI18N
keywordTextField.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(192, 192, 192), 1, true));
keywordTextField.setMinimumSize(new java.awt.Dimension(2, 25));
keywordTextField.setPreferredSize(new java.awt.Dimension(2, 25));
searchTypeGroup.add(exactRadioButton);
exactRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(exactRadioButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.exactRadioButton.text_1")); // NOI18N
searchTypeGroup.add(regexRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(regexRadioButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.regexRadioButton.text_1")); // NOI18N
casesScrollPane.setPreferredSize(new java.awt.Dimension(174, 281));
casesPanel.setBackground(new java.awt.Color(255, 255, 255));
casesPanel.setPreferredSize(new java.awt.Dimension(152, 197));
casesPanel.setLayout(new javax.swing.BoxLayout(casesPanel, javax.swing.BoxLayout.Y_AXIS));
casesScrollPane.setViewportView(casesPanel);
org.openide.awt.Mnemonics.setLocalizedText(casesLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.casesLabel.text_1")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(resultsLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.resultsLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(uncheckButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.uncheckButton.text")); // NOI18N
uncheckButton.setMargin(new java.awt.Insets(2, 6, 2, 6));
uncheckButton.setMaximumSize(new java.awt.Dimension(84, 23));
uncheckButton.setMinimumSize(new java.awt.Dimension(84, 23));
uncheckButton.setPreferredSize(new java.awt.Dimension(84, 23));
uncheckButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
uncheckButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(checkButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.checkButton.text")); // NOI18N
checkButton.setMaximumSize(new java.awt.Dimension(84, 23));
checkButton.setMinimumSize(new java.awt.Dimension(84, 23));
checkButton.setPreferredSize(new java.awt.Dimension(84, 23));
checkButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
checkButtonActionPerformed(evt);
}
});
toolDescriptionTextArea.setEditable(false);
toolDescriptionTextArea.setBackground(new java.awt.Color(240, 240, 240));
toolDescriptionTextArea.setColumns(20);
toolDescriptionTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N
toolDescriptionTextArea.setLineWrap(true);
toolDescriptionTextArea.setRows(3);
toolDescriptionTextArea.setText(org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text")); // NOI18N
toolDescriptionTextArea.setWrapStyleWord(true);
toolDescriptionTextArea.setFocusable(false);
toolDescriptionScrollPane.setViewportView(toolDescriptionTextArea);
resultsScrollPane.setMinimumSize(new java.awt.Dimension(100, 40));
resultsScrollPane.setPreferredSize(new java.awt.Dimension(200, 100));
resultsScrollPane.setRequestFocusEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.cancelButton.text")); // NOI18N
cancelButton.setEnabled(false);
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
warningLabel.setForeground(new java.awt.Color(200, 0, 0));
org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.warningLabel.text")); // NOI18N
warningLabel.setFocusable(false);
org.openide.awt.Mnemonics.setLocalizedText(exportButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.exportButton.text")); // NOI18N
exportButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
exportButton.setMaximumSize(new java.awt.Dimension(84, 23));
exportButton.setMinimumSize(new java.awt.Dimension(84, 23));
exportButton.setPreferredSize(new java.awt.Dimension(84, 23));
exportButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
exportButtonActionPerformed(evt);
}
});
resultsCountLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
org.openide.awt.Mnemonics.setLocalizedText(resultsCountLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.resultsCountLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(viewErrorsButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.viewErrorsButton.text")); // NOI18N
viewErrorsButton.setEnabled(false);
viewErrorsButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
viewErrorsButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(pickCasesButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.pickCasesButton.text_1")); // NOI18N
pickCasesButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pickCasesButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(exactRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(substringRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(regexRadioButton))
.addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 570, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(toolDescriptionScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 84, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(casesLabel)
.addComponent(casesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(searchButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pickCasesButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(uncheckButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(checkButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(resultsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(resultsCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(resultsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(viewErrorsButton)
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 695, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(exportButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(cancelButton, javax.swing.GroupLayout.DEFAULT_SIZE, 87, Short.MAX_VALUE))))))
.addContainerGap())
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(196, 196, 196)
.addComponent(searchProgressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 769, Short.MAX_VALUE)
.addGap(108, 108, 108)))
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {checkButton, uncheckButton});
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(regexRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(exactRadioButton)
.addComponent(substringRadioButton))))
.addComponent(toolDescriptionScrollPane))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(casesLabel)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(resultsLabel)
.addComponent(resultsCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(resultsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(casesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(uncheckButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(checkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(exportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(searchButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(viewErrorsButton)
.addComponent(pickCasesButton))
.addComponent(cancelButton))
.addContainerGap())
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap(433, Short.MAX_VALUE)
.addComponent(searchProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap()))
);
}// </editor-fold>//GEN-END:initComponents
@Messages({
"MultiCaseKeywordSearchPanel.warningText.noCases=At least one case must be selected to perform a search.",
"MultiCaseKeywordSearchPanel.warningText.emptySearch=You must enter something to search for in the text field."
})
/**
* perform a search if the previous search is done or no previous search has
* occured
*/
private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed
if (null == searchThread) {
Collection<String> cases = getCases(true);
String searchString = keywordTextField.getText();
if (cases.isEmpty()) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_warningText_noCases());
} else if (searchString.isEmpty()) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_warningText_emptySearch());
} else {
//perform the search
warningLabel.setText("");
allSearchHits = new ArrayList<>();
searchExceptions = new ArrayList<>();
searchEnabled(false);
exportButton.setEnabled(false);
outline.setRowSelectionAllowed(false);
SearchQuery kwsQuery = new SearchQuery(getQueryType(), searchString);
em.setRootContext(PLEASE_WAIT_NODE);
resultsCountLabel.setText("");
searchThread = new SearchThread(cases, kwsQuery);
searchThread.registerWithSearcher(MultiCaseKeywordSearchPanel.this);
searchThread.start();
}
}
}//GEN-LAST:event_searchButtonActionPerformed
/**
* Get the cases which match the selected status specified by
* isSelected.
*
* @param isSelected true to get selected cases false to get
* unselected cases
*
* @return cases the cases that match the selected status of
* isSelected
*/
private Collection<String> getCases(boolean isSelected) {
Collection<String> cases = new HashSet<>();
for (Component comp : casesPanel.getComponents()) {
if (comp instanceof JCheckBox) {
if (((AbstractButton) comp).isSelected() == isSelected) {
cases.add(((AbstractButton) comp).getText());
}
}
}
return cases;
}
/**
* Get the type of Query which was selected by the user.
*
* @return one of the values of the QueryType enum
*/
private QueryType getQueryType() {
String queryTypeText = "";
Enumeration<AbstractButton> buttonGroup = searchTypeGroup.getElements();
while (buttonGroup.hasMoreElements()) {
AbstractButton dspButton = buttonGroup.nextElement();
if (dspButton.isSelected()) {
queryTypeText = dspButton.getText();
break;
}
}
if (queryTypeText.equals(substringRadioButton.getText())) {
return QueryType.SUBSTRING;
} else if (queryTypeText.equals(regexRadioButton.getText())) {
return QueryType.REGEX;
} else {
//default to Exact match
return QueryType.EXACT_MATCH;
}
}
/**
* Set the column widths to have their width influenced by the width of the
* content in them for up to the first hundred rows.
*/
private void setColumnWidths() {
int widthLimit = 1000;
int margin = 4;
int padding = 8;
for (int col = 0; col < outline.getColumnCount(); col++) {
int width = 115; //min initial width for columns
int rowsToResize = Math.min(outline.getRowCount(), 100);
for (int row = 0; row < rowsToResize; row++) {
if (outline.getValueAt(row, col) != null) {
TableCellRenderer renderer = outline.getCellRenderer(row, col);
Component comp = outline.prepareRenderer(renderer, row, col);
width = Math.max(comp.getPreferredSize().width, width);
}
}
width += 2 * margin + padding;
width = Math.min(width, widthLimit);
TableColumn column = outline.getColumnModel().getColumn(outline.convertColumnIndexToModel(col));
column.setPreferredWidth(width);
}
resultsScrollPane.setPreferredSize(new Dimension(outline.getPreferredSize().width, resultsScrollPane.getPreferredSize().height));
}
/**
* Un-select all check boxes in the cases list
*
* @param evt ignored
*/
private void uncheckButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uncheckButtonActionPerformed
allCheckboxesSetSelected(false);
}//GEN-LAST:event_uncheckButtonActionPerformed
/**
* Select all check boxes in the cases list
*
* @param evt ignored
*/
private void checkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkButtonActionPerformed
allCheckboxesSetSelected(true);
}//GEN-LAST:event_checkButtonActionPerformed
/**
* Cancel the current multi-case search which is being performed.
*
* @param evt ignored
*/
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
cancelSearch();
}//GEN-LAST:event_cancelButtonActionPerformed
/**
* Cancel the current multi-case search which is being performed.
*/
@Messages({
"MultiCaseKeywordSearchPanel.searchThread.cancellingText=Cancelling search"})
private void cancelSearch() {
if (null != searchThread) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_searchThread_cancellingText());
searchThread.interrupt();
}
}
@Messages({"MultiCaseKeywordSearchPanel.searchResultsExport.csvExtensionFilterlbl=Comma Separated Values File (csv)",
"MultiCaseKeywordSearchPanel.searchResultsExport.featureName=Search Results Export",
"MultiCaseKeywordSearchPanel.searchResultsExport.failedExportMsg=Export of search results failed"
})
/**
* Export the currently displayed search results to a file specified by the
* user with data saved in comma seperated format.
*/
private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportButtonActionPerformed
JFileChooser chooser = new JFileChooser();
final String EXTENSION = "csv"; //NON-NLS
FileNameExtensionFilter csvFilter = new FileNameExtensionFilter(
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_csvExtensionFilterlbl(), EXTENSION);
chooser.setFileFilter(csvFilter);
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setName("Choose file to export results to");
chooser.setMultiSelectionEnabled(false);
int returnVal = chooser.showSaveDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File selFile = chooser.getSelectedFile();
if (selFile == null) {
JOptionPane.showMessageDialog(this,
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_failedExportMsg(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.WARNING_MESSAGE);
LOGGER.warning("Selected file was null, when trying to export search results");
return;
}
String fileAbs = selFile.getAbsolutePath();
if (!fileAbs.endsWith("." + EXTENSION)) {
fileAbs = fileAbs + "." + EXTENSION;
selFile = new File(fileAbs);
}
saveResultsAsTextFile(selFile);
}
}//GEN-LAST:event_exportButtonActionPerformed
private void viewErrorsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewErrorsButtonActionPerformed
displaySearchErrors();
}//GEN-LAST:event_viewErrorsButtonActionPerformed
private void pickCasesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pickCasesButtonActionPerformed
if(currentSelections != null) {
caseSelectionDialog.setNodeSelections(currentSelections);
}
caseSelectionDialog.setVisible(true);
}//GEN-LAST:event_pickCasesButtonActionPerformed
private void substringRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_substringRadioButtonActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_substringRadioButtonActionPerformed
/**
* Set the user interface elements to reflect whether the search feature is
* currently enabled or disabled.
*
* @param canSearch True if the search feature should be enabled, false if
* it should be disabled.
*/
private void searchEnabled(boolean canSearch) {
searchButton.setEnabled(canSearch);
cancelButton.setEnabled(!canSearch);
viewErrorsButton.setEnabled(canSearch);
viewErrorsButton.setVisible(!searchExceptions.isEmpty());
}
@Messages({"# {0} - file name",
"MultiCaseKeywordSearchPanel.searchResultsExport.fileExistPrompt=File {0} exists, overwrite?",
"# {0} - file name",
"MultiCaseKeywordSearchPanel.searchResultsExport.exportMsg=Search results exported to {0}"
})
/**
* Saves the results to the file specified
*/
private void saveResultsAsTextFile(File resultsFile) {
if (resultsFile.exists()) {
//if the file already exists ask the user how to proceed
boolean shouldWrite = JOptionPane.showConfirmDialog(null,
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_fileExistPrompt(resultsFile.getName()),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)
== JOptionPane.YES_OPTION;
if (!shouldWrite) {
return;
}
}
try {
BufferedWriter resultsWriter;
resultsWriter = new BufferedWriter(new FileWriter(resultsFile));
int col = 0;
//write headers
while (col < outline.getColumnCount()) {
resultsWriter.write(outline.getColumnName(col));
col++;
if (col < outline.getColumnCount()) {
resultsWriter.write(",");
}
}
resultsWriter.write(System.lineSeparator());
//write data
Children resultsChildren = em.getRootContext().getChildren();
for (int row = 0; row < resultsChildren.getNodesCount(); row++) {
col = 0;
while (col < outline.getColumnCount()) {
if (outline.getValueAt(row, col) instanceof Node.Property) {
resultsWriter.write(((Node.Property) outline.getValueAt(row, col)).getValue().toString());
} else {
resultsWriter.write(outline.getValueAt(row, col).toString());
}
col++;
if (col < outline.getColumnCount()) {
resultsWriter.write(",");
}
}
resultsWriter.write(System.lineSeparator());
}
resultsWriter.flush();
resultsWriter.close();
setColumnWidths();
JOptionPane.showMessageDialog(
WindowManager.getDefault().getMainWindow(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_exportMsg(resultsFile.getName()),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.INFORMATION_MESSAGE);
} catch (IllegalAccessException | IOException | InvocationTargetException ex) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_failedExportMsg(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.WARNING_MESSAGE);
LOGGER.log(Level.WARNING, "Export of search results failed unable to write results csv file", ex);
}
}
/**
* Set the selected status of all checkboxes.
*
* @param selected true if all checkboxes should be selected, false if no
* check boxes should be selected.
*/
private void allCheckboxesSetSelected(boolean selected) {
for (Component comp : casesPanel.getComponents()) {
if (comp instanceof JCheckBox) {
((AbstractButton) comp).setSelected(selected);
}
}
}
/**
* Ask the user if they want to continue their search while this window is
* closed. Cancels the current search if they select no.
*/
@Messages({
"MultiCaseKeywordSearchPanel.continueSearch.text=A search is currently being performed. "
+ "Would you like the search to continue in the background while the search window is closed?",
"MultiCaseKeywordSearchPanel.continueSearch.title=Closing multi-case search"
})
void closeSearchPanel() {
if (cancelButton.isEnabled()) {
boolean shouldContinueSearch = JOptionPane.showConfirmDialog(null,
Bundle.MultiCaseKeywordSearchPanel_continueSearch_text(),
Bundle.MultiCaseKeywordSearchPanel_continueSearch_title(),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)
== JOptionPane.YES_OPTION;
if (!shouldContinueSearch) {
cancelSearch();
}
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton cancelButton;
private javax.swing.JLabel casesLabel;
private javax.swing.JPanel casesPanel;
private javax.swing.JScrollPane casesScrollPane;
private javax.swing.JButton checkButton;
private javax.swing.JRadioButton exactRadioButton;
private javax.swing.JButton exportButton;
private javax.swing.JTextField keywordTextField;
private javax.swing.JButton pickCasesButton;
private javax.swing.JRadioButton regexRadioButton;
private javax.swing.JLabel resultsCountLabel;
private javax.swing.JLabel resultsLabel;
private javax.swing.JScrollPane resultsScrollPane;
private javax.swing.JButton searchButton;
private javax.swing.JProgressBar searchProgressBar;
private javax.swing.ButtonGroup searchTypeGroup;
private javax.swing.JRadioButton substringRadioButton;
private javax.swing.JScrollPane toolDescriptionScrollPane;
private javax.swing.JTextArea toolDescriptionTextArea;
private javax.swing.JButton uncheckButton;
private javax.swing.JButton viewErrorsButton;
private javax.swing.JLabel warningLabel;
// End of variables declaration//GEN-END:variables
/*
* A thread that performs a keyword search of cases
*/
private final class SearchThread extends Thread {
private final Collection<String> caseNames;
private final SearchQuery searchQuery;
private final MultiCaseSearcher multiCaseSearcher = new MultiCaseSearcher();
/**
* Constructs a thread that performs a keyword search of cases
*
* @param caseNames The names of the cases to search.
* @param query The keyword search query to perform.
*/
private SearchThread(Collection<String> caseNames, SearchQuery searchQuery) {
this.caseNames = caseNames;
this.searchQuery = searchQuery;
}
/**
* Register an object with the MultiCaseSearcher eventBus so that the
* object's subscribe methods can receive results.
*
* @param object the object to register with the MultiCaseSearcher
*/
private void registerWithSearcher(Object object) {
multiCaseSearcher.registerWithEventBus(object);
}
/**
* Unregister an object with the MultiCaseSearcher so that the
* object's subscribe methods no longer receive results.
*
* @param object the object to unregister with the MultiCaseSearcher
*/
private void unregisterWithSearcher(Object object) {
multiCaseSearcher.unregisterWithEventBus(object);
}
@Override
public void interrupt() {
super.interrupt();
//in case it is running a method which causes InterruptedExceptions to be ignored
multiCaseSearcher.stopMultiCaseSearch();
}
@Override
public void run() {
multiCaseSearcher.performKeywordSearch(caseNames, searchQuery, new MultiCaseKeywordSearchProgressIndicator(searchProgressBar));
}
}
}

View File

@ -0,0 +1,167 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import org.sleuthkit.autopsy.progress.ProgressIndicator;
/**
* A progress indicator that updates a JProgressBar.
*/
final class MultiCaseKeywordSearchProgressIndicator implements ProgressIndicator {
private final JProgressBar progress;
/**
* Construct a new JProgressIndicator
*
* @param progressBar the JProgressBar you want this indicator to update
*/
MultiCaseKeywordSearchProgressIndicator(JProgressBar progressBar) {
progress = progressBar;
progress.setStringPainted(true);
}
/**
* Start showing progress in the progress bar.
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
* @param max The total number of work units to be completed.
*/
@Override
public void start(String message, int max) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(false);
progress.setMinimum(0);
progress.setString(message); //the message
progress.setValue(0);
progress.setMaximum(max);
progress.setVisible(true);
});
}
/**
* Start showing progress in the progress bar.
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
*/
@Override
public void start(String message) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(true);
progress.setMinimum(0);
progress.setString(message);
progress.setValue(0);
progress.setVisible(true);
});
}
/**
* Switches the progress indicator to indeterminate mode (the total number
* of work units to be completed is unknown).
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
*/
@Override
public void switchToIndeterminate(String message) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(true);
progress.setString(message);
});
}
/**
* Switches the progress indicator to determinate mode (the total number of
* work units to be completed is known).
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
* @param current The number of work units completed so far.
* @param max The total number of work units to be completed.
*/
@Override
public void switchToDeterminate(String message, int current, int max) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(false);
progress.setMinimum(0);
progress.setString(message);
progress.setValue(current);
progress.setMaximum(max);
});
}
/**
* Updates the progress indicator with a progress message.
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
*/
@Override
public void progress(String message) {
SwingUtilities.invokeLater(() -> {
progress.setString(message);
});
}
/**
* Updates the progress indicator with the number of work units completed so
* far when in determinate mode (the total number of work units to be
* completed is known).
*
* @param current Number of work units completed so far.
*/
@Override
public void progress(int current) {
SwingUtilities.invokeLater(() -> {
progress.setValue(current);
});
}
/**
* Updates the progress indicator with a progress message and the number of
* work units completed so far when in determinate mode (the total number of
* work units to be completed is known).
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
* @param current Number of work units completed so far.
*/
@Override
public void progress(String message, int current) {
SwingUtilities.invokeLater(() -> {
progress.setString(message);
progress.setValue(current);
});
}
/**
* Finishes the progress indicator when the task is completed.
*/
@Override
public void finish() {
SwingUtilities.invokeLater(() -> {
progress.setVisible(false);
});
}
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1002, 444]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="902" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="444" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Form>

View File

@ -0,0 +1,140 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import java.awt.BorderLayout;
import java.awt.Component;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.openide.windows.Mode;
@TopComponent.Description(
preferredID = "MultiCaseKeywordSearchTopComponent",
persistenceType = TopComponent.PERSISTENCE_NEVER
)
@TopComponent.Registration(mode = "multiCaseKeywordSearch", openAtStartup = false)
@Messages({
"CTL_MultiCaseKeywordSearchTopComponentAction=Multi-case Keyword Search",
"CTL_MultiCaseKeywordSearchTopComponent=Multi-case Keyword Search"})
/**
* A top level component for the multi case keyword search feature.
*/
final class MultiCaseKeywordSearchTopComponent extends TopComponent {
public final static String PREFERRED_ID = "MultiCaseKeywordSearchTopComponent"; // NON-NLS
private static final long serialVersionUID = 1L;
private static boolean topComponentInitialized = false;
@Messages({
"MultiCaseKeywordSearchTopComponent.exceptionMessage.failedToCreatePanel=Failed to create Multi-case Keyword Search panel.",})
/**
* Open the top level component if it is not already open, if it is open
* bring it to the front and select it.
*/
static void openTopComponent() {
final MultiCaseKeywordSearchTopComponent tc = (MultiCaseKeywordSearchTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) {
if (tc.isOpened() == false) {
topComponentInitialized = true;
Mode mode = WindowManager.getDefault().findMode("multiCaseKeywordSearch"); // NON-NLS
if (mode != null) {
mode.dockInto(tc);
}
tc.open();
}
tc.toFront();
tc.requestActive();
}
}
/**
* Close the top level componet.
*/
static void closeTopComponent() {
if (topComponentInitialized) {
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) {
try {
tc.close();
} catch (Exception e) {
}
}
}
}
@Messages({"MultiCaseKeywordSearchTopComponent.name.text=Multi-case Keyword Search"})
/**
* Construct a new "MultiCaseKeywordSearchTopComponent.
*/
MultiCaseKeywordSearchTopComponent() {
initComponents();
setName(Bundle.MultiCaseKeywordSearchTopComponent_name_text());
setDisplayName(Bundle.MultiCaseKeywordSearchTopComponent_name_text());
setToolTipText(Bundle.MultiCaseKeywordSearchTopComponent_name_text());
setSize(this.getPreferredSize());
setLayout(new BorderLayout());
MultiCaseKeywordSearchPanel searchPanel = new MultiCaseKeywordSearchPanel();
searchPanel.setSize(searchPanel.getPreferredSize());
searchPanel.setVisible(true);
add(searchPanel);
}
@Override
public void componentOpened() {
super.componentOpened();
WindowManager.getDefault().setTopComponentFloating(this, true);
}
@Override
public boolean canClose() {
for (Component component : getComponents()) {
if (component instanceof MultiCaseKeywordSearchPanel) {
((MultiCaseKeywordSearchPanel) component).closeSearchPanel();
}
}
return super.canClose();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setPreferredSize(new java.awt.Dimension(1002, 444));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 902, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 444, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,845 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import com.google.common.eventbus.EventBus;
import java.io.File;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.response.CoreAdminResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.CursorMarkParams;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.UNCPathUtilities;
import org.sleuthkit.autopsy.keywordsearch.Server;
import org.sleuthkit.autopsy.progress.ProgressIndicator;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* Performs keyword searches across multiple cases
*/
final class MultiCaseSearcher {
private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS
private static final String SEARCH_COMPLETE_MESSAGE = "SEARCH_COMPLETE";
private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS
private static final int CASE_DIR_READ_LOCK_TIMEOUT_HOURS = 12; //NON-NLS
private static final String SOLR_SERVER_URL_FORMAT_STRING = "http://%s:%s/solr"; //NON-NLS
private static final String SOLR_CORE_URL_FORMAT_STRING = "http://%s:%s/solr/%s"; //NON-NLS
private final static String SOLR_METADATA_FILE_NAME = "SolrCore.properties"; //NON-NLS
private static final String SOLR_CORE_NAME_XPATH = "/SolrCores/Core/CoreName/text()"; //NON-NLS
private static final String TEXT_INDEX_NAME_XPATH = "/SolrCores/Core/TextIndexPath/text()"; //NON-NLS
private static final String SOLR_CORE_INSTANCE_PATH_PROPERTY = "instanceDir"; //NON-NLS
private static final String SOLR_CONFIG_SET_NAME = "AutopsyConfig"; //NON-NLS
private static final int MAX_RESULTS_PER_CURSOR_MARK = 512;
private static final String SOLR_DOC_ID_FIELD = Server.Schema.ID.toString(); //NON-NLS
private static final String SOLR_DOC_CONTENT_STR_FIELD = Server.Schema.CONTENT_STR.toString(); //NON-NLS
private static final String SOLR_DOC_CHUNK_SIZE_FIELD = Server.Schema.CHUNK_SIZE.toString(); //NON-NLS
private static final String SOLR_DOC_ID_PARTS_SEPARATOR = "_";
private static final Logger logger = Logger.getLogger(MultiCaseSearcher.class.getName());
private final EventBus eventBus = new EventBus("MultiCaseSearcherEventBus");
private static final UNCPathUtilities pathUtils = new UNCPathUtilities();
private volatile boolean searchStopped = true;
MultiCaseSearcher() {
}
static String getSearchCompleteMessage() {
return SEARCH_COMPLETE_MESSAGE;
}
/**
*
* Performs keyword searches across multiple cases
*
* @param caseNames The names of the cases to search.
* @param query The keyword search query to perform.
* @param progressIndicator A progrss indicator for the search.
*
* @return The search results.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
@NbBundle.Messages({
"MultiCaseSearcher.progressMessage.findingCases=Finding selected cases",
"MultiCaseSearcher.progressMessage.creatingSolrQuery=Creating search query for Solr server",
"# {0} - total cases",
"MultiCaseSearcher.progressMessage.startingCaseSearches=Searching {0} case(s)",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.acquiringSharedLockForCase=Acquiring shared lock for \"{0}\" ({1} of {2} case(s))",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.loadingSolrCoreForCase=Loading Solr core for \"{0}\" ({1} of {2} case(s))",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.openingCaseDbForCase=Opening case database for \"{0}\" ({1} of {2} case(s))",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.executingSolrQueryForCase=Getting keyword hits for \"{0}\" ({1} of {2} case(s))",
"# {0} - case directory path",
"MultiCaseSearcher.exceptionMessage.failedToGetCaseDirReadlock=Failed to obtain read lock for case directory at {0}",
"MultiCaseSearcher.exceptionMessage.cancelledMessage=Search cancelled"
})
void performKeywordSearch(final Collection<String> caseNames, final SearchQuery query, final ProgressIndicator progressIndicator) {
progressIndicator.start(Bundle.MultiCaseSearcher_progressMessage_findingCases());
try {
searchStopped = false; //mark the search as started
final List<MultiCaseMetadata> caseMetadata = getMultiCaseMetadata(caseNames);
checkForCancellation();
//eventBus.post("number of cases to search determined");
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_creatingSolrQuery());
final SolrQuery solrQuery = createSolrQuery(query);
checkForCancellation();
final int totalCases = caseMetadata.size();
int caseCounter = 1;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_startingCaseSearches(totalCases));
int totalSteps = 5;
progressIndicator.switchToDeterminate(Bundle.MultiCaseSearcher_progressMessage_startingCaseSearches(totalCases), 0, totalCases * totalSteps);
int caseNumber = 0;
for (MultiCaseMetadata aCase : caseMetadata) {
CaseMetadata metadata = aCase.getCaseMetadata();
String caseName = metadata.getCaseDisplayName();
SleuthkitCase caseDatabase = null;
int stepsCompleted = 0;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_acquiringSharedLockForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
try (CoordinationService.Lock caseDirReadLock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.CASES, aCase.getCaseMetadata().getCaseDirectory(), CASE_DIR_READ_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS)) {
if (null == caseDirReadLock) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDirReadlock(aCase.getCaseMetadata().getCaseDirectory()));
}
checkForCancellation();
++stepsCompleted;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_loadingSolrCoreForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
final HttpSolrServer solrServer = loadSolrCoreForCase(aCase);
checkForCancellation();
++stepsCompleted;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_openingCaseDbForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
caseDatabase = openCase(aCase);
checkForCancellation();
++stepsCompleted;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_executingSolrQueryForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
eventBus.post(executeQuery(solrServer, solrQuery, caseDatabase, aCase));
++stepsCompleted;
progressIndicator.progress(stepsCompleted + caseNumber * totalSteps);
++caseCounter;
} catch (CoordinationService.CoordinationServiceException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDirReadlock(aCase.getCaseMetadata().getCaseDirectory()), ex);
} catch (MultiCaseSearcherException exception) {
logger.log(Level.INFO, "Exception encountered while performing multi-case keyword search", exception);
eventBus.post(exception);
} finally {
if (null != caseDatabase) {
closeCase(caseDatabase);
}
}
caseNumber++;
}
} catch (InterruptedException exception) {
logger.log(Level.INFO, Bundle.MultiCaseSearcher_exceptionMessage_cancelledMessage(), exception);
eventBus.post(exception);
} catch (MultiCaseSearcherException exception) {
logger.log(Level.WARNING, "Exception encountered while performing multi-case keyword search", exception);
eventBus.post(new InterruptedException("Exception encountered while performing multi-case keyword search"));
eventBus.post(exception);
} finally {
progressIndicator.finish();
eventBus.post(SEARCH_COMPLETE_MESSAGE);
}
}
/**
* Gets metadata for the cases associated with one or more with the search
*
* @param caseNames The names of the cases to search.
*
* @return The metadata for the cases.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
private List<MultiCaseMetadata> getMultiCaseMetadata(final Collection<String> caseNames) throws MultiCaseSearcherException, InterruptedException {
final Map<Path, String> casesToCasePaths = getCaseDirectories(caseNames);
checkForCancellation();
final List<MultiCaseMetadata> cases = new ArrayList<>();
for (Map.Entry<Path, String> entry : casesToCasePaths.entrySet()) {
final Path caseDirectoryPath = entry.getKey();
final CaseMetadata caseMetadata = getCaseMetadata(caseDirectoryPath);
checkForCancellation();
final TextIndexMetadata textIndexMetadata = getTextIndexMetadata(caseDirectoryPath);
checkForCancellation();
cases.add(new MultiCaseMetadata(caseMetadata, textIndexMetadata));
}
return cases;
}
/**
* Uses coordination service data to find the case directories of the cases.
*
* @param caseNames The names of the cases.
*
* @return A mapping of case directory paths to case names,
* possibly empty.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
@NbBundle.Messages({
"# {0} - host", "# {1} - port", "MultiCaseSearcher.exceptionMessage.failedToQueryCoordinationServer=Failed to obtain read lock for case directory at {0}:{1}",
"# {0} - list of cases", "MultiCaseSearcher.exceptionMessage.noCasesFound=No cases found for: {0}"
})
private Map<Path, String> getCaseDirectories(final Collection<String> caseNames) throws MultiCaseSearcherException, InterruptedException {
final Map<Path, String> casePathToCaseMap = new HashMap<>();
final List<String> caseNodeNames;
try {
CoordinationService coordinationService = CoordinationService.getInstance();
caseNodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES);
} catch (CoordinationService.CoordinationServiceException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToQueryCoordinationServer(UserPreferences.getIndexingServerHost(), UserPreferences.getIndexingServerPort()), ex);
}
for (String nodeName : caseNodeNames) {
/*
* Find the case directory paths by
* selecting each coordination service case directory lock node path
* that has the case name in the path.
*/
checkForCancellation();
final Path caseDirectoryPath = Paths.get(nodeName);
boolean contansSlash = caseDirectoryPath.toString().contains("\\") || caseDirectoryPath.toString().contains("//");
if (!contansSlash) {
/*
* Skip case name lock nodes.
*/
continue;
}
final String fileName = caseDirectoryPath.getFileName().toString();
if (fileName.equals(CASE_AUTO_INGEST_LOG_NAME) || fileName.endsWith(RESOURCES_LOCK_SUFFIX)) {
/*
* Skip case auto ingest log and case resource lock nodes.
*/
continue;
}
for (String aCase : caseNames) {
checkForCancellation();
final String normalizedCaseName = aCase.toUpperCase();
if (fileName.contains(normalizedCaseName)) {
logger.log(Level.INFO, "Match found: Case node name {0} contains case name {1}", new Object[]{nodeName, normalizedCaseName});
try {
Path realCaseDirectoryPath = caseDirectoryPath.toRealPath(LinkOption.NOFOLLOW_LINKS);
logger.log(Level.INFO, "Case directory path {0} resolves to real path {1}", new Object[]{caseDirectoryPath, realCaseDirectoryPath});
final File caseDirectory = realCaseDirectoryPath.toFile();
if (caseDirectory.exists()) {
casePathToCaseMap.put(realCaseDirectoryPath, aCase);
} else {
logger.log(Level.SEVERE, String.format("Case directory %s does NOT exist", caseDirectoryPath));
}
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Case directory path %s does NOT resolve to a real path", caseDirectoryPath), ex);
}
break;
}
}
}
if (casePathToCaseMap.isEmpty()) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_noCasesFound(StringUtils.join(caseNames, ',')));
}
return casePathToCaseMap;
}
/**
* Gets the metadata for a case from the case metadata file in a given case
* directory.
*
* @param caseDirectoryPath A case directory path.
*
* @return The case metadata.
*
* @throws MultiCaseSearcherException
*/
@NbBundle.Messages({
"# {0} - case directory", "MultiCaseSearcher.exceptionMessage.failedToFindCaseMetadata=Failed to find case metadata file in {0}",
"# {0} - case directory", "MultiCaseSearcher.exceptionMessage.failedToParseCaseMetadata=Failed to parse case file metadata in {0}"
})
private static CaseMetadata getCaseMetadata(Path caseDirectoryPath) throws MultiCaseSearcherException {
CaseMetadata caseMetadata = null;
final File[] caseFiles = caseDirectoryPath.toFile().listFiles();
for (File file : caseFiles) {
final String fileName = file.getName().toLowerCase();
if (fileName.endsWith(CaseMetadata.getFileExtension())) {
try {
return new CaseMetadata(file.toPath());
} catch (CaseMetadata.CaseMetadataException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToParseCaseMetadata(caseDirectoryPath), ex);
}
}
}
if (null == caseMetadata) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToFindCaseMetadata(caseDirectoryPath));
}
return caseMetadata;
}
/**
* Gets the text index metadata from the Solr.properties file in a given
* case directory.
*
* @param caseDirectoryPath A case directory path.
*
* @return The text index metadata.
*
* @throws MultiCaseSearcherException
*/
@NbBundle.Messages({
"# {0} - file name", "# {1} - case directory", "MultiCaseSearcher.exceptionMessage.missingSolrPropertiesFile=Missing {0} file in {1}",
"# {0} - file name", "# {1} - case directory", "MultiCaseSearcher.exceptionMessage.solrPropertiesFileParseError=Error parsing {0} file in {1}",})
private static TextIndexMetadata getTextIndexMetadata(Path caseDirectoryPath) throws MultiCaseSearcherException {
final Path solrMetaDataFilePath = Paths.get(caseDirectoryPath.toString(), SOLR_METADATA_FILE_NAME);
final File solrMetaDataFile = solrMetaDataFilePath.toFile();
if (!solrMetaDataFile.exists() || !solrMetaDataFile.canRead()) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_missingSolrPropertiesFile(SOLR_METADATA_FILE_NAME, caseDirectoryPath));
}
try {
final DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final Document doc = docBuilder.parse(solrMetaDataFile);
final XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression xPathExpr = xPath.compile(SOLR_CORE_NAME_XPATH);
final String solrCoreName = (String) xPathExpr.evaluate(doc, XPathConstants.STRING);
xPathExpr = xPath.compile(TEXT_INDEX_NAME_XPATH);
final String relativeTextIndexPath = (String) xPathExpr.evaluate(doc, XPathConstants.STRING);
Path textIndexPath = caseDirectoryPath.resolve(relativeTextIndexPath);
textIndexPath = textIndexPath.getParent(); // Remove "index" path component
final String textIndexUNCPath = pathUtils.convertPathToUNC(textIndexPath.toString());
return new TextIndexMetadata(caseDirectoryPath, solrCoreName, textIndexUNCPath);
} catch (ParserConfigurationException | SAXException | XPathExpressionException | IOException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_solrPropertiesFileParseError(SOLR_METADATA_FILE_NAME, caseDirectoryPath), ex);
}
}
/**
* Converts a keyword search query into a Solr query.
*
* @param searchQuery A keyword search query.
*
* @return A Solr query.
*/
private static SolrQuery createSolrQuery(SearchQuery searchQuery) {
final SolrQuery solrQuery = new SolrQuery();
solrQuery.setQuery(searchQuery.getSearchTerm());
solrQuery.setRows(MAX_RESULTS_PER_CURSOR_MARK);
/*
* Note that setting the sort order is necessary for cursor based paging
* to work.
*/
solrQuery.setSort(SolrQuery.SortClause.asc(SOLR_DOC_ID_FIELD));
solrQuery.setFields(SOLR_DOC_ID_FIELD, SOLR_DOC_CHUNK_SIZE_FIELD, SOLR_DOC_CONTENT_STR_FIELD);
return solrQuery;
}
/**
* Connects to the Solr server and loads the Solr core for a given case.
*
* @param aCase
*
* @return A Solr server client object that can be used for executing
* queries of the specified text index.
*
* MultiCaseSearcherException
*
* @throws InterruptedException
*/
@NbBundle.Messages({
"# {0} - connection info",
"# {1} - case name",
"# {2} - case directory",
"MultiCaseSearcher.exceptionMessage.errorLoadingCore=Error connecting to Solr server and loading core (URL: {0}) for case {1} in {2}"
})
private HttpSolrServer loadSolrCoreForCase(MultiCaseMetadata aCase) throws MultiCaseSearcherException, InterruptedException {
TextIndexMetadata textIndexMetadata = aCase.getTextIndexMetadata();
Server.IndexingServerProperties indexServer = Server.getMultiUserServerProperties(aCase.getCaseMetadata().getCaseDirectory());
final String serverURL = String.format(SOLR_SERVER_URL_FORMAT_STRING, indexServer.getHost(), indexServer.getPort());
try {
/*
* Connect to the Solr server.
*/
final HttpSolrServer solrServer = new HttpSolrServer(serverURL);
CoreAdminRequest statusRequest = new CoreAdminRequest();
statusRequest.setCoreName(null);
statusRequest.setAction(CoreAdminParams.CoreAdminAction.STATUS);
statusRequest.setIndexInfoNeeded(false);
checkForCancellation();
statusRequest.process(solrServer);
checkForCancellation();
/*
* Load the core for the text index if it is not already loaded.
*/
CoreAdminResponse response = CoreAdminRequest.getStatus(textIndexMetadata.getSolrCoreName(), solrServer);
if (null == response.getCoreStatus(textIndexMetadata.getSolrCoreName()).get(SOLR_CORE_INSTANCE_PATH_PROPERTY)) {
CoreAdminRequest.Create loadCoreRequest = new CoreAdminRequest.Create();
loadCoreRequest.setDataDir(textIndexMetadata.getTextIndexPath());
loadCoreRequest.setCoreName(textIndexMetadata.getSolrCoreName());
loadCoreRequest.setConfigSet(SOLR_CONFIG_SET_NAME);
loadCoreRequest.setIsLoadOnStartup(false);
loadCoreRequest.setIsTransient(true);
solrServer.request(loadCoreRequest);
}
/*
* Create a server client object that can be used for executing
* queries of the specified text index.
*/
final String coreURL = String.format(SOLR_CORE_URL_FORMAT_STRING, indexServer.getHost(), indexServer.getPort(), textIndexMetadata.getSolrCoreName());
final HttpSolrServer coreServer = new HttpSolrServer(coreURL);
return coreServer;
} catch (SolrServerException | IOException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_errorLoadingCore(serverURL, aCase.getCaseMetadata().getCaseName(), textIndexMetadata.getCaseDirectoryPath()), ex);
}
}
/**
* Opens a case database.
*
* @param caseMetadata
*
* @return A case database.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
@NbBundle.Messages({
"# {0} - case_name",
"MultiCaseSearcher.exceptionMessage.failedToGetCaseDatabaseConnectionInfo=Failed to get case database connection info for case {0}",
"# {0} - PostgreSQL server host",
"# {1} - PostgreSQL server port",
"# {2} - case database name",
"# {3} - case directory",
"MultiCaseSearcher.exceptionMessage.errorOpeningCaseDatabase=Error connecting to PostgreSQL server (Host/Port: [{0}:{1}] and opening case database {2} for case at {3}"
})
private SleuthkitCase openCase(MultiCaseMetadata aCase) throws MultiCaseSearcherException, InterruptedException {
CaseDbConnectionInfo dbConnectionInfo;
try {
dbConnectionInfo = UserPreferences.getDatabaseConnectionInfo();
} catch (UserPreferencesException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDatabaseConnectionInfo(aCase.getCaseMetadata().getCaseName()), ex);
}
checkForCancellation();
final CaseMetadata caseMetadata = aCase.getCaseMetadata();
try {
return SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
} catch (UserPreferencesException | TskCoreException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_errorOpeningCaseDatabase(dbConnectionInfo.getHost(), dbConnectionInfo.getPort(), caseMetadata.getCaseDatabaseName(), caseMetadata.getCaseDirectory()), ex);
}
}
/**
* Closes a case database.
*
* @param aCase a case database.
*/
private static void closeCase(SleuthkitCase aCase) {
aCase.close();
}
/**
* Executes a keyword search searchTerm in the text index of a case.
*
* @param solrServer The Solr server.
* @param solrQuery The Solr searchTerm.
* @param caseDatabase The case database.
* @param aCase The case metadata.
*
* @return A list of search results, possibly empty.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
@NbBundle.Messages({
"# {0} - query",
"# {1} - case_name",
"MultiCaseSearcher.exceptionMessage.solrQueryError=Failed to execute query \"{0}\" on case {1}"
})
private Collection<SearchHit> executeQuery(HttpSolrServer solrServer, SolrQuery solrQuery, SleuthkitCase caseDatabase, MultiCaseMetadata aCase) throws MultiCaseSearcherException, InterruptedException {
final List<SearchHit> hits = new ArrayList<>();
final Set<Long> uniqueObjectIds = new HashSet<>();
String cursorMark = CursorMarkParams.CURSOR_MARK_START;
boolean allResultsProcessed = false;
while (!allResultsProcessed) {
checkForCancellation();
solrQuery.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
QueryResponse response;
try {
checkForCancellation();
response = solrServer.query(solrQuery, SolrRequest.METHOD.POST);
} catch (SolrServerException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_solrQueryError(solrQuery.getQuery(), aCase.getCaseMetadata().getCaseName()), ex);
}
SolrDocumentList resultDocuments = response.getResults();
for (SolrDocument resultDoc : resultDocuments) {
checkForCancellation();
String solrDocumentId = resultDoc.getFieldValue(SOLR_DOC_ID_FIELD).toString();
Long solrObjectId = parseSolrObjectId(solrDocumentId);
if (!uniqueObjectIds.contains(solrObjectId)) {
uniqueObjectIds.add(solrObjectId);
checkForCancellation();
hits.add(processHit(solrObjectId, caseDatabase, aCase));
}
}
checkForCancellation();
String nextCursorMark = response.getNextCursorMark();
if (cursorMark.equals(nextCursorMark)) {
allResultsProcessed = true;
}
cursorMark = nextCursorMark;
}
return hits;
}
/**
* Parses a Solr document id to get the Solr object id.
*
* @param solrDocumentId A Solr document id.
*
* @return A Solr object id.
*/
private static Long parseSolrObjectId(String solrDocumentId) {
/**
* A Solr document id is of the form [solr_object_id] for Content object
* metadata documents and
* [solr_object_id][SOLR_DOC_ID_PARTS_SEPARATOR][chunk_id] for Content
* object text chunk documents.
*/
final String[] solrDocumentIdParts = solrDocumentId.split(SOLR_DOC_ID_PARTS_SEPARATOR);
if (1 == solrDocumentIdParts.length) {
return Long.parseLong(solrDocumentId);
} else {
return Long.parseLong(solrDocumentIdParts[0]);
}
}
/**
* Creates a keyword search hit object for a Content object identified by
* its Solr object id.
*
* @param solrObjectId The Solr object id of a Content object.
* @param caseDatabase The case database of the case that has the
* Content.
* @param caseInfo Metadata about the case that has the content.
*
* @return
*
* @throws MultiCaseSearcherException
*/
@NbBundle.Messages({
"# {0} - Solr document id",
"# {1} - case database name",
"# {2} - case directory",
"MultiCaseSearcher.exceptionMessage.hitProcessingError=Failed to query case database for processing of Solr object id {0} of case {1} in {2}"
})
private static SearchHit processHit(Long solrObjectId, SleuthkitCase caseDatabase, MultiCaseMetadata caseInfo) throws MultiCaseSearcherException {
try {
final long objectId = getObjectIdForSolrObjectId(solrObjectId, caseDatabase);
final CaseMetadata caseMetadata = caseInfo.getCaseMetadata();
final String caseDisplayName = caseMetadata.getCaseDisplayName();
final String caseDirectoryPath = caseMetadata.getCaseDirectory();
final Content content = caseDatabase.getContentById(objectId);
final Content dataSource = content.getDataSource();
final String dataSourceName = dataSource.getName();
SearchHit.SourceType sourceType = SearchHit.SourceType.FILE;
String sourceName = "";
String sourcePath = "";
if (content instanceof AbstractFile) {
AbstractFile sourceFile = (AbstractFile) content;
sourceName = sourceFile.getName();
sourcePath = sourceFile.getLocalAbsPath();
if (null == sourcePath) {
sourceType = SearchHit.SourceType.FILE;
sourcePath = sourceFile.getUniquePath();
} else {
sourceType = SearchHit.SourceType.LOCAL_FILE;
sourceName = sourceFile.getName();
}
} else if (content instanceof BlackboardArtifact) {
BlackboardArtifact sourceArtifact = (BlackboardArtifact) content;
sourceType = SearchHit.SourceType.ARTIFACT;
BlackboardArtifact.Type artifactType = caseDatabase.getArtifactType(sourceArtifact.getArtifactTypeName());
sourceName = artifactType.getDisplayName();
Content source = sourceArtifact.getParent();
if (source instanceof AbstractFile) {
AbstractFile sourceFile = (AbstractFile) source;
sourcePath = sourceFile.getLocalAbsPath();
if (null == sourcePath) {
sourcePath = sourceFile.getUniquePath();
}
} else {
sourcePath = source.getUniquePath();
}
}
return new SearchHit(caseDisplayName, caseDirectoryPath, dataSourceName, sourceType, sourceName, sourcePath);
} catch (SQLException | TskCoreException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_hitProcessingError(solrObjectId, caseInfo.getCaseMetadata().getCaseName(), caseInfo.getCaseMetadata().getCaseDirectory()), ex);
}
}
/**
* Gets the Sleuthkit object id that corresponds to the Solr object id of
* some content.
*
* @param solrObjectId A solr object id for some content.
* @param caseDatabase The case database for the case that includes the
* content.
*
* @return The Sleuthkit object id of the content.
*
* @throws MultiCaseSearcherException
* @throws TskCoreException
* @throws SQLException
*/
private static long getObjectIdForSolrObjectId(long solrObjectId, SleuthkitCase caseDatabase) throws MultiCaseSearcherException, TskCoreException, SQLException {
if (0 < solrObjectId) {
return solrObjectId;
} else {
try (SleuthkitCase.CaseDbQuery databaseQuery = caseDatabase.executeQuery("SELECT artifact_obj_id FROM blackboard_artifacts WHERE artifact_id = " + solrObjectId)) {
final ResultSet resultSet = databaseQuery.getResultSet();
if (resultSet.next()) {
return resultSet.getLong("artifact_obj_id");
} else {
throw new TskCoreException("Empty result set getting obj_id for artifact with artifact_id =" + solrObjectId);
}
}
}
}
/**
* Checks to see if the current thread has been interrupted (i.e, the search
* has been cancelled) and throws an InterruptedException if it has been.
*
* @throws InterruptedException
*/
private void checkForCancellation() throws InterruptedException {
if (Thread.currentThread().isInterrupted() || searchStopped) {
throw new InterruptedException("Search Cancelled");
}
}
/**
* A bundle of metadata for a case.
*/
private final static class MultiCaseMetadata {
private final CaseMetadata caseMetadata;
private final TextIndexMetadata textIndexMetadata;
/**
* Contructs a bundle of metadata for a case
*
* @param caseMetadata The case metadata.
* @param textIndexMetaData The text index metadata for the case.
*/
private MultiCaseMetadata(CaseMetadata caseMetadata, TextIndexMetadata textIndexMetaData) {
this.caseMetadata = caseMetadata;
this.textIndexMetadata = textIndexMetaData;
}
/**
* Gets the case metadata.
*
* @return The case metadata.
*/
private CaseMetadata getCaseMetadata() {
return this.caseMetadata;
}
/**
* Gets the text index metadata for the case.
*
* @return The text index metadata.
*/
private TextIndexMetadata getTextIndexMetadata() {
return this.textIndexMetadata;
}
}
/**
* Bundles a case directory path, a Solr core fileName, and a text index UNC
* path.
*/
private final static class TextIndexMetadata {
private final Path caseDirectoryPath;
private final String solrCoreName;
private final String textIndexUNCPath;
/**
* Constructs an object that bundles a Solr core fileName and a text
* index UNC path.
*
* @param caseDirectoryPath The case directory path.
* @param solrCoreName The core fileName.
* @param textIndexUNCPath The text index path.
*/
private TextIndexMetadata(Path caseDirectoryPath, String solrCoreName, String textIndexUNCPath) {
this.caseDirectoryPath = caseDirectoryPath;
this.solrCoreName = solrCoreName;
this.textIndexUNCPath = textIndexUNCPath;
}
/**
* Gets the case directory path.
*
* @return The path.
*/
private Path getCaseDirectoryPath() {
return this.caseDirectoryPath;
}
/**
* Gets the Solr core fileName.
*
* @return The Solr core fileName.
*/
private String getSolrCoreName() {
return this.solrCoreName;
}
/**
*
* Gets the UNC path of the text index.
*
* @return The path.
*/
private String getTextIndexPath() {
return this.textIndexUNCPath;
}
}
/**
* Exception thrown if there is an error executing a search.
*/
static final class MultiCaseSearcherException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Constructs an instance of the exception thrown if there is an error
* executing a search.
*
* @param message The exception message.
*/
private MultiCaseSearcherException(String message) {
super(message);
}
/**
* Constructs an instance of the exception thrown if there is an error
* executing a search.
*
* @param message The exception message.
* @param cause The Throwable that caused the error.
*/
private MultiCaseSearcherException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Tell the MultiCaseSearcher that it's current search can be stopped the
* next time it checks for cancellation.
*/
void stopMultiCaseSearch() {
//This is necessary because if the interrupt occurs during CoreAdminRequest.process,
//CoreAdminRequest.getStatus, or HttpSolrServer.query the interrupt gets ignored
searchStopped = true;
}
/**
* Register an object with the MultiCaseSearcher eventBus so that it's
* subscribe methods can receive results.
*
* @param object the object to register with the eventBus
*/
void registerWithEventBus(Object object) {
eventBus.register(object);
}
/**
* Unregister an object with the MultiCaseSearcher eventBus so that it's
* subscribe methods no longer receive results.
*
* @param object the object to unregister with the eventBus
*/
void unregisterWithEventBus(Object object) {
eventBus.unregister(object);
}
}

View File

@ -0,0 +1,137 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import javax.annotation.concurrent.Immutable;
/**
* A keyword search hit from a multi-case keyword search.
*/
@Immutable
final class SearchHit {
private final String caseDisplayName;
private final String caseDirectoryPath;
private final String dataSourceName;
private final SourceType sourceType;
private final String sourceName;
private final String sourcePath;
/**
* Constructs a keyword search hit from a multi-case search.
*
* @param caseDisplayName The display name of the case where the hit occurred.
* @param caseDirectoryPath The path of the directory of the case where the hit occurred.
* @param dataSourceName The name of the data source within the case
* where the hit occurred.
* @param sourceType The type of the source content object.
* @param sourceName The name of the source, e.g., a file name, an
* artifact type name, or a report module name.
* @param sourcePath The path of the source content, or the path of
* the parent source content object for an artifact
* source.
*/
SearchHit(String caseDisplayName, String caseDirectoryPath, String dataSourceName, SourceType sourceType, String sourceName, String sourcePath) {
this.caseDisplayName = caseDisplayName;
this.caseDirectoryPath = caseDirectoryPath;
this.dataSourceName = dataSourceName;
this.sourceType = sourceType;
this.sourceName = sourceName;
this.sourcePath = sourcePath;
}
/**
* Gets the display name of the case where the hit
* occurred.
*
* @return The case display name.
*/
String getCaseDisplayName() {
return this.caseDisplayName;
}
/**
* Gets the path of the directory of the case where
* the hit occurred.
*
* @return The case directory path.
*/
String getCaseDirectoryPath() {
return this.caseDirectoryPath;
}
/**
* Gets the name of the data source within the case where the hit occurred.
*
* @return
*/
String getDataSourceName() {
return this.dataSourceName;
}
/**
* Gets the type of the source content object.
*
* @return The source type.
*/
SourceType getSourceType() {
return this.sourceType;
}
/**
* Gets the name of the source, e.g., a file name, an artifact type name, or
* a report module name.
*
* @return The source name.
*/
String getSourceName() {
return this.sourceName;
}
/**
* Gets the path of the source content, or the path of the parent source
* content object for an artifact source.
*
* @return The source object path.
*/
String getSourcePath() {
return this.sourcePath;
}
/**
* An enumeration of the source types for keyword search hits.
*/
enum SourceType {
FILE("File"),
LOCAL_FILE("Local File"),
ARTIFACT("Artifact"),
REPORT("Report");
private final String displayName;
private SourceType(String displayName) {
this.displayName = displayName;
}
String getDisplayName() {
return this.displayName;
}
}
}

View File

@ -0,0 +1,156 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.keywordsearch.Server;
/**
* A keyword search query.
*/
@Immutable
final class SearchQuery {
private static final String SEARCH_TERM_CHARS_TO_ESCAPE = "/+-&|!(){}[]^\"~*?:\\";
private static final String SOLR_DOC_CONTENT_STR_FIELD = Server.Schema.CONTENT_STR.toString(); //NON-NLS
private final String searchTerm;
/**
* Constructs a multicase keyword search query.
*
* @param queryType The query type.
* @param searchTerm The search term for the query.
*/
SearchQuery(QueryType queryType, String searchTerm) {
switch (queryType) {
case EXACT_MATCH:
this.searchTerm = prepareExactMatchSearchTerm(searchTerm);
break;
case SUBSTRING:
this.searchTerm = prepareSubstringSearchTerm(searchTerm);
break;
case REGEX:
this.searchTerm = prepareRegexSearchTerm(searchTerm);
break;
default:
this.searchTerm = searchTerm;
break;
}
}
/**
* Gets the search term.
*
* @return The query.
*/
String getSearchTerm() {
return searchTerm;
}
/**
* Escapes and quotes a given search term as required for an exact match
* search query.
*
* @param searchTerm A "raw" input search term.
*
* @return A search term suitable for an exact match query.
*/
private static String prepareExactMatchSearchTerm(String searchTerm) {
String escapedSearchTerm = escapeSearchTerm(searchTerm);
if (!searchTerm.startsWith("\"")) {
escapedSearchTerm = "\"" + escapedSearchTerm;
}
if (!searchTerm.endsWith("\"")) {
escapedSearchTerm += "\"";
}
return escapedSearchTerm;
}
/**
* Adds delimiters and possibly wildcards to a given search terms as
* required for a regular expression search query.
*
* @param searchTerm A "raw" input search term.
*
* @return A search term suitable for a regex query.
*/
private static String prepareRegexSearchTerm(String searchTerm) {
/*
* Add slash delimiters and, if necessary, wildcards (.*) at the
* beginning and end of the search term. The wildcards are added because
* Lucerne automatically adds a '^' prefix and '$' suffix to the search
* terms for regex searches. Without the '.*' wildcards, the search term
* will have to match the entire content_str field, which is not
* generally the intent of the user.
*/
String regexSearchTerm = SOLR_DOC_CONTENT_STR_FIELD
+ ":/"
+ (searchTerm.startsWith(".*") ? "" : ".*")
+ searchTerm.toLowerCase()
+ (searchTerm.endsWith(".*") ? "" : ".*")
+ "/";
return regexSearchTerm;
}
/**
* Escapes and adds delimiters and wpossibly wildcards to a given search
* term as required for a substring search.
*
* @param searchTerm A "raw" input search term.
*
* @return A search term suitable for a substring query.
*/
private static String prepareSubstringSearchTerm(String searchTerm) {
String escapedSearchTerm = escapeSearchTerm(searchTerm);
return prepareRegexSearchTerm(escapedSearchTerm);
}
/**
* Escapes a search term as required for a Lucene query.
*
* @param searchTerm A "raw" input search term.
*
* @return An escaped version of the "raw" input search term.
*/
public static String escapeSearchTerm(String searchTerm) {
String rawSearchTerm = searchTerm.trim();
if (0 == rawSearchTerm.length()) {
return rawSearchTerm;
}
StringBuilder escapedSearchTerm = new StringBuilder(rawSearchTerm.length());
for (int i = 0; i < rawSearchTerm.length(); ++i) {
final char nextChar = rawSearchTerm.charAt(i);
if (SEARCH_TERM_CHARS_TO_ESCAPE.contains(Character.toString(nextChar))) {
escapedSearchTerm.append("\\");
}
escapedSearchTerm.append(nextChar);
}
return escapedSearchTerm.toString();
}
/**
* An enumeration of the supported query types for keywod searches.
*/
enum QueryType {
EXACT_MATCH,
SUBSTRING,
REGEX;
}
}

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrowserCustomizer;
/**
* Customizer for SelectMultiUserCasesPanel. Displays the 'Create date' and 'Directory' columns
*/
public class SelectMultiUserCaseDialogCustomizer implements MultiUserCaseBrowserCustomizer {
@Override
public List<Column> getColumns() {
List<Column> properties = new ArrayList<>();
properties.add(Column.CREATE_DATE);
properties.add(Column.DIRECTORY);
return properties; }
@Override
public List<SortColumn> getSortColumns() {
List<SortColumn> sortColumns = new ArrayList<>();
sortColumns.add(new SortColumn(Column.CREATE_DATE, false, 1));
return sortColumns; }
@Override
public boolean allowMultiSelect() {
return true;
}
@Override
public List<Action> getActions(CaseNodeData nodeData) {
return new ArrayList<>();
}
@Override
public Action getPreferredAction(CaseNodeData nodeData) {
return null;
}
}

View File

@ -0,0 +1,90 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import java.awt.Dialog;
import java.awt.event.ActionListener;
import java.beans.PropertyVetoException;
import org.openide.nodes.Node;
import org.openide.windows.WindowManager;
/**
* Dialog that will display the SelectMultiUserCasesPanel
*/
public class SelectMultiUserCasesDialog extends javax.swing.JDialog {
private static final long serialVersionUID = 1L;
private static SelectMultiUserCasesDialog instance;
private static SelectMultiUserCasesPanel multiUserCasesPanel;
/**
* Gets the singleton JDialog that allows a user to open a multi-user case.
*
* @return The singleton JDialog instance.
*/
public synchronized static SelectMultiUserCasesDialog getInstance() {
if (instance == null) {
instance = new SelectMultiUserCasesDialog();
instance.init();
}
return instance;
}
/**
* Listen for new case selections from the user.
*
* @param l Listener on new case selection events
*/
void subscribeToNewCaseSelections(ActionListener l) {
multiUserCasesPanel.subscribeToNewCaseSelections(l);
}
/**
* Set the node selections for the window
*
* @param selections Nodes to be automatically selected in the explorer view
*/
void setNodeSelections(Node[] selections) {
try {
multiUserCasesPanel.setSelections(selections);
} catch (PropertyVetoException ex) {
}
}
/**
* Constructs a singleton JDialog that allows a user to open a multi-user
* case.
*/
private SelectMultiUserCasesDialog() {
super(WindowManager.getDefault().getMainWindow(), "Select Multi-User Cases", Dialog.ModalityType.APPLICATION_MODAL);
}
/**
* Registers a keyboard action to hide the dialog when the escape key is
* pressed and adds a OpenMultiUserCasePanel child component.
*/
private void init() {
multiUserCasesPanel = new SelectMultiUserCasesPanel(this);
add(multiUserCasesPanel);
pack();
setResizable(false);
multiUserCasesPanel.refreshDisplay();
}
}

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="multiUserCaseScrollPane" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="selectAllButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="deselectAllButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="jLabel1" min="-2" pref="365" max="-2" attributes="0"/>
<EmptySpace pref="367" max="32767" attributes="0"/>
<Component id="confirmSelections" min="-2" pref="63" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cancelButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="multiUserCaseScrollPane" min="-2" pref="486" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="selectAllButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="deselectAllButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="confirmSelections" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cancelButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="15" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="multiUserCaseScrollPane">
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
<Component class="javax.swing.JButton" name="selectAllButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="SelectMultiUserCasesPanel.selectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectAllButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="deselectAllButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="SelectMultiUserCasesPanel.deselectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deselectAllButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="jLabel1">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="viking/bethesda/examiner/multicasekeywordsearch/Bundle.properties" key="SelectMultiUserCasesPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="confirmSelections">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="viking/bethesda/examiner/multicasekeywordsearch/Bundle.properties" key="SelectMultiUserCasesPanel.confirmSelections.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="confirmSelectionsActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="viking/bethesda/examiner/multicasekeywordsearch/Bundle.properties" key="SelectMultiUserCasesPanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,206 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.keywordsearch.multicase;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JDialog;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCasesBrowserPanel;
/**
* Panel for multi-user case selection
*/
public class SelectMultiUserCasesPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private final JDialog parentDialog;
private final MultiUserCasesBrowserPanel caseBrowserPanel;
private final List<ActionListener> listeners;
/**
* Constructs a JPanel that allows a user to open a multi-user case.
*
* @param parentDialog The parent dialog of the panel, may be null. If
* provided, the dialog is hidden when this poanel's
* cancel button is pressed.
*/
SelectMultiUserCasesPanel(JDialog parentDialog) {
initComponents();
this.parentDialog = parentDialog;
initComponents(); // Machine generated code
caseBrowserPanel = new MultiUserCasesBrowserPanel(new ExplorerManager(), new SelectMultiUserCaseDialogCustomizer());
multiUserCaseScrollPane.add(caseBrowserPanel);
multiUserCaseScrollPane.setViewportView(caseBrowserPanel);
listeners = new ArrayList<>();
}
/**
* Refreshes the child component that displays the multi-user cases known to
* the coordination service..
*/
void refreshDisplay() {
caseBrowserPanel.displayCases();
}
/**
* Subscribes to the selections when the user presses the OK button.
*
* @param listener
*/
void subscribeToNewCaseSelections(ActionListener listener) {
listeners.add(listener);
}
/**
* Sets the selections in the panel
*
* @param selections
* @throws PropertyVetoException
*/
void setSelections(Node[] selections) throws PropertyVetoException {
caseBrowserPanel.getExplorerManager().setSelectedNodes(selections);
caseBrowserPanel.requestFocus();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
multiUserCaseScrollPane = new javax.swing.JScrollPane();
selectAllButton = new javax.swing.JButton();
deselectAllButton = new javax.swing.JButton();
jLabel1 = new javax.swing.JLabel();
confirmSelections = new javax.swing.JButton();
cancelButton = new javax.swing.JButton();
org.openide.awt.Mnemonics.setLocalizedText(selectAllButton, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.selectAllButton.text")); // NOI18N
selectAllButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
selectAllButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(deselectAllButton, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.deselectAllButton.text")); // NOI18N
deselectAllButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
deselectAllButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.jLabel1.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(confirmSelections, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.confirmSelections.text")); // NOI18N
confirmSelections.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
confirmSelectionsActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.cancelButton.text")); // NOI18N
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(multiUserCaseScrollPane)
.addGroup(layout.createSequentialGroup()
.addComponent(selectAllButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(deselectAllButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 365, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 367, Short.MAX_VALUE)
.addComponent(confirmSelections, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cancelButton)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(multiUserCaseScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 486, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(selectAllButton)
.addComponent(deselectAllButton)
.addComponent(confirmSelections)
.addComponent(cancelButton))
.addContainerGap(15, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void selectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectAllButtonActionPerformed
try {
caseBrowserPanel.getExplorerManager().setSelectedNodes(caseBrowserPanel.getExplorerManager().getRootContext().getChildren().getNodes());
} catch (PropertyVetoException ex) {
Exceptions.printStackTrace(ex);
}
}//GEN-LAST:event_selectAllButtonActionPerformed
private void deselectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deselectAllButtonActionPerformed
try {
caseBrowserPanel.getExplorerManager().setSelectedNodes(new Node[0]);
} catch (PropertyVetoException ex) {
Exceptions.printStackTrace(ex);
}
}//GEN-LAST:event_deselectAllButtonActionPerformed
private void confirmSelectionsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_confirmSelectionsActionPerformed
listeners.forEach((l) -> {
//Pass along the selected nodes in the event.
l.actionPerformed(new ActionEvent(caseBrowserPanel.getExplorerManager().getSelectedNodes(), -1, ""));
});
parentDialog.setVisible(false);
}//GEN-LAST:event_confirmSelectionsActionPerformed
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
parentDialog.setVisible(false);
}//GEN-LAST:event_cancelButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton cancelButton;
private javax.swing.JButton confirmSelections;
private javax.swing.JButton deselectAllButton;
private javax.swing.JLabel jLabel1;
private javax.swing.JScrollPane multiUserCaseScrollPane;
private javax.swing.JButton selectAllButton;
// End of variables declaration//GEN-END:variables
}