Autopsy  4.17.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-2020 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 static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
39 import java.nio.file.Path;
40 import java.nio.file.Paths;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.Random;
47 import java.util.concurrent.ScheduledThreadPoolExecutor;
48 import java.util.concurrent.TimeUnit;
49 import java.util.concurrent.locks.ReentrantReadWriteLock;
50 import java.util.logging.Level;
51 import javax.swing.AbstractAction;
52 import org.apache.commons.io.FileUtils;
53 import java.util.concurrent.TimeoutException;
54 import static java.util.stream.Collectors.toList;
55 import org.apache.solr.client.solrj.SolrQuery;
56 import org.apache.solr.client.solrj.SolrRequest;
57 import org.apache.solr.client.solrj.SolrServerException;
58 import org.apache.solr.client.solrj.SolrClient;
59 import org.apache.solr.client.solrj.impl.HttpSolrClient;
60 import org.apache.solr.client.solrj.impl.CloudSolrClient;
61 import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient;
62 import org.apache.solr.client.solrj.impl.XMLResponseParser;
63 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
64 import org.apache.solr.client.solrj.response.CollectionAdminResponse;
65 import org.apache.solr.client.solrj.request.CoreAdminRequest;
66 import org.apache.solr.client.solrj.response.CoreAdminResponse;
67 import org.apache.solr.client.solrj.impl.BaseHttpSolrClient.RemoteSolrException;
68 import org.apache.solr.client.solrj.response.QueryResponse;
69 import org.apache.solr.client.solrj.response.TermsResponse;
70 import org.apache.solr.common.SolrDocument;
71 import org.apache.solr.common.SolrDocumentList;
72 import org.apache.solr.common.SolrException;
73 import org.apache.solr.common.SolrInputDocument;
74 import org.apache.solr.common.util.NamedList;
75 import org.openide.modules.InstalledFileLocator;
76 import org.openide.modules.Places;
77 import org.openide.util.NbBundle;
78 import org.openide.windows.WindowManager;
92 import org.sleuthkit.datamodel.Content;
93 
98 public class Server {
99 
103  public static enum Schema {
104 
105  ID {
106  @Override
107  public String toString() {
108  return "id"; //NON-NLS
109  }
110  },
111  IMAGE_ID {
112  @Override
113  public String toString() {
114  return "image_id"; //NON-NLS
115  }
116  },
117  // This is not stored or indexed. it is copied to text by the schema
118  CONTENT {
119  @Override
120  public String toString() {
121  return "content"; //NON-NLS
122  }
123  },
124  // String representation for regular expression searching
125  CONTENT_STR {
126  @Override
127  public String toString() {
128  return "content_str"; //NON-NLS
129  }
130  },
131  // default search field. Populated by schema
132  TEXT {
133  @Override
134  public String toString() {
135  return "text"; //NON-NLS
136  }
137  },
138  // no longer populated. Was used for regular expression searching.
139  // Should not be used.
140  CONTENT_WS {
141  @Override
142  public String toString() {
143  return "content_ws"; //NON-NLS
144  }
145  },
146  CONTENT_JA {
147  @Override
148  public String toString() {
149  return "content_ja"; //NON-NLS
150  }
151  },
152  LANGUAGE {
153  @Override
154  public String toString() {
155  return "language"; //NON-NLS
156  }
157  },
158  FILE_NAME {
159  @Override
160  public String toString() {
161  return "file_name"; //NON-NLS
162  }
163  },
164  // note that we no longer store or index this field
165  CTIME {
166  @Override
167  public String toString() {
168  return "ctime"; //NON-NLS
169  }
170  },
171  // note that we no longer store or index this field
172  ATIME {
173  @Override
174  public String toString() {
175  return "atime"; //NON-NLS
176  }
177  },
178  // note that we no longer store or index this field
179  MTIME {
180  @Override
181  public String toString() {
182  return "mtime"; //NON-NLS
183  }
184  },
185  // note that we no longer store or index this field
186  CRTIME {
187  @Override
188  public String toString() {
189  return "crtime"; //NON-NLS
190  }
191  },
192  NUM_CHUNKS {
193  @Override
194  public String toString() {
195  return "num_chunks"; //NON-NLS
196  }
197  },
198  CHUNK_SIZE {
199  @Override
200  public String toString() {
201  return "chunk_size"; //NON-NLS
202  }
203  },
209  TERMFREQ {
210  @Override
211  public String toString() {
212  return "termfreq"; //NON-NLS
213  }
214  }
215  };
216 
217  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)
218  //max content size we can send to Solr
219  public static final long MAX_CONTENT_SIZE = 1L * 31 * 1024 * 1024;
220  private static final Logger logger = Logger.getLogger(Server.class.getName());
221  public static final String CORE_EVT = "CORE_EVT"; //NON-NLS
222  @Deprecated
223  public static final char ID_CHUNK_SEP = '_';
224  public static final String CHUNK_ID_SEPARATOR = "_";
225  private String javaPath = "java";
226  public static final Charset DEFAULT_INDEXED_TEXT_CHARSET = Charset.forName("UTF-8");
227  private Process curSolrProcess = null;
228  static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
229  static final String PROPERTIES_CURRENT_SERVER_PORT = "IndexingServerPort"; //NON-NLS
230  static final String PROPERTIES_CURRENT_STOP_PORT = "IndexingServerStopPort"; //NON-NLS
231  private static final String KEY = "jjk#09s"; //NON-NLS
232  static final String DEFAULT_SOLR_SERVER_HOST = "localhost"; //NON-NLS
233  static final int DEFAULT_SOLR_SERVER_PORT = 23232;
234  static final int DEFAULT_SOLR_STOP_PORT = 34343;
235  private int localSolrServerPort = 0;
236  private int localSolrStopPort = 0;
237  private File localSolrFolder;
238  private static final String SOLR = "solr";
239  private static final String CORE_PROPERTIES = "core.properties";
240  private static final boolean DEBUG = false;//(Version.getBuildType() == Version.Type.DEVELOPMENT);
241  private static final int NUM_COLLECTION_CREATION_RETRIES = 5;
242 
243  public enum CORE_EVT_STATES {
244 
245  STOPPED, STARTED
246  };
247 
248  private enum SOLR_VERSION {
249 
250  SOLR8, SOLR4
251  };
252 
253  // A reference to the locally running Solr instance.
254  private HttpSolrClient localSolrServer = null;
255  private SOLR_VERSION localServerVersion = SOLR_VERSION.SOLR8; // start local Solr 8 by default
256 
257  // A reference to the remote/network running Solr instance.
258  private HttpSolrClient remoteSolrServer;
259 
260  private Collection currentCollection;
261  private final ReentrantReadWriteLock currentCoreLock;
262 
263  private final ServerAction serverAction;
265 
270  Server() {
271  initSettings();
272 
273  serverAction = new ServerAction();
274  File solr8Folder = InstalledFileLocator.getDefault().locate("solr", Server.class.getPackage().getName(), false); //NON-NLS
275  File solr4Folder = InstalledFileLocator.getDefault().locate("solr4", Server.class.getPackage().getName(), false); //NON-NLS
276 
277  // Figure out where Java is located. The Java home location
278  // will be passed as the SOLR_JAVA_HOME environment
279  // variable to the Solr script but it can be overridden by the user in
280  // either autopsy-solr.cmd or autopsy-solr-in.cmd.
281  javaPath = PlatformUtil.getJavaPath();
282 
283  Path solr8Home = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "solr"); //NON-NLS
284  try {
285  // Always copy the config files, as they may have changed. Otherwise potentially stale Solr configuration is being used.
286  if (!solr8Home.toFile().exists()) {
287  Files.createDirectory(solr8Home);
288  } else {
289  // delete the configsets directory as the Autopsy configset could have changed
290  FileUtil.deleteDir(solr8Home.resolve("configsets").toFile());
291  }
292  Files.copy(Paths.get(solr8Folder.getAbsolutePath(), "server", "solr", "solr.xml"), solr8Home.resolve("solr.xml"), REPLACE_EXISTING); //NON-NLS
293  Files.copy(Paths.get(solr8Folder.getAbsolutePath(), "server", "solr", "zoo.cfg"), solr8Home.resolve("zoo.cfg"), REPLACE_EXISTING); //NON-NLS
294  FileUtils.copyDirectory(Paths.get(solr8Folder.getAbsolutePath(), "server", "solr", "configsets").toFile(), solr8Home.resolve("configsets").toFile()); //NON-NLS
295  } catch (IOException ex) {
296  logger.log(Level.SEVERE, "Failed to create Solr 8 home folder:", ex); //NON-NLS
297  }
298 
299  Path solr4Home = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "solr4"); //NON-NLS
300  try {
301  // Always copy the config files, as they may have changed. Otherwise potentially stale Solr configuration is being used.
302  if (!solr4Home.toFile().exists()) {
303  Files.createDirectory(solr4Home);
304  }
305  Files.copy(Paths.get(solr4Folder.getAbsolutePath(), "solr", "solr.xml"), solr4Home.resolve("solr.xml"), REPLACE_EXISTING); //NON-NLS
306  Files.copy(Paths.get(solr4Folder.getAbsolutePath(), "solr", "zoo.cfg"), solr4Home.resolve("zoo.cfg"), REPLACE_EXISTING); //NON-NLS
307  } catch (IOException ex) {
308  logger.log(Level.SEVERE, "Failed to create Solr 4 home folder:", ex); //NON-NLS
309  }
310 
311  currentCoreLock = new ReentrantReadWriteLock(true);
312 
313  logger.log(Level.INFO, "Created Server instance using Java at {0}", javaPath); //NON-NLS
314  }
315 
316  private void initSettings() {
317 
318  if (ModuleSettings.settingExists(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT)) {
319  try {
320  localSolrServerPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT));
321  } catch (NumberFormatException nfe) {
322  logger.log(Level.WARNING, "Could not decode indexing server port, value was not a valid port number, using the default. ", nfe); //NON-NLS
323  localSolrServerPort = DEFAULT_SOLR_SERVER_PORT;
324  }
325  } else {
326  localSolrServerPort = DEFAULT_SOLR_SERVER_PORT;
327  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT, String.valueOf(localSolrServerPort));
328  }
329 
330  if (ModuleSettings.settingExists(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT)) {
331  try {
332  localSolrStopPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT));
333  } catch (NumberFormatException nfe) {
334  logger.log(Level.WARNING, "Could not decode indexing server stop port, value was not a valid port number, using default", nfe); //NON-NLS
335  localSolrStopPort = DEFAULT_SOLR_STOP_PORT;
336  }
337  } else {
338  localSolrStopPort = DEFAULT_SOLR_STOP_PORT;
339  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT, String.valueOf(localSolrStopPort));
340  }
341  }
342 
343  private HttpSolrClient getSolrClient(String solrUrl) {
344  int connectionTimeoutMs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getConnectionTimeout();
345  return new HttpSolrClient.Builder(solrUrl)
346  .withSocketTimeout(connectionTimeoutMs)
347  .withConnectionTimeout(connectionTimeoutMs)
348  .withResponseParser(new XMLResponseParser())
349  .build();
350  }
351 
352  private ConcurrentUpdateSolrClient getConcurrentClient(String solrUrl) {
353  int numThreads = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getNumThreads();
354  int numDocs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getDocumentsQueueSize();
355  int connectionTimeoutMs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getConnectionTimeout();
356  logger.log(Level.INFO, "Creating new ConcurrentUpdateSolrClient: {0}", solrUrl); //NON-NLS
357  logger.log(Level.INFO, "Queue size = {0}, Number of threads = {1}, Connection Timeout (ms) = {2}", new Object[]{numDocs, numThreads, connectionTimeoutMs}); //NON-NLS
358  ConcurrentUpdateSolrClient client = new ConcurrentUpdateSolrClient.Builder(solrUrl)
359  .withQueueSize(numDocs)
360  .withThreadCount(numThreads)
361  .withSocketTimeout(connectionTimeoutMs)
362  .withConnectionTimeout(connectionTimeoutMs)
363  .withResponseParser(new XMLResponseParser())
364  .build();
365 
366  return client;
367  }
368 
369  private CloudSolrClient getCloudSolrClient(String host, String port, String defaultCollectionName) throws KeywordSearchModuleException {
370  List<String> solrServerList = getSolrServerList(host, port);
371  List<String> solrUrls = new ArrayList<>();
372  for (String server : solrServerList) {
373  solrUrls.add("http://" + server + "/solr");
374  logger.log(Level.INFO, "Using Solr server: {0}", server);
375  }
376 
377  logger.log(Level.INFO, "Creating new CloudSolrClient"); //NON-NLS
378  int connectionTimeoutMs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getConnectionTimeout();
379  CloudSolrClient client = new CloudSolrClient.Builder(solrUrls)
380  .withConnectionTimeout(connectionTimeoutMs)
381  .withSocketTimeout(connectionTimeoutMs)
382  .withResponseParser(new XMLResponseParser())
383  .build();
384  if (!defaultCollectionName.isEmpty()) {
385  client.setDefaultCollection(defaultCollectionName);
386  }
387  client.connect();
388  return client;
389  }
390 
391  @Override
392  public void finalize() throws java.lang.Throwable {
393  stop();
394  super.finalize();
395  }
396 
397  public void addServerActionListener(PropertyChangeListener l) {
398  serverAction.addPropertyChangeListener(l);
399  }
400 
401  int getLocalSolrServerPort() {
402  return localSolrServerPort;
403  }
404 
405  int getLocalSolrStopPort() {
406  return localSolrStopPort;
407  }
408 
412  private static class InputStreamPrinterThread extends Thread {
413 
414  InputStream stream;
415  OutputStream out;
416  volatile boolean doRun = true;
417 
418  InputStreamPrinterThread(InputStream stream, String type) {
419  this.stream = stream;
420  try {
421  final String log = Places.getUserDirectory().getAbsolutePath()
422  + File.separator + "var" + File.separator + "log" //NON-NLS
423  + File.separator + "solr.log." + type; //NON-NLS
424  File outputFile = new File(log.concat(".0"));
425  File first = new File(log.concat(".1"));
426  File second = new File(log.concat(".2"));
427  if (second.exists()) {
428  second.delete();
429  }
430  if (first.exists()) {
431  first.renameTo(second);
432  }
433  if (outputFile.exists()) {
434  outputFile.renameTo(first);
435  } else {
436  outputFile.createNewFile();
437  }
438  out = new FileOutputStream(outputFile);
439 
440  } catch (Exception ex) {
441  logger.log(Level.WARNING, "Failed to create solr log file", ex); //NON-NLS
442  }
443  }
444 
445  void stopRun() {
446  doRun = false;
447  }
448 
449  @Override
450  public void run() {
451 
452  try (InputStreamReader isr = new InputStreamReader(stream);
453  BufferedReader br = new BufferedReader(isr);
454  OutputStreamWriter osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
455  BufferedWriter bw = new BufferedWriter(osw);) {
456 
457  String line = null;
458  while (doRun && (line = br.readLine()) != null) {
459  bw.write(line);
460  bw.newLine();
461  if (DEBUG) {
462  //flush buffers if dev version for debugging
463  bw.flush();
464  }
465  }
466  bw.flush();
467  } catch (IOException ex) {
468  logger.log(Level.SEVERE, "Error redirecting Solr output stream", ex); //NON-NLS
469  }
470  }
471  }
472 
482  private Process runLocalSolr8ControlCommand(List<String> solrArguments) throws IOException {
483  final String MAX_SOLR_MEM_MB_PAR = "-Xmx" + UserPreferences.getMaxSolrVMSize() + "m"; //NON-NLS
484 
485  // This is our customized version of the Solr batch script to start/stop Solr.
486  File solr8Folder = InstalledFileLocator.getDefault().locate("solr", Server.class.getPackage().getName(), false); //NON-NLS
487  Path solr8CmdPath;
489  solr8CmdPath = Paths.get(solr8Folder.getAbsolutePath(), "bin", "autopsy-solr.cmd"); //NON-NLS
490  } else {
491  solr8CmdPath = Paths.get(solr8Folder.getAbsolutePath(), "bin", "autopsy-solr"); //NON-NLS
492  }
493  Path solr8Home = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "solr"); //NON-NLS
494 
495  List<String> commandLine = new ArrayList<>();
496  commandLine.add(solr8CmdPath.toString());
497  commandLine.addAll(solrArguments);
498 
499  ProcessBuilder solrProcessBuilder = new ProcessBuilder(commandLine);
500  solrProcessBuilder.directory(solr8Folder);
501 
502  // Redirect stdout and stderr to files to prevent blocking.
503  Path solrStdoutPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stdout"); //NON-NLS
504  solrProcessBuilder.redirectOutput(solrStdoutPath.toFile());
505 
506  Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stderr"); //NON-NLS
507  solrProcessBuilder.redirectError(solrStderrPath.toFile());
508 
509  // get the path to the JRE folder. That's what Solr needs as SOLR_JAVA_HOME
510  String jreFolderPath = new File(javaPath).getParentFile().getParentFile().getAbsolutePath();
511 
512  solrProcessBuilder.environment().put("SOLR_JAVA_HOME", jreFolderPath); // NON-NLS
513  solrProcessBuilder.environment().put("SOLR_HOME", solr8Home.toString()); // NON-NLS
514  solrProcessBuilder.environment().put("STOP_KEY", KEY); // NON-NLS
515  solrProcessBuilder.environment().put("SOLR_JAVA_MEM", MAX_SOLR_MEM_MB_PAR); // NON-NLS
516  logger.log(Level.INFO, "Setting Solr 8 directory: {0}", solr8Folder.toString()); //NON-NLS
517  logger.log(Level.INFO, "Running Solr 8 command: {0} from {1}", new Object[]{solrProcessBuilder.command(), solr8Folder.toString()}); //NON-NLS
518  Process process = solrProcessBuilder.start();
519  logger.log(Level.INFO, "Finished running Solr 8 command"); //NON-NLS
520  return process;
521  }
522 
532  private Process runLocalSolr4ControlCommand(List<String> solrArguments) throws IOException {
533  final String MAX_SOLR_MEM_MB_PAR = "-Xmx" + UserPreferences.getMaxSolrVMSize() + "m"; //NON-NLS
534  File solr4Folder = InstalledFileLocator.getDefault().locate("solr4", Server.class.getPackage().getName(), false); //NON-NLS
535 
536  List<String> commandLine = new ArrayList<>();
537  commandLine.add(javaPath);
538  commandLine.add(MAX_SOLR_MEM_MB_PAR);
539  commandLine.add("-DSTOP.PORT=" + localSolrStopPort); //NON-NLS
540  commandLine.add("-Djetty.port=" + localSolrServerPort); //NON-NLS
541  commandLine.add("-DSTOP.KEY=" + KEY); //NON-NLS
542  commandLine.add("-jar"); //NON-NLS
543  commandLine.add("start.jar"); //NON-NLS
544 
545  commandLine.addAll(solrArguments);
546 
547  ProcessBuilder solrProcessBuilder = new ProcessBuilder(commandLine);
548  solrProcessBuilder.directory(solr4Folder);
549 
550  // Redirect stdout and stderr to files to prevent blocking.
551  Path solrStdoutPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stdout"); //NON-NLS
552  solrProcessBuilder.redirectOutput(solrStdoutPath.toFile());
553 
554  Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stderr"); //NON-NLS
555  solrProcessBuilder.redirectError(solrStderrPath.toFile());
556 
557  logger.log(Level.INFO, "Running Solr 4 command: {0}", solrProcessBuilder.command()); //NON-NLS
558  Process process = solrProcessBuilder.start();
559  logger.log(Level.INFO, "Finished running Solr 4 command"); //NON-NLS
560  return process;
561  }
562 
568  List<Long> getSolrPIDs() {
569  List<Long> pids = new ArrayList<>();
570 
571  //NOTE: these needs to be in sync with process start string in start()
572  final String pidsQuery = "Args.*.eq=-DSTOP.KEY=" + KEY + ",Args.*.eq=start.jar"; //NON-NLS
573 
574  long[] pidsArr = PlatformUtil.getJavaPIDs(pidsQuery);
575  if (pidsArr != null) {
576  for (int i = 0; i < pidsArr.length; ++i) {
577  pids.add(pidsArr[i]);
578  }
579  }
580 
581  return pids;
582  }
583 
588  void killSolr() {
589  List<Long> solrPids = getSolrPIDs();
590  for (long pid : solrPids) {
591  logger.log(Level.INFO, "Trying to kill old Solr process, PID: {0}", pid); //NON-NLS
592  PlatformUtil.killProcess(pid);
593  }
594  }
595 
596  void start() throws KeywordSearchModuleException, SolrServerNoPortException, SolrServerException {
597  startLocalSolr(SOLR_VERSION.SOLR8);
598  }
599 
600  private void configureSolrConnection(Case theCase, Index index) throws KeywordSearchModuleException, SolrServerNoPortException {
601 
602  try {
603  if (theCase.getCaseType() == CaseType.SINGLE_USER_CASE) {
604 
605  // makes sure the proper local Solr server is running
606  if (IndexFinder.getCurrentSolrVersion().equals(index.getSolrVersion())) {
607  startLocalSolr(SOLR_VERSION.SOLR8);
608  } else {
609  startLocalSolr(SOLR_VERSION.SOLR4);
610  }
611 
612  // check if the local Solr server is running
613  if (!this.isLocalSolrRunning()) {
614  logger.log(Level.SEVERE, "Local Solr server is not running"); //NON-NLS
615  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.msg"));
616  }
617  } else {
618  // create SolrJ client to connect to remore Solr server
619  remoteSolrServer = configureMultiUserConnection(theCase, index, "");
620 
621  // test the connection
622  connectToSolrServer(remoteSolrServer);
623  }
624  } catch (SolrServerException | IOException ex) {
625  throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
626  }
627  }
628 
643  private HttpSolrClient configureMultiUserConnection(Case theCase, Index index, String name) throws KeywordSearchModuleException {
644 
645  // read Solr connection info from user preferences, unless "solrserver.txt" is present
646  IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory());
647  if (properties.host.isEmpty() || properties.port.isEmpty()) {
648  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.connectionInfoMissing.exception.msg", index.getSolrVersion()));
649  }
650  String solrUrl = "http://" + properties.host + ":" + properties.port + "/solr";
651 
652  if (!name.isEmpty()) {
653  solrUrl = solrUrl + "/" + name;
654  }
655 
656  // create SolrJ client to connect to remore Solr server
657  return getSolrClient(solrUrl);
658  }
659 
665  @NbBundle.Messages({
666  "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.",})
667  void startLocalSolr(SOLR_VERSION version) throws KeywordSearchModuleException, SolrServerNoPortException, SolrServerException {
668 
669  if (isLocalSolrRunning()) {
670  if (localServerVersion.equals(version)) {
671  // this version of local server is already running
672  return;
673  } else {
674  // wrong version of local server is running, stop it
675  stop();
676  }
677  }
678 
679  // set which version of local server is currently running
680  localServerVersion = version;
681 
682  if (!isPortAvailable(localSolrServerPort)) {
683  // There is something already listening on our port. Let's see if
684  // this is from an earlier run that didn't successfully shut down
685  // and if so kill it.
686  final List<Long> pids = this.getSolrPIDs();
687 
688  // If the culprit listening on the port is not a Solr process
689  // we refuse to start.
690  if (pids.isEmpty()) {
691  throw new SolrServerNoPortException(localSolrServerPort);
692  }
693 
694  // Ok, we've tried to stop it above but there still appears to be
695  // a Solr process listening on our port so we forcefully kill it.
696  killSolr();
697 
698  // If either of the ports are still in use after our attempt to kill
699  // previously running processes we give up and throw an exception.
700  if (!isPortAvailable(localSolrServerPort)) {
701  throw new SolrServerNoPortException(localSolrServerPort);
702  }
703  if (!isPortAvailable(localSolrStopPort)) {
704  throw new SolrServerNoPortException(localSolrStopPort);
705  }
706  }
707 
708  if (isPortAvailable(localSolrServerPort)) {
709  logger.log(Level.INFO, "Port [{0}] available, starting Solr", localSolrServerPort); //NON-NLS
710  try {
711  if (version == SOLR_VERSION.SOLR8) {
712  logger.log(Level.INFO, "Starting Solr 8 server"); //NON-NLS
713  localSolrFolder = InstalledFileLocator.getDefault().locate("solr", Server.class.getPackage().getName(), false); //NON-NLS
714  curSolrProcess = runLocalSolr8ControlCommand(new ArrayList<>(Arrays.asList("start", "-p", //NON-NLS
715  Integer.toString(localSolrServerPort)))); //NON-NLS
716  } else {
717  // solr4
718  localSolrFolder = InstalledFileLocator.getDefault().locate("solr4", Server.class.getPackage().getName(), false); //NON-NLS
719  logger.log(Level.INFO, "Starting Solr 4 server"); //NON-NLS
720  curSolrProcess = runLocalSolr4ControlCommand(new ArrayList<>(
721  Arrays.asList("-Dbootstrap_confdir=../solr/configsets/AutopsyConfig/conf", //NON-NLS
722  "-Dcollection.configName=AutopsyConfig"))); //NON-NLS
723  }
724 
725  // Wait for the Solr server to start and respond to a statusRequest request.
726  for (int numRetries = 0; numRetries < 6; numRetries++) {
727  if (isLocalSolrRunning()) {
728  localSolrServer = getSolrClient("http://localhost:" + localSolrServerPort + "/solr");
729  final List<Long> pids = this.getSolrPIDs();
730  logger.log(Level.INFO, "New Solr process PID: {0}", pids); //NON-NLS
731  return;
732  }
733 
734  // Local Solr server did not respond so we sleep for
735  // 5 seconds before trying again.
736  try {
737  TimeUnit.SECONDS.sleep(5);
738  } catch (InterruptedException ex) {
739  logger.log(Level.WARNING, "Timer interrupted"); //NON-NLS
740  }
741  }
742 
743  // If we get here the Solr server has not responded to connection
744  // attempts in a timely fashion.
745  logger.log(Level.WARNING, "Local Solr server failed to respond to status requests.");
746  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
747  @Override
748  public void run() {
749  MessageNotifyUtil.Notify.error(
750  NbBundle.getMessage(this.getClass(), "Installer.errorInitKsmMsg"),
751  Bundle.Server_status_failed_msg());
752  }
753  });
754  } catch (SecurityException ex) {
755  throw new KeywordSearchModuleException(
756  NbBundle.getMessage(this.getClass(), "Server.start.exception.cantStartSolr.msg"), ex);
757  } catch (IOException ex) {
758  throw new KeywordSearchModuleException(
759  NbBundle.getMessage(this.getClass(), "Server.start.exception.cantStartSolr.msg2"), ex);
760  }
761  }
762  }
763 
769  static boolean isPortAvailable(int port) {
770  ServerSocket ss = null;
771  try {
772 
773  ss = new ServerSocket(port, 0, java.net.Inet4Address.getByName("localhost")); //NON-NLS
774  if (ss.isBound()) {
775  ss.setReuseAddress(true);
776  ss.close();
777  return true;
778  }
779 
780  } catch (IOException e) {
781  } finally {
782  if (ss != null) {
783  try {
784  ss.close();
785  } catch (IOException e) {
786  /*
787  * should not be thrown
788  */
789  }
790  }
791  }
792  return false;
793  }
794 
800  void changeSolrServerPort(int port) {
801  localSolrServerPort = port;
802  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT, String.valueOf(port));
803  }
804 
810  void changeSolrStopPort(int port) {
811  localSolrStopPort = port;
812  ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT, String.valueOf(port));
813  }
814 
820  synchronized void stop() {
821 
822  try {
823  // Close any open core before stopping server
824  closeCore();
825  } catch (KeywordSearchModuleException e) {
826  logger.log(Level.WARNING, "Failed to close core: ", e); //NON-NLS
827  }
828 
829  stopLocalSolr();
830  }
831 
835  private void stopLocalSolr() {
836  try {
837  //try graceful shutdown
838  Process process;
839  if (localServerVersion == SOLR_VERSION.SOLR8) {
840  logger.log(Level.INFO, "Stopping Solr 8 server"); //NON-NLS
841  process = runLocalSolr8ControlCommand(new ArrayList<>(Arrays.asList("stop", "-k", KEY, "-p", Integer.toString(localSolrServerPort)))); //NON-NLS
842  } else {
843  // solr 4
844  logger.log(Level.INFO, "Stopping Solr 4 server"); //NON-NLS
845  process = runLocalSolr4ControlCommand(new ArrayList<>(Arrays.asList("--stop"))); //NON-NLS
846  }
847 
848  logger.log(Level.INFO, "Waiting for Solr server to stop"); //NON-NLS
849  process.waitFor();
850 
851  //if still running, forcefully stop it
852  if (curSolrProcess != null) {
853  curSolrProcess.destroy();
854  curSolrProcess = null;
855  }
856 
857  } catch (IOException | InterruptedException ex) {
858  logger.log(Level.WARNING, "Error while attempting to stop Solr server", ex);
859  } finally {
860  //stop Solr stream -> log redirect threads
861  try {
862  if (errorRedirectThread != null) {
863  errorRedirectThread.stopRun();
864  errorRedirectThread = null;
865  }
866  } finally {
867  //if still running, kill it
868  killSolr();
869  }
870 
871  logger.log(Level.INFO, "Finished stopping Solr server"); //NON-NLS
872  }
873  }
874 
882  synchronized boolean isLocalSolrRunning() throws KeywordSearchModuleException {
883  try {
884 
885  if (isPortAvailable(localSolrServerPort)) {
886  return false;
887  }
888 
889  // making a statusRequest request here instead of just doing solrServer.ping(), because
890  // that doesn't work when there are no cores
891  //TODO handle timeout in cases when some other type of server on that port
893 
894  logger.log(Level.INFO, "Solr server is running"); //NON-NLS
895  } catch (SolrServerException ex) {
896 
897  Throwable cause = ex.getRootCause();
898 
899  // TODO: check if SocketExceptions should actually happen (is
900  // probably caused by starting a connection as the server finishes
901  // shutting down)
902  if (cause instanceof ConnectException || cause instanceof SocketException) { //|| cause instanceof NoHttpResponseException) {
903  logger.log(Level.INFO, "Solr server is not running, cause: {0}", cause.getMessage()); //NON-NLS
904  return false;
905  } else {
906  throw new KeywordSearchModuleException(
907  NbBundle.getMessage(this.getClass(), "Server.isRunning.exception.errCheckSolrRunning.msg"), ex);
908  }
909  } catch (SolrException ex) {
910  // Just log 404 errors for now...
911  logger.log(Level.INFO, "Solr server is not running", ex); //NON-NLS
912  return false;
913  } catch (IOException ex) {
914  throw new KeywordSearchModuleException(
915  NbBundle.getMessage(this.getClass(), "Server.isRunning.exception.errCheckSolrRunning.msg2"), ex);
916  }
917 
918  return true;
919  }
920 
921  /*
922  * ** Convenience methods for use while we only open one case at a time ***
923  */
933  void openCoreForCase(Case theCase, Index index) throws KeywordSearchModuleException {
934  currentCoreLock.writeLock().lock();
935  try {
936  currentCollection = openCore(theCase, index);
937 
938  try {
939  // execute a test query. if it fails, an exception will be thrown
941  } catch (NoOpenCoreException ex) {
942  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex);
943  }
944 
945  serverAction.putValue(CORE_EVT, CORE_EVT_STATES.STARTED);
946  } finally {
947  currentCoreLock.writeLock().unlock();
948  }
949  }
950 
956  boolean coreIsOpen() {
957  currentCoreLock.readLock().lock();
958  try {
959  return (null != currentCollection);
960  } finally {
961  currentCoreLock.readLock().unlock();
962  }
963  }
964 
965  Index getIndexInfo() throws NoOpenCoreException {
966  currentCoreLock.readLock().lock();
967  try {
968  if (null == currentCollection) {
969  throw new NoOpenCoreException();
970  }
971  return currentCollection.getIndexInfo();
972  } finally {
973  currentCoreLock.readLock().unlock();
974  }
975  }
976 
977  void closeCore() throws KeywordSearchModuleException {
978  currentCoreLock.writeLock().lock();
979  try {
980  if (null != currentCollection) {
981  currentCollection.close();
982  serverAction.putValue(CORE_EVT, CORE_EVT_STATES.STOPPED);
983  }
984  } finally {
985  currentCollection = null;
986  currentCoreLock.writeLock().unlock();
987  }
988  }
989 
990  void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException, NoOpenCoreException {
991  currentCoreLock.readLock().lock();
992  try {
993  if (null == currentCollection) {
994  throw new NoOpenCoreException();
995  }
996  TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Index chunk");
997  currentCollection.addDocument(doc);
998  HealthMonitor.submitTimingMetric(metric);
999  } finally {
1000  currentCoreLock.readLock().unlock();
1001  }
1002  }
1003 
1012  @NbBundle.Messages({
1013  "# {0} - colelction name", "Server.deleteCore.exception.msg=Failed to delete Solr colelction {0}",})
1014  void deleteCollection(String coreName, CaseMetadata metadata) throws KeywordSearchServiceException, KeywordSearchModuleException {
1015  try {
1016  HttpSolrClient solrServer;
1017  if (metadata.getCaseType() == CaseType.SINGLE_USER_CASE) {
1018  solrServer = getSolrClient("http://localhost:" + localSolrServerPort + "/solr"); //NON-NLS
1019  CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, solrServer);
1020  if (null != response.getCoreStatus(coreName).get("instanceDir")) { //NON-NLS
1021  /*
1022  * Send a core unload request to the Solr server, with the
1023  * parameter set that request deleting the index and the
1024  * instance directory (deleteInstanceDir = true). Note that
1025  * this removes everything related to the core on the server
1026  * (the index directory, the configuration files, etc.), but
1027  * does not delete the actual Solr text index because it is
1028  * currently stored in the case directory.
1029  */
1030  org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(coreName, true, true, solrServer);
1031  }
1032  } else {
1033  IndexingServerProperties properties = getMultiUserServerProperties(metadata.getCaseDirectory());
1034  solrServer = getSolrClient("http://" + properties.getHost() + ":" + properties.getPort() + "/solr");
1035  connectToSolrServer(solrServer);
1036 
1037  CollectionAdminRequest.Delete deleteCollectionRequest = CollectionAdminRequest.deleteCollection(coreName);
1038  CollectionAdminResponse response = deleteCollectionRequest.process(solrServer);
1039  if (response.isSuccess()) {
1040  logger.log(Level.INFO, "Deleted collection {0}", coreName); //NON-NLS
1041  } else {
1042  logger.log(Level.WARNING, "Unable to delete collection {0}", coreName); //NON-NLS
1043  }
1044  }
1045  } catch (SolrServerException | IOException ex) {
1046  // We will get a RemoteSolrException with cause == null and detailsMessage
1047  // == "Already closed" if the core is not loaded. This is not an error in this scenario.
1048  if (!ex.getMessage().equals("Already closed")) { // NON-NLS
1049  throw new KeywordSearchServiceException(Bundle.Server_deleteCore_exception_msg(coreName), ex);
1050  }
1051  }
1052  }
1053 
1065  @NbBundle.Messages({
1066  "Server.exceptionMessage.unableToCreateCollection=Unable to create Solr collection",
1067  "Server.exceptionMessage.unableToBackupCollection=Unable to backup Solr collection",
1068  "Server.exceptionMessage.unableToRestoreCollection=Unable to restore Solr collection",
1069  })
1070  private Collection openCore(Case theCase, Index index) throws KeywordSearchModuleException {
1071 
1072  int numShardsToUse = 1;
1073  try {
1074  // connect to proper Solr server
1075  configureSolrConnection(theCase, index);
1076 
1077  if (theCase.getCaseType() == CaseType.MULTI_USER_CASE) {
1078  // select number of shards to use
1079  numShardsToUse = getNumShardsToUse();
1080  }
1081  } catch (Exception ex) {
1082  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1083  throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
1084  }
1085 
1086  try {
1087  String collectionName = index.getIndexName();
1088 
1089  if (theCase.getCaseType() == CaseType.MULTI_USER_CASE) {
1090  if (!collectionExists(collectionName)) {
1091  /*
1092  * The collection does not exist. Make a request that will cause the colelction to be created.
1093  */
1094  boolean doRetry = false;
1095  for (int reTryAttempt = 0; reTryAttempt < NUM_COLLECTION_CREATION_RETRIES; reTryAttempt++) {
1096  try {
1097  doRetry = false;
1098  createMultiUserCollection(collectionName, numShardsToUse);
1099  } catch (Exception ex) {
1100  if (reTryAttempt >= NUM_COLLECTION_CREATION_RETRIES) {
1101  logger.log(Level.SEVERE, "Unable to create Solr collection " + collectionName, ex); //NON-NLS
1102  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex);
1103  } else {
1104  logger.log(Level.SEVERE, "Unable to create Solr collection " + collectionName + ". Re-trying...", ex); //NON-NLS
1105  Thread.sleep(1000L);
1106  doRetry = true;
1107  }
1108  }
1109  if (!doRetry) {
1110  break;
1111  }
1112  }
1113  }
1114  } else {
1115  if (!coreIsLoaded(collectionName)) {
1116  // In single user mode, the index is stored in case output directory
1117  File dataDir = new File(new File(index.getIndexPath()).getParent()); // "data dir" is the parent of the index directory
1118  if (!dataDir.exists()) {
1119  dataDir.mkdirs();
1120  }
1121 
1122  // In single user mode, if there is a core.properties file already,
1123  // we've hit a solr bug. Compensate by deleting it.
1124  if (theCase.getCaseType() == CaseType.SINGLE_USER_CASE) {
1125  Path corePropertiesFile = Paths.get(localSolrFolder.toString(), SOLR, collectionName, CORE_PROPERTIES);
1126  if (corePropertiesFile.toFile().exists()) {
1127  try {
1128  corePropertiesFile.toFile().delete();
1129  } catch (Exception ex) {
1130  logger.log(Level.INFO, "Could not delete pre-existing core.properties prior to opening the core."); //NON-NLS
1131  }
1132  }
1133  }
1134 
1135  // for single user cases, we unload the core when we close the case. So we have to load the core again.
1136  CoreAdminRequest.Create createCoreRequest = new CoreAdminRequest.Create();
1137  createCoreRequest.setDataDir(dataDir.getAbsolutePath());
1138  createCoreRequest.setCoreName(collectionName);
1139  createCoreRequest.setConfigSet("AutopsyConfig"); //NON-NLS
1140  createCoreRequest.setIsLoadOnStartup(false);
1141  createCoreRequest.setIsTransient(true);
1142  localSolrServer.request(createCoreRequest);
1143 
1144  if (!coreIndexFolderExists(collectionName)) {
1145  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.noIndexDir.msg"));
1146  }
1147  }
1148  }
1149 
1150  return new Collection(collectionName, theCase, index);
1151 
1152  } catch (Exception ex) {
1153  logger.log(Level.SEVERE, "Exception during Solr collection creation.", ex); //NON-NLS
1154  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex);
1155  }
1156  }
1157 
1158  private int getNumShardsToUse() throws KeywordSearchModuleException {
1159 
1160  // if we want to use a specific sharding strategy, use that
1161  if (org.sleuthkit.autopsy.keywordsearch.UserPreferences.getMaxNumShards() > 0) {
1162  return org.sleuthkit.autopsy.keywordsearch.UserPreferences.getMaxNumShards();
1163  }
1164 
1165  // otherwise get list of all live Solr servers in the cluster
1166  List<String> solrServerList = getSolrServerList(remoteSolrServer);
1167  // shard across all available servers
1168  return solrServerList.size();
1169  }
1170 
1171  /*
1172  * Poll the remote Solr server for list of existing collections, and check if
1173  * the collection of interest exists.
1174  *
1175  * @param collectionName The name of the collection.
1176  *
1177  * @return True if the collection exists, false otherwise.
1178  *
1179  * @throws SolrServerException If there is a problem communicating with the
1180  * Solr server.
1181  * @throws IOException If there is a problem communicating with the Solr
1182  * server.
1183  */
1184  private boolean collectionExists(String collectionName) throws SolrServerException, IOException {
1185  CollectionAdminRequest.List req = new CollectionAdminRequest.List();
1186  CollectionAdminResponse response = req.process(remoteSolrServer);
1187  List<String> existingCollections = (List<String>) response.getResponse().get("collections");
1188  if (existingCollections == null) {
1189  existingCollections = new ArrayList<>();
1190  }
1191  return existingCollections.contains(collectionName);
1192  }
1193 
1194  /* NOTE: Keeping this code for reference, since it works.
1195  private boolean collectionExists(String collectionName) throws SolrServerException, IOException {
1196 
1197  // TODO we could potentially use this API. Currently set exception "Solr instance is not running in SolrCloud mode"
1198  // List<String> list = CollectionAdminRequest.listCollections(localSolrServer);
1199 
1200  CollectionAdminRequest.ClusterStatus statusRequest = CollectionAdminRequest.getClusterStatus().setCollectionName(collectionName);
1201  CollectionAdminResponse statusResponse;
1202  try {
1203  statusResponse = statusRequest.process(remoteSolrServer);
1204  } catch (RemoteSolrException ex) {
1205  // collection doesn't exist
1206  return false;
1207  }
1208 
1209  if (statusResponse == null) {
1210  return false;
1211  }
1212 
1213  NamedList error = (NamedList) statusResponse.getResponse().get("error");
1214  if (error != null) {
1215  return false;
1216  }
1217 
1218  // For some reason this returns info about all collections even though it's supposed to only return about the one we are requesting
1219  NamedList cluster = (NamedList) statusResponse.getResponse().get("cluster");
1220  NamedList collections = (NamedList) cluster.get("collections");
1221  if (collections != null) {
1222  Object collection = collections.get(collectionName);
1223  return (collection != null);
1224  } else {
1225  return false;
1226  }
1227  }*/
1228 
1229  private void createMultiUserCollection(String collectionName, int numShardsToUse) throws KeywordSearchModuleException, SolrServerException, IOException {
1230  /*
1231  * The core either does not exist or it is not loaded. Make a
1232  * request that will cause the core to be created if it does not
1233  * exist or loaded if it already exists.
1234  */
1235 
1236  Integer numShards = numShardsToUse;
1237  logger.log(Level.INFO, "numShardsToUse: {0}", numShardsToUse);
1238  Integer numNrtReplicas = 1;
1239  Integer numTlogReplicas = 0;
1240  Integer numPullReplicas = 0;
1241  CollectionAdminRequest.Create createCollectionRequest = CollectionAdminRequest.createCollection(collectionName, "AutopsyConfig", numShards, numNrtReplicas, numTlogReplicas, numPullReplicas);
1242 
1243  CollectionAdminResponse createResponse = createCollectionRequest.process(remoteSolrServer);
1244  if (createResponse.isSuccess()) {
1245  logger.log(Level.INFO, "Collection {0} successfully created.", collectionName);
1246  } else {
1247  logger.log(Level.SEVERE, "Unable to create Solr collection {0}", collectionName); //NON-NLS
1248  throw new KeywordSearchModuleException(Bundle.Server_exceptionMessage_unableToCreateCollection());
1249  }
1250 
1251  /* If we need core name:
1252  Map<String, NamedList<Integer>> status = createResponse.getCollectionCoresStatus();
1253  existingCoreName = status.keySet().iterator().next();*/
1254  if (!collectionExists(collectionName)) {
1255  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.noIndexDir.msg"));
1256  }
1257  }
1258 
1259  private void backupCollection(String collectionName, String backupName, String pathToBackupLocation) throws SolrServerException, IOException, KeywordSearchModuleException {
1260  CollectionAdminRequest.Backup backup = CollectionAdminRequest.backupCollection(collectionName, backupName)
1261  .setLocation(pathToBackupLocation);
1262 
1263  CollectionAdminResponse backupResponse = backup.process(remoteSolrServer);
1264  if (backupResponse.isSuccess()) {
1265  logger.log(Level.INFO, "Collection {0} successfully backep up.", collectionName);
1266  } else {
1267  logger.log(Level.SEVERE, "Unable to back up Solr collection {0}", collectionName); //NON-NLS
1268  throw new KeywordSearchModuleException(Bundle.Server_exceptionMessage_unableToBackupCollection());
1269  }
1270  }
1271 
1272  private void restoreCollection(String backupName, String restoreCollectionName, String pathToBackupLocation) throws SolrServerException, IOException, KeywordSearchModuleException {
1273 
1274  CollectionAdminRequest.Restore restore = CollectionAdminRequest.restoreCollection(restoreCollectionName, backupName)
1275  .setLocation(pathToBackupLocation);
1276 
1277  CollectionAdminResponse restoreResponse = restore.process(remoteSolrServer);
1278  if (restoreResponse.isSuccess()) {
1279  logger.log(Level.INFO, "Collection {0} successfully resored.", restoreCollectionName);
1280  } else {
1281  logger.log(Level.SEVERE, "Unable to restore Solr collection {0}", restoreCollectionName); //NON-NLS
1282  throw new KeywordSearchModuleException(Bundle.Server_exceptionMessage_unableToRestoreCollection());
1283  }
1284  }
1285 
1300  private boolean coreIsLoaded(String coreName) throws SolrServerException, IOException {
1301  CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, localSolrServer);
1302  return response.getCoreStatus(coreName).get("instanceDir") != null; //NON-NLS
1303  }
1304 
1317  private boolean coreIndexFolderExists(String coreName) throws SolrServerException, IOException {
1318  CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, localSolrServer);
1319  Object dataDirPath = response.getCoreStatus(coreName).get("dataDir"); //NON-NLS
1320  if (null != dataDirPath) {
1321  File indexDir = Paths.get((String) dataDirPath, "index").toFile(); //NON-NLS
1322  return indexDir.exists();
1323  } else {
1324  return false;
1325  }
1326  }
1327 
1339  public static IndexingServerProperties getMultiUserServerProperties(String caseDirectory) {
1340 
1341  // if "solrserver.txt" is present, use those connection settings
1342  Path serverFilePath = Paths.get(caseDirectory, "solrserver.txt"); //NON-NLS
1343  if (serverFilePath.toFile().exists()) {
1344  try {
1345  List<String> lines = Files.readAllLines(serverFilePath);
1346  if (lines.isEmpty()) {
1347  logger.log(Level.SEVERE, "solrserver.txt file does not contain any data"); //NON-NLS
1348  } else if (!lines.get(0).contains(",")) {
1349  logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0)); //NON-NLS
1350  } else {
1351  String[] parts = lines.get(0).split(",");
1352  if (parts.length != 2) {
1353  logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0)); //NON-NLS
1354  } else {
1355  return new IndexingServerProperties(parts[0], parts[1]);
1356  }
1357  }
1358  } catch (IOException ex) {
1359  logger.log(Level.SEVERE, "solrserver.txt file could not be read", ex); //NON-NLS
1360  }
1361  }
1362 
1363  // otherwise (or if an error occurred) determine Solr version of the current case
1364  List<Index> indexes = new ArrayList<>();
1365  try {
1366  IndexMetadata indexMetadata = new IndexMetadata(caseDirectory);
1367  indexes = indexMetadata.getIndexes();
1368  } catch (IndexMetadata.TextIndexMetadataException ex) {
1369  logger.log(Level.SEVERE, "Unable to read text index metadata file: " + caseDirectory, ex);
1370 
1371  // default to using the latest Solr version settings
1372  String host = UserPreferences.getIndexingServerHost();
1373  String port = UserPreferences.getIndexingServerPort();
1374  return new IndexingServerProperties(host, port);
1375  }
1376 
1377  // select which index to use. In practice, all cases always have only one
1378  // index but there is support for having multiple indexes.
1379  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
1380  if (indexToUse == null) {
1381  // unable to find index that can be used
1382  logger.log(Level.SEVERE, "Unable to find index that can be used for case: {0}", caseDirectory);
1383 
1384  // default to using the latest Solr version settings
1385  String host = UserPreferences.getIndexingServerHost();
1386  String port = UserPreferences.getIndexingServerPort();
1387  return new IndexingServerProperties(host, port);
1388  }
1389 
1390  // return connection info for the Solr version of the current index
1391  if (IndexFinder.getCurrentSolrVersion().equals(indexToUse.getSolrVersion())) {
1392  // Solr 8
1393  String host = UserPreferences.getIndexingServerHost();
1394  String port = UserPreferences.getIndexingServerPort();
1395  return new IndexingServerProperties(host, port);
1396  } else {
1397  // Solr 4
1398  String host = UserPreferences.getSolr4ServerHost().trim();
1399  String port = UserPreferences.getSolr4ServerPort().trim();
1400  return new IndexingServerProperties(host, port);
1401  }
1402  }
1403 
1415  public static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath) throws KeywordSearchModuleException {
1416  // Look for the solr server list file
1417  String serverListName = "solrServerList.txt"; //NON-NLS
1418  Path serverListPath = Paths.get(rootOutputDirectory.toString(), serverListName);
1419  if (serverListPath.toFile().exists()) {
1420 
1421  // Read the list of solr servers
1422  List<String> lines;
1423  try {
1424  lines = Files.readAllLines(serverListPath);
1425  } catch (IOException ex) {
1426  throw new KeywordSearchModuleException(serverListName + " could not be read", ex); //NON-NLS
1427  }
1428 
1429  // Remove any lines that don't contain a comma (these are likely just whitespace)
1430  for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) {
1431  String line = iterator.next();
1432  if (!line.contains(",")) {
1433  // Remove the current element from the iterator and the list.
1434  iterator.remove();
1435  }
1436  }
1437  if (lines.isEmpty()) {
1438  throw new KeywordSearchModuleException(serverListName + " had no valid server information"); //NON-NLS
1439  }
1440 
1441  // Choose which server to use
1442  int rnd = new Random().nextInt(lines.size());
1443  String[] parts = lines.get(rnd).split(",");
1444  if (parts.length != 2) {
1445  throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd)); //NON-NLS
1446  }
1447 
1448  // Split it up just to do a sanity check on the data
1449  String host = parts[0];
1450  String port = parts[1];
1451  if (host.isEmpty() || port.isEmpty()) {
1452  throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd)); //NON-NLS
1453  }
1454 
1455  // Write the server data to a file
1456  Path serverFile = Paths.get(caseDirectoryPath.toString(), "solrserver.txt"); //NON-NLS
1457  try {
1458  caseDirectoryPath.toFile().mkdirs();
1459  if (!caseDirectoryPath.toFile().exists()) {
1460  throw new KeywordSearchModuleException("Case directory " + caseDirectoryPath.toString() + " does not exist"); //NON-NLS
1461  }
1462  Files.write(serverFile, lines.get(rnd).getBytes());
1463  } catch (IOException ex) {
1464  throw new KeywordSearchModuleException(serverFile.toString() + " could not be written", ex); //NON-NLS
1465  }
1466  }
1467  }
1468 
1472  public static class IndexingServerProperties {
1473 
1474  private final String host;
1475  private final String port;
1476 
1477  IndexingServerProperties(String host, String port) {
1478  this.host = host;
1479  this.port = port;
1480  }
1481 
1487  public String getHost() {
1488  return host;
1489  }
1490 
1496  public String getPort() {
1497  return port;
1498  }
1499  }
1500 
1506  void commit() throws SolrServerException, NoOpenCoreException {
1507  currentCoreLock.readLock().lock();
1508  try {
1509  if (null == currentCollection) {
1510  throw new NoOpenCoreException();
1511  }
1512  currentCollection.commit();
1513  } finally {
1514  currentCoreLock.readLock().unlock();
1515  }
1516  }
1517 
1518  NamedList<Object> request(SolrRequest request) throws SolrServerException, RemoteSolrException, NoOpenCoreException {
1519  currentCoreLock.readLock().lock();
1520  try {
1521  if (null == currentCollection) {
1522  throw new NoOpenCoreException();
1523  }
1524  return currentCollection.request(request);
1525  } finally {
1526  currentCoreLock.readLock().unlock();
1527  }
1528  }
1529 
1540  public int queryNumIndexedFiles() throws KeywordSearchModuleException, NoOpenCoreException {
1541  currentCoreLock.readLock().lock();
1542  try {
1543  if (null == currentCollection) {
1544  throw new NoOpenCoreException();
1545  }
1546  try {
1547  return currentCollection.queryNumIndexedFiles();
1548  } catch (Exception ex) {
1549  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1550  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumIdxFiles.exception.msg"), ex);
1551  }
1552  } finally {
1553  currentCoreLock.readLock().unlock();
1554  }
1555  }
1556 
1566  public int queryNumIndexedChunks() throws KeywordSearchModuleException, NoOpenCoreException {
1567  currentCoreLock.readLock().lock();
1568  try {
1569  if (null == currentCollection) {
1570  throw new NoOpenCoreException();
1571  }
1572  try {
1573  return currentCollection.queryNumIndexedChunks();
1574  } catch (Exception ex) {
1575  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1576  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumIdxChunks.exception.msg"), ex);
1577  }
1578  } finally {
1579  currentCoreLock.readLock().unlock();
1580  }
1581  }
1582 
1592  public int queryNumIndexedDocuments() throws KeywordSearchModuleException, NoOpenCoreException {
1593  currentCoreLock.readLock().lock();
1594  try {
1595  if (null == currentCollection) {
1596  throw new NoOpenCoreException();
1597  }
1598  try {
1599  return currentCollection.queryNumIndexedDocuments();
1600  } catch (Exception ex) {
1601  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1602  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumIdxDocs.exception.msg"), ex);
1603  }
1604  } finally {
1605  currentCoreLock.readLock().unlock();
1606  }
1607  }
1608 
1619  public boolean queryIsIndexed(long contentID) throws KeywordSearchModuleException, NoOpenCoreException {
1620  currentCoreLock.readLock().lock();
1621  try {
1622  if (null == currentCollection) {
1623  throw new NoOpenCoreException();
1624  }
1625  try {
1626  return currentCollection.queryIsIndexed(contentID);
1627  } catch (Exception ex) {
1628  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1629  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryIsIdxd.exception.msg"), ex);
1630  }
1631 
1632  } finally {
1633  currentCoreLock.readLock().unlock();
1634  }
1635  }
1636 
1648  public int queryNumFileChunks(long fileID) throws KeywordSearchModuleException, NoOpenCoreException {
1649  currentCoreLock.readLock().lock();
1650  try {
1651  if (null == currentCollection) {
1652  throw new NoOpenCoreException();
1653  }
1654  try {
1655  return currentCollection.queryNumFileChunks(fileID);
1656  } catch (Exception ex) {
1657  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1658  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryNumFileChunks.exception.msg"), ex);
1659  }
1660  } finally {
1661  currentCoreLock.readLock().unlock();
1662  }
1663  }
1664 
1675  public QueryResponse query(SolrQuery sq) throws KeywordSearchModuleException, NoOpenCoreException, IOException {
1676  currentCoreLock.readLock().lock();
1677  try {
1678  if (null == currentCollection) {
1679  throw new NoOpenCoreException();
1680  }
1681  try {
1682  return currentCollection.query(sq);
1683  } catch (Exception ex) {
1684  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1685  logger.log(Level.SEVERE, "Solr query failed: " + sq.getQuery(), ex); //NON-NLS
1686  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.query.exception.msg", sq.getQuery()), ex);
1687  }
1688  } finally {
1689  currentCoreLock.readLock().unlock();
1690  }
1691  }
1692 
1704  public QueryResponse query(SolrQuery sq, SolrRequest.METHOD method) throws KeywordSearchModuleException, NoOpenCoreException {
1705  currentCoreLock.readLock().lock();
1706  try {
1707  if (null == currentCollection) {
1708  throw new NoOpenCoreException();
1709  }
1710  try {
1711  return currentCollection.query(sq, method);
1712  } catch (Exception ex) {
1713  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1714  logger.log(Level.SEVERE, "Solr query failed: " + sq.getQuery(), ex); //NON-NLS
1715  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.query2.exception.msg", sq.getQuery()), ex);
1716  }
1717  } finally {
1718  currentCoreLock.readLock().unlock();
1719  }
1720  }
1721 
1732  public TermsResponse queryTerms(SolrQuery sq) throws KeywordSearchModuleException, NoOpenCoreException {
1733  currentCoreLock.readLock().lock();
1734  try {
1735  if (null == currentCollection) {
1736  throw new NoOpenCoreException();
1737  }
1738  try {
1739  return currentCollection.queryTerms(sq);
1740  } catch (Exception ex) {
1741  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1742  logger.log(Level.SEVERE, "Solr terms query failed: " + sq.getQuery(), ex); //NON-NLS
1743  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.queryTerms.exception.msg", sq.getQuery()), ex);
1744  }
1745  } finally {
1746  currentCoreLock.readLock().unlock();
1747  }
1748  }
1749 
1757  void deleteDataSource(Long dataSourceId) throws IOException, KeywordSearchModuleException, NoOpenCoreException, SolrServerException {
1758  try {
1759  currentCoreLock.writeLock().lock();
1760  if (null == currentCollection) {
1761  throw new NoOpenCoreException();
1762  }
1763  currentCollection.deleteDataSource(dataSourceId);
1764  currentCollection.commit();
1765  } finally {
1766  currentCoreLock.writeLock().unlock();
1767  }
1768  }
1769 
1779  public String getSolrContent(final Content content) throws NoOpenCoreException {
1780  currentCoreLock.readLock().lock();
1781  try {
1782  if (null == currentCollection) {
1783  throw new NoOpenCoreException();
1784  }
1785  return currentCollection.getSolrContent(content.getId(), 0);
1786  } finally {
1787  currentCoreLock.readLock().unlock();
1788  }
1789  }
1790 
1803  public String getSolrContent(final Content content, int chunkID) throws NoOpenCoreException {
1804  currentCoreLock.readLock().lock();
1805  try {
1806  if (null == currentCollection) {
1807  throw new NoOpenCoreException();
1808  }
1809  return currentCollection.getSolrContent(content.getId(), chunkID);
1810  } finally {
1811  currentCoreLock.readLock().unlock();
1812  }
1813  }
1814 
1824  public String getSolrContent(final long objectID) throws NoOpenCoreException {
1825  currentCoreLock.readLock().lock();
1826  try {
1827  if (null == currentCollection) {
1828  throw new NoOpenCoreException();
1829  }
1830  return currentCollection.getSolrContent(objectID, 0);
1831  } finally {
1832  currentCoreLock.readLock().unlock();
1833  }
1834  }
1835 
1846  public String getSolrContent(final long objectID, final int chunkID) throws NoOpenCoreException {
1847  currentCoreLock.readLock().lock();
1848  try {
1849  if (null == currentCollection) {
1850  throw new NoOpenCoreException();
1851  }
1852  return currentCollection.getSolrContent(objectID, chunkID);
1853  } finally {
1854  currentCoreLock.readLock().unlock();
1855  }
1856  }
1857 
1867  public static String getChunkIdString(long parentID, int childID) {
1868  return Long.toString(parentID) + Server.CHUNK_ID_SEPARATOR + Integer.toString(childID);
1869  }
1870 
1877  private void connectToEmbeddedSolrServer() throws SolrServerException, IOException {
1878  HttpSolrClient solrServer = getSolrClient("http://localhost:" + localSolrServerPort + "/solr");
1879  TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check");
1880  CoreAdminRequest.getStatus(null, solrServer);
1882  }
1883 
1884 
1885  void connectToSolrServer(String host, String port) throws SolrServerException, IOException {
1886  try (HttpSolrClient solrServer = getSolrClient("http://" + host + ":" + port + "/solr")) {
1887  connectToSolrServer(solrServer);
1888  }
1889  }
1890 
1900  private void connectToSolrServer(HttpSolrClient solrServer) throws SolrServerException, IOException {
1901  TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check");
1902  CollectionAdminRequest.ClusterStatus statusRequest = CollectionAdminRequest.getClusterStatus();
1903  CollectionAdminResponse statusResponse = statusRequest.process(solrServer);
1904  int statusCode = Integer.valueOf(((NamedList) statusResponse.getResponse().get("responseHeader")).get("status").toString());
1905  if (statusCode != 0) {
1906  logger.log(Level.WARNING, "Could not connect to Solr server "); //NON-NLS
1907  } else {
1908  logger.log(Level.INFO, "Connected to Solr server "); //NON-NLS
1909  }
1911  }
1912 
1913  private List<String> getSolrServerList(String host, String port) throws KeywordSearchModuleException {
1914  HttpSolrClient solrServer = getSolrClient("http://" + host + ":" + port + "/solr");
1915  return getSolrServerList(solrServer);
1916  }
1917 
1918  private List<String> getSolrServerList(HttpSolrClient solrServer) throws KeywordSearchModuleException {
1919 
1920  try {
1921  CollectionAdminRequest.ClusterStatus statusRequest = CollectionAdminRequest.getClusterStatus();
1922  CollectionAdminResponse statusResponse;
1923  try {
1924  statusResponse = statusRequest.process(solrServer);
1925  } catch (RemoteSolrException ex) {
1926  // collection doesn't exist
1927  return Collections.emptyList();
1928  }
1929 
1930  if (statusResponse == null) {
1931  return Collections.emptyList();
1932  }
1933 
1934  NamedList error = (NamedList) statusResponse.getResponse().get("error");
1935  if (error != null) {
1936  return Collections.emptyList();
1937  }
1938 
1939  NamedList cluster = (NamedList) statusResponse.getResponse().get("cluster");
1940  ArrayList<String> liveNodes = (ArrayList) cluster.get("live_nodes");
1941  return liveNodes;
1942  } catch (Exception ex) {
1943  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
1944  throw new KeywordSearchModuleException(
1945  NbBundle.getMessage(this.getClass(), "Server.serverList.exception.msg", solrServer.getBaseURL()));
1946  }
1947  }
1948 
1949  /* ELTODO leaving this for reference, will delete later
1950  private boolean clusterStatusWithCollection(String collectionName) throws IOException, SolrServerException {
1951  ModifiableSolrParams params = new ModifiableSolrParams();
1952  params.set("action", CollectionParams.CollectionAction.CLUSTERSTATUS.toString());
1953  params.set("collection", collectionName);
1954  SolrRequest request = new QueryRequest(params);
1955  request.setPath("/admin/collections");
1956 
1957  NamedList<Object> statusResponse;
1958  try {
1959  statusResponse = currentSolrServer.request(request);
1960  } catch (RemoteSolrException ex) {
1961  // collection doesn't exist
1962  return false;
1963  }
1964 
1965  if (statusResponse == null) {
1966  logger.log(Level.SEVERE, "Collections response should not be null"); //NON-NLS
1967  return false;
1968  }
1969 
1970  NamedList<Object> cluster = (NamedList<Object>) statusResponse.get("cluster");
1971  if (cluster == null) {
1972  logger.log(Level.SEVERE, "Cluster should not be null"); //NON-NLS
1973  return false;
1974  }
1975  NamedList<Object> collections = (NamedList<Object>) cluster.get("collections");
1976  if (cluster == null) {
1977  logger.log(Level.SEVERE, "Collections should not be null in cluster state"); //NON-NLS
1978  return false;
1979  }
1980  if (collections.size() == 0) {
1981  logger.log(Level.SEVERE, "Collections should not be empty in cluster state"); //NON-NLS
1982  return false;
1983  }
1984 
1985  Object collection = collections.get(collectionName);
1986  return (collection != null);
1987  }*/
1988 
1989  class Collection {
1990 
1991  // handle to the collection in Solr
1992  private final String name;
1993 
1994  private final CaseType caseType;
1995 
1996  private final Index textIndex;
1997 
1998  // We use different Solr clients for different operations. HttpSolrClient is geared towards query performance.
1999  // ConcurrentUpdateSolrClient is geared towards batching solr documents for better indexing throughput. We
2000  // have implemented our own batching algorithm so we will probably not use ConcurrentUpdateSolrClient.
2001  // CloudSolrClient is geared towards SolrCloud deployments. These are only good for collection-specific operations.
2002  private HttpSolrClient queryClient;
2003  private SolrClient indexingClient;
2004 
2005  private final int maxBufferSize;
2006  private final List<SolrInputDocument> buffer;
2007  private final Object bufferLock;
2008 
2009  private final ScheduledThreadPoolExecutor periodicTasksExecutor;
2010  private static final long PERIODIC_BATCH_SEND_INTERVAL_MINUTES = 10;
2011  private static final int NUM_BATCH_UPDATE_RETRIES = 10;
2012  private static final long SLEEP_BETWEEN_RETRIES_MS = 10000; // 10 seconds
2013 
2014  private Collection(String name, Case theCase, Index index) throws TimeoutException, InterruptedException, KeywordSearchModuleException {
2015  this.name = name;
2016  this.caseType = theCase.getCaseType();
2017  this.textIndex = index;
2018  bufferLock = new Object();
2019 
2020  if (caseType == CaseType.SINGLE_USER_CASE) {
2021  // get SolrJ client
2022  queryClient = getSolrClient("http://localhost:" + localSolrServerPort + "/solr/" + name); // HttpClient
2023  indexingClient = getSolrClient("http://localhost:" + localSolrServerPort + "/solr/" + name); // HttpClient
2024  } else {
2025  // read Solr connection info from user preferences, unless "solrserver.txt" is present
2026  queryClient = configureMultiUserConnection(theCase, index, name);
2027 
2028  // for MU cases, use CloudSolrClient for indexing. Indexing is only supported for Solr 8.
2029  if (IndexFinder.getCurrentSolrVersion().equals(index.getSolrVersion())) {
2030  IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory());
2031  indexingClient = getCloudSolrClient(properties.getHost(), properties.getPort(), name); // CloudClient
2032  } else {
2033  indexingClient = configureMultiUserConnection(theCase, index, name); // HttpClient
2034  }
2035  }
2036 
2037  // document batching
2038  maxBufferSize = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getDocumentsQueueSize();
2039  logger.log(Level.INFO, "Using Solr document queue size = {0}", maxBufferSize); //NON-NLS
2040  buffer = new ArrayList<>(maxBufferSize);
2041  periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("periodic-batched-document-task-%d").build()); //NON-NLS
2042  periodicTasksExecutor.scheduleWithFixedDelay(new SendBatchedDocumentsTask(), PERIODIC_BATCH_SEND_INTERVAL_MINUTES, PERIODIC_BATCH_SEND_INTERVAL_MINUTES, TimeUnit.MINUTES);
2043  }
2044 
2051  private final class SendBatchedDocumentsTask implements Runnable {
2052 
2053  @Override
2054  public void run() {
2055  List<SolrInputDocument> clone;
2056  synchronized (bufferLock) {
2057 
2058  if (buffer.isEmpty()) {
2059  return;
2060  }
2061 
2062  // Buffer is full. Make a clone and release the lock, so that we don't
2063  // hold other ingest threads
2064  clone = buffer.stream().collect(toList());
2065  buffer.clear();
2066  }
2067 
2068  try {
2069  // send the cloned list to Solr
2070  sendBufferedDocs(clone);
2071  } catch (KeywordSearchModuleException ex) {
2072  logger.log(Level.SEVERE, "Periodic batched document update failed", ex); //NON-NLS
2073  }
2074  }
2075  }
2076 
2082  String getName() {
2083  return name;
2084  }
2085 
2086  private Index getIndexInfo() {
2087  return this.textIndex;
2088  }
2089 
2090  private QueryResponse query(SolrQuery sq) throws SolrServerException, IOException {
2091  return queryClient.query(sq);
2092  }
2093 
2094  private NamedList<Object> request(SolrRequest request) throws SolrServerException, RemoteSolrException {
2095  try {
2096  return queryClient.request(request);
2097  } catch (Exception e) {
2098  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
2099  logger.log(Level.WARNING, "Could not issue Solr request. ", e); //NON-NLS
2100  throw new SolrServerException(
2101  NbBundle.getMessage(this.getClass(), "Server.request.exception.exception.msg"), e);
2102  }
2103 
2104  }
2105 
2106  private QueryResponse query(SolrQuery sq, SolrRequest.METHOD method) throws SolrServerException, IOException {
2107  return queryClient.query(sq, method);
2108  }
2109 
2110  private TermsResponse queryTerms(SolrQuery sq) throws SolrServerException, IOException {
2111  QueryResponse qres = queryClient.query(sq);
2112  return qres.getTermsResponse();
2113  }
2114 
2115  private void commit() throws SolrServerException {
2116  List<SolrInputDocument> clone;
2117  synchronized (bufferLock) {
2118  // Make a clone and release the lock, so that we don't
2119  // hold other ingest threads
2120  clone = buffer.stream().collect(toList());
2121  buffer.clear();
2122  }
2123 
2124  try {
2125  sendBufferedDocs(clone);
2126  } catch (KeywordSearchModuleException ex) {
2127  throw new SolrServerException(NbBundle.getMessage(this.getClass(), "Server.commit.exception.msg"), ex);
2128  }
2129 
2130  try {
2131  //commit and block
2132  indexingClient.commit(true, true);
2133  } catch (Exception e) {
2134  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
2135  logger.log(Level.WARNING, "Could not commit index. ", e); //NON-NLS
2136  throw new SolrServerException(NbBundle.getMessage(this.getClass(), "Server.commit.exception.msg"), e);
2137  }
2138  }
2139 
2140  private void deleteDataSource(Long dsObjId) throws IOException, SolrServerException {
2141  String dataSourceId = Long.toString(dsObjId);
2142  String deleteQuery = "image_id:" + dataSourceId;
2143 
2144  queryClient.deleteByQuery(deleteQuery);
2145  }
2146 
2155  void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException {
2156 
2157  List<SolrInputDocument> clone;
2158  synchronized (bufferLock) {
2159  buffer.add(doc);
2160  // buffer documents if the buffer is not full
2161  if (buffer.size() < maxBufferSize) {
2162  return;
2163  }
2164 
2165  // Buffer is full. Make a clone and release the lock, so that we don't
2166  // hold other ingest threads
2167  clone = buffer.stream().collect(toList());
2168  buffer.clear();
2169  }
2170 
2171  // send the cloned list to Solr
2172  sendBufferedDocs(clone);
2173  }
2174 
2182  // ELTODO DECIDE ON SYNCHRONIZATION
2183  private void sendBufferedDocs(List<SolrInputDocument> docBuffer) throws KeywordSearchModuleException {
2184 
2185  if (docBuffer.isEmpty()) {
2186  return;
2187  }
2188 
2189  try {
2190  boolean success = true;
2191  for (int reTryAttempt = 0; reTryAttempt < NUM_BATCH_UPDATE_RETRIES; reTryAttempt++) {
2192  try {
2193  success = true;
2194  indexingClient.add(docBuffer);
2195  } catch (Exception ex) {
2196  success = false;
2197  if (reTryAttempt < NUM_BATCH_UPDATE_RETRIES - 1) {
2198  logger.log(Level.WARNING, "Unable to send document batch to Solr. Re-trying...", ex); //NON-NLS
2199  try {
2200  Thread.sleep(SLEEP_BETWEEN_RETRIES_MS);
2201  } catch (InterruptedException ignore) {
2202  throw new KeywordSearchModuleException(
2203  NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg"), ex); //NON-NLS
2204  }
2205  }
2206  }
2207  if (success) {
2208  if (reTryAttempt > 0) {
2209  logger.log(Level.INFO, "Batch update suceeded after {0} re-try", reTryAttempt); //NON-NLS
2210  }
2211  return;
2212  }
2213  }
2214  // if we are here, it means all re-try attempts failed
2215  logger.log(Level.SEVERE, "Unable to send document batch to Solr. All re-try attempts failed!"); //NON-NLS
2216  throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg")); //NON-NLS
2217  } catch (Exception ex) {
2218  // Solr throws a lot of unexpected exception types
2219  logger.log(Level.SEVERE, "Could not add batched documents to index", ex); //NON-NLS
2220  throw new KeywordSearchModuleException(
2221  NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg"), ex); //NON-NLS
2222  } finally {
2223  docBuffer.clear();
2224  }
2225  }
2226 
2237  private String getSolrContent(long contentID, int chunkID) {
2238  final SolrQuery q = new SolrQuery();
2239  q.setQuery("*:*");
2240  String filterQuery = Schema.ID.toString() + ":" + KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
2241  if (chunkID != 0) {
2242  filterQuery = filterQuery + Server.CHUNK_ID_SEPARATOR + chunkID;
2243  }
2244  q.addFilterQuery(filterQuery);
2245  q.setFields(Schema.TEXT.toString());
2246  try {
2247  // Get the first result.
2248  SolrDocumentList solrDocuments = queryClient.query(q).getResults();
2249 
2250  if (!solrDocuments.isEmpty()) {
2251  SolrDocument solrDocument = solrDocuments.get(0);
2252  if (solrDocument != null) {
2253  java.util.Collection<Object> fieldValues = solrDocument.getFieldValues(Schema.TEXT.toString());
2254  if (fieldValues.size() == 1) // The indexed text field for artifacts will only have a single value.
2255  {
2256  return fieldValues.toArray(new String[0])[0];
2257  } else // The indexed text for files has 2 values, the file name and the file content.
2258  // We return the file content value.
2259  {
2260  return fieldValues.toArray(new String[0])[1];
2261  }
2262  }
2263  }
2264  } catch (Exception ex) {
2265  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
2266  logger.log(Level.SEVERE, "Error getting content from Solr. Solr document id " + contentID + ", chunk id " + chunkID + ", query: " + filterQuery, ex); //NON-NLS
2267  return null;
2268  }
2269 
2270  return null;
2271  }
2272 
2273  synchronized void close() throws KeywordSearchModuleException {
2274  try {
2275 
2276  // stop the periodic batch update task. If the task is already running,
2277  // allow it to finish.
2278  ThreadUtils.shutDownTaskExecutor(periodicTasksExecutor);
2279 
2280  // We only unload cores for "single-user" cases.
2281  if (this.caseType == CaseType.MULTI_USER_CASE) {
2282  return;
2283  }
2284 
2285  CoreAdminRequest.unloadCore(this.name, localSolrServer);
2286  } catch (Exception ex) {
2287  // intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
2288  throw new KeywordSearchModuleException(
2289  NbBundle.getMessage(this.getClass(), "Server.close.exception.msg"), ex);
2290  } finally {
2291  try {
2292  queryClient.close();
2293  queryClient = null;
2294  indexingClient.close();
2295  indexingClient = null;
2296  } catch (IOException ex) {
2297  throw new KeywordSearchModuleException(
2298  NbBundle.getMessage(this.getClass(), "Server.close.exception.msg2"), ex);
2299  }
2300  }
2301  }
2302 
2312  private int queryNumIndexedFiles() throws SolrServerException, IOException {
2314  }
2315 
2325  private int queryNumIndexedChunks() throws SolrServerException, IOException {
2326  SolrQuery q = new SolrQuery(Server.Schema.ID + ":*" + Server.CHUNK_ID_SEPARATOR + "*");
2327  q.setRows(0);
2328  int numChunks = (int) query(q).getResults().getNumFound();
2329  return numChunks;
2330  }
2331 
2342  private int queryNumIndexedDocuments() throws SolrServerException, IOException {
2343  SolrQuery q = new SolrQuery("*:*");
2344  q.setRows(0);
2345  return (int) query(q).getResults().getNumFound();
2346  }
2347 
2357  private boolean queryIsIndexed(long contentID) throws SolrServerException, IOException {
2358  String id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
2359  SolrQuery q = new SolrQuery("*:*");
2360  q.addFilterQuery(Server.Schema.ID.toString() + ":" + id);
2361  //q.setFields(Server.Schema.ID.toString());
2362  q.setRows(0);
2363  return (int) query(q).getResults().getNumFound() != 0;
2364  }
2365 
2377  private int queryNumFileChunks(long contentID) throws SolrServerException, IOException {
2378  String id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
2379  final SolrQuery q
2380  = new SolrQuery(Server.Schema.ID + ":" + id + Server.CHUNK_ID_SEPARATOR + "*");
2381  q.setRows(0);
2382  return (int) query(q).getResults().getNumFound();
2383  }
2384  }
2385 
2386  class ServerAction extends AbstractAction {
2387 
2388  private static final long serialVersionUID = 1L;
2389 
2390  @Override
2391  public void actionPerformed(ActionEvent e) {
2392  logger.log(Level.INFO, e.paramString().trim());
2393  }
2394  }
2395 
2399  class SolrServerNoPortException extends SocketException {
2400 
2401  private static final long serialVersionUID = 1L;
2402 
2406  private final int port;
2407 
2408  SolrServerNoPortException(int port) {
2409  super(NbBundle.getMessage(Server.class, "Server.solrServerNoPortException.msg", port,
2410  Server.PROPERTIES_CURRENT_SERVER_PORT));
2411  this.port = port;
2412  }
2413 
2414  int getPortNumber() {
2415  return port;
2416  }
2417  }
2418 }
static synchronized String getConfigSetting(String moduleName, String settingName)
String getSolrContent(final long objectID)
Definition: Server.java:1824
final ReentrantReadWriteLock currentCoreLock
Definition: Server.java:261
ConcurrentUpdateSolrClient getConcurrentClient(String solrUrl)
Definition: Server.java:352
static IndexingServerProperties getMultiUserServerProperties(String caseDirectory)
Definition: Server.java:1339
Collection openCore(Case theCase, Index index)
Definition: Server.java:1070
void connectToSolrServer(HttpSolrClient solrServer)
Definition: Server.java:1900
boolean coreIsLoaded(String coreName)
Definition: Server.java:1300
Process runLocalSolr8ControlCommand(List< String > solrArguments)
Definition: Server.java:482
List< String > getSolrServerList(String host, String port)
Definition: Server.java:1913
static final int NUM_COLLECTION_CREATION_RETRIES
Definition: Server.java:241
void backupCollection(String collectionName, String backupName, String pathToBackupLocation)
Definition: Server.java:1259
void configureSolrConnection(Case theCase, Index index)
Definition: Server.java:600
CloudSolrClient getCloudSolrClient(String host, String port, String defaultCollectionName)
Definition: Server.java:369
boolean collectionExists(String collectionName)
Definition: Server.java:1184
static TimingMetric getTimingMetric(String name)
static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath)
Definition: Server.java:1415
void addServerActionListener(PropertyChangeListener l)
Definition: Server.java:397
static synchronized boolean settingExists(String moduleName, String settingName)
static final String HL_ANALYZE_CHARS_UNLIMITED
Definition: Server.java:217
String getSolrContent(final Content content)
Definition: Server.java:1779
boolean coreIndexFolderExists(String coreName)
Definition: Server.java:1317
void restoreCollection(String backupName, String restoreCollectionName, String pathToBackupLocation)
Definition: Server.java:1272
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
HttpSolrClient configureMultiUserConnection(Case theCase, Index index, String name)
Definition: Server.java:643
static final Charset DEFAULT_INDEXED_TEXT_CHARSET
default Charset to index text as
Definition: Server.java:226
InputStreamPrinterThread errorRedirectThread
Definition: Server.java:264
Process runLocalSolr4ControlCommand(List< String > solrArguments)
Definition: Server.java:532
List< String > getSolrServerList(HttpSolrClient solrServer)
Definition: Server.java:1918
QueryResponse query(SolrQuery sq)
Definition: Server.java:1675
static synchronized String getJavaPath()
static void submitTimingMetric(TimingMetric metric)
TermsResponse queryTerms(SolrQuery sq)
Definition: Server.java:1732
boolean queryIsIndexed(long contentID)
Definition: Server.java:1619
void createMultiUserCollection(String collectionName, int numShardsToUse)
Definition: Server.java:1229
String getSolrContent(final Content content, int chunkID)
Definition: Server.java:1803
String getSolrContent(final long objectID, final int chunkID)
Definition: Server.java:1846
HttpSolrClient getSolrClient(String solrUrl)
Definition: Server.java:343
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static synchronized long[] getJavaPIDs(String sigarSubQuery)
static String getChunkIdString(long parentID, int childID)
Definition: Server.java:1867
static boolean deleteDir(File dirPath)
Definition: FileUtil.java:47
QueryResponse query(SolrQuery sq, SolrRequest.METHOD method)
Definition: Server.java:1704

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