Autopsy  4.10.0
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 2015-2018 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.File;
22 import java.io.IOException;
23 import java.io.Reader;
24 import java.lang.reflect.InvocationTargetException;
25 import java.net.InetAddress;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.MissingResourceException;
29 import java.util.logging.Level;
30 import javax.swing.JDialog;
31 import javax.swing.JOptionPane;
32 import javax.swing.SwingUtilities;
33 import org.apache.commons.lang.math.NumberUtils;
34 import org.apache.commons.io.FileUtils;
35 import org.apache.solr.client.solrj.SolrServerException;
36 import org.apache.solr.client.solrj.impl.HttpSolrServer;
37 import org.openide.util.NbBundle;
38 import org.openide.util.lookup.ServiceProvider;
39 import org.openide.util.lookup.ServiceProviders;
51 import org.sleuthkit.datamodel.BlackboardArtifact;
52 import org.sleuthkit.datamodel.Content;
53 import org.sleuthkit.datamodel.TskCoreException;
54 
59 @ServiceProviders(value = {
60  @ServiceProvider(service = KeywordSearchService.class)
61  ,
62  @ServiceProvider(service = AutopsyService.class)}
63 )
65 
66  private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS
67  private static final String SERVER_REFUSED_CONNECTION = "server refused connection"; //NON-NLS
68  private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
69  private static final int LARGE_INDEX_SIZE_GB = 50;
70  private static final int GIANT_INDEX_SIZE_GB = 500;
71  private static final Logger logger = Logger.getLogger(SolrSearchService.class.getName());
72 
88  @Override
89  public void index(Content content) throws TskCoreException {
90  /*
91  * TODO (JIRA-1099): The following code has some issues that need to be
92  * resolved. For artifacts, it is assumed that the posting of artifacts
93  * is only occuring during an ingest job with an enabled keyword search
94  * ingest module handling index commits; it also assumes that the
95  * artifacts are only posted by modules in the either the file level
96  * ingest pipeline or the first stage data source level ingest pipeline,
97  * so that the artifacts will be searched during a periodic or final
98  * keyword search. It also assumes that the only other type of Content
99  * for which this API will be called are Reports generated at a time
100  * when doing a commit is required and desirable, i.e., in a context
101  * other than an ingest job.
102  */
103  if (content == null) {
104  return;
105  }
106  final Ingester ingester = Ingester.getDefault();
107  if (content instanceof BlackboardArtifact) {
108  BlackboardArtifact artifact = (BlackboardArtifact) content;
109  if (artifact.getArtifactID() > 0) {
110  /*
111  * Artifact indexing is only supported for artifacts that use
112  * negative artifact ids to avoid overlapping with the object
113  * ids of other types of Content.
114  */
115  return;
116  }
117  try {
118  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor(content, null);
119  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
120  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
121  ingester.indexMetaDataOnly(artifact, sourceName);
122  ingester.indexText(blackboardExtractedTextReader, artifact.getArtifactID(), sourceName, content, null);
123  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
124  throw new TskCoreException("Error indexing artifact", ex);
125  }
126  } else {
127  try {
128  TextExtractor contentExtractor = TextExtractorFactory.getExtractor(content, null);
129  Reader contentExtractedTextReader = contentExtractor.getReader();
130  ingester.indexText(contentExtractedTextReader, content.getId(), content.getName(), content, null);
131  } catch (TextExtractorFactory.NoTextExtractorFound | Ingester.IngesterException | TextExtractor.InitReaderException ex) {
132  try {
133  // Try the StringsTextExtractor if Tika extractions fails.
134  TextExtractor stringsExtractor = TextExtractorFactory.getStringsExtractor(content, null);
135  Reader stringsExtractedTextReader = stringsExtractor.getReader();
136  ingester.indexText(stringsExtractedTextReader,content.getId(),content.getName(), content, null);
137  } catch (Ingester.IngesterException | TextExtractor.InitReaderException ex1) {
138  throw new TskCoreException("Error indexing content", ex1);
139  }
140  }
141  ingester.commit();
142  }
143  }
144 
153  @Override
154  public void tryConnect(String host, int port) throws KeywordSearchServiceException {
155  HttpSolrServer solrServer = null;
156  if (host == null || host.isEmpty()) {
157  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.MissingHostname")); //NON-NLS
158  }
159  try {
160  solrServer = new HttpSolrServer("http://" + host + ":" + Integer.toString(port) + "/solr"); //NON-NLS
161  KeywordSearch.getServer().connectToSolrServer(solrServer);
162  } catch (SolrServerException ex) {
163  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort")); //NON-NLS
164  } catch (IOException ex) {
165  String result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
166  String message = ex.getCause().getMessage().toLowerCase();
167  if (message.startsWith(SERVER_REFUSED_CONNECTION)) {
168  try {
169  if (InetAddress.getByName(host).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
170  // if we can reach the host, then it's probably port problem
171  result = Bundle.SolrConnectionCheck_Port();
172  } else {
173  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
174  }
175  } catch (IOException | MissingResourceException any) {
176  // it may be anything
177  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
178  }
179  } else if (message.startsWith(BAD_IP_ADDRESS_FORMAT)) {
180  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.Hostname"); //NON-NLS
181  }
182  throw new KeywordSearchServiceException(result);
183  } catch (NumberFormatException ex) {
184  throw new KeywordSearchServiceException(Bundle.SolrConnectionCheck_Port());
185  } catch (IllegalArgumentException ex) {
186  throw new KeywordSearchServiceException(ex.getMessage());
187  } finally {
188  if (null != solrServer) {
189  solrServer.shutdown();
190  }
191  }
192  }
193 
199  @NbBundle.Messages({
200  "# {0} - case directory", "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from case directory: {0}",
201  "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case",
202  "# {0} - index folder path", "SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0}"
203  })
204  @Override
206  String caseDirectory = metadata.getCaseDirectory();
207  IndexMetadata indexMetadata;
208  try {
209  indexMetadata = new IndexMetadata(caseDirectory);
210  } catch (IndexMetadata.TextIndexMetadataException ex) {
211  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
212  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
213  }
214  //find the index for the current version of solr (the one we are connected to) and delete its core using the index name
215  String currentSchema = IndexFinder.getCurrentSchemaVersion();
216  String currentSolr = IndexFinder.getCurrentSolrVersion();
217  for (Index index : indexMetadata.getIndexes()) {
218  if (index.getSolrVersion().equals(currentSolr) && index.getSchemaVersion().equals(currentSchema)) {
219  /*
220  * Unload/delete the core on the server and then delete the text
221  * index files.
222  */
223  KeywordSearch.getServer().deleteCore(index.getIndexName(), metadata);
224  if (!FileUtil.deleteDir(new File(index.getIndexPath()).getParentFile())) {
225  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath()));
226  }
227  }
228  return; //only one core exists for each combination of solr and schema version
229  }
230 
231  //this code this code will only execute if an index for the current core was not found
232  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class,
233  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
234  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class,
235  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
236  }
237 
238  @Override
239  public void close() throws IOException {
240  }
241 
242  @Override
243  public String getServiceName() {
244  return NbBundle.getMessage(this.getClass(), "SolrSearchService.ServiceName");
245  }
246 
255  @Override
256  @NbBundle.Messages({
257  "SolrSearch.lookingForMetadata.msg=Looking for text index metadata file",
258  "SolrSearch.readingIndexes.msg=Reading text index metadata file",
259  "SolrSearch.findingIndexes.msg=Looking for existing text index directories",
260  "SolrSearch.creatingNewIndex.msg=Creating new text index",
261  "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version",
262  "SolrSearch.indentifyingIndex.msg=Identifying text index to use",
263  "SolrSearch.openCore.msg=Opening text index",
264  "SolrSearch.openLargeCore.msg=Opening text index. This may take several minutes.",
265  "SolrSearch.openGiantCore.msg=Opening text index. Text index for this case is very large and may take long time to load.",
266  "SolrSearch.complete.msg=Text index successfully opened"})
268  if (context.cancelRequested()) {
269  return;
270  }
271 
272  ProgressIndicator progress = context.getProgressIndicator();
273  int totalNumProgressUnits = 7;
274  int progressUnitsCompleted = 0;
275 
276  String caseDirPath = context.getCase().getCaseDirectory();
277  Case theCase = context.getCase();
278  List<Index> indexes = new ArrayList<>();
279  progress.start(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
280  if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
281  try {
282  // metadata file exists, get list of existing Solr cores for this case
283  progressUnitsCompleted++;
284  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
285  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath);
286  indexes = indexMetadata.getIndexes();
287  } catch (IndexMetadata.TextIndexMetadataException ex) {
288  logger.log(Level.SEVERE, String.format("Unable to read text index metadata file"), ex);
289  throw new AutopsyServiceException("Unable to read text index metadata file", ex);
290  }
291  } else {
292  // metadata file doesn't exist.
293  // do case subdirectory search to look for Solr 4 Schema 1.8 indexes
294  progressUnitsCompleted++;
295  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
296  Index oldIndex = IndexFinder.findOldIndexDir(theCase);
297  if (oldIndex != null) {
298  // add index to the list of indexes that exist for this case
299  indexes.add(oldIndex);
300  }
301  }
302 
303  if (context.cancelRequested()) {
304  return;
305  }
306 
307  // check if we found any existing indexes
308  Index currentVersionIndex = null;
309  if (indexes.isEmpty()) {
310  // new case that doesn't have an existing index. create new index folder
311  progressUnitsCompleted++;
312  progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
313  currentVersionIndex = IndexFinder.createLatestVersionIndexDir(theCase);
314  // add current index to the list of indexes that exist for this case
315  indexes.add(currentVersionIndex);
316  } else {
317  // check if one of the existing indexes is for latest Solr version and schema
318  progressUnitsCompleted++;
319  progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
320  currentVersionIndex = IndexFinder.findLatestVersionIndexDir(indexes);
321  if (currentVersionIndex == null) {
322  // found existing index(es) but none were for latest Solr version and schema version
323  progressUnitsCompleted++;
324  progress.progress(Bundle.SolrSearch_indentifyingIndex_msg(), progressUnitsCompleted);
325  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
326  if (indexToUse == null) {
327  // unable to find index that can be used
328  throw new AutopsyServiceException("Unable to find index that can be used for this case");
329  }
330 
331  if (context.cancelRequested()) {
332  return;
333  }
334 
335  double currentSolrVersion = NumberUtils.toDouble(IndexFinder.getCurrentSolrVersion());
336  double indexSolrVersion = NumberUtils.toDouble(indexToUse.getSolrVersion());
337  if (indexSolrVersion == currentSolrVersion) {
338  // latest Solr version but schema not compatible. index should be used in read-only mode
339  if (!indexToUse.isCompatible(IndexFinder.getCurrentSchemaVersion()) && RuntimeProperties.runningWithGUI()) {
340  // pop up a message box to indicate the read-only restrictions.
341  JOptionPane optionPane = new JOptionPane(
342  NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.msg"),
343  JOptionPane.WARNING_MESSAGE,
344  JOptionPane.DEFAULT_OPTION);
345  try {
346  SwingUtilities.invokeAndWait(() -> {
347  JDialog dialog = optionPane.createDialog(NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.title"));
348  dialog.setVisible(true);
349  });
350  } catch (InterruptedException ex) {
351  // Cancelled
352  return;
353  } catch (InvocationTargetException ex) {
354  throw new AutopsyServiceException("Error displaying limited search features warning dialog", ex);
355  }
356  }
357  // proceed with case open
358  currentVersionIndex = indexToUse;
359  } else {
360  // index needs to be upgraded to latest supported version of Solr
361  throw new AutopsyServiceException("Unable to find index to use for Case open");
362  }
363  }
364  }
365 
366  try {
367  // update text index metadata file
368  if (!indexes.isEmpty()) {
369  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath, indexes);
370  }
371  } catch (IndexMetadata.TextIndexMetadataException ex) {
372  throw new AutopsyServiceException("Failed to save Solr core info in text index metadata file", ex);
373  }
374 
375  // open core
376  try {
377  // check text index size to gauge estimated time to open/load the index
378  long indexSizeInBytes = FileUtils.sizeOfDirectory(new File(currentVersionIndex.getIndexPath()));
379  long sizeInGb = indexSizeInBytes / 1000000000;
380  if (sizeInGb < LARGE_INDEX_SIZE_GB) {
381  progress.progress(Bundle.SolrSearch_openCore_msg(), totalNumProgressUnits - 1);
382  } else if (sizeInGb >= LARGE_INDEX_SIZE_GB && sizeInGb < GIANT_INDEX_SIZE_GB) {
383  progress.switchToIndeterminate(Bundle.SolrSearch_openLargeCore_msg());
384  } else {
385  progress.switchToIndeterminate(Bundle.SolrSearch_openGiantCore_msg());
386  }
387 
388  KeywordSearch.getServer().openCoreForCase(theCase, currentVersionIndex);
389  } catch (KeywordSearchModuleException ex) {
390  throw new AutopsyServiceException(String.format("Failed to open or create core for %s", caseDirPath), ex);
391  }
392 
393  progress.progress(Bundle.SolrSearch_complete_msg(), totalNumProgressUnits);
394  }
395 
404  @Override
406  /*
407  * TODO (JIRA 2525): The following code KeywordSearch.CaseChangeListener
408  * gambles that any BlackboardResultWriters (SwingWorkers) will complete
409  * in less than roughly two seconds. This stuff should be reworked using
410  * an ExecutorService and tasks with Futures.
411  */
412  AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters();
413  try {
414  Thread.sleep(2000);
415  } catch (InterruptedException ex) {
416  logger.log(Level.SEVERE, "Unexpected interrupt while waiting for BlackboardResultWriters to terminate", ex);
417  }
418 
419  try {
420  KeywordSearch.getServer().closeCore();
421  } catch (KeywordSearchModuleException ex) {
422  throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex);
423  }
424  }
425 
435  @Deprecated
436  @Override
437  public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException {
438  if (artifact == null) {
439  return;
440  }
441 
442  // We only support artifact indexing for Autopsy versions that use
443  // the negative range for artifact ids.
444  if (artifact.getArtifactID() > 0) {
445  return;
446  }
447  final Ingester ingester = Ingester.getDefault();
448 
449  try {
450  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
451  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor((Content) artifact, null);
452  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
453  ingester.indexMetaDataOnly(artifact, sourceName);
454  ingester.indexText(blackboardExtractedTextReader, artifact.getId(), sourceName, artifact, null);
455  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
456  throw new TskCoreException(ex.getCause().getMessage(), ex);
457  }
458  }
459 
460 }
void start(String message, int totalWorkUnits)
static TextExtractor getStringsExtractor(Content content, Lookup context)
static TextExtractor getExtractor(Content content, Lookup context)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static boolean deleteDir(File dirPath)
Definition: FileUtil.java:47

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.