19 package org.sleuthkit.autopsy.keywordsearch;
21 import java.awt.event.ActionEvent;
22 import java.beans.PropertyChangeListener;
23 import java.io.BufferedReader;
24 import java.io.BufferedWriter;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.io.OutputStream;
31 import java.io.OutputStreamWriter;
32 import java.net.ConnectException;
33 import java.net.ServerSocket;
34 import java.net.SocketException;
35 import java.nio.charset.Charset;
36 import java.nio.file.Files;
37 import java.nio.file.Paths;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.List;
41 import java.util.logging.Level;
43 import org.openide.util.NbBundle;
45 import javax.swing.AbstractAction;
46 import org.apache.solr.client.solrj.SolrQuery;
47 import org.apache.solr.client.solrj.SolrServer;
48 import org.apache.solr.client.solrj.SolrServerException;
49 import org.apache.solr.client.solrj.request.CoreAdminRequest;
50 import org.apache.solr.client.solrj.response.QueryResponse;
51 import org.apache.solr.client.solrj.response.TermsResponse;
52 import org.apache.solr.client.solrj.SolrRequest;
53 import org.apache.solr.client.solrj.impl.HttpSolrServer;
54 import org.apache.solr.common.util.NamedList;
55 import org.openide.modules.InstalledFileLocator;
56 import org.openide.modules.Places;
61 import org.apache.solr.common.SolrInputDocument;
62 import org.apache.solr.client.solrj.impl.XMLResponseParser;
63 import org.apache.solr.common.SolrDocument;
64 import org.apache.solr.common.SolrException;
75 public String toString() {
81 public String toString() {
88 public String toString() {
94 public String toString() {
100 public String toString() {
106 public String toString() {
113 public String toString() {
120 public String toString() {
127 public String toString() {
134 public String toString() {
140 public String toString() {
158 static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
159 static final String PROPERTIES_CURRENT_SERVER_PORT =
"IndexingServerPort";
160 static final String PROPERTIES_CURRENT_STOP_PORT =
"IndexingServerStopPort";
161 private static final String
KEY =
"jjk#09s";
162 static final int DEFAULT_SOLR_SERVER_PORT = 23232;
163 static final int DEFAULT_SOLR_STOP_PORT = 34343;
166 private static final boolean DEBUG =
false;
187 this.solrUrl =
"http://localhost:" + currentSolrServerPort +
"/solr";
188 this.solrServer =
new HttpSolrServer(solrUrl);
189 serverAction =
new ServerAction();
190 solrFolder = InstalledFileLocator.getDefault().locate(
"solr",
Server.class.getPackage().getName(),
false);
191 instanceDir = solrFolder.getAbsolutePath() +
File.separator +
"solr";
194 logger.log(Level.INFO,
"Created Server instance");
201 }
catch (NumberFormatException nfe) {
202 logger.log(Level.WARNING,
"Could not decode indexing server port, value was not a valid port number, using the default. ", nfe);
203 currentSolrServerPort = DEFAULT_SOLR_SERVER_PORT;
206 currentSolrServerPort = DEFAULT_SOLR_SERVER_PORT;
213 }
catch (NumberFormatException nfe) {
214 logger.log(Level.WARNING,
"Could not decode indexing server stop port, value was not a valid port number, using default", nfe);
215 currentSolrStopPort = DEFAULT_SOLR_STOP_PORT;
218 currentSolrStopPort = DEFAULT_SOLR_STOP_PORT;
224 public void finalize() throws java.lang.Throwable {
230 serverAction.addPropertyChangeListener(l);
233 int getCurrentSolrServerPort() {
237 int getCurrentSolrStopPort() {
248 volatile boolean doRun =
true;
251 this.stream = stream;
253 final String log = Places.getUserDirectory().getAbsolutePath()
254 +
File.separator +
"var" +
File.separator +
"log"
255 +
File.separator +
"solr.log." + type;
256 File outputFile =
new File(log.concat(
".0"));
257 File first =
new File(log.concat(
".1"));
258 File second =
new File(log.concat(
".2"));
259 if (second.exists()) {
262 if (first.exists()) {
263 first.renameTo(second);
265 if (outputFile.
exists()) {
266 outputFile.renameTo(first);
268 outputFile.createNewFile();
270 out =
new FileOutputStream(outputFile);
272 }
catch (Exception ex) {
273 logger.log(Level.WARNING,
"Failed to create solr log file", ex);
283 InputStreamReader isr =
new InputStreamReader(stream);
284 BufferedReader br =
new BufferedReader(isr);
285 OutputStreamWriter osw = null;
286 BufferedWriter bw = null;
289 bw =
new BufferedWriter(osw);
291 while (doRun && (line = br.readLine()) != null) {
300 }
catch (IOException ex) {
301 logger.log(Level.WARNING,
"Error redirecting Solr output stream");
306 }
catch (IOException ex) {
307 logger.log(Level.WARNING,
"Error closing Solr output stream writer");
313 }
catch (IOException ex) {
314 logger.log(Level.WARNING,
"Error closing Solr output stream reader");
326 List<Long> getSolrPIDs() {
327 List<Long> pids =
new ArrayList<Long>();
330 final String pidsQuery =
"Args.4.eq=-DSTOP.KEY=" + KEY +
",Args.7.eq=start.jar";
333 if (pidsArr != null) {
334 for (
int i = 0; i < pidsArr.length; ++i) {
335 pids.add(pidsArr[i]);
347 List<Long> solrPids = getSolrPIDs();
348 for (
long pid : solrPids) {
349 logger.log(Level.INFO,
"Trying to kill old Solr process, PID: " + pid);
350 PlatformUtil.killProcess(pid);
359 void start() throws KeywordSearchModuleException, SolrServerNoPortException {
360 logger.log(Level.INFO,
"Starting Solr server from: " + solrFolder.getAbsolutePath());
361 if (isPortAvailable(currentSolrServerPort)) {
362 logger.log(Level.INFO,
"Port [" + currentSolrServerPort +
"] available, starting Solr");
364 final String MAX_SOLR_MEM_MB_PAR =
"-Xmx" + Integer.toString(MAX_SOLR_MEM_MB) +
"m";
366 String loggingPropertiesOpt =
"-Djava.util.logging.config.file=";
367 String loggingPropertiesFilePath = instanceDir + File.separator +
"conf" + File.separator;
370 loggingPropertiesFilePath +=
"logging-development.properties";
372 loggingPropertiesFilePath +=
"logging-release.properties";
375 final String loggingProperties = loggingPropertiesOpt + loggingPropertiesFilePath;
377 final String [] SOLR_START_CMD = {
387 StringBuilder cmdSb =
new StringBuilder();
388 for (
int i = 0; i<SOLR_START_CMD.length; ++i ) {
389 cmdSb.append(SOLR_START_CMD[i]).append(
" ");
392 logger.log(Level.INFO,
"Starting Solr using: " + cmdSb.toString());
393 curSolrProcess = Runtime.getRuntime().exec(SOLR_START_CMD, null, solrFolder);
394 logger.log(Level.INFO,
"Finished starting Solr");
399 Thread.sleep(10 * 1000);
400 }
catch (InterruptedException ex) {
401 logger.log(Level.WARNING,
"Timer interrupted");
405 errorRedirectThread =
new InputStreamPrinterThread(curSolrProcess.getErrorStream(),
"stderr");
406 errorRedirectThread.start();
408 final List<Long> pids = this.getSolrPIDs();
409 logger.log(Level.INFO,
"New Solr process PID: " + pids);
410 }
catch (SecurityException ex) {
411 logger.log(Level.SEVERE,
"Could not start Solr process!", ex);
412 throw new KeywordSearchModuleException(
413 NbBundle.getMessage(
this.getClass(),
"Server.start.exception.cantStartSolr.msg"), ex);
414 }
catch (IOException ex) {
415 logger.log(Level.SEVERE,
"Could not start Solr server process!", ex);
416 throw new KeywordSearchModuleException(
417 NbBundle.getMessage(
this.getClass(),
"Server.start.exception.cantStartSolr.msg2"), ex);
420 logger.log(Level.SEVERE,
"Could not start Solr server process, port [" + currentSolrServerPort +
"] not available!");
421 throw new SolrServerNoPortException(currentSolrServerPort);
430 static boolean isPortAvailable(
int port) {
431 ServerSocket ss = null;
434 ss =
new ServerSocket(port, 0, java.net.Inet4Address.getByName(
"localhost"));
436 ss.setReuseAddress(
true);
441 }
catch (IOException e) {
446 }
catch (IOException e) {
459 void changeSolrServerPort(
int port) {
460 currentSolrServerPort = port;
461 ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT, String.valueOf(port));
469 void changeSolrStopPort(
int port) {
470 currentSolrStopPort = port;
471 ModuleSettings.setConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_STOP_PORT, String.valueOf(port));
479 synchronized void stop() {
481 logger.log(Level.INFO,
"Stopping Solr server from: " + solrFolder.getAbsolutePath());
483 final String [] SOLR_STOP_CMD = {
491 Process stop = Runtime.getRuntime().exec(SOLR_STOP_CMD, null, solrFolder);
492 logger.log(Level.INFO,
"Waiting for stopping Solr server");
496 if (curSolrProcess != null) {
497 curSolrProcess.destroy();
498 curSolrProcess = null;
501 }
catch (InterruptedException ex) {
502 }
catch (IOException ex) {
506 if (errorRedirectThread != null) {
507 errorRedirectThread.stopRun();
508 errorRedirectThread = null;
515 logger.log(Level.INFO,
"Finished stopping Solr server");
526 synchronized boolean isRunning() throws KeywordSearchModuleException {
534 CoreAdminRequest.getStatus(null, solrServer);
536 logger.log(Level.INFO,
"Solr server is running");
537 }
catch (SolrServerException ex) {
539 Throwable cause = ex.getRootCause();
544 if (cause instanceof ConnectException || cause instanceof SocketException) {
545 logger.log(Level.INFO,
"Solr server is not running, cause: {0}", cause.getMessage());
548 throw new KeywordSearchModuleException(
549 NbBundle.getMessage(
this.getClass(),
"Server.isRunning.exception.errCheckSolrRunning.msg"), ex);
551 }
catch (SolrException ex) {
553 logger.log(Level.INFO,
"Solr server is not running", ex);
555 }
catch (IOException ex) {
556 throw new KeywordSearchModuleException(
557 NbBundle.getMessage(
this.getClass(),
"Server.isRunning.exception.errCheckSolrRunning.msg2"), ex);
567 synchronized void openCore() throws KeywordSearchModuleException {
568 if (currentCore != null) {
569 throw new KeywordSearchModuleException(
570 NbBundle.getMessage(
this.getClass(),
"Server.openCore.exception.alreadyOpen.msg"));
577 currentCore = openCore(currentCase);
586 logger.log(Level.INFO,
"Validating keyword search index location");
587 String properIndexPath = getIndexDirPath(theCase);
590 +
File.separator +
"keywordsearch" +
File.separator +
"data";
593 File properIndexDir =
new File(properIndexPath);
594 File legacyIndexDir =
new File(legacyIndexPath);
595 if (!properIndexDir.
exists()
596 && legacyIndexDir.
exists() && legacyIndexDir.isDirectory()) {
597 logger.log(Level.INFO,
"Moving keyword search index location from: "
598 + legacyIndexPath +
" to: " + properIndexPath);
600 Files.move(Paths.get(legacyIndexDir.
getParent()), Paths.get(properIndexDir.
getParent()));
601 }
catch (IOException | SecurityException ex) {
602 logger.log(Level.WARNING,
"Error moving keyword search index folder from: "
603 + legacyIndexPath +
" to: " + properIndexPath
604 +
" will recreate a new index.", ex);
609 synchronized void closeCore() throws KeywordSearchModuleException {
610 if (currentCore == null) {
615 serverAction.putValue(CORE_EVT, CORE_EVT_STATES.STOPPED);
618 void addDocument(SolrInputDocument doc)
throws KeywordSearchModuleException {
619 currentCore.addDocument(doc);
628 String getIndexDirPath(Case theCase) {
629 String indexDir = theCase.getModulesOutputDirAbsPath()
630 + File.separator +
"keywordsearch" + File.separator +
"data";
643 private synchronized Core
openCore(
Case theCase)
throws KeywordSearchModuleException {
644 String dataDir = getIndexDirPath(theCase);
645 return this.openCore(DEFAULT_CORE_NAME,
new File(dataDir));
654 if (currentCore == null) {
655 throw new NoOpenCoreException();
657 currentCore.commit();
660 NamedList<Object> request(SolrRequest request)
throws SolrServerException, NoOpenCoreException {
661 if (currentCore == null) {
662 throw new NoOpenCoreException();
664 return currentCore.request(request);
677 if (currentCore == null) {
678 throw new NoOpenCoreException();
681 return currentCore.queryNumIndexedFiles();
682 }
catch (SolrServerException ex) {
683 throw new KeywordSearchModuleException(
684 NbBundle.getMessage(
this.getClass(),
"Server.queryNumIdxFiles.exception.msg"), ex);
699 if (currentCore == null) {
700 throw new NoOpenCoreException();
703 return currentCore.queryNumIndexedChunks();
704 }
catch (SolrServerException ex) {
705 throw new KeywordSearchModuleException(
706 NbBundle.getMessage(
this.getClass(),
"Server.queryNumIdxChunks.exception.msg"), ex);
719 if (currentCore == null) {
720 throw new NoOpenCoreException();
723 return currentCore.queryNumIndexedDocuments();
724 }
catch (SolrServerException ex) {
725 throw new KeywordSearchModuleException(
726 NbBundle.getMessage(
this.getClass(),
"Server.queryNumIdxDocs.exception.msg"), ex);
738 public boolean queryIsIndexed(
long contentID)
throws KeywordSearchModuleException, NoOpenCoreException {
739 if (currentCore == null) {
740 throw new NoOpenCoreException();
743 return currentCore.queryIsIndexed(contentID);
744 }
catch (SolrServerException ex) {
745 throw new KeywordSearchModuleException(
746 NbBundle.getMessage(
this.getClass(),
"Server.queryIsIdxd.exception.msg"), ex);
759 public int queryNumFileChunks(
long fileID)
throws KeywordSearchModuleException, NoOpenCoreException {
760 if (currentCore == null) {
761 throw new NoOpenCoreException();
764 return currentCore.queryNumFileChunks(fileID);
765 }
catch (SolrServerException ex) {
766 throw new KeywordSearchModuleException(
767 NbBundle.getMessage(
this.getClass(),
"Server.queryNumFileChunks.exception.msg"), ex);
779 public QueryResponse
query(SolrQuery sq)
throws KeywordSearchModuleException, NoOpenCoreException {
780 if (currentCore == null) {
781 throw new NoOpenCoreException();
784 return currentCore.query(sq);
785 }
catch (SolrServerException ex) {
786 throw new KeywordSearchModuleException(
787 NbBundle.getMessage(
this.getClass(),
"Server.query.exception.msg", sq.getQuery()), ex);
800 public QueryResponse
query(SolrQuery sq, SolrRequest.METHOD method) throws KeywordSearchModuleException, NoOpenCoreException {
801 if (currentCore == null) {
802 throw new NoOpenCoreException();
805 return currentCore.query(sq, method);
806 }
catch (SolrServerException ex) {
807 throw new KeywordSearchModuleException(
808 NbBundle.getMessage(
this.getClass(),
"Server.query2.exception.msg", sq.getQuery()), ex);
820 public TermsResponse
queryTerms(SolrQuery sq)
throws KeywordSearchModuleException, NoOpenCoreException {
821 if (currentCore == null) {
822 throw new NoOpenCoreException();
825 return currentCore.queryTerms(sq);
826 }
catch (SolrServerException ex) {
827 throw new KeywordSearchModuleException(
828 NbBundle.getMessage(
this.getClass(),
"Server.queryTerms.exception.msg", sq.getQuery()), ex);
840 if (currentCore == null) {
841 throw new NoOpenCoreException();
843 return currentCore.getSolrContent(content.getId(), 0);
856 if (currentCore == null) {
857 throw new NoOpenCoreException();
859 return currentCore.getSolrContent(content.getId(), chunkID);
868 String
getSolrContent(
final long objectID)
throws NoOpenCoreException {
869 if (currentCore == null) {
870 throw new NoOpenCoreException();
872 return currentCore.getSolrContent(objectID, 0);
882 String
getSolrContent(
final long objectID,
final int chunkID)
throws NoOpenCoreException {
883 if (currentCore == null) {
884 throw new NoOpenCoreException();
886 return currentCore.getSolrContent(objectID, chunkID);
895 return Ingester.getDefault();
917 private Core
openCore(String coreName,
File dataDir)
throws KeywordSearchModuleException {
919 if (!dataDir.exists()) {
924 if (!this.isRunning()) {
925 logger.log(Level.WARNING,
"Core open requested, but server not yet running");
926 throw new KeywordSearchModuleException(
927 NbBundle.getMessage(
this.getClass(),
"Server.openCore.exception.msg"));
930 CoreAdminRequest.Create createCore =
new CoreAdminRequest.Create();
931 createCore.setDataDir(dataDir.getAbsolutePath());
932 createCore.setInstanceDir(instanceDir);
933 createCore.setCoreName(coreName);
935 this.solrServer.request(createCore);
937 final Core newCore =
new Core(coreName);
941 }
catch (SolrServerException ex) {
942 throw new KeywordSearchModuleException(
943 NbBundle.getMessage(
this.getClass(),
"Server.openCore.exception.cantOpen.msg"), ex);
944 }
catch (IOException ex) {
945 throw new KeywordSearchModuleException(
946 NbBundle.getMessage(
this.getClass(),
"Server.openCore.exception.cantOpen.msg2"), ex);
956 private HttpSolrServer solrCore;
958 private Core(String name) {
961 this.solrCore =
new HttpSolrServer(solrUrl +
"/" + name);
966 solrCore.setDefaultMaxConnectionsPerHost(2);
967 solrCore.setMaxTotalConnections(5);
968 solrCore.setFollowRedirects(
false);
971 solrCore.setAllowCompression(
true);
972 solrCore.setMaxRetries(1);
973 solrCore.setParser(
new XMLResponseParser());
978 private QueryResponse
query(SolrQuery sq)
throws SolrServerException {
979 return solrCore.query(sq);
982 private NamedList<Object> request(SolrRequest request)
throws SolrServerException {
984 return solrCore.request(request);
985 }
catch (IOException e) {
986 logger.log(Level.WARNING,
"Could not issue Solr request. ", e);
987 throw new SolrServerException(
988 NbBundle.getMessage(
this.getClass(),
"Server.request.exception.exception.msg"), e);
993 private QueryResponse
query(SolrQuery sq, SolrRequest.METHOD method) throws SolrServerException {
994 return solrCore.query(sq, method);
997 private TermsResponse
queryTerms(SolrQuery sq)
throws SolrServerException {
998 QueryResponse qres = solrCore.query(sq);
999 return qres.getTermsResponse();
1002 private void commit() throws SolrServerException {
1005 solrCore.commit(
true,
true);
1006 }
catch (IOException e) {
1007 logger.log(Level.WARNING,
"Could not commit index. ", e);
1008 throw new SolrServerException(NbBundle.getMessage(
this.getClass(),
"Server.commit.exception.msg"), e);
1012 void addDocument(SolrInputDocument doc)
throws KeywordSearchModuleException {
1015 }
catch (SolrServerException ex) {
1016 logger.log(Level.SEVERE,
"Could not add document to index via update handler: " + doc.getField(
"id"), ex);
1017 throw new KeywordSearchModuleException(
1018 NbBundle.getMessage(
this.getClass(),
"Server.addDoc.exception.msg", doc.getField(
"id")), ex);
1019 }
catch (IOException ex) {
1020 logger.log(Level.SEVERE,
"Could not add document to index via update handler: " + doc.getField(
"id"), ex);
1021 throw new KeywordSearchModuleException(
1022 NbBundle.getMessage(
this.getClass(),
"Server.addDoc.exception.msg2", doc.getField(
"id")), ex);
1033 final SolrQuery q =
new SolrQuery();
1035 String filterQuery = Schema.ID.toString() +
":" + KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
1037 filterQuery = filterQuery + Server.ID_CHUNK_SEP + chunkID;
1039 q.addFilterQuery(filterQuery);
1040 q.setFields(Schema.TEXT.toString());
1043 SolrDocument solrDocument = solrCore.query(q).getResults().get(0);
1044 if (solrDocument != null) {
1045 Collection<Object> fieldValues = solrDocument.getFieldValues(Schema.TEXT.toString());
1046 if (fieldValues.size() == 1)
1048 return fieldValues.toArray(
new String[0])[0];
1052 return fieldValues.toArray(
new String[0])[1];
1054 }
catch (SolrServerException ex) {
1055 logger.log(Level.WARNING,
"Error getting content from Solr", ex);
1062 synchronized void close() throws KeywordSearchModuleException {
1064 CoreAdminRequest.unloadCore(this.name, solrServer);
1065 }
catch (SolrServerException ex) {
1066 throw new KeywordSearchModuleException(
1067 NbBundle.getMessage(
this.getClass(),
"Server.close.exception.msg"), ex);
1068 }
catch (IOException ex) {
1069 throw new KeywordSearchModuleException(
1070 NbBundle.getMessage(
this.getClass(),
"Server.close.exception.msg2"), ex);
1094 SolrQuery q =
new SolrQuery(Server.Schema.ID +
":*" + Server.ID_CHUNK_SEP +
"*");
1096 int numChunks = (int)
query(q).getResults().getNumFound();
1110 SolrQuery q =
new SolrQuery(
"*:*");
1112 return (
int)
query(q).getResults().getNumFound();
1122 private boolean queryIsIndexed(
long contentID)
throws SolrServerException {
1123 String
id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
1124 SolrQuery q =
new SolrQuery(
"*:*");
1125 q.addFilterQuery(Server.Schema.ID.toString() +
":" + id);
1128 return (
int)
query(q).getResults().getNumFound() != 0;
1141 String
id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
1143 new SolrQuery(Server.Schema.ID +
":" +
id + Server.ID_CHUNK_SEP +
"*");
1145 return (
int)
query(q).getResults().getNumFound();
1149 class ServerAction
extends AbstractAction {
1152 public void actionPerformed(ActionEvent e) {
1153 logger.log(Level.INFO, e.paramString().trim());
1160 class SolrServerNoPortException
extends SocketException {
1167 SolrServerNoPortException(
int port) {
1168 super(NbBundle.getMessage(Server.class,
"Server.solrServerNoPortException.msg", port,
1169 Server.PROPERTIES_CURRENT_SERVER_PORT));
1173 int getPortNumber() {
int queryNumIndexedFiles()
synchronized Content getParent()
ServerAction serverAction
int queryNumIndexedChunks()
static final char ID_CHUNK_SEP
String getCaseDirectory()
static final Logger logger
void validateIndexLocation(Case theCase)
void addServerActionListener(PropertyChangeListener l)
static final String HL_ANALYZE_CHARS_UNLIMITED
synchronized Core openCore(Case theCase)
String getSolrContent(final Content content)
static final long MAX_CONTENT_SIZE
int queryNumIndexedDocuments()
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
static final String CORE_EVT
static final Charset DEFAULT_INDEXED_TEXT_CHARSET
default Charset to index text as
InputStreamPrinterThread errorRedirectThread
static final String DEFAULT_CORE_NAME
QueryResponse query(SolrQuery sq)
static String getConfigSetting(String moduleName, String settingName)
int currentSolrServerPort
TermsResponse queryTerms(SolrQuery sq)
Core openCore(String coreName, File dataDir)
boolean queryIsIndexed(long contentID)
String getSolrContent(final Content content, int chunkID)
static Case getCurrentCase()
volatile Core currentCore
static final int MAX_SOLR_MEM_MB
static Ingester getIngester()
static String getChunkIdString(long parentID, int childID)
static boolean settingExists(String moduleName, String settingName)
int queryNumFileChunks(long fileID)
static Logger getLogger(String name)
static final boolean DEBUG
QueryResponse query(SolrQuery sq, SolrRequest.METHOD method)