118 public String toString() {
124 public String toString() {
131 public String toString() {
138 public String toString() {
139 return "content_str";
145 public String toString() {
153 public String toString() {
159 public String toString() {
165 public String toString() {
171 public String toString() {
178 public String toString() {
185 public String toString() {
192 public String toString() {
199 public String toString() {
205 public String toString() {
211 public String toString() {
222 public String toString() {
239 static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
240 static final String PROPERTIES_CURRENT_SERVER_PORT =
"IndexingServerPort";
241 static final String PROPERTIES_CURRENT_STOP_PORT =
"IndexingServerStopPort";
242 private static final String
KEY =
"jjk#09s";
243 static final String DEFAULT_SOLR_SERVER_HOST =
"localhost";
244 static final int DEFAULT_SOLR_SERVER_PORT = 23232;
245 static final int DEFAULT_SOLR_STOP_PORT = 34343;
249 private static final String
SOLR =
"solr";
251 private static final boolean DEBUG =
false;
289 File solr8Folder = InstalledFileLocator.getDefault().locate(
"solr", Server.class.getPackage().getName(),
false);
290 File solr4Folder = InstalledFileLocator.getDefault().locate(
"solr4", Server.class.getPackage().getName(),
false);
301 if (!solr8Home.toFile().exists()) {
302 Files.createDirectory(solr8Home);
307 Files.copy(Paths.get(solr8Folder.getAbsolutePath(),
"server",
"solr",
"solr.xml"), solr8Home.resolve(
"solr.xml"), REPLACE_EXISTING);
308 Files.copy(Paths.get(solr8Folder.getAbsolutePath(),
"server",
"solr",
"zoo.cfg"), solr8Home.resolve(
"zoo.cfg"), REPLACE_EXISTING);
309 FileUtils.copyDirectory(Paths.get(solr8Folder.getAbsolutePath(),
"server",
"solr",
"configsets").toFile(), solr8Home.resolve(
"configsets").toFile());
310 }
catch (IOException ex) {
311 logger.log(Level.SEVERE,
"Failed to create Solr 8 home folder:", ex);
317 if (!solr4Home.toFile().exists()) {
318 Files.createDirectory(solr4Home);
320 Files.copy(Paths.get(solr4Folder.getAbsolutePath(),
"solr",
"solr.xml"), solr4Home.resolve(
"solr.xml"), REPLACE_EXISTING);
321 Files.copy(Paths.get(solr4Folder.getAbsolutePath(),
"solr",
"zoo.cfg"), solr4Home.resolve(
"zoo.cfg"), REPLACE_EXISTING);
322 }
catch (IOException ex) {
323 logger.log(Level.SEVERE,
"Failed to create Solr 4 home folder:", ex);
328 logger.log(Level.INFO,
"Created Server instance using Java at {0}",
javaPath);
336 }
catch (NumberFormatException nfe) {
337 logger.log(Level.WARNING,
"Could not decode indexing server port, value was not a valid port number, using the default. ", nfe);
348 }
catch (NumberFormatException nfe) {
349 logger.log(Level.WARNING,
"Could not decode indexing server stop port, value was not a valid port number, using default", nfe);
359 int connectionTimeoutMs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getConnectionTimeout();
360 return new HttpSolrClient.Builder(solrUrl)
361 .withSocketTimeout(connectionTimeoutMs)
362 .withConnectionTimeout(connectionTimeoutMs)
367 int numThreads = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getNumThreads();
368 int numDocs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getDocumentsQueueSize();
369 int connectionTimeoutMs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getConnectionTimeout();
370 logger.log(Level.INFO,
"Creating new ConcurrentUpdateSolrClient: {0}", solrUrl);
371 logger.log(Level.INFO,
"Queue size = {0}, Number of threads = {1}, Connection Timeout (ms) = {2}",
new Object[]{numDocs, numThreads, connectionTimeoutMs});
372 ConcurrentUpdateSolrClient client =
new ConcurrentUpdateSolrClient.Builder(solrUrl)
373 .withQueueSize(numDocs)
374 .withThreadCount(numThreads)
375 .withSocketTimeout(connectionTimeoutMs)
376 .withConnectionTimeout(connectionTimeoutMs)
384 List<String> solrUrls =
new ArrayList<>();
385 for (String server : solrServerList) {
386 solrUrls.add(
"http://" + server +
"/solr");
387 logger.log(Level.INFO,
"Using Solr server: {0}", server);
390 logger.log(Level.INFO,
"Creating new CloudSolrClient");
391 int connectionTimeoutMs = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getConnectionTimeout();
392 CloudSolrClient client =
new CloudSolrClient.Builder(solrUrls)
393 .withConnectionTimeout(connectionTimeoutMs)
394 .withSocketTimeout(connectionTimeoutMs)
396 if (!defaultCollectionName.isEmpty()) {
397 client.setDefaultCollection(defaultCollectionName);
404 public void finalize() throws java.lang.Throwable {
413 int getLocalSolrServerPort() {
417 int getLocalSolrStopPort() {
424 private static class InputStreamPrinterThread
extends Thread {
428 volatile boolean doRun =
true;
430 InputStreamPrinterThread(InputStream stream, String type) {
431 this.stream = stream;
433 final String log = Places.getUserDirectory().getAbsolutePath()
434 + File.separator +
"var" + File.separator +
"log"
435 + File.separator +
"solr.log." + type;
436 File outputFile =
new File(log.concat(
".0"));
437 File first =
new File(log.concat(
".1"));
438 File second =
new File(log.concat(
".2"));
439 if (second.exists()) {
442 if (first.exists()) {
443 first.renameTo(second);
445 if (outputFile.exists()) {
446 outputFile.renameTo(first);
448 outputFile.createNewFile();
450 out =
new FileOutputStream(outputFile);
452 }
catch (Exception ex) {
453 logger.log(Level.WARNING,
"Failed to create solr log file", ex);
464 try (InputStreamReader isr =
new InputStreamReader(stream);
465 BufferedReader br =
new BufferedReader(isr);
467 BufferedWriter bw =
new BufferedWriter(osw);) {
470 while (doRun && (line = br.readLine()) !=
null) {
479 }
catch (IOException ex) {
480 logger.log(Level.SEVERE,
"Error redirecting Solr output stream", ex);
498 File solr8Folder = InstalledFileLocator.getDefault().locate(
"solr", Server.class.getPackage().getName(),
false);
501 solr8CmdPath = Paths.get(solr8Folder.getAbsolutePath(),
"bin",
"autopsy-solr.cmd");
503 solr8CmdPath = Paths.get(solr8Folder.getAbsolutePath(),
"bin",
"autopsy-solr");
507 List<String> commandLine =
new ArrayList<>();
508 commandLine.add(solr8CmdPath.toString());
509 commandLine.addAll(solrArguments);
511 ProcessBuilder solrProcessBuilder =
new ProcessBuilder(commandLine);
512 solrProcessBuilder.directory(solr8Folder);
515 Path solrStdoutPath = Paths.get(Places.getUserDirectory().getAbsolutePath(),
"var",
"log",
"solr.log.stdout");
516 solrProcessBuilder.redirectOutput(solrStdoutPath.toFile());
518 Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(),
"var",
"log",
"solr.log.stderr");
519 solrProcessBuilder.redirectError(solrStderrPath.toFile());
522 String jreFolderPath =
new File(
javaPath).getParentFile().getParentFile().getAbsolutePath();
524 solrProcessBuilder.environment().put(
"SOLR_JAVA_HOME", jreFolderPath);
525 solrProcessBuilder.environment().put(
"SOLR_HOME", solr8Home.toString());
526 solrProcessBuilder.environment().put(
"STOP_KEY",
KEY);
527 solrProcessBuilder.environment().put(
"SOLR_JAVA_MEM", MAX_SOLR_MEM_MB_PAR);
528 logger.log(Level.INFO,
"Setting Solr 8 directory: {0}", solr8Folder.toString());
529 logger.log(Level.INFO,
"Running Solr 8 command: {0} from {1}",
new Object[]{solrProcessBuilder.command(), solr8Folder.toString()});
530 Process process = solrProcessBuilder.start();
531 logger.log(Level.INFO,
"Finished running Solr 8 command");
546 File solr4Folder = InstalledFileLocator.getDefault().locate(
"solr4", Server.class.getPackage().getName(),
false);
548 List<String> commandLine =
new ArrayList<>();
550 commandLine.add(MAX_SOLR_MEM_MB_PAR);
553 commandLine.add(
"-DSTOP.KEY=" +
KEY);
554 commandLine.add(
"-jar");
555 commandLine.add(
"start.jar");
557 commandLine.addAll(solrArguments);
559 ProcessBuilder solrProcessBuilder =
new ProcessBuilder(commandLine);
560 solrProcessBuilder.directory(solr4Folder);
563 Path solrStdoutPath = Paths.get(Places.getUserDirectory().getAbsolutePath(),
"var",
"log",
"solr.log.stdout");
564 solrProcessBuilder.redirectOutput(solrStdoutPath.toFile());
566 Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(),
"var",
"log",
"solr.log.stderr");
567 solrProcessBuilder.redirectError(solrStderrPath.toFile());
569 logger.log(Level.INFO,
"Running Solr 4 command: {0}", solrProcessBuilder.command());
570 Process process = solrProcessBuilder.start();
571 logger.log(Level.INFO,
"Finished running Solr 4 command");
580 List<Long> getSolrPIDs() {
581 List<Long> pids =
new ArrayList<>();
584 final String pidsQuery =
"-DSTOP.KEY=" +
KEY +
"%start.jar";
587 if (pidsArr !=
null) {
588 for (
int i = 0; i < pidsArr.length; ++i) {
589 pids.add(pidsArr[i]);
601 List<Long> solrPids = getSolrPIDs();
602 for (
long pid : solrPids) {
603 logger.log(Level.INFO,
"Trying to kill old Solr process, PID: {0}", pid);
604 PlatformUtil.killProcess(pid);
608 void start() throws KeywordSearchModuleException, SolrServerNoPortException, SolrServerException {
613 "# {0} - indexVersion",
614 "Server_configureSolrConnection_illegalSolrVersion=The solr version in the case: {0}, is not supported."
622 if (!IndexFinder.getCurrentSolrVersion().equals(index.getSolrVersion())) {
629 if (!this.isLocalSolrRunning()) {
630 logger.log(Level.SEVERE,
"Local Solr server is not running");
640 }
catch (SolrServerException | IOException ex) {
641 throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class,
"Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
663 if (properties.
host.isEmpty() || properties.
port.isEmpty()) {
664 throw new KeywordSearchModuleException(NbBundle.getMessage(
this.getClass(),
"Server.connectionInfoMissing.exception.msg", index.getSolrVersion()));
666 String solrUrl =
"http://" + properties.host +
":" + properties.port +
"/solr";
668 if (!name.isEmpty()) {
669 solrUrl = solrUrl +
"/" + name;
682 "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.",})
683 synchronized void startLocalSolr(SOLR_VERSION version)
throws KeywordSearchModuleException, SolrServerNoPortException, SolrServerException {
685 logger.log(Level.INFO,
"Starting local Solr " + version +
" server");
686 if (version == SOLR_VERSION.SOLR8) {
687 localSolrFolder = InstalledFileLocator.getDefault().locate(
"solr", Server.class.getPackage().getName(),
false);
689 throw new KeywordSearchModuleException(Bundle.Server_configureSolrConnection_illegalSolrVersion(version.name()));
692 if (isLocalSolrRunning()) {
695 logger.log(Level.INFO,
"Local Solr " + version +
" server is already running");
710 final List<Long> pids = this.getSolrPIDs();
714 if (pids.isEmpty()) {
736 logger.log(Level.INFO,
"Starting Solr 8 server");
741 logger.log(Level.INFO,
"Starting Solr 4 server");
743 Arrays.asList(
"-Dbootstrap_confdir=../solr/configsets/AutopsyConfig/conf",
744 "-Dcollection.configName=AutopsyConfig")));
749 if (isLocalSolrRunning()) {
750 final List<Long> pids = this.getSolrPIDs();
751 logger.log(Level.INFO,
"New Solr process PID: {0}", pids);
759 }
catch (InterruptedException ex) {
760 logger.log(Level.WARNING,
"Timer interrupted");
766 logger.log(Level.WARNING,
"Local Solr server failed to respond to status requests.");
767 WindowManager.getDefault().invokeWhenUIReady(
new Runnable() {
770 MessageNotifyUtil.Notify.error(
771 NbBundle.getMessage(
this.getClass(),
"Installer.errorInitKsmMsg"),
772 Bundle.Server_status_failed_msg());
775 }
catch (SecurityException ex) {
776 throw new KeywordSearchModuleException(
777 NbBundle.getMessage(
this.getClass(),
"Server.start.exception.cantStartSolr.msg"), ex);
778 }
catch (IOException ex) {
779 throw new KeywordSearchModuleException(
780 NbBundle.getMessage(
this.getClass(),
"Server.start.exception.cantStartSolr.msg2"), ex);
791 static boolean isPortAvailable(
int port) {
793 if (port < 1 || port > 65535) {
794 throw new IllegalArgumentException(
"Invalid start port: " + port);
797 ServerSocket ss =
null;
798 DatagramSocket ds =
null;
800 ss =
new ServerSocket(port);
801 ss.setReuseAddress(
true);
802 ds =
new DatagramSocket(port);
803 ds.setReuseAddress(
true);
805 }
catch (IOException e) {
814 }
catch (IOException e) {
829 void changeSolrServerPort(
int port) {
831 ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT, String.valueOf(port));
839 void changeSolrStopPort(
int port) {
841 ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT, String.valueOf(port));
849 synchronized void stop() {
854 }
catch (KeywordSearchModuleException e) {
855 logger.log(Level.WARNING,
"Failed to close core: ", e);
869 logger.log(Level.INFO,
"Stopping Solr 8 server");
873 logger.log(Level.INFO,
"Stopping Solr 4 server");
877 logger.log(Level.INFO,
"Waiting for Solr server to stop");
886 }
catch (IOException | InterruptedException ex) {
887 logger.log(Level.WARNING,
"Error while attempting to stop Solr server", ex);
900 logger.log(Level.INFO,
"Finished stopping Solr server");
923 logger.log(Level.INFO,
"Solr server is running");
924 }
catch (SolrServerException ex) {
926 Throwable cause = ex.getRootCause();
931 if (cause instanceof ConnectException || cause instanceof SocketException) {
932 logger.log(Level.INFO,
"Solr server is not running, cause: {0}", cause.getMessage());
935 throw new KeywordSearchModuleException(
936 NbBundle.getMessage(
this.getClass(),
"Server.isRunning.exception.errCheckSolrRunning.msg"), ex);
938 }
catch (SolrException ex) {
940 logger.log(Level.INFO,
"Solr server is not running", ex);
942 }
catch (IOException ex) {
943 throw new KeywordSearchModuleException(
944 NbBundle.getMessage(
this.getClass(),
"Server.isRunning.exception.errCheckSolrRunning.msg2"), ex);
962 void openCoreForCase(Case theCase, Index index)
throws KeywordSearchModuleException {
970 }
catch (NoOpenCoreException ex) {
971 throw new KeywordSearchModuleException(NbBundle.getMessage(
this.getClass(),
"Server.openCore.exception.cantOpen.msg"), ex);
985 boolean coreIsOpen() {
994 Index getIndexInfo() throws NoOpenCoreException {
998 throw new NoOpenCoreException();
1006 void closeCore() throws KeywordSearchModuleException {
1019 void addDocument(SolrInputDocument doc)
throws KeywordSearchModuleException, NoOpenCoreException {
1023 throw new NoOpenCoreException();
1025 TimingMetric metric = HealthMonitor.getTimingMetric(
"Solr: Index chunk");
1027 HealthMonitor.submitTimingMetric(metric);
1041 @NbBundle.Messages({
1042 "# {0} - colelction name",
"Server.deleteCore.exception.msg=Failed to delete Solr colelction {0}",})
1043 void deleteCollection(String coreName, CaseMetadata metadata)
throws KeywordSearchServiceException, KeywordSearchModuleException {
1045 HttpSolrClient solrServer;
1046 if (metadata.getCaseType() == CaseType.SINGLE_USER_CASE) {
1048 CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, solrServer);
1049 if (
null != response.getCoreStatus(coreName).get(
"instanceDir")) {
1059 org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(coreName,
true,
true, solrServer);
1063 solrServer =
getSolrClient(
"http://" + properties.getHost() +
":" + properties.getPort() +
"/solr");
1064 connectToSolrServer(solrServer);
1066 CollectionAdminRequest.Delete deleteCollectionRequest = CollectionAdminRequest.deleteCollection(coreName);
1067 CollectionAdminResponse response = deleteCollectionRequest.process(solrServer);
1068 if (response.isSuccess()) {
1069 logger.log(Level.INFO,
"Deleted collection {0}", coreName);
1071 logger.log(Level.WARNING,
"Unable to delete collection {0}", coreName);
1074 }
catch (SolrServerException | IOException ex) {
1077 if (!ex.getMessage().equals(
"Already closed")) {
1078 throw new KeywordSearchServiceException(Bundle.Server_deleteCore_exception_msg(coreName), ex);
1094 @NbBundle.Messages({
1095 "Server.exceptionMessage.unableToCreateCollection=Unable to create Solr collection",
1096 "Server.exceptionMessage.unableToBackupCollection=Unable to backup Solr collection",
1097 "Server.exceptionMessage.unableToRestoreCollection=Unable to restore Solr collection",
1101 int numShardsToUse = 1;
1110 }
catch (Exception ex) {
1112 throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class,
"Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
1116 String collectionName = index.getIndexName();
1123 boolean doRetry =
false;
1128 }
catch (Exception ex) {
1130 logger.log(Level.SEVERE,
"Unable to create Solr collection " + collectionName, ex);
1133 logger.log(Level.SEVERE,
"Unable to create Solr collection " + collectionName +
". Re-trying...", ex);
1134 Thread.sleep(1000L);
1146 File dataDir =
new File(
new File(index.getIndexPath()).getParent());
1147 if (!dataDir.exists()) {
1155 if (corePropertiesFile.toFile().exists()) {
1157 corePropertiesFile.toFile().delete();
1158 }
catch (Exception ex) {
1159 logger.log(Level.INFO,
"Could not delete pre-existing core.properties prior to opening the core.");
1165 CoreAdminRequest.Create createCoreRequest =
new CoreAdminRequest.Create();
1166 createCoreRequest.setDataDir(dataDir.getAbsolutePath());
1167 createCoreRequest.setCoreName(collectionName);
1168 createCoreRequest.setConfigSet(
"AutopsyConfig");
1169 createCoreRequest.setIsLoadOnStartup(
false);
1170 createCoreRequest.setIsTransient(
true);
1179 return new Collection(collectionName, theCase, index);
1181 }
catch (Exception ex) {
1182 logger.log(Level.SEVERE,
"Exception during Solr collection creation.", ex);
1190 if (org.sleuthkit.autopsy.keywordsearch.UserPreferences.getMaxNumShards() > 0) {
1191 return org.sleuthkit.autopsy.keywordsearch.UserPreferences.getMaxNumShards();
1197 return solrServerList.size();
1213 private boolean collectionExists(String collectionName)
throws SolrServerException, IOException {
1214 CollectionAdminRequest.List req =
new CollectionAdminRequest.List();
1216 List<?> existingCollections = (List<?>) response.getResponse().get(
"collections");
1217 if (existingCollections ==
null) {
1218 existingCollections =
new ArrayList<>();
1220 return existingCollections.contains(collectionName);
1265 Integer numShards = numShardsToUse;
1266 logger.log(Level.INFO,
"numShardsToUse: {0}", numShardsToUse);
1267 Integer numNrtReplicas = 1;
1268 Integer numTlogReplicas = 0;
1269 Integer numPullReplicas = 0;
1270 CollectionAdminRequest.Create createCollectionRequest = CollectionAdminRequest.createCollection(collectionName,
"AutopsyConfig", numShards, numNrtReplicas, numTlogReplicas, numPullReplicas);
1272 CollectionAdminResponse createResponse = createCollectionRequest.process(
remoteSolrServer);
1273 if (createResponse.isSuccess()) {
1274 logger.log(Level.INFO,
"Collection {0} successfully created.", collectionName);
1276 logger.log(Level.SEVERE,
"Unable to create Solr collection {0}", collectionName);
1289 CollectionAdminRequest.Backup backup = CollectionAdminRequest.backupCollection(collectionName, backupName)
1290 .setLocation(pathToBackupLocation);
1293 if (backupResponse.isSuccess()) {
1294 logger.log(Level.INFO,
"Collection {0} successfully backep up.", collectionName);
1296 logger.log(Level.SEVERE,
"Unable to back up Solr collection {0}", collectionName);
1303 CollectionAdminRequest.Restore restore = CollectionAdminRequest.restoreCollection(restoreCollectionName, backupName)
1304 .setLocation(pathToBackupLocation);
1306 CollectionAdminResponse restoreResponse = restore.process(
remoteSolrServer);
1307 if (restoreResponse.isSuccess()) {
1308 logger.log(Level.INFO,
"Collection {0} successfully resored.", restoreCollectionName);
1310 logger.log(Level.SEVERE,
"Unable to restore Solr collection {0}", restoreCollectionName);
1329 private boolean coreIsLoaded(String coreName)
throws SolrServerException, IOException {
1330 CoreAdminResponse response = CoreAdminRequest.getStatus(coreName,
localSolrServer);
1331 return response.getCoreStatus(coreName).get(
"instanceDir") !=
null;
1347 CoreAdminResponse response = CoreAdminRequest.getStatus(coreName,
localSolrServer);
1348 Object dataDirPath = response.getCoreStatus(coreName).get(
"dataDir");
1349 if (
null != dataDirPath) {
1350 File indexDir = Paths.get((String) dataDirPath,
"index").toFile();
1351 return indexDir.exists();
1371 Path serverFilePath = Paths.get(caseDirectory,
"solrserver.txt");
1372 if (serverFilePath.toFile().exists()) {
1374 List<String> lines = Files.readAllLines(serverFilePath);
1375 if (lines.isEmpty()) {
1376 logger.log(Level.SEVERE,
"solrserver.txt file does not contain any data");
1377 }
else if (!lines.get(0).contains(
",")) {
1378 logger.log(Level.SEVERE,
"solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
1380 String[] parts = lines.get(0).split(
",");
1381 if (parts.length != 2) {
1382 logger.log(Level.SEVERE,
"solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
1387 }
catch (IOException ex) {
1388 logger.log(Level.SEVERE,
"solrserver.txt file could not be read", ex);
1393 List<Index> indexes =
new ArrayList<>();
1395 IndexMetadata indexMetadata =
new IndexMetadata(caseDirectory);
1396 indexes = indexMetadata.getIndexes();
1397 }
catch (IndexMetadata.TextIndexMetadataException ex) {
1398 logger.log(Level.SEVERE,
"Unable to read text index metadata file: " + caseDirectory, ex);
1408 Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
1409 if (indexToUse ==
null) {
1411 logger.log(Level.SEVERE,
"Unable to find index that can be used for case: {0}", caseDirectory);
1420 if (IndexFinder.getCurrentSolrVersion().equals(indexToUse.getSolrVersion())) {
1446 String serverListName =
"solrServerList.txt";
1447 Path serverListPath = Paths.get(rootOutputDirectory.toString(), serverListName);
1448 if (serverListPath.toFile().exists()) {
1453 lines = Files.readAllLines(serverListPath);
1454 }
catch (IOException ex) {
1459 for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) {
1460 String line = iterator.next();
1461 if (!line.contains(
",")) {
1466 if (lines.isEmpty()) {
1471 int rnd =
new Random().nextInt(lines.size());
1472 String[] parts = lines.get(rnd).split(
",");
1473 if (parts.length != 2) {
1478 String host = parts[0];
1479 String port = parts[1];
1480 if (host.isEmpty() || port.isEmpty()) {
1485 Path serverFile = Paths.get(caseDirectoryPath.toString(),
"solrserver.txt");
1487 caseDirectoryPath.toFile().mkdirs();
1488 if (!caseDirectoryPath.toFile().exists()) {
1491 Files.write(serverFile, lines.get(rnd).getBytes());
1492 }
catch (IOException ex) {
1501 public static class IndexingServerProperties {
1506 IndexingServerProperties(String
host, String
port) {
1547 NamedList<Object> request(SolrRequest<?> request)
throws SolrServerException, RemoteSolrException, NoOpenCoreException {
1551 throw new NoOpenCoreException();
1577 }
catch (Exception ex) {
1603 }
catch (Exception ex) {
1629 }
catch (Exception ex) {
1656 if (totalNumChunks == 0) {
1661 return numIndexedChunks == totalNumChunks;
1662 }
catch (Exception ex) {
1691 }
catch (Exception ex) {
1718 }
catch (Exception ex) {
1720 logger.log(Level.SEVERE,
"Solr query failed: " + sq.getQuery(), ex);
1747 }
catch (Exception ex) {
1749 logger.log(Level.SEVERE,
"Solr query failed: " + sq.getQuery(), ex);
1775 }
catch (Exception ex) {
1777 logger.log(Level.SEVERE,
"Solr terms query failed: " + sq.getQuery(), ex);
1813 @NbBundle.Messages({
1814 "Server.getAllTerms.error=Extraction of all unique Solr terms failed:"})
1815 void extractAllTermsForDataSource(Path outputFile, ReportProgressPanel progressPanel)
throws KeywordSearchModuleException, NoOpenCoreException {
1819 throw new NoOpenCoreException();
1823 }
catch (Exception ex) {
1825 logger.log(Level.SEVERE,
"Extraction of all unique Solr terms failed: ", ex);
1826 throw new KeywordSearchModuleException(Bundle.Server_getAllTerms_error(), ex);
1957 void connectToSolrServer(String host, String port)
throws SolrServerException, IOException {
1958 try (HttpSolrClient solrServer =
getSolrClient(
"http://" + host +
":" + port +
"/solr")) {
1959 connectToSolrServer(solrServer);
1974 CollectionAdminRequest.ClusterStatus statusRequest = CollectionAdminRequest.getClusterStatus();
1975 CollectionAdminResponse statusResponse = statusRequest.process(solrServer);
1976 int statusCode = Integer.valueOf(((NamedList) statusResponse.getResponse().get(
"responseHeader")).get(
"status").toString());
1977 if (statusCode != 0) {
1978 logger.log(Level.WARNING,
"Could not connect to Solr server ");
1980 logger.log(Level.INFO,
"Connected to Solr server ");
1986 HttpSolrClient solrServer =
getSolrClient(
"http://" + host +
":" + port +
"/solr");
1993 CollectionAdminRequest.ClusterStatus statusRequest = CollectionAdminRequest.getClusterStatus();
1994 CollectionAdminResponse statusResponse;
1996 statusResponse = statusRequest.process(solrServer);
1997 }
catch (RemoteSolrException ex) {
1999 return Collections.emptyList();
2002 if (statusResponse ==
null) {
2003 return Collections.emptyList();
2006 NamedList<?> error = (NamedList) statusResponse.getResponse().get(
"error");
2007 if (error !=
null) {
2008 return Collections.emptyList();
2011 NamedList<?> cluster = (NamedList) statusResponse.getResponse().get(
"cluster");
2012 @SuppressWarnings(
"unchecked")
2013 List<String> liveNodes = (ArrayList) cluster.get(
"live_nodes");
2015 if (liveNodes !=
null) {
2016 liveNodes = liveNodes.stream()
2017 .map(serverStr -> serverStr.endsWith(
"_solr")
2018 ? serverStr.substring(0, serverStr.length() -
"_solr".length())
2020 .collect(Collectors.toList());
2023 }
catch (Exception ex) {
2026 NbBundle.getMessage(
this.getClass(),
"Server.serverList.exception.msg", solrServer.getBaseURL()));
2033 private final String name;
2037 private final Index textIndex;
2043 private HttpSolrClient queryClient;
2044 private SolrClient indexingClient;
2046 private final int maxBufferSize;
2047 private final List<SolrInputDocument> buffer;
2048 private final Object bufferLock;
2053 private static final int MAX_NUM_CONSECUTIVE_FAILURES = 5;
2054 private AtomicInteger numConsecutiveFailures =
new AtomicInteger(0);
2055 private AtomicBoolean skipIndexing =
new AtomicBoolean(
false);
2057 private final ScheduledThreadPoolExecutor periodicTasksExecutor;
2058 private static final long PERIODIC_BATCH_SEND_INTERVAL_MINUTES = 10;
2059 private static final int NUM_BATCH_UPDATE_RETRIES = 10;
2060 private static final long SLEEP_BETWEEN_RETRIES_MS = 10000;
2065 this.textIndex = index;
2066 bufferLock =
new Object();
2077 if (IndexFinder.getCurrentSolrVersion().equals(index.getSolrVersion())) {
2079 indexingClient =
getCloudSolrClient(properties.getHost(), properties.getPort(), name);
2086 maxBufferSize = org.sleuthkit.autopsy.keywordsearch.UserPreferences.getDocumentsQueueSize();
2087 logger.log(Level.INFO,
"Using Solr document queue size = {0}", maxBufferSize);
2088 buffer =
new ArrayList<>(maxBufferSize);
2089 periodicTasksExecutor =
new ScheduledThreadPoolExecutor(1,
new ThreadFactoryBuilder().setNameFormat(
"periodic-batched-document-task-%d").build());
2090 periodicTasksExecutor.scheduleWithFixedDelay(
new SendBatchedDocumentsTask(), PERIODIC_BATCH_SEND_INTERVAL_MINUTES, PERIODIC_BATCH_SEND_INTERVAL_MINUTES, TimeUnit.MINUTES);
2104 if (skipIndexing.get()) {
2108 List<SolrInputDocument> clone;
2109 synchronized (bufferLock) {
2111 if (buffer.isEmpty()) {
2117 clone = buffer.stream().collect(toList());
2123 sendBufferedDocs(clone);
2125 logger.log(Level.SEVERE,
"Periodic batched document update failed", ex);
2139 private Index getIndexInfo() {
2140 return this.textIndex;
2143 private QueryResponse query(SolrQuery sq)
throws SolrServerException, IOException {
2144 return queryClient.query(sq);
2147 private NamedList<Object> request(SolrRequest<?> request)
throws SolrServerException, RemoteSolrException {
2149 return queryClient.request(request);
2150 }
catch (Exception e) {
2152 logger.log(Level.WARNING,
"Could not issue Solr request. ", e);
2153 throw new SolrServerException(
2154 NbBundle.getMessage(
this.getClass(),
"Server.request.exception.exception.msg"), e);
2159 private QueryResponse query(SolrQuery sq, SolrRequest.METHOD method)
throws SolrServerException, IOException {
2160 return queryClient.query(sq, method);
2163 private TermsResponse queryTerms(SolrQuery sq)
throws SolrServerException, IOException {
2164 QueryResponse qres = queryClient.query(sq);
2165 return qres.getTermsResponse();
2168 private void commit() throws SolrServerException {
2169 List<SolrInputDocument> clone;
2170 synchronized (bufferLock) {
2173 clone = buffer.stream().collect(toList());
2178 sendBufferedDocs(clone);
2179 }
catch (KeywordSearchModuleException ex) {
2180 throw new SolrServerException(NbBundle.getMessage(
this.getClass(),
"Server.commit.exception.msg"), ex);
2185 indexingClient.commit(
true,
true);
2186 }
catch (Exception e) {
2188 logger.log(Level.WARNING,
"Could not commit index. ", e);
2189 throw new SolrServerException(NbBundle.getMessage(
this.getClass(),
"Server.commit.exception.msg"), e);
2193 private void deleteDataSource(Long dsObjId)
throws IOException, SolrServerException {
2194 String dataSourceId = Long.toString(dsObjId);
2195 String deleteQuery =
"image_id:" + dataSourceId;
2197 queryClient.deleteByQuery(deleteQuery);
2211 @NbBundle.Messages({
2212 "# {0} - Number of extracted terms",
2213 "ExtractAllTermsReport.numberExtractedTerms=Extracted {0} terms..."
2215 private void extractAllTermsForDataSource(Path outputFile, ReportProgressPanel progressPanel)
throws IOException, SolrServerException, NoCurrentCaseException, KeywordSearchModuleException {
2217 Files.deleteIfExists(outputFile);
2218 OpenOption[] options =
new OpenOption[] { java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.APPEND };
2221 int termStep = 1000;
2222 long numExtractedTerms = 0;
2223 String firstTerm =
"";
2225 SolrQuery query =
new SolrQuery();
2226 query.setRequestHandler(
"/terms");
2227 query.setTerms(
true);
2228 query.setTermsLimit(termStep);
2229 query.setTermsLower(firstTerm);
2230 query.setTermsLowerInclusive(
false);
2235 query.setTermsSortString(
"index");
2238 query.addTermsField(Server.Schema.TEXT.toString());
2239 query.setTermsMinCount(0);
2244 QueryRequest request =
new QueryRequest(query);
2245 TermsResponse response = request.process(queryClient).getTermsResponse();
2246 List<Term> terms = response.getTerms(Server.Schema.TEXT.toString());
2248 if (terms ==
null || terms.isEmpty()) {
2249 numExtractedTerms += terms.size();
2250 progressPanel.updateStatusLabel(Bundle.ExtractAllTermsReport_numberExtractedTerms(numExtractedTerms));
2255 firstTerm = terms.get(terms.size()-1).getTerm();
2257 List<String> listTerms = terms.stream().map(Term::getTerm).collect(Collectors.toList());
2258 Files.write(outputFile, listTerms, options);
2260 numExtractedTerms += termStep;
2261 progressPanel.updateStatusLabel(Bundle.ExtractAllTermsReport_numberExtractedTerms(numExtractedTerms));
2273 void addDocument(SolrInputDocument doc)
throws KeywordSearchModuleException {
2275 if (skipIndexing.get()) {
2279 List<SolrInputDocument> clone;
2280 synchronized (bufferLock) {
2283 if (buffer.size() < maxBufferSize) {
2289 clone = buffer.stream().collect(toList());
2294 sendBufferedDocs(clone);
2304 @NbBundle.Messages({
2305 "Collection.unableToIndexData.error=Unable to add data to text index. All future text indexing for the current case will be skipped.",
2308 private void sendBufferedDocs(List<SolrInputDocument> docBuffer)
throws KeywordSearchModuleException {
2310 if (docBuffer.isEmpty()) {
2315 boolean success =
true;
2316 for (
int reTryAttempt = 0; reTryAttempt < NUM_BATCH_UPDATE_RETRIES; reTryAttempt++) {
2319 indexingClient.add(docBuffer);
2320 }
catch (Exception ex) {
2322 if (reTryAttempt < NUM_BATCH_UPDATE_RETRIES - 1) {
2323 logger.log(Level.WARNING,
"Unable to send document batch to Solr. Re-trying...", ex);
2325 Thread.sleep(SLEEP_BETWEEN_RETRIES_MS);
2326 }
catch (InterruptedException ignore) {
2327 throw new KeywordSearchModuleException(
2328 NbBundle.getMessage(
this.getClass(),
"Server.addDocBatch.exception.msg"), ex);
2333 numConsecutiveFailures.set(0);
2334 if (reTryAttempt > 0) {
2335 logger.log(Level.INFO,
"Batch update suceeded after {0} re-try", reTryAttempt);
2341 logger.log(Level.SEVERE,
"Unable to send document batch to Solr. All re-try attempts failed!");
2342 throw new KeywordSearchModuleException(NbBundle.getMessage(
this.getClass(),
"Server.addDocBatch.exception.msg"));
2343 }
catch (Exception ex) {
2345 numConsecutiveFailures.incrementAndGet();
2346 logger.log(Level.SEVERE,
"Could not add batched documents to index", ex);
2349 MessageNotifyUtil.Notify.error(
2350 NbBundle.getMessage(
this.getClass(),
"Server.addDocBatch.exception.msg"),
2351 NbBundle.getMessage(
this.getClass(),
"Server.addDocBatch.exception.msg"));
2352 throw new KeywordSearchModuleException(
2353 NbBundle.getMessage(
this.getClass(),
"Server.addDocBatch.exception.msg"), ex);
2355 if (numConsecutiveFailures.get() >= MAX_NUM_CONSECUTIVE_FAILURES) {
2357 skipIndexing.set(
true);
2358 logger.log(Level.SEVERE,
"Unable to add data to text index. All future text indexing for the current case will be skipped!");
2361 MessageNotifyUtil.Notify.error(
2362 NbBundle.getMessage(
this.getClass(),
"Server.addDocBatch.exception.msg"),
2363 Bundle.Collection_unableToIndexData_error());
2364 if (RuntimeProperties.runningWithGUI()) {
2365 MessageNotifyUtil.Message.error(Bundle.Collection_unableToIndexData_error());
2382 private String getSolrContent(
long contentID,
int chunkID) {
2383 final SolrQuery q =
new SolrQuery();
2385 String filterQuery = Schema.ID.toString() +
":" + KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
2387 filterQuery = filterQuery + Server.CHUNK_ID_SEPARATOR + chunkID;
2389 q.addFilterQuery(filterQuery);
2390 q.setFields(Schema.TEXT.toString());
2393 SolrDocumentList solrDocuments = queryClient.query(q).getResults();
2395 if (!solrDocuments.isEmpty()) {
2396 SolrDocument solrDocument = solrDocuments.get(0);
2397 if (solrDocument !=
null) {
2398 java.util.Collection<Object> fieldValues = solrDocument.getFieldValues(Schema.TEXT.toString());
2399 if (fieldValues.size() == 1)
2401 return fieldValues.toArray(
new String[0])[0];
2405 return fieldValues.toArray(
new String[0])[1];
2409 }
catch (Exception ex) {
2411 logger.log(Level.SEVERE,
"Error getting content from Solr. Solr document id " + contentID +
", chunk id " + chunkID +
", query: " + filterQuery, ex);
2418 synchronized void close() throws KeywordSearchModuleException {
2423 ThreadUtils.shutDownTaskExecutor(periodicTasksExecutor);
2426 if (this.caseType == CaseType.MULTI_USER_CASE) {
2431 }
catch (Exception ex) {
2433 throw new KeywordSearchModuleException(
2434 NbBundle.getMessage(
this.getClass(),
"Server.close.exception.msg"), ex);
2437 queryClient.close();
2439 indexingClient.close();
2440 indexingClient =
null;
2441 }
catch (IOException ex) {
2442 throw new KeywordSearchModuleException(
2443 NbBundle.getMessage(
this.getClass(),
"Server.close.exception.msg2"), ex);
2471 SolrQuery q =
new SolrQuery(Server.Schema.ID +
":*" + Server.CHUNK_ID_SEPARATOR +
"*");
2473 int numChunks = (int) query(q).getResults().getNumFound();
2488 SolrQuery q =
new SolrQuery(
"*:*");
2490 return (
int) query(q).getResults().getNumFound();
2502 private boolean queryIsIndexed(
long contentID)
throws SolrServerException, IOException {
2503 String
id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
2504 SolrQuery q =
new SolrQuery(
"*:*");
2505 q.addFilterQuery(Server.Schema.ID.toString() +
":" +
id);
2508 return (
int) query(q).getResults().getNumFound() != 0;
2525 private int queryTotalNumFileChunks(
long contentID)
throws SolrServerException, IOException {
2526 final SolrQuery q =
new SolrQuery();
2528 String filterQuery = Schema.ID.toString() +
":" + KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
2529 q.addFilterQuery(filterQuery);
2530 q.setFields(Schema.NUM_CHUNKS.toString());
2532 SolrDocumentList solrDocuments = query(q).getResults();
2533 if (!solrDocuments.isEmpty()) {
2534 SolrDocument solrDocument = solrDocuments.get(0);
2535 if (solrDocument !=
null && !solrDocument.isEmpty()) {
2536 Object fieldValue = solrDocument.getFieldValue(Schema.NUM_CHUNKS.toString());
2537 return (Integer)fieldValue;
2540 }
catch (Exception ex) {
2542 logger.log(Level.SEVERE,
"Error getting content from Solr. Solr document id " + contentID +
", query: " + filterQuery, ex);
2561 SolrQuery q =
new SolrQuery(Server.Schema.ID +
":" + KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID)) + Server.CHUNK_ID_SEPARATOR +
"*");
2563 int numChunks = (int) query(q).getResults().getNumFound();
2568 class ServerAction
extends AbstractAction {
2570 private static final long serialVersionUID = 1L;
2573 public void actionPerformed(ActionEvent e) {
2574 logger.log(Level.INFO, e.paramString().trim());
2581 class SolrServerNoPortException
extends SocketException {
2583 private static final long serialVersionUID = 1L;
2588 private final int port;
2590 SolrServerNoPortException(
int port) {
2591 super(NbBundle.getMessage(Server.class,
"Server.solrServerNoPortException.msg", port,
2592 Server.PROPERTIES_CURRENT_SERVER_PORT));
2596 int getPortNumber() {