diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java index 65b57c35ec..d28765132e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java @@ -31,7 +31,7 @@ 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 enum QueryType {WORD, REGEX}; public static final String NUM_FILES_CHANGE_EVT = "NUM_FILES_CHANGE_EVT"; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchDataExplorer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchDataExplorer.java index c6728a4fa0..a295c08220 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchDataExplorer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchDataExplorer.java @@ -28,6 +28,7 @@ import javax.swing.JOptionPane; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataExplorer; import org.sleuthkit.autopsy.keywordsearch.KeywordSearch.QueryType; +import org.sleuthkit.autopsy.keywordsearch.KeywordSearchQueryManager.Presentation; /** * Provides a data explorer to perform Solr searches with @@ -48,7 +49,7 @@ public class KeywordSearchDataExplorer implements DataExplorer { tc.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); QueryType queryType = null; if (tc.isLuceneQuerySelected()) { - queryType = QueryType.LUCENE; + queryType = QueryType.WORD; } else { queryType = QueryType.REGEX; } @@ -78,39 +79,16 @@ public class KeywordSearchDataExplorer implements DataExplorer { * @param solrQuery */ private void search(String query, QueryType queryType) { + KeywordSearchQueryManager man = new KeywordSearchQueryManager(query, queryType, Presentation.DETAIL); - 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) { - TermComponentQuery rq = new TermComponentQuery(regexQuery); - if (rq.validate()) { - rq.execute(); + if (man.validate()) { + man.execute(); } else { - displayErrorDialog("Invalid RegEx query syntax: " + regexQuery); + displayErrorDialog("Invalid query syntax: " + query); } } - /** - * Executes a Lucene query and populates a DataResult tab with the results - * @param solrQuery - */ - private void searchLuceneQuery(String luceneQuery) { - new LuceneQuery(luceneQuery).execute(); - } @Override public org.openide.windows.TopComponent getTopComponent() { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java index 7839c7706f..33605dfe7b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java @@ -47,5 +47,17 @@ public interface KeywordSearchQuery { */ public void escape(); + /** + * return original query string + * @return the query String supplied originally + */ + public String getQueryString(); + + /** + * return escaped query string if escaping was done + * @return the escaped query string, or original string if no escaping done + */ + public String getEscapedQueryString(); + } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryManager.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryManager.java new file mode 100644 index 0000000000..383e8e92aa --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryManager.java @@ -0,0 +1,158 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier sleuthkit 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.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.autopsy.keywordsearch.KeywordSearch.QueryType; +import org.sleuthkit.datamodel.FsContent; + +/** + * Query manager responsible for running appropriate queries and displaying results + * for single, multi keyword queries, with detailed or collapsed results + */ +public class KeywordSearchQueryManager implements KeywordSearchQuery { + + public enum Presentation { + + COLLAPSE, DETAIL + }; + //map query->boolean (true if literal, false otherwise) + private Map queries; + private Presentation presentation; + private List queryDelegates; + private QueryType queryType; + private static Logger logger = Logger.getLogger(KeywordSearchQueryManager.class.getName()); + + public KeywordSearchQueryManager(Map queries, Presentation presentation) { + this.queries = queries; + this.presentation = presentation; + queryType = QueryType.REGEX; + init(); + } + + public KeywordSearchQueryManager(String query, QueryType qt, Presentation presentation) { + queries = new LinkedHashMap(); + queries.put(query, false); + this.presentation = presentation; + queryType = qt; + init(); + } + + public KeywordSearchQueryManager(String query, boolean isLiteral, Presentation presentation) { + queries = new LinkedHashMap(); + queries.put(query, isLiteral); + this.presentation = presentation; + queryType = QueryType.REGEX; + init(); + } + + private void init() { + queryDelegates = new ArrayList(); + for (String query : queries.keySet()) { + KeywordSearchQuery del = null; + switch (queryType) { + case WORD: + del = new LuceneQuery(query); + break; + case REGEX: + del = new TermComponentQuery(query); + break; + default: + ; + } + + escape(); + queryDelegates.add(del); + } + + } + + @Override + public void execute() { + + if (queryType == QueryType.WORD || presentation == Presentation.DETAIL) { + for (KeywordSearchQuery q : queryDelegates) { + q.execute(); + } + } else { + for (KeywordSearchQuery q : queryDelegates) { + List fsContents = q.performQuery(); + //TODO create view, send to proper factory + } + + } + + + } + + @Override + public void escape() { + for (KeywordSearchQuery q : queryDelegates) { + boolean shouldEscape = queries.get(q.getQueryString()); + if (shouldEscape) { + q.escape(); + } + } + + } + + @Override + public List performQuery() { + //not done here + return null; + } + + @Override + public String getEscapedQueryString() { + StringBuilder sb = new StringBuilder(); + final String SEP = queryType == QueryType.WORD ? " " : "|"; + for (KeywordSearchQuery q : queryDelegates) { + sb.append(q.getEscapedQueryString()).append(SEP); + } + return sb.toString(); + } + + @Override + public String getQueryString() { + StringBuilder sb = new StringBuilder(); + final String SEP = queryType == QueryType.WORD ? " " : "|"; + for (KeywordSearchQuery q : queryDelegates) { + sb.append(q.getQueryString()).append(SEP); + } + return sb.toString(); + } + + @Override + public boolean validate() { + boolean allValid = true; + for (KeywordSearchQuery tcq : queryDelegates) { + if (!tcq.validate()) { + logger.log(Level.WARNING, "Query has invalid syntax: " + tcq.getQueryString()); + allValid = false; + break; + } + } + return allValid; + } +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java index d97a31a6ff..094bad06c3 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java @@ -20,18 +20,14 @@ package org.sleuthkit.autopsy.keywordsearch; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.solr.client.solrj.SolrQuery; -import org.apache.solr.client.solrj.SolrServerException; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; @@ -39,22 +35,19 @@ import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode.FsContentPropertyType; import org.sleuthkit.autopsy.datamodel.KeyValueNode; import org.sleuthkit.autopsy.datamodel.KeyValueThing; +import org.sleuthkit.autopsy.keywordsearch.KeywordSearchQueryManager.Presentation; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.File; import org.sleuthkit.datamodel.FsContent; /** * - * factory responsible for assembling nodes in the right way + * factory produces top level nodes with query + * responsible for assembling nodes and columns in the right way * and performing lazy queries as needed */ public class KeywordSearchResultFactory extends ChildFactory { - public enum Presentation { - - COLLAPSE, STRUCTURE - }; - //common properties (superset of all Node properties) to be displayed as columns //these are merged with FsContentPropertyType defined properties public static enum CommonPropertyTypes { @@ -133,17 +126,66 @@ public class KeywordSearchResultFactory extends ChildFactory { @Override protected Node createNodeForKey(KeyValueThing thing) { - return new KeyValueNode(thing, Children.create(new RegexResultChildFactory(things), true)); + ChildFactory childFactory = null; + switch (presentation) { + case COLLAPSE: + childFactory = null; + break; + case DETAIL: + childFactory = new ResulTermsMatchesChildFactory(things); + break; + default: + } + + return new KeyValueNode(thing, Children.create(childFactory, true)); + } + + /** + * factory produces collapsed view of all fscontent matches per query + * the node produced is a child node + * The factory actually executes query. + */ + class ResulCollapsedChildFactory extends ChildFactory { + + KeyValueThing queryThing; + + ResulCollapsedChildFactory(KeyValueThing queryThing) { + this.queryThing = queryThing; + } + + @Override + protected boolean createKeys(List toPopulate) { + String origQuery = queryThing.getName(); + TermComponentQuery tcq = new TermComponentQuery(origQuery); + Map map = new LinkedHashMap(); + if (tcq.validate()) { + map.put("query_valid", true); + return true; + } + else { + map.put("query_valid", false); + return false; + } + + + //return toPopulate.addAll(things); + } + + @Override + protected Node createNodeForKey(KeyValueThing thing) { + return new KeyValueNode(thing, Children.LEAF); + //return new KeyValueNode(thing, Children.create(new ResultFilesChildFactory(thing), true)); + } } /** * factory produces top level result nodes showing *exact* regex match result */ - class RegexResultChildFactory extends ChildFactory { + class ResulTermsMatchesChildFactory extends ChildFactory { Collection things; - RegexResultChildFactory(Collection things) { + ResulTermsMatchesChildFactory(Collection things) { this.things = things; } @@ -155,7 +197,7 @@ public class KeywordSearchResultFactory extends ChildFactory { @Override protected Node createNodeForKey(KeyValueThing thing) { //return new KeyValueNode(thing, Children.LEAF); - return new KeyValueNode(thing, Children.create(new RegexResultDetailsChildFactory(thing), true)); + return new KeyValueNode(thing, Children.create(new ResultFilesChildFactory(thing), true)); } /** @@ -164,11 +206,11 @@ public class KeywordSearchResultFactory extends ChildFactory { * To implement exact regex match detail view, we need to extract files content * returned by Lucene and further narrow down by applying a Java regex */ - class RegexResultDetailsChildFactory extends ChildFactory { + class ResultFilesChildFactory extends ChildFactory { private KeyValueThing thing; - RegexResultDetailsChildFactory(KeyValueThing thing) { + ResultFilesChildFactory(KeyValueThing thing) { this.thing = thing; } @@ -200,7 +242,7 @@ public class KeywordSearchResultFactory extends ChildFactory { final Content content = thingContent.getContent(); final String query = thingContent.getQuery(); - final String contentStr = getSolrContent(content); + final String contentStr = KeywordSearch.getServer().getCore().getSolrContent(content); //make sure the file contains a match (this gets rid of large number of false positives) //TODO option in GUI to include approximate matches (faster) @@ -228,19 +270,7 @@ public class KeywordSearchResultFactory extends ChildFactory { } } - private String getSolrContent(final Content content) { - final Server.Core solrCore = KeywordSearch.getServer().getCore(); - final SolrQuery q = new SolrQuery(); - q.setQuery("*:*"); - q.addFilterQuery("id:" + content.getId()); - q.setFields("content"); - try { - return (String) solrCore.query(q).getResults().get(0).getFieldValue("content"); - } catch (SolrServerException ex) { - logger.log(Level.WARNING, "Error getting content from Solr and validating regex match", ex); - return null; - } - } + } /* diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index aa514a23e2..f028dc512c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -42,7 +42,6 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class LuceneQuery implements KeywordSearchQuery { private static final Logger logger = Logger.getLogger(LuceneQuery.class.getName()); - private String query; //original unescaped query private String queryEscaped; private boolean isEscaped; @@ -58,8 +57,17 @@ public class LuceneQuery implements KeywordSearchQuery { queryEscaped = KeywordSearchUtil.escapeLuceneQuery(query); isEscaped = true; } - - + + @Override + public String getEscapedQueryString() { + return this.queryEscaped; + } + + @Override + public String getQueryString() { + return this.query; + } + /** * Just perform the query and return result without updating the GUI * This utility is used in this class, can be potentially reused by other classes @@ -76,7 +84,7 @@ public class LuceneQuery implements KeywordSearchQuery { Server.Core solrCore = KeywordSearch.getServer().getCore(); SolrQuery q = new SolrQuery(); - + q.setQuery(queryEscaped); q.setRows(ROWS_PER_FETCH); q.setFields("id"); @@ -120,7 +128,7 @@ public class LuceneQuery implements KeywordSearchQuery { public void execute() { escape(); List matches = performQuery(); - + String pathText = "Lucene query: " + query; Node rootNode = new KeywordSearchNode(matches, query); Node filteredRootNode = new TableFilterNode(rootNode, true); @@ -131,6 +139,6 @@ public class LuceneQuery implements KeywordSearchQuery { @Override public boolean validate() { - throw new UnsupportedOperationException("Not supported yet."); + return query != null && ! query.equals(""); } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 0c3bbbedba..d9425b5522 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -42,19 +42,22 @@ import org.apache.commons.httpclient.NoHttpResponseException; import org.openide.modules.InstalledFileLocator; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Content; /** * Handles for keeping track of a Solr server and its cores */ class Server { + private static final Logger logger = Logger.getLogger(Server.class.getName()); - private static final String DEFAULT_CORE_NAME = "coreCase"; // TODO: DEFAULT_CORE_NAME needs to be replaced with unique names to support multiple open cases - - public static final String CORE_EVT = "CORE_EVT"; - public enum CORE_EVT_STATES { STOPPED, STARTED }; + public static final String CORE_EVT = "CORE_EVT"; + public enum CORE_EVT_STATES { + + STOPPED, STARTED + }; private CommonsHttpSolrServer solrServer; private String instanceDir; private File solrFolder; @@ -70,22 +73,22 @@ class Server { } catch (MalformedURLException ex) { throw new RuntimeException(ex); } - + serverAction = new ServerAction(); solrFolder = InstalledFileLocator.getDefault().locate("solr", Server.class.getPackage().getName(), false); instanceDir = solrFolder.getAbsolutePath() + File.separator + "solr"; } - + @Override public void finalize() throws java.lang.Throwable { stop(); super.finalize(); } - + public void addServerActionListener(PropertyChangeListener l) { serverAction.addPropertyChangeListener(l); } - + /** * Helper threads to handle stderr/stdout from Solr process */ @@ -111,8 +114,7 @@ class Server { } } } - - + /** * Tries to start a Solr instance in a separate process. Returns immediately * (probably before the server is ready) and doesn't check whether it was @@ -122,11 +124,11 @@ class Server { logger.log(Level.INFO, "Starting Solr server from: " + solrFolder.getAbsolutePath()); try { Process start = Runtime.getRuntime().exec("java -DSTOP.PORT=8079 -DSTOP.KEY=mysecret -jar start.jar", null, solrFolder); - + // Handle output to prevent process from blocking (new InputStreamPrinterThread(start.getInputStream())).start(); (new InputStreamPrinterThread(start.getErrorStream())).start(); - + } catch (IOException ex) { throw new RuntimeException(ex); } @@ -144,15 +146,14 @@ class Server { logger.log(Level.INFO, "Stopping Solr server from: " + solrFolder.getAbsolutePath()); Process stop = Runtime.getRuntime().exec("java -DSTOP.PORT=8079 -DSTOP.KEY=mysecret -jar start.jar --stop", null, solrFolder); return stop.waitFor() == 0; - + } catch (InterruptedException ex) { throw new RuntimeException(ex); } catch (IOException ex) { throw new RuntimeException(ex); } } - - + /** * Tests if there's a Solr server running by sending it a core-status request. * @return false if the request failed with a connection error, otherwise true @@ -165,9 +166,9 @@ class Server { CoreAdminRequest.getStatus(null, solrServer); } catch (SolrServerException ex) { - + Throwable cause = ex.getRootCause(); - + // TODO: check if SocketExceptions should actually happen (is // probably caused by starting a connection as the server finishes // shutting down) @@ -182,11 +183,9 @@ class Server { return true; } - /**** Convenience methods for use while we only open one case at a time ****/ - private Core currentCore = null; - + void openCore() { if (currentCore != null) { throw new RuntimeException("Already an open Core!"); @@ -194,7 +193,7 @@ class Server { currentCore = openCore(Case.getCurrentCase()); serverAction.putValue(CORE_EVT, CORE_EVT_STATES.STARTED); } - + void closeCore() { if (currentCore == null) { throw new RuntimeException("No currently open Core!"); @@ -203,18 +202,15 @@ class Server { currentCore = null; serverAction.putValue(CORE_EVT, CORE_EVT_STATES.STOPPED); } - + Core getCore() { if (currentCore == null) { throw new RuntimeException("No currently open Core!"); } return currentCore; } - - - /**** end single-case specific methods ****/ - + /**** end single-case specific methods ****/ /** * Open a core for the given case * @param c @@ -258,7 +254,6 @@ class Server { // handle to the core in Solr private String name; - // the server to access a core needs to be built from a URL with the // core in it, and is only good for core-specific operations private SolrServer solrCore; @@ -271,20 +266,33 @@ class Server { throw new RuntimeException(ex); } } - - Ingester getIngester() { + + public Ingester getIngester() { return new Ingester(this.solrCore); } - - QueryResponse query(SolrQuery sq) throws SolrServerException { + + public QueryResponse query(SolrQuery sq) throws SolrServerException { return solrCore.query(sq); } - - TermsResponse queryTerms(SolrQuery sq) throws SolrServerException { + + public TermsResponse queryTerms(SolrQuery sq) throws SolrServerException { QueryResponse qres = solrCore.query(sq); return qres.getTermsResponse(); } - + + public String getSolrContent(final Content content) { + final SolrQuery q = new SolrQuery(); + q.setQuery("*:*"); + q.addFilterQuery("id:" + content.getId()); + q.setFields("content"); + try { + return (String) solrCore.query(q).getResults().get(0).getFieldValue("content"); + } catch (SolrServerException ex) { + logger.log(Level.WARNING, "Error getting content from Solr and validating regex match", ex); + return null; + } + } + void close() { try { CoreAdminRequest.unloadCore(this.name, solrServer); @@ -294,7 +302,7 @@ class Server { throw new RuntimeException(ex); } } - + /** * Execute query that gets only number of all Solr documents indexed * without actually returning the documents @@ -302,17 +310,17 @@ class Server { * @throws SolrServerException */ public int queryNumIndexedFiles() throws SolrServerException { - SolrQuery q = new SolrQuery("*:*"); - q.setRows(0); - return (int)query(q).getResults().getNumFound(); + SolrQuery q = new SolrQuery("*:*"); + q.setRows(0); + return (int) query(q).getResults().getNumFound(); + } } -} - + class ServerAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { logger.log(Level.INFO, e.paramString().trim()); - } + } } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java index 2e157eef40..979b52a08a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java @@ -24,9 +24,12 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.swing.SwingWorker; @@ -42,6 +45,7 @@ import org.openide.nodes.Node; import org.openide.windows.TopComponent; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.datamodel.KeyValueThing; +import org.sleuthkit.autopsy.keywordsearch.KeywordSearchQueryManager.Presentation; import org.sleuthkit.datamodel.FsContent; public class TermComponentQuery implements KeywordSearchQuery { @@ -51,31 +55,23 @@ public class TermComponentQuery implements KeywordSearchQuery { private static final String TERMS_SEARCH_FIELD = "content_ws"; private static final String TERMS_HANDLER = "/terms"; private static final int TERMS_TIMEOUT = 90 * 1000; //in ms - private static Logger logger = Logger.getLogger(TermComponentQuery.class.getName()); - - - private String termsQuery; private String queryEscaped; private boolean isEscaped; - public TermComponentQuery(String query) { this.termsQuery = query; this.queryEscaped = query; isEscaped = false; } - + @Override public void escape() { + //treat as literal + queryEscaped = Pattern.quote(termsQuery); isEscaped = true; - //will use prefix terms component query instead of regex - //to treat the query as a word } - - - @Override public boolean validate() { @@ -90,8 +86,10 @@ public class TermComponentQuery implements KeywordSearchQuery { return valid; } - - protected void executeQuery() { + /* + * helper method to create a Solr terms component query + */ + protected SolrQuery createQuery() { final SolrQuery q = new SolrQuery(); q.setQueryType(TERMS_HANDLER); q.setTerms(true); @@ -104,24 +102,90 @@ public class TermComponentQuery implements KeywordSearchQuery { q.addTermsField(TERMS_SEARCH_FIELD); q.setTimeAllowed(TERMS_TIMEOUT); + return q; + + } + + /* + * execute query and return terms + * helper method, can be called from the same or threaded query context + */ + protected List executeQuery(SolrQuery q) { + Server.Core solrCore = KeywordSearch.getServer().getCore(); + + List terms = null; + try { + TermsResponse tr = solrCore.queryTerms(q); + terms = tr.getTerms(TERMS_SEARCH_FIELD); + return terms; + } catch (SolrServerException ex) { + logger.log(Level.SEVERE, "Error executing the regex terms query: " + termsQuery, ex); + return null; //no need to create result view, just display error dialog + } + } + + + @Override + public String getEscapedQueryString() { + return this.queryEscaped; + } + + @Override + public String getQueryString() { + return this.termsQuery; + } + + /** + * return collapsed matches with all files for the query + * without per match breakdown + */ + @Override + public List performQuery() { + List results = new ArrayList(); + + final SolrQuery q = createQuery(); + List terms = executeQuery(q); + //get unique match result files + Set uniqueMatches = new TreeSet(); + + for (Term term : terms) { + String word = term.getTerm(); + LuceneQuery filesQuery = new LuceneQuery(word); + filesQuery.escape(); + List matches = filesQuery.performQuery(); + uniqueMatches.addAll(matches); + } + + //filter out non-matching files + //escape regex if needed + //(if the original query was not literal, this must be) + String literalQuery = null; + if (isEscaped) { + literalQuery = this.queryEscaped; + } else { + literalQuery = Pattern.quote(this.termsQuery); + } + for (FsContent f : uniqueMatches) { + Pattern p = Pattern.compile(literalQuery, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + final String contentStr = KeywordSearch.getServer().getCore().getSolrContent(f); + Matcher m = p.matcher(contentStr); + if (m.find()) { + results.add(f); + } + } + + return results; + } + + @Override + public void execute() { + SolrQuery q = createQuery(); + logger.log(Level.INFO, "Executing TermsComponent query: " + q.toString()); final SwingWorker worker = new TermsQueryWorker(q); worker.execute(); } - - @Override - public List performQuery() { - return null; - } - - - - @Override - public void execute() { - executeQuery(); - - } /** * map Terms to generic Nodes with key/value pairs properties @@ -146,9 +210,9 @@ public class TermComponentQuery implements KeywordSearchQuery { } Node rootNode = null; - if (things.size() > 0) { + if (things.size() > 0) { Children childThingNodes = - Children.create(new KeywordSearchResultFactory(termsQuery, things, KeywordSearchResultFactory.Presentation.COLLAPSE), true); + Children.create(new KeywordSearchResultFactory(termsQuery, things, Presentation.DETAIL), true); rootNode = new AbstractNode(childThingNodes); } else { @@ -156,17 +220,13 @@ public class TermComponentQuery implements KeywordSearchQuery { } final String pathText = "RegEx query"; - // String pathText = "RegEx query: " + termsQuery + // String pathText = "RegEx query: " + termsQuery //+ " Files with exact matches: " + Long.toString(totalMatches) + " (also listing approximate matches)"; TopComponent searchResultWin = DataResultTopComponent.createInstance("Keyword search", pathText, rootNode, things.size()); searchResultWin.requestActive(); // make it the active top component } - - - - class TermsQueryWorker extends SwingWorker, Void> { @@ -183,17 +243,7 @@ public class TermComponentQuery implements KeywordSearchQuery { progress.start(); progress.progress("Running Terms query."); - Server.Core solrCore = KeywordSearch.getServer().getCore(); - - - List terms = null; - try { - TermsResponse tr = solrCore.queryTerms(q); - terms = tr.getTerms(TERMS_SEARCH_FIELD); - } catch (SolrServerException ex) { - logger.log(Level.SEVERE, "Error executing the regex terms query: " + termsQuery, ex); - return null; //no need to create result view, just display error dialog - } + List terms = executeQuery(q); progress.progress("Terms query completed."); @@ -223,8 +273,6 @@ public class TermComponentQuery implements KeywordSearchQuery { progress.finish(); } } - - } } }