Autopsy  4.19.2
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-2021 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.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 org.apache.solr.client.solrj.SolrServerException;
30 import org.openide.util.NbBundle;
31 import org.openide.util.lookup.ServiceProvider;
32 import org.openide.util.lookup.ServiceProviders;
44 import org.sleuthkit.datamodel.BlackboardArtifact;
45 import org.sleuthkit.datamodel.Content;
46 import org.sleuthkit.datamodel.TskCoreException;
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 
75  @Override
76  public void index(Content content) throws TskCoreException {
77  if (content == null) {
78  return;
79  }
80  final Ingester ingester = Ingester.getDefault();
81  if (content instanceof BlackboardArtifact) {
82  BlackboardArtifact artifact = (BlackboardArtifact) content;
83  if (artifact.getArtifactID() > 0) {
84  /*
85  * Artifact indexing is only supported for artifacts that use
86  * negative artifact ids to avoid overlapping with the object
87  * ids of other types of Content.
88  */
89  return;
90  }
91  try {
92  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor(content, null);
93  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
94  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
95  ingester.indexMetaDataOnly(artifact, sourceName);
96  ingester.indexText(blackboardExtractedTextReader, artifact.getArtifactID(), sourceName, content, null);
97  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
98  throw new TskCoreException("Error indexing artifact", ex);
99  }
100  } else {
101  try {
102  TextExtractor contentExtractor = TextExtractorFactory.getExtractor(content, null);
103  Reader contentExtractedTextReader = contentExtractor.getReader();
104  ingester.indexText(contentExtractedTextReader, content.getId(), content.getName(), content, null);
105  } catch (TextExtractorFactory.NoTextExtractorFound | Ingester.IngesterException | TextExtractor.InitReaderException ex) {
106  try {
107  // Try the StringsTextExtractor if Tika extractions fails.
108  TextExtractor stringsExtractor = TextExtractorFactory.getStringsExtractor(content, null);
109  Reader stringsExtractedTextReader = stringsExtractor.getReader();
110  ingester.indexStrings(stringsExtractedTextReader, content.getId(), content.getName(), content, null);
111  } catch (Ingester.IngesterException | TextExtractor.InitReaderException ex1) {
112  throw new TskCoreException("Error indexing content", ex1);
113  }
114  }
115  // only do a Solr commit if ingest is not running. If ingest is running, the changes will
116  // be committed via a periodic commit or via final commit after the ingest job has finished.
118  ingester.commit();
119  }
120  }
121  }
122 
131  @Override
132  public void tryConnect(String host, int port) throws KeywordSearchServiceException {
133  if (host == null || host.isEmpty()) {
134  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.MissingHostname")); //NON-NLS
135  }
136  try {
137  KeywordSearch.getServer().connectToSolrServer(host, Integer.toString(port));
138  } catch (SolrServerException ex) {
139  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
140  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort")); //NON-NLS*/
141  } catch (IOException ex) {
142  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
143  String result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
144  String message = ex.getCause().getMessage().toLowerCase();
145  if (message.startsWith(SERVER_REFUSED_CONNECTION)) {
146  try {
147  if (InetAddress.getByName(host).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
148  // if we can reach the host, then it's probably port problem
149  result = Bundle.SolrConnectionCheck_Port();
150  } else {
151  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
152  }
153  } catch (IOException | MissingResourceException any) {
154  // it may be anything
155  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
156  }
157  } else if (message.startsWith(BAD_IP_ADDRESS_FORMAT)) {
158  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.Hostname"); //NON-NLS
159  }
160  throw new KeywordSearchServiceException(result);
161  } catch (NumberFormatException ex) {
162  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
163  throw new KeywordSearchServiceException(Bundle.SolrConnectionCheck_Port());
164  } catch (IllegalArgumentException ex) {
165  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
166  throw new KeywordSearchServiceException(ex.getMessage());
167  }
168  }
169 
178  @Override
179  public void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException {
180 
181  try {
182  Server ddsServer = KeywordSearch.getServer();
183  ddsServer.deleteDataSource(dataSourceId);
184  } catch (IOException | KeywordSearchModuleException | NoOpenCoreException | SolrServerException ex) {
185  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
186  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
187  }
188  }
189 
195  @NbBundle.Messages({
196  "# {0} - case directory", "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from case directory: {0}",
197  "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case",
198  "# {0} - collection name", "SolrSearchService.exceptionMessage.unableToDeleteCollection=Unable to delete collection {0}",
199  "# {0} - index folder path", "SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0}"
200  })
201  @Override
203  String caseDirectory = metadata.getCaseDirectory();
204  IndexMetadata indexMetadata;
205  try {
206  indexMetadata = new IndexMetadata(caseDirectory);
207  } catch (IndexMetadata.TextIndexMetadataException ex) {
208  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
209  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
210  }
211 
212  if (indexMetadata.getIndexes().isEmpty()) {
213  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class,
214  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
215  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class,
216  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
217  }
218 
219  // delete index(es) for this case
220  for (Index index : indexMetadata.getIndexes()) {
221  try {
222  // Unload/delete the collection on the server and then delete the text index files.
223  KeywordSearch.getServer().deleteCollection(index.getIndexName(), metadata);
224  } catch (KeywordSearchModuleException ex) {
225  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_unableToDeleteCollection(index.getIndexName()), ex);
226  }
227  File indexDir = new File(index.getIndexPath()).getParentFile();
228  if (indexDir.exists()) {
229  if (!FileUtil.deleteDir(indexDir)) {
230  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath()));
231  }
232  }
233  }
234  }
235 
236  @Override
237  public String getServiceName() {
238  return NbBundle.getMessage(this.getClass(), "SolrSearchService.ServiceName");
239  }
240 
249  @Override
250  @NbBundle.Messages({
251  "SolrSearch.lookingForMetadata.msg=Looking for text index metadata file",
252  "SolrSearch.readingIndexes.msg=Reading text index metadata file",
253  "SolrSearch.findingIndexes.msg=Looking for existing text index directories",
254  "SolrSearch.creatingNewIndex.msg=Creating new text index",
255  "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version",
256  "SolrSearch.indentifyingIndex.msg=Identifying text index to use",
257  "SolrSearch.openCore.msg=Opening text index. For large cases this may take several minutes.",
258  "# {0} - futureVersion", "# {1} - currentVersion",
259  "SolrSearch.futureIndexVersion.msg=The text index for the case is for Solr {0}. This version of Autopsy is compatible with Solr {1}.",
260  "SolrSearch.unableToFindIndex.msg=Unable to find index that can be used for this case",
261  "SolrSearch.complete.msg=Text index successfully opened"})
263  if (context.cancelRequested()) {
264  return;
265  }
266 
267  ProgressIndicator progress = context.getProgressIndicator();
268  int totalNumProgressUnits = 7;
269  int progressUnitsCompleted = 0;
270 
271  String caseDirPath = context.getCase().getCaseDirectory();
272  Case theCase = context.getCase();
273  List<Index> indexes = new ArrayList<>();
274  progress.progress(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
275  if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
276  try {
277  // metadata file exists, get list of existing Solr cores for this case
278  progressUnitsCompleted++;
279  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
280  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath);
281  indexes = indexMetadata.getIndexes();
282  } catch (IndexMetadata.TextIndexMetadataException ex) {
283  logger.log(Level.SEVERE, String.format("Unable to read text index metadata file"), ex);
284  throw new AutopsyServiceException("Unable to read text index metadata file", ex);
285  }
286  }
287 
288  if (context.cancelRequested()) {
289  return;
290  }
291 
292  // check if we found any existing indexes
293  Index currentVersionIndex = null;
294  if (indexes.isEmpty()) {
295  // new case that doesn't have an existing index. create new index folder
296  progressUnitsCompleted++;
297  progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
298  currentVersionIndex = IndexFinder.createLatestVersionIndex(theCase);
299  // add current index to the list of indexes that exist for this case
300  indexes.add(currentVersionIndex);
301  } else {
302  // check if one of the existing indexes is for latest Solr version and schema
303  progressUnitsCompleted++;
304  progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
305  currentVersionIndex = IndexFinder.findLatestVersionIndex(indexes);
306  if (currentVersionIndex == null) {
307  // found existing index(es) but none were for latest Solr version and schema version
308  progressUnitsCompleted++;
309  progress.progress(Bundle.SolrSearch_indentifyingIndex_msg(), progressUnitsCompleted);
310  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
311  if (indexToUse == null) {
312  // unable to find index that can be used. check if the available index is for a "future" version of Solr,
313  // i.e. the user is using an "old/legacy" version of Autopsy to open cases created by later versions of Autopsy.
314  String futureIndexVersion = IndexFinder.isFutureIndexPresent(indexes);
315  if (!futureIndexVersion.isEmpty()) {
316  throw new AutopsyServiceException(Bundle.SolrSearch_futureIndexVersion_msg(futureIndexVersion, IndexFinder.getCurrentSolrVersion()));
317  }
318  throw new AutopsyServiceException(Bundle.SolrSearch_unableToFindIndex_msg());
319  }
320 
321  if (context.cancelRequested()) {
322  return;
323  }
324 
325  // check if schema is compatible
326  if (!indexToUse.isCompatible(IndexFinder.getCurrentSchemaVersion())) {
327  String msg = "Text index schema version " + indexToUse.getSchemaVersion() + " is not compatible with current schema";
328  logger.log(Level.WARNING, msg);
329  throw new AutopsyServiceException(msg);
330  }
331  // proceed with case open
332  currentVersionIndex = indexToUse;
333  }
334  }
335 
336  try {
337  // update text index metadata file
338  if (!indexes.isEmpty()) {
339  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath, indexes);
340  }
341  } catch (IndexMetadata.TextIndexMetadataException ex) {
342  throw new AutopsyServiceException("Failed to save Solr core info in text index metadata file", ex);
343  }
344 
345  // open core
346  try {
347  progress.progress(Bundle.SolrSearch_openCore_msg(), totalNumProgressUnits - 1);
348  KeywordSearch.getServer().openCoreForCase(theCase, currentVersionIndex);
349  } catch (KeywordSearchModuleException ex) {
350  throw new AutopsyServiceException(String.format("Failed to open or create core for %s", caseDirPath), ex);
351  }
352  if (context.cancelRequested()) {
353  return;
354  }
355 
356  theCase.getSleuthkitCase().registerForEvents(this);
357 
358  progress.progress(Bundle.SolrSearch_complete_msg(), totalNumProgressUnits);
359  }
360 
369  @Override
371  /*
372  * TODO (JIRA 2525): The following code KeywordSearch.CaseChangeListener
373  * gambles that any BlackboardResultWriters (SwingWorkers) will complete
374  * in less than roughly two seconds. This stuff should be reworked using
375  * an ExecutorService and tasks with Futures.
376  */
377  AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters();
378  try {
379  Thread.sleep(2000);
380  } catch (InterruptedException ex) {
381  logger.log(Level.SEVERE, "Unexpected interrupt while waiting for BlackboardResultWriters to terminate", ex);
382  }
383 
384  try {
385  KeywordSearch.getServer().closeCore();
386  } catch (KeywordSearchModuleException ex) {
387  throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex);
388  }
389 
390  if (context.getCase().getSleuthkitCase() != null) {
391  context.getCase().getSleuthkitCase().unregisterForEvents(this);
392  }
393  }
394 
404  @Deprecated
405  @Override
406  public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException {
407  if (artifact == null) {
408  return;
409  }
410 
411  // We only support artifact indexing for Autopsy versions that use
412  // the negative range for artifact ids.
413  if (artifact.getArtifactID() > 0) {
414  return;
415  }
416  final Ingester ingester = Ingester.getDefault();
417 
418  try {
419  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
420  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor((Content) artifact, null);
421  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
422  ingester.indexMetaDataOnly(artifact, sourceName);
423  ingester.indexText(blackboardExtractedTextReader, artifact.getId(), sourceName, artifact, null);
424  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
425  throw new TskCoreException(ex.getCause().getMessage(), ex);
426  }
427  }
428 }
static synchronized IngestManager getInstance()
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-2021 Basis Technology. Generated on: Tue Feb 22 2022
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.