Autopsy  4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
SolrSearchService.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2017 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.keywordsearch;
20 
21 import java.io.IOException;
22 import java.lang.reflect.InvocationTargetException;
23 import java.net.InetAddress;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.MissingResourceException;
27 import java.util.logging.Level;
28 import javax.swing.JDialog;
29 import javax.swing.JOptionPane;
30 import javax.swing.SwingUtilities;
31 import org.apache.commons.lang.math.NumberUtils;
32 import org.apache.solr.client.solrj.SolrServerException;
33 import org.apache.solr.client.solrj.impl.HttpSolrServer;
34 import org.openide.util.NbBundle;
35 import org.openide.util.lookup.ServiceProvider;
36 import org.openide.util.lookup.ServiceProviders;
47 
52 @ServiceProviders(value = {
53  @ServiceProvider(service = KeywordSearchService.class),
54  @ServiceProvider(service = AutopsyService.class)}
55 )
57 
58  private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS
59  private static final String SERVER_REFUSED_CONNECTION = "server refused connection"; //NON-NLS
60  private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
61  private static final Logger logger = Logger.getLogger(SolrSearchService.class.getName());
62 
71  @Override
72  public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException {
73  if (artifact == null) {
74  return;
75  }
76 
77  // We only support artifact indexing for Autopsy versions that use
78  // the negative range for artifact ids.
79  if (artifact.getArtifactID() > 0) {
80  return;
81  }
82  final Ingester ingester = Ingester.getDefault();
83 
84  try {
85  ingester.indexMetaDataOnly(artifact);
86  ingester.indexText(new ArtifactTextExtractor(), artifact, null);
87  } catch (Ingester.IngesterException ex) {
88  throw new TskCoreException(ex.getCause().getMessage(), ex);
89  }
90  }
91 
100  @Override
101  public void tryConnect(String host, int port) throws KeywordSearchServiceException {
102  HttpSolrServer solrServer = null;
103  if (host == null || host.isEmpty()) {
104  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.MissingHostname")); //NON-NLS
105  }
106  try {
107  solrServer = new HttpSolrServer("http://" + host + ":" + Integer.toString(port) + "/solr"); //NON-NLS
108  KeywordSearch.getServer().connectToSolrServer(solrServer);
109  } catch (SolrServerException ex) {
110  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort")); //NON-NLS
111  } catch (IOException ex) {
112  String result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
113  String message = ex.getCause().getMessage().toLowerCase();
114  if (message.startsWith(SERVER_REFUSED_CONNECTION)) {
115  try {
116  if (InetAddress.getByName(host).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
117  // if we can reach the host, then it's probably port problem
118  result = Bundle.SolrConnectionCheck_Port();
119  } else {
120  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
121  }
122  } catch (IOException | MissingResourceException any) {
123  // it may be anything
124  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
125  }
126  } else if (message.startsWith(BAD_IP_ADDRESS_FORMAT)) {
127  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.Hostname"); //NON-NLS
128  }
129  throw new KeywordSearchServiceException(result);
130  } catch (NumberFormatException ex) {
131  throw new KeywordSearchServiceException(Bundle.SolrConnectionCheck_Port());
132  } catch (IllegalArgumentException ex) {
133  throw new KeywordSearchServiceException(ex.getMessage());
134  } finally {
135  if (null != solrServer) {
136  solrServer.shutdown();
137  }
138  }
139  }
140 
141  @NbBundle.Messages({"# {0} - case directory",
142  "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from caseDirectory: {0}",
143  "# {0} - case directory",
144  "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case"
145  })
151  @Override
153  String caseDirectory = metadata.getCaseDirectory();
154  IndexMetadata indexMetadata;
155  try {
156  indexMetadata = new IndexMetadata(caseDirectory);
157  } catch (IndexMetadata.TextIndexMetadataException ex) {
158  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
159  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
160  }
161  //find the index for the current version of solr (the one we are connected to) and delete its core using the index name
162  String currentSchema = IndexFinder.getCurrentSchemaVersion();
163  String currentSolr = IndexFinder.getCurrentSolrVersion();
164  for (Index index : indexMetadata.getIndexes()) {
165  if (index.getSolrVersion().equals(currentSolr) && index.getSchemaVersion().equals(currentSchema)) {
166  KeywordSearch.getServer().deleteCore(index.getIndexName());
167  return; //only one core exists for each combination of solr and schema version
168  }
169  }
170  //this code this code will only execute if an index for the current core was not found
171  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
172  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
173  }
174 
175  @Override
176  public void close() throws IOException {
177  }
178 
179  @Override
180  public String getServiceName() {
181  return NbBundle.getMessage(this.getClass(), "SolrSearchService.ServiceName");
182  }
183 
192  @Override
193  @NbBundle.Messages({
194  "SolrSearch.lookingForMetadata.msg=Looking for text index metadata file",
195  "SolrSearch.readingIndexes.msg=Reading text index metadata file",
196  "SolrSearch.findingIndexes.msg=Looking for existing text index directories",
197  "SolrSearch.creatingNewIndex.msg=Creating new text index",
198  "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version",
199  "SolrSearch.indentifyingIndex.msg=Identifying text index to use",
200  "SolrSearch.openCore.msg=Opening text index",
201  "SolrSearch.complete.msg=Text index successfully opened"})
203  ProgressIndicator progress = context.getProgressIndicator();
204  int totalNumProgressUnits = 7;
205  int progressUnitsCompleted = 0;
206 
207  String caseDirPath = context.getCase().getCaseDirectory();
208  Case theCase = context.getCase();
209  List<Index> indexes = new ArrayList<>();
210  progress.start(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
211  if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
212  try {
213  // metadata file exists, get list of existing Solr cores for this case
214  progressUnitsCompleted++;
215  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
216  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath);
217  indexes = indexMetadata.getIndexes();
218  } catch (IndexMetadata.TextIndexMetadataException ex) {
219  logger.log(Level.SEVERE, String.format("Unable to read text index metadata file"), ex);
220  throw new AutopsyServiceException("Unable to read text index metadata file", ex);
221  }
222  } else {
223  // metadata file doesn't exist.
224  // do case subdirectory search to look for Solr 4 Schema 1.8 indexes
225  progressUnitsCompleted++;
226  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
227  Index oldIndex = IndexFinder.findOldIndexDir(theCase);
228  if (oldIndex != null) {
229  // add index to the list of indexes that exist for this case
230  indexes.add(oldIndex);
231  }
232  }
233 
234  if (context.cancelRequested()) {
235  return;
236  }
237 
238  // check if we found any existing indexes
239  Index currentVersionIndex = null;
240  if (indexes.isEmpty()) {
241  // new case that doesn't have an existing index. create new index folder
242  progressUnitsCompleted++;
243  progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
244  currentVersionIndex = IndexFinder.createLatestVersionIndexDir(theCase);
245  // add current index to the list of indexes that exist for this case
246  indexes.add(currentVersionIndex);
247  } else {
248  // check if one of the existing indexes is for latest Solr version and schema
249  progressUnitsCompleted++;
250  progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
251  currentVersionIndex = IndexFinder.findLatestVersionIndexDir(indexes);
252  if (currentVersionIndex == null) {
253  // found existing index(es) but none were for latest Solr version and schema version
254  progressUnitsCompleted++;
255  progress.progress(Bundle.SolrSearch_indentifyingIndex_msg(), progressUnitsCompleted);
256  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
257  if (indexToUse == null) {
258  // unable to find index that can be used
259  throw new AutopsyServiceException("Unable to find index that can be used for this case");
260  }
261 
262  if (context.cancelRequested()) {
263  return;
264  }
265 
266  double currentSolrVersion = NumberUtils.toDouble(IndexFinder.getCurrentSolrVersion());
267  double indexSolrVersion = NumberUtils.toDouble(indexToUse.getSolrVersion());
268  if (indexSolrVersion == currentSolrVersion) {
269  // latest Solr version but not latest schema. index should be used in read-only mode
271  // pop up a message box to indicate the read-only restrictions.
272  JOptionPane optionPane = new JOptionPane(
273  NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.msg"),
274  JOptionPane.WARNING_MESSAGE,
275  JOptionPane.DEFAULT_OPTION);
276  try {
277  SwingUtilities.invokeAndWait(() -> {
278  JDialog dialog = optionPane.createDialog(NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.title"));
279  dialog.setVisible(true);
280  });
281  } catch (InterruptedException ex) {
282  // Cancelled
283  return;
284  } catch (InvocationTargetException ex) {
285  throw new AutopsyServiceException("Error displaying limited search features warning dialog", ex);
286  }
287  }
288  // proceed with case open
289  currentVersionIndex = indexToUse;
290  } else {
291  // index needs to be upgraded to latest supported version of Solr
292  throw new AutopsyServiceException("Unable to find index to use for Case open");
293  }
294  }
295  }
296 
297  try {
298  // update text index metadata file
299  if (!indexes.isEmpty()) {
300  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath, indexes);
301  }
302  } catch (IndexMetadata.TextIndexMetadataException ex) {
303  throw new AutopsyServiceException("Failed to save Solr core info in text index metadata file", ex);
304  }
305 
306  // open core
307  try {
308  progress.progress(Bundle.SolrSearch_openCore_msg(), totalNumProgressUnits - 1);
309  KeywordSearch.getServer().openCoreForCase(theCase, currentVersionIndex);
310  } catch (KeywordSearchModuleException ex) {
311  throw new AutopsyServiceException(String.format("Failed to open or create core for %s", caseDirPath), ex);
312  }
313 
314  progress.progress(Bundle.SolrSearch_complete_msg(), totalNumProgressUnits);
315  }
316 
324  @Override
326  /*
327  * TODO (JIRA 2525): The following code KeywordSearch.CaseChangeListener
328  * gambles that any BlackboardResultWriters (SwingWorkers) will complete
329  * in less than roughly two seconds. This stuff should be reworked using
330  * an ExecutorService and tasks with Futures.
331  */
332  KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters();
333  try {
334  Thread.sleep(2000);
335  } catch (InterruptedException ex) {
336  logger.log(Level.SEVERE, "Unexpected interrupt while waiting for BlackboardResultWriters to terminate", ex);
337  }
338 
339  try {
340  KeywordSearch.getServer().closeCore();
341  } catch (KeywordSearchModuleException ex) {
342  throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex);
343  }
344  }
345 }
void start(String message, int totalWorkUnits)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161

Copyright © 2012-2016 Basis Technology. Generated on: Mon Apr 24 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.