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

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