TSK-277 - Add more advanced regular expression searching

initial changes: refactored current lucene query code to new class, GUI changes
This commit is contained in:
adam-m 2011-12-21 16:23:22 -05:00
parent 1a122c97ec
commit ef9d7e884d
8 changed files with 305 additions and 75 deletions

View File

@ -2,7 +2,7 @@ OpenIDE-Module-Name=KeywordSearch
IndexProgressPanel.statusText.text=Status text
IndexProgressPanel.cancelButton.text=Cancel
KeywordSearchTopComponent.searchButton.text=Search
KeywordSearchTopComponent.queryLabel.text=Solr query:
KeywordSearchTopComponent.queryLabel.text=Query:
KeywordSearchTopComponent.filesIndexedNameLabel.text=Files indexed:
KeywordSearchTopComponent.filesIndexedValLabel.text=-
KeywordSearchTopComponent.filesIndexedNameLabel.AccessibleContext.accessibleName=Files indexed:
@ -14,3 +14,5 @@ ExtractedContentPanel.hitTotalLabel.text=-
ExtractedContentPanel.hitButtonsLabel.text=Match
ExtractedContentPanel.hitPreviousButton.text=
ExtractedContentPanel.hitNextButton.text=
KeywordSearchTopComponent.luceneQRadioButton.text=Lucene
KeywordSearchTopComponent.regexQRadioButton.text=RegEx

View File

@ -31,10 +31,13 @@ class KeywordSearch {
private static final String BASE_URL = "http://localhost:8983/solr/";
private static final Server SERVER = new Server(BASE_URL);
public enum QueryType {LUCENE, REGEX};
public static final String NUM_FILES_CHANGE_EVT = "NUM_FILES_CHANGE_EVT";
static PropertyChangeSupport changeSupport = new PropertyChangeSupport(KeywordSearch.class);
static Server getServer() {
return SERVER;
}

View File

@ -18,28 +18,16 @@
*/
package org.sleuthkit.autopsy.keywordsearch;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.openide.nodes.Node;
import javax.swing.JOptionPane;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.TopComponent;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataExplorer;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.datamodel.FsContent;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.autopsy.keywordsearch.KeywordSearch.QueryType;
/**
* Provides a data explorer to perform Solr searches with
@ -58,8 +46,14 @@ public class KeywordSearchDataExplorer implements DataExplorer {
@Override
public void actionPerformed(ActionEvent e) {
tc.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
QueryType queryType = null;
if (tc.isLuceneQuerySelected()) {
queryType = QueryType.LUCENE;
} else {
queryType = QueryType.REGEX;
}
try {
search(tc.getQueryText());
search(tc.getQueryText(), queryType);
} finally {
tc.setCursor(null);
}
@ -81,57 +75,39 @@ public class KeywordSearchDataExplorer implements DataExplorer {
* Executes a query and populates a DataResult tab with the results
* @param solrQuery
*/
private void search(String solrQuery) {
private void search(String query, QueryType queryType) {
List<FsContent> matches = new ArrayList<FsContent>();
boolean allMatchesFetched = false;
final int ROWS_PER_FETCH = 10000;
Server.Core solrCore = KeywordSearch.getServer().getCore();
SolrQuery q = new SolrQuery();
q.setQuery(solrQuery);
q.setRows(ROWS_PER_FETCH);
q.setFields("id");
for (int start = 0; !allMatchesFetched; start = start + ROWS_PER_FETCH) {
q.setStart(start);
try {
QueryResponse response = solrCore.query(q);
SolrDocumentList resultList = response.getResults();
long results = resultList.getNumFound();
allMatchesFetched = start + ROWS_PER_FETCH >= results;
for (SolrDocument resultDoc : resultList) {
long id = Long.parseLong((String) resultDoc.getFieldValue("id"));
SleuthkitCase sc = Case.getCurrentCase().getSleuthkitCase();
// TODO: has to be a better way to get files. Also, need to
// check that we actually get 1 hit for each id
ResultSet rs = sc.runQuery("select * from tsk_files where obj_id=" + id);
matches.addAll(sc.resultSetToFsContents(rs));
rs.close();
}
} catch (SolrServerException ex) {
throw new RuntimeException(ex);
// TODO: handle bad query strings, among other issues
} catch (SQLException ex) {
// TODO: handle error getting files from database
}
switch (queryType) {
case LUCENE:
searchLuceneQuery(query);
break;
case REGEX:
searchRegexQuery(query);
break;
default:
}
}
/**
* Executes a Lucene query and populates a DataResult tab with the results
* @param solrQuery
*/
private void searchRegexQuery(String regexQuery) {
RegexQuery rq = new RegexQuery(regexQuery);
if (rq.validate()) {
rq.execute();
} else {
displayErrorDialog("Invalid RegEx query: " + regexQuery);
}
String pathText = "Solr query: " + solrQuery;
Node rootNode = new KeywordSearchNode(matches, solrQuery);
}
TopComponent searchResultWin = DataResultTopComponent.createInstance("Keyword search", pathText, rootNode, matches.size());
searchResultWin.requestActive(); // make it the active top component
/**
* Executes a Lucene query and populates a DataResult tab with the results
* @param solrQuery
*/
private void searchLuceneQuery(String luceneQuery) {
new LuceneQuery(luceneQuery).execute();
}
@Override
@ -143,13 +119,22 @@ public class KeywordSearchDataExplorer implements DataExplorer {
public void propertyChange(PropertyChangeEvent evt) {
}
private void displayErrorDialog(final String message) {
final Component parentComponent = null; // Use default window frame.
final String title = "Keyword Search Error";
final int messageType = JOptionPane.ERROR_MESSAGE;
JOptionPane.showMessageDialog(
parentComponent,
message,
title,
messageType);
}
class IndexChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String changed = evt.getPropertyName();
//Object oldValue = evt.getOldValue();
Object newValue = evt.getNewValue();
@ -162,7 +147,7 @@ public class KeywordSearchDataExplorer implements DataExplorer {
} else {
String msg = "Unsupported change event: " + changed;
throw new UnsupportedOperationException(msg);
}
}
}
}
}

View File

@ -0,0 +1,33 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 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;
public interface KeywordSearchQuery {
/**
* validate the query pre execution
* @return true if the query passed validation
*/
public boolean validate();
/**
* execute the query
*/
public void execute();
}

View File

@ -1,6 +1,10 @@
<?xml version="1.1" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="buttonGroup1">
</Component>
</NonVisualComponents>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -19,7 +23,13 @@
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="queryLabel" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<Component id="queryLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="luceneQRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="regexQRadioButton" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="jScrollPane1" alignment="0" pref="599" max="32767" attributes="0"/>
<Component id="searchButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
@ -36,7 +46,11 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="queryLabel" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="queryLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="luceneQRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="regexQRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="jScrollPane1" min="-2" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
@ -46,7 +60,7 @@
<Component id="filesIndexedNameLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="filesIndexedValLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="107" max="32767" attributes="0"/>
<EmptySpace pref="106" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -105,5 +119,20 @@
</Property>
</AccessibilityProperties>
</Component>
<Component class="javax.swing.JRadioButton" name="luceneQRadioButton">
<Properties>
<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/Bundle.properties" key="KeywordSearchTopComponent.luceneQRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="regexQRadioButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="KeywordSearchTopComponent.regexQRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -35,7 +35,8 @@ public class KeywordSearchTopComponent extends TopComponent {
public KeywordSearchTopComponent() {
initComponents();
setName("Keyword Search");
buttonGroup1.add(luceneQRadioButton);
buttonGroup1.add(regexQRadioButton);
searchButton.setEnabled(false);
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE);
@ -54,12 +55,15 @@ public class KeywordSearchTopComponent extends TopComponent {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonGroup1 = new javax.swing.ButtonGroup();
jScrollPane1 = new javax.swing.JScrollPane();
queryTextArea = new javax.swing.JTextArea();
searchButton = new javax.swing.JButton();
queryLabel = new javax.swing.JLabel();
filesIndexedNameLabel = new javax.swing.JLabel();
filesIndexedValLabel = new javax.swing.JLabel();
luceneQRadioButton = new javax.swing.JRadioButton();
regexQRadioButton = new javax.swing.JRadioButton();
queryTextArea.setColumns(20);
queryTextArea.setRows(5);
@ -73,6 +77,11 @@ public class KeywordSearchTopComponent extends TopComponent {
filesIndexedValLabel.setText(org.openide.util.NbBundle.getMessage(KeywordSearchTopComponent.class, "KeywordSearchTopComponent.filesIndexedValLabel.text")); // NOI18N
luceneQRadioButton.setSelected(true);
luceneQRadioButton.setText(org.openide.util.NbBundle.getMessage(KeywordSearchTopComponent.class, "KeywordSearchTopComponent.luceneQRadioButton.text")); // NOI18N
regexQRadioButton.setText(org.openide.util.NbBundle.getMessage(KeywordSearchTopComponent.class, "KeywordSearchTopComponent.regexQRadioButton.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@ -80,7 +89,12 @@ public class KeywordSearchTopComponent extends TopComponent {
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(queryLabel)
.addGroup(layout.createSequentialGroup()
.addComponent(queryLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(luceneQRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(regexQRadioButton))
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 599, Short.MAX_VALUE)
.addComponent(searchButton)
.addGroup(layout.createSequentialGroup()
@ -93,7 +107,10 @@ public class KeywordSearchTopComponent extends TopComponent {
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(queryLabel)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(queryLabel)
.addComponent(luceneQRadioButton)
.addComponent(regexQRadioButton))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
@ -102,18 +119,21 @@ public class KeywordSearchTopComponent extends TopComponent {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(filesIndexedNameLabel)
.addComponent(filesIndexedValLabel))
.addContainerGap(107, Short.MAX_VALUE))
.addContainerGap(106, Short.MAX_VALUE))
);
filesIndexedNameLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(KeywordSearchTopComponent.class, "KeywordSearchTopComponent.filesIndexedNameLabel.AccessibleContext.accessibleName")); // NOI18N
filesIndexedValLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(KeywordSearchTopComponent.class, "KeywordSearchTopComponent.filesIndexedValLabel.AccessibleContext.accessibleName")); // NOI18N
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.JLabel filesIndexedNameLabel;
private javax.swing.JLabel filesIndexedValLabel;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JRadioButton luceneQRadioButton;
private javax.swing.JLabel queryLabel;
private javax.swing.JTextArea queryTextArea;
private javax.swing.JRadioButton regexQRadioButton;
private javax.swing.JButton searchButton;
// End of variables declaration//GEN-END:variables
@ -123,13 +143,21 @@ public class KeywordSearchTopComponent extends TopComponent {
// clear old search
queryTextArea.setText("");
}
void addSearchButtonListener(ActionListener l) {
public void addSearchButtonListener(ActionListener l) {
searchButton.addActionListener(l);
}
String getQueryText() {
public String getQueryText() {
return queryTextArea.getText();
}
public boolean isLuceneQuerySelected() {
return luceneQRadioButton.isSelected();
}
public boolean isRegexQuerySelected() {
return regexQRadioButton.isSelected();
}
/**
* Overwrite when you want to change default persistence type. Default

View File

@ -0,0 +1,102 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 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;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.openide.nodes.Node;
import org.openide.windows.TopComponent;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.datamodel.FsContent;
import org.sleuthkit.datamodel.SleuthkitCase;
public class LuceneQuery implements KeywordSearchQuery {
private String query;
public LuceneQuery(String query) {
this.query = query;
}
@Override
public void execute() {
List<FsContent> matches = new ArrayList<FsContent>();
boolean allMatchesFetched = false;
final int ROWS_PER_FETCH = 10000;
Server.Core solrCore = KeywordSearch.getServer().getCore();
SolrQuery q = new SolrQuery();
q.setQuery(query);
q.setRows(ROWS_PER_FETCH);
q.setFields("id");
for (int start = 0; !allMatchesFetched; start = start + ROWS_PER_FETCH) {
q.setStart(start);
try {
QueryResponse response = solrCore.query(q);
SolrDocumentList resultList = response.getResults();
long results = resultList.getNumFound();
allMatchesFetched = start + ROWS_PER_FETCH >= results;
for (SolrDocument resultDoc : resultList) {
long id = Long.parseLong((String) resultDoc.getFieldValue("id"));
SleuthkitCase sc = Case.getCurrentCase().getSleuthkitCase();
// TODO: has to be a better way to get files. Also, need to
// check that we actually get 1 hit for each id
ResultSet rs = sc.runQuery("select * from tsk_files where obj_id=" + id);
matches.addAll(sc.resultSetToFsContents(rs));
rs.close();
}
} catch (SolrServerException ex) {
throw new RuntimeException(ex);
// TODO: handle bad query strings, among other issues
} catch (SQLException ex) {
// TODO: handle error getting files from database
}
}
String pathText = "Lucene query: " + query;
Node rootNode = new KeywordSearchNode(matches, query);
TopComponent searchResultWin = DataResultTopComponent.createInstance("Keyword search", pathText, rootNode, matches.size());
searchResultWin.requestActive(); // make it the active top component
}
@Override
public boolean validate() {
throw new UnsupportedOperationException("Not supported yet.");
}
}

View File

@ -0,0 +1,48 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 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;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class RegexQuery implements KeywordSearchQuery {
private String query;
public RegexQuery(String query) {
this.query = query;
}
@Override
public boolean validate() {
boolean valid = true;
try {
Pattern.compile(query);
} catch (PatternSyntaxException ex1) {
valid = false;
} catch (IllegalArgumentException ex2) {
valid = false;
}
return valid;
}
@Override
public void execute() {
}
}