Autopsy  4.16.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
Server.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2019 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 com.google.common.util.concurrent.ThreadFactoryBuilder;
22 import java.awt.event.ActionEvent;
23 import java.beans.PropertyChangeListener;
24 import java.io.BufferedReader;
25 import java.io.BufferedWriter;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.io.OutputStream;
32 import java.io.OutputStreamWriter;
33 import java.net.ConnectException;
34 import java.net.ServerSocket;
35 import java.net.SocketException;
36 import java.nio.charset.Charset;
37 import java.nio.file.Files;
38 import java.nio.file.Path;
39 import java.nio.file.Paths;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collection;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Random;
46 import java.util.concurrent.ScheduledThreadPoolExecutor;
47 import java.util.concurrent.TimeUnit;
48 import java.util.concurrent.locks.ReentrantReadWriteLock;
49 import java.util.logging.Level;
50 import static java.util.stream.Collectors.toList;
51 import javax.swing.AbstractAction;
52 import org.apache.solr.client.solrj.SolrQuery;
53 import org.apache.solr.client.solrj.SolrRequest;
54 import org.apache.solr.client.solrj.SolrServerException;
55 import org.apache.solr.client.solrj.impl.HttpSolrServer;
56 import org.apache.solr.client.solrj.impl.XMLResponseParser;
57 import org.apache.solr.client.solrj.request.CoreAdminRequest;
58 import org.apache.solr.client.solrj.response.CoreAdminResponse;
59 import org.apache.solr.client.solrj.response.QueryResponse;
60 import org.apache.solr.client.solrj.response.TermsResponse;
61 import org.apache.solr.common.SolrDocument;
62 import org.apache.solr.common.SolrDocumentList;
63 import org.apache.solr.common.SolrException;
64 import org.apache.solr.common.SolrInputDocument;
65 import org.apache.solr.common.params.CoreAdminParams;
66 import org.apache.solr.common.util.NamedList;
67 import org.openide.modules.InstalledFileLocator;
68 import org.openide.modules.Places;
69 import org.openide.util.NbBundle;
70 import org.openide.windows.WindowManager;
82 import org.sleuthkit.datamodel.Content;
83 
88 public class Server {
89 
93  public static enum Schema {
94 
95  ID {
96  @Override
97  public String toString() {
98  return "id"; //NON-NLS
99  }
100  },
101  IMAGE_ID {
102  @Override
103  public String toString() {
104  return "image_id"; //NON-NLS
105  }
106  },
107  // This is not stored or indexed. it is copied to text by the schema
108  CONTENT {
109  @Override
110  public String toString() {
111  return "content"; //NON-NLS
112  }
113  },
114  // String representation for regular expression searching
115  CONTENT_STR {
116  @Override
117  public String toString() {
118  return "content_str"; //NON-NLS
119  }
120  },
121  // default search field. Populated by schema
122  TEXT {
123  @Override
124  public String toString() {
125  return "text"; //NON-NLS
126  }
127  },
128  // no longer populated. Was used for regular expression searching.
129  // Should not be used.
130  CONTENT_WS {
131  @Override
132  public String toString() {
133  return "content_ws"; //NON-NLS
134  }
135  },
136  CONTENT_JA {
137  @Override
138  public String toString() {
139  return "content_ja"; //NON-NLS
140  }
141  },
142  LANGUAGE {
143  @Override
144  public String toString() {
145  return "language"; //NON-NLS
146  }
147  },
148  FILE_NAME {
149  @Override
150  public String toString() {
151  return "file_name"; //NON-NLS
152  }
153  },
154  // note that we no longer store or index this field
155  CTIME {
156  @Override
157  public String toString() {
158  return "ctime"; //NON-NLS
159  }
160  },
161  // note that we no longer store or index this field
162  ATIME {
163  @Override
164  public String toString() {
165  return "atime"; //NON-NLS
166  }
167  },
168  // note that we no longer store or index this field
169  MTIME {
170  @Override
171  public String toString() {
172  return "mtime"; //NON-NLS
173  }
174  },
175  // note that we no longer store or index this field
176  CRTIME {
177  @Override
178  public String toString() {
179  return "crtime"; //NON-NLS
180  }
181  },
182  NUM_CHUNKS {
183  @Override
184  public String toString() {
185  return "num_chunks"; //NON-NLS
186  }
187  },
188  CHUNK_SIZE {
189  @Override
190  public String toString() {
191  return "chunk_size"; //NON-NLS
192  }
193  },
199  TERMFREQ {
200  @Override
201  public String toString() {
202  return "termfreq"; //NON-NLS
203  }
204  }
205  };
206 
207  public static final String HL_ANALYZE_CHARS_UNLIMITED = "500000"; //max 1MB in a chunk. use -1 for unlimited, but -1 option may not be supported (not documented)
208  //max content size we can send to Solr
209  public static final long MAX_CONTENT_SIZE = 1L * 31 * 1024 * 1024;
210  private static final Logger logger = Logger.getLogger(Server.class.getName());
211  public static final String CORE_EVT = "CORE_EVT"; //NON-NLS
212  @Deprecated
213  public static final char ID_CHUNK_SEP = '_';
214  public static final String CHUNK_ID_SEPARATOR = "_";
215  private String javaPath = "java";
216  public static final Charset DEFAULT_INDEXED_TEXT_CHARSET = Charset.forName("UTF-8");
217  private Process curSolrProcess = null;
218  static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
219  static final String PROPERTIES_CURRENT_SERVER_PORT = "IndexingServerPort"; //NON-NLS
220  static final String PROPERTIES_CURRENT_STOP_PORT = "IndexingServerStopPort"; //NON-NLS
221  private static final String KEY = "jjk#09s"; //NON-NLS
222  static final String DEFAULT_SOLR_SERVER_HOST = "localhost"; //NON-NLS
223  static final int DEFAULT_SOLR_SERVER_PORT = 23232;
224  static final int DEFAULT_SOLR_STOP_PORT = 34343;
225  private int currentSolrServerPort = 0;
226  private int currentSolrStopPort = 0;
227  private static final boolean DEBUG = false;//(Version.getBuildType() == Version.Type.DEVELOPMENT);
228  private static final String SOLR = "solr";
229  private static final String CORE_PROPERTIES = "core.properties";
230 
231  public enum CORE_EVT_STATES {
232 
233  STOPPED, STARTED
234  };
235 
236  // A reference to the locally running Solr instance.
237  private final HttpSolrServer localSolrServer;
238 
239  // A reference to the Solr server we are currently connected to for the Case.
240  // This could be a local or remote server.
241  private HttpSolrServer currentSolrServer;
242 
243  private Core currentCore;
244  private final ReentrantReadWriteLock currentCoreLock;
245 
246  private final File solrFolder;
247  private Path solrHome;
248  private final ServerAction serverAction;
250 
255  Server() {
256  initSettings();
257 
258  this.localSolrServer = new HttpSolrServer("http://localhost:" + currentSolrServerPort + "/solr"); //NON-NLS
259  serverAction = new ServerAction();
260  solrFolder = InstalledFileLocator.getDefault().locate("solr", Server.class.getPackage().getName(), false); //NON-NLS
261  javaPath = PlatformUtil.getJavaPath();
262 
263  solrHome = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "solr"); //NON-NLS
264  if (!solrHome.toFile().exists()) {
265  try {
266  Files.createDirectory(solrHome);
267  Files.copy(Paths.get(solrFolder.getAbsolutePath(), "solr", "solr.xml"), solrHome.resolve("solr.xml")); //NON-NLS
268  Files.copy(Paths.get(solrFolder.getAbsolutePath(), "solr", "zoo.cfg"), solrHome.resolve("zoo.cfg")); //NON-NLS
269  } catch (IOException ex) {
270  logger.log(Level.SEVERE, "Failed to create Solr home folder:", ex); //NON-NLS
271  }
272  }
273  currentCoreLock = new ReentrantReadWriteLock(true);
274 
275  logger.log(Level.INFO, "Created Server instance using Java at {0}", javaPath); //NON-NLS
276  }
277 
278  private void initSettings() {
279 
280  if (ModuleSettings.settingExists(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT)) {
281  try {
282  currentSolrServerPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT));
283  } catch (NumberFormatException nfe) {
284  logger.log(Level.WARNING, "Could not decode indexing server port, value was not a valid port number, using the default. ", nfe); //NON-NLS
285  currentSolrServerPort = DEFAULT_SOLR_SERVER_PORT;
286  }
287  } else {
288  currentSolrServerPort = DEFAULT_SOLR_SERVER_PORT;
289  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT, String.valueOf(currentSolrServerPort));
290  }
291 
292  if (ModuleSettings.settingExists(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT)) {
293  try {
294  currentSolrStopPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT));
295  } catch (NumberFormatException nfe) {
296  logger.log(Level.WARNING, "Could not decode indexing server stop port, value was not a valid port number, using default", nfe); //NON-NLS
297  currentSolrStopPort = DEFAULT_SOLR_STOP_PORT;
298  }
299  } else {
300  currentSolrStopPort = DEFAULT_SOLR_STOP_PORT;
301  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT, String.valueOf(currentSolrStopPort));
302  }
303  }
304 
305  @Override
306  public void finalize() throws java.lang.Throwable {
307  stop();
308  super.finalize();
309  }
310 
311  public void addServerActionListener(PropertyChangeListener l) {
312  serverAction.addPropertyChangeListener(l);
313  }
314 
315  int getCurrentSolrServerPort() {
316  return currentSolrServerPort;
317  }
318 
319  int getCurrentSolrStopPort() {
320  return currentSolrStopPort;
321  }
322 
326  private static class InputStreamPrinterThread extends Thread {
327 
328  InputStream stream;
329  OutputStream out;
330  volatile boolean doRun = true;
331 
332  InputStreamPrinterThread(InputStream stream, String type) {
333  this.stream = stream;
334  try {
335  final String log = Places.getUserDirectory().getAbsolutePath()
336  + File.separator + "var" + File.separator + "log" //NON-NLS
337  + File.separator + "solr.log." + type; //NON-NLS
338  File outputFile = new File(log.concat(".0"));
339  File first = new File(log.concat(".1"));
340  File second = new File(log.concat(".2"));
341  if (second.exists()) {
342  second.delete();
343  }
344  if (first.exists()) {
345  first.renameTo(second);
346  }
347  if (outputFile.exists()) {
348  outputFile.renameTo(first);
349  } else {
350  outputFile.createNewFile();
351  }
352  out = new FileOutputStream(outputFile);
353 
354  } catch (Exception ex) {
355  logger.log(Level.WARNING, "Failed to create solr log file", ex); //NON-NLS
356  }
357  }
358 
359  void stopRun() {
360  doRun = false;
361  }
362 
363  @Override
364  public void run() {
365 
366  try (InputStreamReader isr = new InputStreamReader(stream);
367  BufferedReader br = new BufferedReader(isr);
368  OutputStreamWriter osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
369  BufferedWriter bw = new BufferedWriter(osw);) {
370 
371  String line = null;
372  while (doRun && (line = br.readLine()) != null) {
373  bw.write(line);
374  bw.newLine();
375  if (DEBUG) {
376  //flush buffers if dev version for debugging
377  bw.flush();
378  }
379  }
380  bw.flush();
381  } catch (IOException ex) {
382  logger.log(Level.SEVERE, "Error redirecting Solr output stream", ex); //NON-NLS
383  }
384  }
385  }
386 
396  private Process runSolrCommand(List<String> solrArguments) throws IOException {
397  final String MAX_SOLR_MEM_MB_PAR = "-Xmx" + UserPreferences.getMaxSolrVMSize() + "m"; //NON-NLS
398 
399  List<String> commandLine = new ArrayList<>();
400  commandLine.add(javaPath);
401  commandLine.add(MAX_SOLR_MEM_MB_PAR);
402  commandLine.add("-DSTOP.PORT=" + currentSolrStopPort); //NON-NLS
403  commandLine.add("-Djetty.port=" + currentSolrServerPort); //NON-NLS
404  commandLine.add("-DSTOP.KEY=" + KEY); //NON-NLS
405  commandLine.add("-jar"); //NON-NLS
406  commandLine.add("start.jar"); //NON-NLS
407 
408  commandLine.addAll(solrArguments);
409 
410  ProcessBuilder solrProcessBuilder = new ProcessBuilder(commandLine);
411  solrProcessBuilder.directory(solrFolder);
412 
413  // Redirect stdout and stderr to files to prevent blocking.
414  Path solrStdoutPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stdout"); //NON-NLS
415  solrProcessBuilder.redirectOutput(solrStdoutPath.toFile());
416 
417  Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stderr"); //NON-NLS
418  solrProcessBuilder.redirectError(solrStderrPath.toFile());
419 
420  logger.log(Level.INFO, "Running Solr command: {0}", solrProcessBuilder.command()); //NON-NLS
421  Process process = solrProcessBuilder.start();
422  logger.log(Level.INFO, "Finished running Solr command"); //NON-NLS
423  return process;
424  }
425 
431  List<Long> getSolrPIDs() {
432  List<Long> pids = new ArrayList<>();
433 
434  //NOTE: these needs to be in sync with process start string in start()
435  final String pidsQuery = "Args.*.eq=-DSTOP.KEY=" + KEY + ",Args.*.eq=start.jar"; //NON-NLS
436 
437  long[] pidsArr = PlatformUtil.getJavaPIDs(pidsQuery);
438  if (pidsArr != null) {
439  for (int i = 0; i < pidsArr.length; ++i) {
440  pids.add(pidsArr[i]);
441  }
442  }
443 
444  return pids;
445  }
446 
451  void killSolr() {
452  List<Long> solrPids = getSolrPIDs();
453  for (long pid : solrPids) {
454  logger.log(Level.INFO, "Trying to kill old Solr process, PID: {0}", pid); //NON-NLS
455  PlatformUtil.killProcess(pid);
456  }
457  }
458 
464  @NbBundle.Messages({
465  "Server.status.failed.msg=Local Solr server did not respond to status request. This may be because the server failed to start or is taking too long to initialize.",})
466  void start() throws KeywordSearchModuleException, SolrServerNoPortException {
467  if (isRunning()) {
468  // If a Solr server is running we stop it.
469  stop();
470  }
471 
472  if (!isPortAvailable(currentSolrServerPort)) {
473  // There is something already listening on our port. Let's see if
474  // this is from an earlier run that didn't successfully shut down
475  // and if so kill it.
476  final List<Long> pids = this.getSolrPIDs();
477 
478  // If the culprit listening on the port is not a Solr process
479  // we refuse to start.
480  if (pids.isEmpty()) {
481  throw new SolrServerNoPortException(currentSolrServerPort);
482  }
483 
484  // Ok, we've tried to stop it above but there still appears to be
485  // a Solr process listening on our port so we forcefully kill it.
486  killSolr();
487 
488  // If either of the ports are still in use after our attempt to kill
489  // previously running processes we give up and throw an exception.
490  if (!isPortAvailable(currentSolrServerPort)) {
491  throw new SolrServerNoPortException(currentSolrServerPort);
492  }
493  if (!isPortAvailable(currentSolrStopPort)) {
494  throw new SolrServerNoPortException(currentSolrStopPort);
495  }
496  }
497 
498  logger.log(Level.INFO, "Starting Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
499 
500  if (isPortAvailable(currentSolrServerPort)) {
501  logger.log(Level.INFO, "Port [{0}] available, starting Solr", currentSolrServerPort); //NON-NLS
502  try {
503  curSolrProcess = runSolrCommand(new ArrayList<>(
504  Arrays.asList("-Dbootstrap_confdir=../solr/configsets/AutopsyConfig/conf", //NON-NLS
505  "-Dcollection.configName=AutopsyConfig"))); //NON-NLS
506 
507  // Wait for the Solr server to start and respond to a status request.
508  for (int numRetries = 0; numRetries < 6; numRetries++) {
509  if (isRunning()) {
510  final List<Long> pids = this.getSolrPIDs();
511  logger.log(Level.INFO, "New Solr process PID: {0}", pids); //NON-NLS
512  return;
513  }
514 
515  // Local Solr server did not respond so we sleep for
516  // 5 seconds before trying again.
517  try {
518  TimeUnit.SECONDS.sleep(5);
519  } catch (InterruptedException ex) {
520  logger.log(Level.WARNING, "Timer interrupted"); //NON-NLS
521  }
522  }
523 
524  // If we get here the Solr server has not responded to connection
525  // attempts in a timely fashion.
526  logger.log(Level.WARNING, "Local Solr server failed to respond to status requests.");
527  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
528  @Override
529  public void run() {
530  MessageNotifyUtil.Notify.error(
531  NbBundle.getMessage(this.getClass(), "Installer.errorInitKsmMsg"),
532  Bundle.Server_status_failed_msg());
533  }
534  });
535  } catch (SecurityException ex) {
536  logger.log(Level.SEVERE, "Could not start Solr process!", ex); //NON-NLS
537  throw new KeywordSearchModuleException(
538  NbBundle.getMessage(this.getClass(), "Server.start.exception.cantStartSolr.msg"), ex);
539  } catch (IOException ex) {
540  logger.log(Level.SEVERE, "Could not start Solr server process!", ex); //NON-NLS
541  throw new KeywordSearchModuleException(
542  NbBundle.getMessage(this.getClass(), "Server.start.exception.cantStartSolr.msg2"), ex);
543  }
544  }
545  }
546 
552  static boolean isPortAvailable(int port) {
553  ServerSocket ss = null;
554  try {
555 
556  ss = new ServerSocket(port, 0, java.net.Inet4Address.getByName("localhost")); //NON-NLS
557  if (ss.isBound()) {
558  ss.setReuseAddress(true);
559  ss.close();
560  return true;
561  }
562 
563  } catch (IOException e) {
564  } finally {
565  if (ss != null) {
566  try {
567  ss.close();
568  } catch (IOException e) {
569  /*
570  * should not be thrown
571  */
572  }
573  }
574  }
575  return false;
576  }
577 
583  void changeSolrServerPort(int port) {
584  currentSolrServerPort = port;
585  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT, String.valueOf(port));
586  }
587 
593  void changeSolrStopPort(int port) {
594  currentSolrStopPort = port;
595  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT, String.valueOf(port));
596  }
597 
603  synchronized void stop() {
604 
605  try {
606  // Close any open core before stopping server
607  closeCore();
608  } catch (KeywordSearchModuleException e) {
609  logger.log(Level.WARNING, "Failed to close core: ", e); //NON-NLS
610  }
611 
612  try {
613  logger.log(Level.INFO, "Stopping Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
614 
615  //try graceful shutdown
616  Process process = runSolrCommand(new ArrayList<>(Arrays.asList("--stop"))); //NON-NLS
617 
618  logger.log(Level.INFO, "Waiting for Solr server to stop"); //NON-NLS
619  process.waitFor();
620 
621  //if still running, forcefully stop it
622  if (curSolrProcess != null) {
623  curSolrProcess.destroy();
624  curSolrProcess = null;
625  }
626 
627  } catch (IOException | InterruptedException ex) {
628  logger.log(Level.WARNING, "Error while attempting to stop Solr server", ex);
629  } finally {
630  //stop Solr stream -> log redirect threads
631  try {
632  if (errorRedirectThread != null) {
633  errorRedirectThread.stopRun();
634  errorRedirectThread = null;
635  }
636  } finally {
637  //if still running, kill it
638  killSolr();
639  }
640 
641  logger.log(Level.INFO, "Finished stopping Solr server"); //NON-NLS
642  }
643  }
644 
652  synchronized boolean isRunning() throws KeywordSearchModuleException {
653  try {
654 
655  if (isPortAvailable(currentSolrServerPort)) {
656  return false;
657  }
658 
659  // making a status request here instead of just doing solrServer.ping(), because
660  // that doesn't work when there are no cores
661  //TODO handle timeout in cases when some other type of server on that port
662  connectToSolrServer(localSolrServer);
663 
664  logger.log(Level.INFO, "Solr server is running"); //NON-NLS
665  } catch (SolrServerException ex) {
666 
667  Throwable cause = ex.getRootCause();
668 
669  // TODO: check if SocketExceptions should actually happen (is
670  // probably caused by starting a connection as the server finishes
671  // shutting down)
672  if (cause instanceof ConnectException || cause instanceof SocketException) { //|| cause instanceof NoHttpResponseException) {
673  logger.log(Level.INFO, "Solr server is not running, cause: {0}", cause.getMessage()); //NON-NLS
674  return false;
675  } else {
676  throw new KeywordSearchModuleException(
677  NbBundle.getMessage(this.getClass(), "Server.isRunning.exception.errCheckSolrRunning.msg"), ex);
678  }
679  } catch (SolrException ex) {
680  // Just log 404 errors for now...
681  logger.log(Level.INFO, "Solr server is not running", ex); //NON-NLS
682  return false;
683  } catch (IOException ex) {
684  throw new KeywordSearchModuleException(
685  NbBundle.getMessage(this.getClass(), "Server.isRunning.exception.errCheckSolrRunning.msg2"), ex);
686  }
687 
688  return true;
689  }
690 
691  /*
692  * ** Convenience methods for use while we only open one case at a time ***
693  */
703  void openCoreForCase(Case theCase, Index index) throws KeywordSearchModuleException {
704  currentCoreLock.writeLock().lock();
705  try {
706  currentCore = openCore(theCase, index);
707 
708  try {
709  // execute a test query. if it fails, an exception will be thrown
711  } catch (NoOpenCoreException ex) {
712  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex);
713  }
714 
715  serverAction.putValue(CORE_EVT, CORE_EVT_STATES.STARTED);
716  } finally {
717  currentCoreLock.writeLock().unlock();
718  }
719  }
720 
726  boolean coreIsOpen() {
727  currentCoreLock.readLock().lock();
728  try {
729  return (null != currentCore);
730  } finally {
731  currentCoreLock.readLock().unlock();
732  }
733  }
734 
735  Index getIndexInfo() throws NoOpenCoreException {
736  currentCoreLock.readLock().lock();
737  try {
738  if (null == currentCore) {
739  throw new NoOpenCoreException();
740  }
741  return currentCore.getIndexInfo();
742  } finally {
743  currentCoreLock.readLock().unlock();
744  }
745  }
746 
747  void closeCore() throws KeywordSearchModuleException {
748  currentCoreLock.writeLock().lock();
749  try {
750  if (null != currentCore) {
751  currentCore.close();
752  currentCore = null;
753  serverAction.putValue(CORE_EVT, CORE_EVT_STATES.STOPPED);
754  }
755  } finally {
756  currentCoreLock.writeLock().unlock();
757  }
758  }
759 
760  void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException, NoOpenCoreException {
761  currentCoreLock.readLock().lock();
762  try {
763  if (null == currentCore) {
764  throw new NoOpenCoreException();
765  }
766  TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Index chunk");
767  currentCore.addDocument(doc);
768  HealthMonitor.submitTimingMetric(metric);
769  } finally {
770  currentCoreLock.readLock().unlock();
771  }
772  }
773 
782  @NbBundle.Messages({
783  "# {0} - core name", "Server.deleteCore.exception.msg=Failed to delete Solr core {0}",})
784  void deleteCore(String coreName, CaseMetadata metadata) throws KeywordSearchServiceException {
785  try {
786  HttpSolrServer solrServer;
787  if (metadata.getCaseType() == CaseType.SINGLE_USER_CASE) {
788  Integer localSolrServerPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT));
789  solrServer = new HttpSolrServer("http://localhost:" + localSolrServerPort + "/solr"); //NON-NLS
790  } else {
791  IndexingServerProperties properties = getMultiUserServerProperties(metadata.getCaseDirectory());
792  solrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS
793  }
794  connectToSolrServer(solrServer);
795  CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, solrServer);
796  if (null != response.getCoreStatus(coreName).get("instanceDir")) { //NON-NLS
797  /*
798  * Send a core unload request to the Solr server, with the
799  * parameter set that request deleting the index and the
800  * instance directory (deleteInstanceDir = true). Note that this
801  * removes everything related to the core on the server (the
802  * index directory, the configuration files, etc.), but does not
803  * delete the actual Solr text index because it is currently
804  * stored in the case directory.
805  */
806  org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(coreName, true, true, solrServer);
807  }
808  } catch (SolrServerException | HttpSolrServer.RemoteSolrException | IOException ex) {
809  // We will get a RemoteSolrException with cause == null and detailsMessage
810  // == "Already closed" if the core is not loaded. This is not an error in this scenario.
811  if (!ex.getMessage().equals("Already closed")) { // NON-NLS
812  throw new KeywordSearchServiceException(Bundle.Server_deleteCore_exception_msg(coreName), ex);
813  }
814  }
815  }
816 
828  private Core openCore(Case theCase, Index index) throws KeywordSearchModuleException {
829 
830  try {
831  if (theCase.getCaseType() == CaseType.SINGLE_USER_CASE) {
832  currentSolrServer = this.localSolrServer;
833  } else {
834  IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory());
835  currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS
836  }
837  TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check");
838  connectToSolrServer(currentSolrServer);
840 
841  } catch (Exception ex) {
842  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
843  throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
844  }
845 
846  try {
847  File dataDir = new File(new File(index.getIndexPath()).getParent()); // "data dir" is the parent of the index directory
848  if (!dataDir.exists()) {
849  dataDir.mkdirs();
850  }
851 
852  if (!this.isRunning()) {
853  logger.log(Level.SEVERE, "Core create/open requested, but server not yet running"); //NON-NLS
854  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.msg"));
855  }
856 
857  String coreName = index.getIndexName();
858  if (!coreIsLoaded(coreName)) {
859  /*
860  * The core either does not exist or it is not loaded. Make a
861  * request that will cause the core to be created if it does not
862  * exist or loaded if it already exists.
863  */
864 
865  // In single user mode, if there is a core.properties file already,
866  // we've hit a solr bug. Compensate by deleting it.
867  if (theCase.getCaseType() == CaseType.SINGLE_USER_CASE) {
868  Path corePropertiesFile = Paths.get(solrFolder.toString(), SOLR, coreName, CORE_PROPERTIES);
869  if (corePropertiesFile.toFile().exists()) {
870  try {
871  corePropertiesFile.toFile().delete();
872  } catch (Exception ex) {
873  logger.log(Level.INFO, "Could not delete pre-existing core.properties prior to opening the core."); //NON-NLS
874  }
875  }
876  }
877 
878  CoreAdminRequest.Create createCoreRequest = new CoreAdminRequest.Create();
879  createCoreRequest.setDataDir(dataDir.getAbsolutePath());
880  createCoreRequest.setCoreName(coreName);
881  createCoreRequest.setConfigSet("AutopsyConfig"); //NON-NLS
882  createCoreRequest.setIsLoadOnStartup(false);
883  createCoreRequest.setIsTransient(true);
884  currentSolrServer.request(createCoreRequest);
885  }
886 
887  if (!coreIndexFolderExists(coreName)) {
888  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.noIndexDir.msg"));
889  }
890 
891  return new Core(coreName, theCase.getCaseType(), index);
892 
893  } catch (Exception ex) {
894  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex);
895  }
896  }
897 
908  public static IndexingServerProperties getMultiUserServerProperties(String caseDirectory) {
909 
910  Path serverFilePath = Paths.get(caseDirectory, "solrserver.txt");
911  if (serverFilePath.toFile().exists()) {
912  try {
913  List<String> lines = Files.readAllLines(serverFilePath);
914  if (lines.isEmpty()) {
915  logger.log(Level.SEVERE, "solrserver.txt file does not contain any data");
916  } else if (!lines.get(0).contains(",")) {
917  logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
918  } else {
919  String[] parts = lines.get(0).split(",");
920  if (parts.length != 2) {
921  logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
922  } else {
923  return new IndexingServerProperties(parts[0], parts[1]);
924  }
925  }
926  } catch (IOException ex) {
927  logger.log(Level.SEVERE, "solrserver.txt file could not be read", ex);
928  }
929  }
930 
931  // Default back to the user preferences if the solrserver.txt file was not found or if an error occurred
932  String host = UserPreferences.getIndexingServerHost();
933  String port = UserPreferences.getIndexingServerPort();
934  return new IndexingServerProperties(host, port);
935  }
936 
949  public static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath) throws KeywordSearchModuleException {
950  // Look for the solr server list file
951  String serverListName = "solrServerList.txt";
952  Path serverListPath = Paths.get(rootOutputDirectory.toString(), serverListName);
953  if (serverListPath.toFile().exists()) {
954 
955  // Read the list of solr servers
956  List<String> lines;
957  try {
958  lines = Files.readAllLines(serverListPath);
959  } catch (IOException ex) {
960  throw new KeywordSearchModuleException(serverListName + " could not be read", ex);
961  }
962 
963  // Remove any lines that don't contain a comma (these are likely just whitespace)
964  for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) {
965  String line = iterator.next();
966  if (!line.contains(",")) {
967  // Remove the current element from the iterator and the list.
968  iterator.remove();
969  }
970  }
971  if (lines.isEmpty()) {
972  throw new KeywordSearchModuleException(serverListName + " had no valid server information");
973  }
974 
975  // Choose which server to use
976  int rnd = new Random().nextInt(lines.size());
977  String[] parts = lines.get(rnd).split(",");
978  if (parts.length != 2) {
979  throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd));
980  }
981 
982  // Split it up just to do a sanity check on the data
983  String host = parts[0];
984  String port = parts[1];
985  if (host.isEmpty() || port.isEmpty()) {
986  throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd));
987  }
988 
989  // Write the server data to a file
990  Path serverFile = Paths.get(caseDirectoryPath.toString(), "solrserver.txt");
991  try {
992  caseDirectoryPath.toFile().mkdirs();
993  if (!caseDirectoryPath.toFile().exists()) {
994  throw new KeywordSearchModuleException("Case directory " + caseDirectoryPath.toString() + " does not exist");
995  }
996  Files.write(serverFile, lines.get(rnd).getBytes());
997  } catch (IOException ex) {
998  throw new KeywordSearchModuleException(serverFile.toString() + " could not be written", ex);
999  }
1000  }
1001  }
1002 
1006  public static class IndexingServerProperties {
1007 
1008  private final String host;
1009  private final String port;
1010 
1011  IndexingServerProperties(String host, String port) {
1012  this.host = host;
1013  this.port = port;
1014  }
1015 
1021  public String getHost() {
1022  return host;
1023  }
1024 
1030  public String getPort() {
1031  return port;
1032  }
1033  }
1034 
1040  void commit() throws SolrServerException, NoOpenCoreException {
1041  currentCoreLock.readLock().lock();
1042  try {
1043  if (null == currentCore) {
1044  throw new NoOpenCoreException();
1045  }
1046  currentCore.commit();
1047  } finally {
1048  currentCoreLock.readLock().unlock();
1049  }
1050  }
1051 
1052  NamedList<Object> request(SolrRequest request) throws SolrServerException, NoOpenCoreException {
1053  currentCoreLock.readLock().lock();
1054  try {
1055  if (null == currentCore) {
1056  throw new NoOpenCoreException();
1057  }
1058  return currentCore.request(request);
1059  } finally {
1060  currentCoreLock.readLock().unlock();
1061  }
1062  }
1063 
1074  public int queryNumIndexedFiles() throws KeywordSearchModuleException, NoOpenCoreException {
1075  currentCoreLock.readLock().lock();
1076  try {
1077  if (null == currentCore) {
1078  throw new NoOpenCoreException();
1079  }
1080  try {
1081  return currentCore.queryNumIndexedFiles();
1082  } catch (Exception ex) {
1083  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1084  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumIdxFiles.exception.msg"), ex);
1085  }
1086  } finally {
1087  currentCoreLock.readLock().unlock();
1088  }
1089  }
1090 
1100  public int queryNumIndexedChunks() throws KeywordSearchModuleException, NoOpenCoreException {
1101  currentCoreLock.readLock().lock();
1102  try {
1103  if (null == currentCore) {
1104  throw new NoOpenCoreException();
1105  }
1106  try {
1107  return currentCore.queryNumIndexedChunks();
1108  } catch (Exception ex) {
1109  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1110  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumIdxChunks.exception.msg"), ex);
1111  }
1112  } finally {
1113  currentCoreLock.readLock().unlock();
1114  }
1115  }
1116 
1126  public int queryNumIndexedDocuments() throws KeywordSearchModuleException, NoOpenCoreException {
1127  currentCoreLock.readLock().lock();
1128  try {
1129  if (null == currentCore) {
1130  throw new NoOpenCoreException();
1131  }
1132  try {
1133  return currentCore.queryNumIndexedDocuments();
1134  } catch (Exception ex) {
1135  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1136  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumIdxDocs.exception.msg"), ex);
1137  }
1138  } finally {
1139  currentCoreLock.readLock().unlock();
1140  }
1141  }
1142 
1153  public boolean queryIsIndexed(long contentID) throws KeywordSearchModuleException, NoOpenCoreException {
1154  currentCoreLock.readLock().lock();
1155  try {
1156  if (null == currentCore) {
1157  throw new NoOpenCoreException();
1158  }
1159  try {
1160  return currentCore.queryIsIndexed(contentID);
1161  } catch (Exception ex) {
1162  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1163  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryIsIdxd.exception.msg"), ex);
1164  }
1165 
1166  } finally {
1167  currentCoreLock.readLock().unlock();
1168  }
1169  }
1170 
1182  public int queryNumFileChunks(long fileID) throws KeywordSearchModuleException, NoOpenCoreException {
1183  currentCoreLock.readLock().lock();
1184  try {
1185  if (null == currentCore) {
1186  throw new NoOpenCoreException();
1187  }
1188  try {
1189  return currentCore.queryNumFileChunks(fileID);
1190  } catch (Exception ex) {
1191  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1192  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumFileChunks.exception.msg"), ex);
1193  }
1194  } finally {
1195  currentCoreLock.readLock().unlock();
1196  }
1197  }
1198 
1209  public QueryResponse query(SolrQuery sq) throws KeywordSearchModuleException, NoOpenCoreException, IOException {
1210  currentCoreLock.readLock().lock();
1211  try {
1212  if (null == currentCore) {
1213  throw new NoOpenCoreException();
1214  }
1215  try {
1216  return currentCore.query(sq);
1217  } catch (Exception ex) {
1218  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1219  logger.log(Level.SEVERE, "Solr query failed: " + sq.getQuery(), ex); //NON-NLS
1220  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.query.exception.msg", sq.getQuery()), ex);
1221  }
1222  } finally {
1223  currentCoreLock.readLock().unlock();
1224  }
1225  }
1226 
1238  public QueryResponse query(SolrQuery sq, SolrRequest.METHOD method) throws KeywordSearchModuleException, NoOpenCoreException {
1239  currentCoreLock.readLock().lock();
1240  try {
1241  if (null == currentCore) {
1242  throw new NoOpenCoreException();
1243  }
1244  try {
1245  return currentCore.query(sq, method);
1246  } catch (Exception ex) {
1247  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1248  logger.log(Level.SEVERE, "Solr query failed: " + sq.getQuery(), ex); //NON-NLS
1249  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.query2.exception.msg", sq.getQuery()), ex);
1250  }
1251  } finally {
1252  currentCoreLock.readLock().unlock();
1253  }
1254  }
1255 
1266  public TermsResponse queryTerms(SolrQuery sq) throws KeywordSearchModuleException, NoOpenCoreException {
1267  currentCoreLock.readLock().lock();
1268  try {
1269  if (null == currentCore) {
1270  throw new NoOpenCoreException();
1271  }
1272  try {
1273  return currentCore.queryTerms(sq);
1274  } catch (Exception ex) {
1275  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1276  logger.log(Level.SEVERE, "Solr terms query failed: " + sq.getQuery(), ex); //NON-NLS
1277  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryTerms.exception.msg", sq.getQuery()), ex);
1278  }
1279  } finally {
1280  currentCoreLock.readLock().unlock();
1281  }
1282  }
1283 
1291  void deleteDataSource(Long dataSourceId) throws IOException, KeywordSearchModuleException, NoOpenCoreException, SolrServerException {
1292  try {
1293  currentCoreLock.writeLock().lock();
1294  if (null == currentCore) {
1295  throw new NoOpenCoreException();
1296  }
1297  currentCore.deleteDataSource(dataSourceId);
1298  currentCore.commit();
1299  } finally {
1300  currentCoreLock.writeLock().unlock();
1301  }
1302  }
1303 
1313  public String getSolrContent(final Content content) throws NoOpenCoreException {
1314  currentCoreLock.readLock().lock();
1315  try {
1316  if (null == currentCore) {
1317  throw new NoOpenCoreException();
1318  }
1319  return currentCore.getSolrContent(content.getId(), 0);
1320  } finally {
1321  currentCoreLock.readLock().unlock();
1322  }
1323  }
1324 
1337  public String getSolrContent(final Content content, int chunkID) throws NoOpenCoreException {
1338  currentCoreLock.readLock().lock();
1339  try {
1340  if (null == currentCore) {
1341  throw new NoOpenCoreException();
1342  }
1343  return currentCore.getSolrContent(content.getId(), chunkID);
1344  } finally {
1345  currentCoreLock.readLock().unlock();
1346  }
1347  }
1348 
1358  public String getSolrContent(final long objectID) throws NoOpenCoreException {
1359  currentCoreLock.readLock().lock();
1360  try {
1361  if (null == currentCore) {
1362  throw new NoOpenCoreException();
1363  }
1364  return currentCore.getSolrContent(objectID, 0);
1365  } finally {
1366  currentCoreLock.readLock().unlock();
1367  }
1368  }
1369 
1380  public String getSolrContent(final long objectID, final int chunkID) throws NoOpenCoreException {
1381  currentCoreLock.readLock().lock();
1382  try {
1383  if (null == currentCore) {
1384  throw new NoOpenCoreException();
1385  }
1386  return currentCore.getSolrContent(objectID, chunkID);
1387  } finally {
1388  currentCoreLock.readLock().unlock();
1389  }
1390  }
1391 
1401  public static String getChunkIdString(long parentID, int childID) {
1402  return Long.toString(parentID) + Server.CHUNK_ID_SEPARATOR + Integer.toString(childID);
1403  }
1404 
1413  void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException {
1414  TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check");
1415  CoreAdminRequest statusRequest = new CoreAdminRequest();
1416  statusRequest.setCoreName(null);
1417  statusRequest.setAction(CoreAdminParams.CoreAdminAction.STATUS);
1418  statusRequest.setIndexInfoNeeded(false);
1419  statusRequest.process(solrServer);
1421  }
1422 
1436  private boolean coreIsLoaded(String coreName) throws SolrServerException, IOException {
1437  CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, currentSolrServer);
1438  return response.getCoreStatus(coreName).get("instanceDir") != null; //NON-NLS
1439  }
1440 
1451  private boolean coreIndexFolderExists(String coreName) throws SolrServerException, IOException {
1452  CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, currentSolrServer);
1453  Object dataDirPath = response.getCoreStatus(coreName).get("dataDir"); //NON-NLS
1454  if (null != dataDirPath) {
1455  File indexDir = Paths.get((String) dataDirPath, "index").toFile(); //NON-NLS
1456  return indexDir.exists();
1457  } else {
1458  return false;
1459  }
1460  }
1461 
1462  class Core {
1463 
1464  // handle to the core in Solr
1465  private final String name;
1466 
1467  private final CaseType caseType;
1468 
1469  private final Index textIndex;
1470 
1471  // the server to access a core needs to be built from a URL with the
1472  // core in it, and is only good for core-specific operations
1473  private final HttpSolrServer solrCore;
1474 
1475  private final int maxBufferSize;
1476  private final List<SolrInputDocument> buffer;
1477  private final Object bufferLock;
1478 
1479  private final ScheduledThreadPoolExecutor periodicTasksExecutor;
1480  private static final long PERIODIC_BATCH_SEND_INTERVAL_MINUTES = 10;
1481  private static final int NUM_BATCH_UPDATE_RETRIES = 10;
1482  private static final long SLEEP_BETWEEN_RETRIES_MS = 10000; // 10 seconds
1483 
1484  private final int QUERY_TIMEOUT_MILLISECONDS = 86400000; // 24 Hours = 86,400,000 Milliseconds
1485 
1486  private Core(String name, CaseType caseType, Index index) {
1487  this.name = name;
1488  this.caseType = caseType;
1489  this.textIndex = index;
1490  bufferLock = new Object();
1491 
1492  this.solrCore = new HttpSolrServer(currentSolrServer.getBaseURL() + "/" + name); //NON-NLS
1493 
1494  //TODO test these settings
1495  // socket read timeout, make large enough so can index larger files
1496  solrCore.setSoTimeout(QUERY_TIMEOUT_MILLISECONDS);
1497  //solrCore.setConnectionTimeout(1000);
1498  solrCore.setDefaultMaxConnectionsPerHost(32);
1499  solrCore.setMaxTotalConnections(32);
1500  solrCore.setFollowRedirects(false); // defaults to false
1501  // allowCompression defaults to false.
1502  // Server side must support gzip or deflate for this to have any effect.
1503  solrCore.setAllowCompression(true);
1504  solrCore.setParser(new XMLResponseParser()); // binary parser is used by default
1505 
1506  // document batching
1507  maxBufferSize = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getDocumentsQueueSize();
1508  logger.log(Level.INFO, "Using Solr document queue size = {0}", maxBufferSize); //NON-NLS
1509  buffer = new ArrayList<>(maxBufferSize);
1510  periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("periodic-batched-document-task-%d").build()); //NON-NLS
1511  periodicTasksExecutor.scheduleWithFixedDelay(new SendBatchedDocumentsTask(), PERIODIC_BATCH_SEND_INTERVAL_MINUTES, PERIODIC_BATCH_SEND_INTERVAL_MINUTES, TimeUnit.MINUTES);
1512  }
1513 
1520  private final class SendBatchedDocumentsTask implements Runnable {
1521 
1522  @Override
1523  public void run() {
1524  List<SolrInputDocument> clone;
1525  synchronized (bufferLock) {
1526 
1527  if (buffer.isEmpty()) {
1528  return;
1529  }
1530 
1531  // Buffer is full. Make a clone and release the lock, so that we don't
1532  // hold other ingest threads
1533  clone = buffer.stream().collect(toList());
1534  buffer.clear();
1535  }
1536 
1537  try {
1538  // send the cloned list to Solr
1539  sendBufferedDocs(clone);
1540  } catch (KeywordSearchModuleException ex) {
1541  logger.log(Level.SEVERE, "Periodic batched document update failed", ex); //NON-NLS
1542  }
1543  }
1544  }
1545 
1551  String getName() {
1552  return name;
1553  }
1554 
1555  private Index getIndexInfo() {
1556  return this.textIndex;
1557  }
1558 
1559  private QueryResponse query(SolrQuery sq) throws SolrServerException, IOException {
1560  return solrCore.query(sq);
1561  }
1562 
1563  private NamedList<Object> request(SolrRequest request) throws SolrServerException {
1564  try {
1565  return solrCore.request(request);
1566  } catch (Exception e) {
1567  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1568  logger.log(Level.WARNING, "Could not issue Solr request. ", e); //NON-NLS
1569  throw new SolrServerException(
1570  NbBundle.getMessage(this.getClass(), "Server.request.exception.exception.msg"), e);
1571  }
1572 
1573  }
1574 
1575  private QueryResponse query(SolrQuery sq, SolrRequest.METHOD method) throws SolrServerException, IOException {
1576  return solrCore.query(sq, method);
1577  }
1578 
1579  private TermsResponse queryTerms(SolrQuery sq) throws SolrServerException, IOException {
1580  QueryResponse qres = solrCore.query(sq);
1581  return qres.getTermsResponse();
1582  }
1583 
1584  private void commit() throws SolrServerException {
1585  List<SolrInputDocument> clone;
1586  synchronized (bufferLock) {
1587  // Make a clone and release the lock, so that we don't
1588  // hold other ingest threads
1589  clone = buffer.stream().collect(toList());
1590  buffer.clear();
1591  }
1592 
1593  try {
1594  sendBufferedDocs(clone);
1595  } catch (KeywordSearchModuleException ex) {
1596  throw new SolrServerException(NbBundle.getMessage(this.getClass(), "Server.commit.exception.msg"), ex);
1597  }
1598 
1599  try {
1600  //commit and block
1601  solrCore.commit(true, true);
1602  } catch (Exception e) {
1603  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1604  logger.log(Level.WARNING, "Could not commit index. ", e); //NON-NLS
1605  throw new SolrServerException(NbBundle.getMessage(this.getClass(), "Server.commit.exception.msg"), e);
1606  }
1607  }
1608 
1609  private void deleteDataSource(Long dsObjId) throws IOException, SolrServerException {
1610  String dataSourceId = Long.toString(dsObjId);
1611  String deleteQuery = "image_id:" + dataSourceId;
1612 
1613  solrCore.deleteByQuery(deleteQuery);
1614  }
1615 
1624  void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException {
1625 
1626  List<SolrInputDocument> clone;
1627  synchronized (bufferLock) {
1628  buffer.add(doc);
1629  // buffer documents if the buffer is not full
1630  if (buffer.size() < maxBufferSize) {
1631  return;
1632  }
1633 
1634  // Buffer is full. Make a clone and release the lock, so that we don't
1635  // hold other ingest threads
1636  clone = buffer.stream().collect(toList());
1637  buffer.clear();
1638  }
1639 
1640  // send the cloned list to Solr
1641  sendBufferedDocs(clone);
1642  }
1643 
1651  private void sendBufferedDocs(List<SolrInputDocument> docBuffer) throws KeywordSearchModuleException {
1652 
1653  if (docBuffer.isEmpty()) {
1654  return;
1655  }
1656 
1657  try {
1658  boolean success = true;
1659  for (int reTryAttempt = 0; reTryAttempt < NUM_BATCH_UPDATE_RETRIES; reTryAttempt++) {
1660  try {
1661  success = true;
1662  solrCore.add(docBuffer);
1663  } catch (Exception ex) {
1664  success = false;
1665  if (reTryAttempt < NUM_BATCH_UPDATE_RETRIES - 1) {
1666  logger.log(Level.WARNING, "Unable to send document batch to Solr. Re-trying...", ex); //NON-NLS
1667  try {
1668  Thread.sleep(SLEEP_BETWEEN_RETRIES_MS);
1669  } catch (InterruptedException ignore) {
1670  throw new KeywordSearchModuleException(
1671  NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg"), ex); //NON-NLS
1672  }
1673  }
1674  }
1675  if (success) {
1676  if (reTryAttempt > 0) {
1677  logger.log(Level.INFO, "Batch update suceeded after {0} re-try", reTryAttempt); //NON-NLS
1678  }
1679  return;
1680  }
1681  }
1682  // if we are here, it means all re-try attempts failed
1683  logger.log(Level.SEVERE, "Unable to send document batch to Solr. All re-try attempts failed!"); //NON-NLS
1684  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg")); //NON-NLS
1685  } finally {
1686  docBuffer.clear();
1687  }
1688  }
1689 
1700  private String getSolrContent(long contentID, int chunkID) {
1701  final SolrQuery q = new SolrQuery();
1702  q.setQuery("*:*");
1703  String filterQuery = Schema.ID.toString() + ":" + KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
1704  if (chunkID != 0) {
1705  filterQuery = filterQuery + Server.CHUNK_ID_SEPARATOR + chunkID;
1706  }
1707  q.addFilterQuery(filterQuery);
1708  q.setFields(Schema.TEXT.toString());
1709  try {
1710  // Get the first result.
1711  SolrDocumentList solrDocuments = solrCore.query(q).getResults();
1712 
1713  if (!solrDocuments.isEmpty()) {
1714  SolrDocument solrDocument = solrDocuments.get(0);
1715  if (solrDocument != null) {
1716  Collection<Object> fieldValues = solrDocument.getFieldValues(Schema.TEXT.toString());
1717  if (fieldValues.size() == 1) // The indexed text field for artifacts will only have a single value.
1718  {
1719  return fieldValues.toArray(new String[0])[0];
1720  } else // The indexed text for files has 2 values, the file name and the file content.
1721  // We return the file content value.
1722  {
1723  return fieldValues.toArray(new String[0])[1];
1724  }
1725  }
1726  }
1727  } catch (Exception ex) {
1728  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1729  logger.log(Level.SEVERE, "Error getting content from Solr. Solr document id " + contentID + ", chunk id " + chunkID + ", query: " + filterQuery, ex); //NON-NLS
1730  return null;
1731  }
1732 
1733  return null;
1734  }
1735 
1736  synchronized void close() throws KeywordSearchModuleException {
1737  // We only unload cores for "single-user" cases.
1738  if (this.caseType == CaseType.MULTI_USER_CASE) {
1739  return;
1740  }
1741 
1742  try {
1743  CoreAdminRequest.unloadCore(this.name, currentSolrServer);
1744  } catch (Exception ex) {
1745  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1746  throw new KeywordSearchModuleException(
1747  NbBundle.getMessage(this.getClass(), "Server.close.exception.msg"), ex);
1748  }
1749  }
1750 
1760  private int queryNumIndexedFiles() throws SolrServerException, IOException {
1762  }
1763 
1773  private int queryNumIndexedChunks() throws SolrServerException, IOException {
1774  SolrQuery q = new SolrQuery(Server.Schema.ID + ":*" + Server.CHUNK_ID_SEPARATOR + "*");
1775  q.setRows(0);
1776  int numChunks = (int) query(q).getResults().getNumFound();
1777  return numChunks;
1778  }
1779 
1790  private int queryNumIndexedDocuments() throws SolrServerException, IOException {
1791  SolrQuery q = new SolrQuery("*:*");
1792  q.setRows(0);
1793  return (int) query(q).getResults().getNumFound();
1794  }
1795 
1805  private boolean queryIsIndexed(long contentID) throws SolrServerException, IOException {
1806  String id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
1807  SolrQuery q = new SolrQuery("*:*");
1808  q.addFilterQuery(Server.Schema.ID.toString() + ":" + id);
1809  //q.setFields(Server.Schema.ID.toString());
1810  q.setRows(0);
1811  return (int) query(q).getResults().getNumFound() != 0;
1812  }
1813 
1825  private int queryNumFileChunks(long contentID) throws SolrServerException, IOException {
1826  String id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
1827  final SolrQuery q
1828  = new SolrQuery(Server.Schema.ID + ":" + id + Server.CHUNK_ID_SEPARATOR + "*");
1829  q.setRows(0);
1830  return (int) query(q).getResults().getNumFound();
1831  }
1832  }
1833 
1834  class ServerAction extends AbstractAction {
1835 
1836  private static final long serialVersionUID = 1L;
1837 
1838  @Override
1839  public void actionPerformed(ActionEvent e) {
1840  logger.log(Level.INFO, e.paramString().trim());
1841  }
1842  }
1843 
1847  class SolrServerNoPortException extends SocketException {
1848 
1849  private static final long serialVersionUID = 1L;
1850 
1854  private final int port;
1855 
1856  SolrServerNoPortException(int port) {
1857  super(NbBundle.getMessage(Server.class, "Server.solrServerNoPortException.msg", port,
1858  Server.PROPERTIES_CURRENT_SERVER_PORT));
1859  this.port = port;
1860  }
1861 
1862  int getPortNumber() {
1863  return port;
1864  }
1865  }
1866 }
static synchronized String getConfigSetting(String moduleName, String settingName)
String getSolrContent(final long objectID)
Definition: Server.java:1358
final ReentrantReadWriteLock currentCoreLock
Definition: Server.java:244
static IndexingServerProperties getMultiUserServerProperties(String caseDirectory)
Definition: Server.java:908
boolean coreIsLoaded(String coreName)
Definition: Server.java:1436
static TimingMetric getTimingMetric(String name)
static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath)
Definition: Server.java:949
void addServerActionListener(PropertyChangeListener l)
Definition: Server.java:311
static synchronized boolean settingExists(String moduleName, String settingName)
static final String HL_ANALYZE_CHARS_UNLIMITED
Definition: Server.java:207
Process runSolrCommand(List< String > solrArguments)
Definition: Server.java:396
String getSolrContent(final Content content)
Definition: Server.java:1313
boolean coreIndexFolderExists(String coreName)
Definition: Server.java:1451
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
static final Charset DEFAULT_INDEXED_TEXT_CHARSET
default Charset to index text as
Definition: Server.java:216
InputStreamPrinterThread errorRedirectThread
Definition: Server.java:249
QueryResponse query(SolrQuery sq)
Definition: Server.java:1209
static synchronized String getJavaPath()
static void submitTimingMetric(TimingMetric metric)
TermsResponse queryTerms(SolrQuery sq)
Definition: Server.java:1266
boolean queryIsIndexed(long contentID)
Definition: Server.java:1153
String getSolrContent(final Content content, int chunkID)
Definition: Server.java:1337
String getSolrContent(final long objectID, final int chunkID)
Definition: Server.java:1380
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static synchronized long[] getJavaPIDs(String sigarSubQuery)
Core openCore(Case theCase, Index index)
Definition: Server.java:828
static String getChunkIdString(long parentID, int childID)
Definition: Server.java:1401
QueryResponse query(SolrQuery sq, SolrRequest.METHOD method)
Definition: Server.java:1238

Copyright © 2012-2020 Basis Technology. Generated on: Tue Sep 22 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.