Autopsy  4.4
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.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.solr.client.solrj.SolrServerException;
34 import org.apache.solr.client.solrj.impl.HttpSolrServer;
35 import org.openide.util.NbBundle;
36 import org.openide.util.lookup.ServiceProvider;
37 import org.openide.util.lookup.ServiceProviders;
47 import org.sleuthkit.datamodel.BlackboardArtifact;
48 import org.sleuthkit.datamodel.TskCoreException;
49 
54 @ServiceProviders(value = {
55  @ServiceProvider(service = KeywordSearchService.class)
56  ,
57  @ServiceProvider(service = AutopsyService.class)}
58 )
60 
61  private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS
62  private static final String SERVER_REFUSED_CONNECTION = "server refused connection"; //NON-NLS
63  private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
64  private static final Logger logger = Logger.getLogger(SolrSearchService.class.getName());
65 
74  @Override
75  public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException {
76  if (artifact == null) {
77  return;
78  }
79 
80  // We only support artifact indexing for Autopsy versions that use
81  // the negative range for artifact ids.
82  if (artifact.getArtifactID() > 0) {
83  return;
84  }
85  final Ingester ingester = Ingester.getDefault();
86 
87  try {
88  ingester.indexMetaDataOnly(artifact);
89  ingester.indexText(new ArtifactTextExtractor(), artifact, null);
90  } catch (Ingester.IngesterException ex) {
91  throw new TskCoreException(ex.getCause().getMessage(), ex);
92  }
93  }
94 
103  @Override
104  public void tryConnect(String host, int port) throws KeywordSearchServiceException {
105  HttpSolrServer solrServer = null;
106  if (host == null || host.isEmpty()) {
107  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.MissingHostname")); //NON-NLS
108  }
109  try {
110  solrServer = new HttpSolrServer("http://" + host + ":" + Integer.toString(port) + "/solr"); //NON-NLS
111  KeywordSearch.getServer().connectToSolrServer(solrServer);
112  } catch (SolrServerException ex) {
113  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort")); //NON-NLS
114  } catch (IOException ex) {
115  String result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
116  String message = ex.getCause().getMessage().toLowerCase();
117  if (message.startsWith(SERVER_REFUSED_CONNECTION)) {
118  try {
119  if (InetAddress.getByName(host).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
120  // if we can reach the host, then it's probably port problem
121  result = Bundle.SolrConnectionCheck_Port();
122  } else {
123  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
124  }
125  } catch (IOException | MissingResourceException any) {
126  // it may be anything
127  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
128  }
129  } else if (message.startsWith(BAD_IP_ADDRESS_FORMAT)) {
130  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.Hostname"); //NON-NLS
131  }
132  throw new KeywordSearchServiceException(result);
133  } catch (NumberFormatException ex) {
134  throw new KeywordSearchServiceException(Bundle.SolrConnectionCheck_Port());
135  } catch (IllegalArgumentException ex) {
136  throw new KeywordSearchServiceException(ex.getMessage());
137  } finally {
138  if (null != solrServer) {
139  solrServer.shutdown();
140  }
141  }
142  }
143 
149  @NbBundle.Messages({
150  "# {0} - case directory", "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from case directory: {0}",
151  "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case",
152  "# {0} - index folder path", "SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0}"
153  })
154  @Override
156  String caseDirectory = metadata.getCaseDirectory();
157  IndexMetadata indexMetadata;
158  try {
159  indexMetadata = new IndexMetadata(caseDirectory);
160  } catch (IndexMetadata.TextIndexMetadataException ex) {
161  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
162  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
163  }
164  //find the index for the current version of solr (the one we are connected to) and delete its core using the index name
165  String currentSchema = IndexFinder.getCurrentSchemaVersion();
166  String currentSolr = IndexFinder.getCurrentSolrVersion();
167  for (Index index : indexMetadata.getIndexes()) {
168  if (index.getSolrVersion().equals(currentSolr) && index.getSchemaVersion().equals(currentSchema)) {
169  /*
170  * Unload/delete the core on the server and then delete the text
171  * index files.
172  */
173  KeywordSearch.getServer().deleteCore(index.getIndexName(), metadata.getCaseType());
174  if (!FileUtil.deleteDir(new File(index.getIndexPath()).getParentFile())) {
175  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath()));
176  }
177  }
178  return; //only one core exists for each combination of solr and schema version
179  }
180 
181  //this code this code will only execute if an index for the current core was not found
182  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class,
183  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
184  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class,
185  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
186  }
187 
188  @Override
189  public void close() throws IOException {
190  }
191 
192  @Override
193  public String getServiceName() {
194  return NbBundle.getMessage(this.getClass(), "SolrSearchService.ServiceName");
195  }
196 
205  @Override
206  @NbBundle.Messages({
207  "SolrSearch.lookingForMetadata.msg=Looking for text index metadata file",
208  "SolrSearch.readingIndexes.msg=Reading text index metadata file",
209  "SolrSearch.findingIndexes.msg=Looking for existing text index directories",
210  "SolrSearch.creatingNewIndex.msg=Creating new text index",
211  "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version",
212  "SolrSearch.indentifyingIndex.msg=Identifying text index to use",
213  "SolrSearch.openCore.msg=Opening text index",
214  "SolrSearch.complete.msg=Text index successfully opened"})
216  if (context.cancelRequested()) {
217  return;
218  }
219 
220  ProgressIndicator progress = context.getProgressIndicator();
221  int totalNumProgressUnits = 7;
222  int progressUnitsCompleted = 0;
223 
224  String caseDirPath = context.getCase().getCaseDirectory();
225  Case theCase = context.getCase();
226  List<Index> indexes = new ArrayList<>();
227  progress.start(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
228  if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
229  try {
230  // metadata file exists, get list of existing Solr cores for this case
231  progressUnitsCompleted++;
232  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
233  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath);
234  indexes = indexMetadata.getIndexes();
235  } catch (IndexMetadata.TextIndexMetadataException ex) {
236  logger.log(Level.SEVERE, String.format("Unable to read text index metadata file"), ex);
237  throw new AutopsyServiceException("Unable to read text index metadata file", ex);
238  }
239  } else {
240  // metadata file doesn't exist.
241  // do case subdirectory search to look for Solr 4 Schema 1.8 indexes
242  progressUnitsCompleted++;
243  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
244  Index oldIndex = IndexFinder.findOldIndexDir(theCase);
245  if (oldIndex != null) {
246  // add index to the list of indexes that exist for this case
247  indexes.add(oldIndex);
248  }
249  }
250 
251  if (context.cancelRequested()) {
252  return;
253  }
254 
255  // check if we found any existing indexes
256  Index currentVersionIndex = null;
257  if (indexes.isEmpty()) {
258  // new case that doesn't have an existing index. create new index folder
259  progressUnitsCompleted++;
260  progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
261  currentVersionIndex = IndexFinder.createLatestVersionIndexDir(theCase);
262  // add current index to the list of indexes that exist for this case
263  indexes.add(currentVersionIndex);
264  } else {
265  // check if one of the existing indexes is for latest Solr version and schema
266  progressUnitsCompleted++;
267  progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
268  currentVersionIndex = IndexFinder.findLatestVersionIndexDir(indexes);
269  if (currentVersionIndex == null) {
270  // found existing index(es) but none were for latest Solr version and schema version
271  progressUnitsCompleted++;
272  progress.progress(Bundle.SolrSearch_indentifyingIndex_msg(), progressUnitsCompleted);
273  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
274  if (indexToUse == null) {
275  // unable to find index that can be used
276  throw new AutopsyServiceException("Unable to find index that can be used for this case");
277  }
278 
279  if (context.cancelRequested()) {
280  return;
281  }
282 
283  double currentSolrVersion = NumberUtils.toDouble(IndexFinder.getCurrentSolrVersion());
284  double indexSolrVersion = NumberUtils.toDouble(indexToUse.getSolrVersion());
285  if (indexSolrVersion == currentSolrVersion) {
286  // latest Solr version but not latest schema. index should be used in read-only mode
288  // pop up a message box to indicate the read-only restrictions.
289  JOptionPane optionPane = new JOptionPane(
290  NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.msg"),
291  JOptionPane.WARNING_MESSAGE,
292  JOptionPane.DEFAULT_OPTION);
293  try {
294  SwingUtilities.invokeAndWait(() -> {
295  JDialog dialog = optionPane.createDialog(NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.title"));
296  dialog.setVisible(true);
297  });
298  } catch (InterruptedException ex) {
299  // Cancelled
300  return;
301  } catch (InvocationTargetException ex) {
302  throw new AutopsyServiceException("Error displaying limited search features warning dialog", ex);
303  }
304  }
305  // proceed with case open
306  currentVersionIndex = indexToUse;
307  } else {
308  // index needs to be upgraded to latest supported version of Solr
309  throw new AutopsyServiceException("Unable to find index to use for Case open");
310  }
311  }
312  }
313 
314  try {
315  // update text index metadata file
316  if (!indexes.isEmpty()) {
317  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath, indexes);
318  }
319  } catch (IndexMetadata.TextIndexMetadataException ex) {
320  throw new AutopsyServiceException("Failed to save Solr core info in text index metadata file", ex);
321  }
322 
323  // open core
324  try {
325  progress.progress(Bundle.SolrSearch_openCore_msg(), totalNumProgressUnits - 1);
326  KeywordSearch.getServer().openCoreForCase(theCase, currentVersionIndex);
327  } catch (KeywordSearchModuleException ex) {
328  throw new AutopsyServiceException(String.format("Failed to open or create core for %s", caseDirPath), ex);
329  }
330 
331  progress.progress(Bundle.SolrSearch_complete_msg(), totalNumProgressUnits);
332  }
333 
341  @Override
343  /*
344  * TODO (JIRA 2525): The following code KeywordSearch.CaseChangeListener
345  * gambles that any BlackboardResultWriters (SwingWorkers) will complete
346  * in less than roughly two seconds. This stuff should be reworked using
347  * an ExecutorService and tasks with Futures.
348  */
349  KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters();
350  try {
351  Thread.sleep(2000);
352  } catch (InterruptedException ex) {
353  logger.log(Level.SEVERE, "Unexpected interrupt while waiting for BlackboardResultWriters to terminate", ex);
354  }
355 
356  try {
357  KeywordSearch.getServer().closeCore();
358  } catch (KeywordSearchModuleException ex) {
359  throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex);
360  }
361  }
362 }
void start(String message, int totalWorkUnits)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161
static boolean deleteDir(File dirPath)
Definition: FileUtil.java:47

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