19 package org.sleuthkit.autopsy.keywordsearch.multicase;
21 import com.google.common.eventbus.EventBus;
23 import java.io.IOException;
24 import java.nio.file.LinkOption;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.sql.ResultSet;
28 import java.sql.SQLException;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
36 import java.util.concurrent.TimeUnit;
37 import java.util.logging.Level;
38 import java.util.stream.Collectors;
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.parsers.ParserConfigurationException;
42 import javax.xml.xpath.XPath;
43 import javax.xml.xpath.XPathConstants;
44 import javax.xml.xpath.XPathExpression;
45 import javax.xml.xpath.XPathExpressionException;
46 import javax.xml.xpath.XPathFactory;
47 import org.apache.commons.lang.StringUtils;
48 import org.apache.solr.client.solrj.SolrQuery;
49 import org.apache.solr.client.solrj.SolrRequest;
50 import org.apache.solr.client.solrj.SolrServerException;
51 import org.apache.solr.client.solrj.impl.HttpSolrServer;
52 import org.apache.solr.client.solrj.request.CoreAdminRequest;
53 import org.apache.solr.client.solrj.response.CoreAdminResponse;
54 import org.apache.solr.client.solrj.response.QueryResponse;
55 import org.apache.solr.common.SolrDocument;
56 import org.apache.solr.common.SolrDocumentList;
57 import org.apache.solr.common.params.CoreAdminParams;
58 import org.apache.solr.common.params.CursorMarkParams;
59 import org.openide.util.Exceptions;
60 import org.openide.util.NbBundle;
77 import org.w3c.dom.Document;
78 import org.xml.sax.SAXException;
83 final class MultiCaseSearcher {
85 private static final String CASE_AUTO_INGEST_LOG_NAME =
"AUTO_INGEST_LOG.TXT";
86 private static final String SEARCH_COMPLETE_MESSAGE =
"SEARCH_COMPLETE";
87 private static final String RESOURCES_LOCK_SUFFIX =
"_RESOURCES";
88 private static final int CASE_DIR_READ_LOCK_TIMEOUT_HOURS = 12;
89 private static final String SOLR_SERVER_URL_FORMAT_STRING =
"http://%s:%s/solr";
90 private static final String SOLR_CORE_URL_FORMAT_STRING =
"http://%s:%s/solr/%s";
91 private final static String SOLR_METADATA_FILE_NAME =
"SolrCore.properties";
92 private static final String SOLR_CORE_NAME_XPATH =
"/SolrCores/Core/CoreName/text()";
93 private static final String TEXT_INDEX_NAME_XPATH =
"/SolrCores/Core/TextIndexPath/text()";
94 private static final String SOLR_CORE_INSTANCE_PATH_PROPERTY =
"instanceDir";
95 private static final String SOLR_CONFIG_SET_NAME =
"AutopsyConfig";
96 private static final int MAX_RESULTS_PER_CURSOR_MARK = 512;
97 private static final String SOLR_DOC_ID_FIELD = Server.Schema.ID.toString();
98 private static final String SOLR_DOC_CONTENT_STR_FIELD = Server.Schema.CONTENT_STR.toString();
99 private static final String SOLR_DOC_CHUNK_SIZE_FIELD = Server.Schema.CHUNK_SIZE.toString();
100 private static final String SOLR_DOC_ID_PARTS_SEPARATOR =
"_";
101 private static final Logger logger = Logger.getLogger(MultiCaseSearcher.class.getName());
102 private final EventBus eventBus =
new EventBus(
"MultiCaseSearcherEventBus");
103 private static final UNCPathUtilities pathUtils =
new UNCPathUtilities();
104 private volatile boolean searchStopped =
true;
106 MultiCaseSearcher() {
110 static String getSearchCompleteMessage() {
111 return SEARCH_COMPLETE_MESSAGE;
128 "MultiCaseSearcher.progressMessage.findingCases=Finding selected cases",
129 "MultiCaseSearcher.progressMessage.creatingSolrQuery=Creating search query for Solr server",
130 "# {0} - total cases",
131 "MultiCaseSearcher.progressMessage.startingCaseSearches=Searching {0} case(s)",
133 "# {1} - case counter",
134 "# {2} - total cases",
135 "MultiCaseSearcher.progressMessage.acquiringSharedLockForCase=Acquiring shared lock for \"{0}\" ({1} of {2} case(s))",
137 "# {1} - case counter",
138 "# {2} - total cases",
139 "MultiCaseSearcher.progressMessage.loadingSolrCoreForCase=Loading Solr core for \"{0}\" ({1} of {2} case(s))",
141 "# {1} - case counter",
142 "# {2} - total cases",
143 "MultiCaseSearcher.progressMessage.openingCaseDbForCase=Opening case database for \"{0}\" ({1} of {2} case(s))",
145 "# {1} - case counter",
146 "# {2} - total cases",
147 "MultiCaseSearcher.progressMessage.executingSolrQueryForCase=Getting keyword hits for \"{0}\" ({1} of {2} case(s))",
148 "# {0} - case directory path",
149 "MultiCaseSearcher.exceptionMessage.failedToGetCaseDirReadlock=Failed to obtain read lock for case directory at {0}",
150 "MultiCaseSearcher.exceptionMessage.cancelledMessage=Search cancelled"
152 void performKeywordSearch(
final Collection<CaseNodeData> caseNodes,
final SearchQuery query,
final ProgressIndicator progressIndicator) {
153 progressIndicator.start(Bundle.MultiCaseSearcher_progressMessage_findingCases());
155 searchStopped =
false;
156 final List<MultiCaseMetadata> caseMetadata = getMultiCaseMetadata(caseNodes);
157 checkForCancellation();
159 progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_creatingSolrQuery());
160 final SolrQuery solrQuery = createSolrQuery(query);
161 checkForCancellation();
162 final int totalCases = caseMetadata.size();
164 progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_startingCaseSearches(totalCases));
166 progressIndicator.switchToDeterminate(Bundle.MultiCaseSearcher_progressMessage_startingCaseSearches(totalCases), 0, totalCases * totalSteps);
168 for (MultiCaseMetadata aCase : caseMetadata) {
169 CaseMetadata metadata = aCase.getCaseMetadata();
170 String caseName = metadata.getCaseDisplayName();
171 SleuthkitCase caseDatabase = null;
173 int stepsCompleted = 0;
174 progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_acquiringSharedLockForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
175 try (CoordinationService.Lock caseDirReadLock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.CASES, aCase.getCaseMetadata().getCaseDirectory(), CASE_DIR_READ_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS)) {
176 if (null == caseDirReadLock) {
177 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDirReadlock(aCase.getCaseMetadata().getCaseDirectory()));
179 checkForCancellation();
181 progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_loadingSolrCoreForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
182 final HttpSolrServer solrServer = loadSolrCoreForCase(aCase);
183 checkForCancellation();
185 progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_openingCaseDbForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
186 caseDatabase = openCase(aCase);
187 checkForCancellation();
189 progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_executingSolrQueryForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
190 eventBus.post(executeQuery(solrServer, solrQuery, caseDatabase, aCase));
193 progressIndicator.progress(stepsCompleted + caseNumber * totalSteps);
195 }
catch (CoordinationService.CoordinationServiceException ex) {
196 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDirReadlock(aCase.getCaseMetadata().getCaseDirectory()), ex);
197 }
catch (MultiCaseSearcherException exception) {
198 logger.log(Level.INFO,
"Exception encountered while performing multi-case keyword search", exception);
199 eventBus.post(exception);
201 if (null != caseDatabase) {
202 closeCase(caseDatabase);
207 }
catch (InterruptedException exception) {
208 logger.log(Level.INFO, Bundle.MultiCaseSearcher_exceptionMessage_cancelledMessage(), exception);
209 eventBus.post(exception);
210 }
catch (MultiCaseSearcherException exception) {
211 logger.log(Level.WARNING,
"Exception encountered while performing multi-case keyword search", exception);
212 eventBus.post(
new InterruptedException(
"Exception encountered while performing multi-case keyword search"));
213 eventBus.post(exception);
215 progressIndicator.finish();
216 eventBus.post(SEARCH_COMPLETE_MESSAGE);
230 private List<MultiCaseMetadata> getMultiCaseMetadata(
final Collection<CaseNodeData> caseNodes)
throws MultiCaseSearcherException, InterruptedException {
231 final Map<Path, String> casesToCasePaths = caseNodes.stream()
233 checkForCancellation();
234 final List<MultiCaseMetadata> cases =
new ArrayList<>();
235 for (Map.Entry<Path, String> entry : casesToCasePaths.entrySet()) {
236 final Path caseDirectoryPath = entry.getKey();
237 final CaseMetadata caseMetadata = getCaseMetadata(caseDirectoryPath);
238 checkForCancellation();
239 final TextIndexMetadata textIndexMetadata = getTextIndexMetadata(caseDirectoryPath);
240 checkForCancellation();
241 cases.add(
new MultiCaseMetadata(caseMetadata, textIndexMetadata));
257 "# {0} - case directory",
"MultiCaseSearcher.exceptionMessage.failedToFindCaseMetadata=Failed to find case metadata file in {0}",
258 "# {0} - case directory",
"MultiCaseSearcher.exceptionMessage.failedToParseCaseMetadata=Failed to parse case file metadata in {0}"
261 private static CaseMetadata getCaseMetadata(Path caseDirectoryPath)
throws MultiCaseSearcherException {
262 Path metadataPath = CaseMetadata.getCaseMetadataFile(caseDirectoryPath);
263 if (metadataPath != null) {
265 return new CaseMetadata(metadataPath);
266 }
catch (CaseMetadata.CaseMetadataException ex) {
267 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToParseCaseMetadata(caseDirectoryPath), ex);
270 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToFindCaseMetadata(caseDirectoryPath));
284 "# {0} - file name",
"# {1} - case directory",
"MultiCaseSearcher.exceptionMessage.missingSolrPropertiesFile=Missing {0} file in {1}",
285 "# {0} - file name",
"# {1} - case directory",
"MultiCaseSearcher.exceptionMessage.solrPropertiesFileParseError=Error parsing {0} file in {1}",})
286 private static TextIndexMetadata getTextIndexMetadata(Path caseDirectoryPath)
throws MultiCaseSearcherException {
287 final Path solrMetaDataFilePath = Paths.get(caseDirectoryPath.toString(), SOLR_METADATA_FILE_NAME);
288 final File solrMetaDataFile = solrMetaDataFilePath.toFile();
289 if (!solrMetaDataFile.exists() || !solrMetaDataFile.canRead()) {
290 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_missingSolrPropertiesFile(SOLR_METADATA_FILE_NAME, caseDirectoryPath));
293 final DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
294 final Document doc = docBuilder.parse(solrMetaDataFile);
295 final XPath xPath = XPathFactory.newInstance().newXPath();
296 XPathExpression xPathExpr = xPath.compile(SOLR_CORE_NAME_XPATH);
297 final String solrCoreName = (String) xPathExpr.evaluate(doc, XPathConstants.STRING);
298 xPathExpr = xPath.compile(TEXT_INDEX_NAME_XPATH);
299 final String relativeTextIndexPath = (String) xPathExpr.evaluate(doc, XPathConstants.STRING);
300 Path textIndexPath = caseDirectoryPath.resolve(relativeTextIndexPath);
301 textIndexPath = textIndexPath.getParent();
302 final String textIndexUNCPath = pathUtils.convertPathToUNC(textIndexPath.toString());
303 return new TextIndexMetadata(caseDirectoryPath, solrCoreName, textIndexUNCPath);
304 }
catch (ParserConfigurationException | SAXException | XPathExpressionException | IOException ex) {
305 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_solrPropertiesFileParseError(SOLR_METADATA_FILE_NAME, caseDirectoryPath), ex);
316 private static SolrQuery createSolrQuery(SearchQuery searchQuery) {
317 final SolrQuery solrQuery =
new SolrQuery();
318 solrQuery.setQuery(searchQuery.getSearchTerm());
319 solrQuery.setRows(MAX_RESULTS_PER_CURSOR_MARK);
324 solrQuery.setSort(SolrQuery.SortClause.asc(SOLR_DOC_ID_FIELD));
325 solrQuery.setFields(SOLR_DOC_ID_FIELD, SOLR_DOC_CHUNK_SIZE_FIELD, SOLR_DOC_CONTENT_STR_FIELD);
342 "# {0} - connection info",
344 "# {2} - case directory",
345 "MultiCaseSearcher.exceptionMessage.errorLoadingCore=Error connecting to Solr server and loading core (URL: {0}) for case {1} in {2}"
347 private HttpSolrServer loadSolrCoreForCase(MultiCaseMetadata aCase)
throws MultiCaseSearcherException, InterruptedException {
348 TextIndexMetadata textIndexMetadata = aCase.getTextIndexMetadata();
349 Server.IndexingServerProperties indexServer = Server.getMultiUserServerProperties(aCase.getCaseMetadata().getCaseDirectory());
350 final String serverURL = String.format(SOLR_SERVER_URL_FORMAT_STRING, indexServer.getHost(), indexServer.getPort());
355 final HttpSolrServer solrServer =
new HttpSolrServer(serverURL);
356 CoreAdminRequest statusRequest =
new CoreAdminRequest();
357 statusRequest.setCoreName(null);
358 statusRequest.setAction(CoreAdminParams.CoreAdminAction.STATUS);
359 statusRequest.setIndexInfoNeeded(
false);
360 checkForCancellation();
361 statusRequest.process(solrServer);
362 checkForCancellation();
367 CoreAdminResponse response = CoreAdminRequest.getStatus(textIndexMetadata.getSolrCoreName(), solrServer);
368 if (null == response.getCoreStatus(textIndexMetadata.getSolrCoreName()).
get(SOLR_CORE_INSTANCE_PATH_PROPERTY)) {
369 CoreAdminRequest.Create loadCoreRequest =
new CoreAdminRequest.Create();
370 loadCoreRequest.setDataDir(textIndexMetadata.getTextIndexPath());
371 loadCoreRequest.setCoreName(textIndexMetadata.getSolrCoreName());
372 loadCoreRequest.setConfigSet(SOLR_CONFIG_SET_NAME);
373 loadCoreRequest.setIsLoadOnStartup(
false);
374 loadCoreRequest.setIsTransient(
true);
375 solrServer.request(loadCoreRequest);
382 final String coreURL = String.format(SOLR_CORE_URL_FORMAT_STRING, indexServer.getHost(), indexServer.getPort(), textIndexMetadata.getSolrCoreName());
383 final HttpSolrServer coreServer =
new HttpSolrServer(coreURL);
386 }
catch (SolrServerException | IOException ex) {
387 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_errorLoadingCore(serverURL, aCase.getCaseMetadata().getCaseName(), textIndexMetadata.getCaseDirectoryPath()), ex);
403 "MultiCaseSearcher.exceptionMessage.failedToGetCaseDatabaseConnectionInfo=Failed to get case database connection info for case {0}",
404 "# {0} - PostgreSQL server host",
405 "# {1} - PostgreSQL server port",
406 "# {2} - case database name",
407 "# {3} - case directory",
408 "MultiCaseSearcher.exceptionMessage.errorOpeningCaseDatabase=Error connecting to PostgreSQL server (Host/Port: [{0}:{1}] and opening case database {2} for case at {3}"
410 private SleuthkitCase openCase(MultiCaseMetadata aCase)
throws MultiCaseSearcherException, InterruptedException {
411 CaseDbConnectionInfo dbConnectionInfo;
413 dbConnectionInfo = UserPreferences.getDatabaseConnectionInfo();
414 }
catch (UserPreferencesException ex) {
415 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDatabaseConnectionInfo(aCase.getCaseMetadata().getCaseName()), ex);
417 checkForCancellation();
418 final CaseMetadata caseMetadata = aCase.getCaseMetadata();
420 return SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
421 }
catch (UserPreferencesException | TskCoreException ex) {
422 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_errorOpeningCaseDatabase(dbConnectionInfo.getHost(), dbConnectionInfo.getPort(), caseMetadata.getCaseDatabaseName(), caseMetadata.getCaseDirectory()), ex);
431 private static void closeCase(SleuthkitCase aCase) {
451 "MultiCaseSearcher.exceptionMessage.solrQueryError=Failed to execute query \"{0}\" on case {1}"
453 private Collection<SearchHit> executeQuery(HttpSolrServer solrServer, SolrQuery solrQuery, SleuthkitCase caseDatabase, MultiCaseMetadata aCase)
throws MultiCaseSearcherException, InterruptedException {
454 final List<SearchHit> hits =
new ArrayList<>();
455 final Set<Long> uniqueObjectIds =
new HashSet<>();
456 String cursorMark = CursorMarkParams.CURSOR_MARK_START;
457 boolean allResultsProcessed =
false;
458 while (!allResultsProcessed) {
459 checkForCancellation();
460 solrQuery.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
461 QueryResponse response;
463 checkForCancellation();
464 response = solrServer.query(solrQuery, SolrRequest.METHOD.POST);
465 }
catch (SolrServerException ex) {
466 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_solrQueryError(solrQuery.getQuery(), aCase.getCaseMetadata().getCaseName()), ex);
468 SolrDocumentList resultDocuments = response.getResults();
469 for (SolrDocument resultDoc : resultDocuments) {
470 checkForCancellation();
471 String solrDocumentId = resultDoc.getFieldValue(SOLR_DOC_ID_FIELD).toString();
472 Long solrObjectId = parseSolrObjectId(solrDocumentId);
473 if (!uniqueObjectIds.contains(solrObjectId)) {
474 uniqueObjectIds.add(solrObjectId);
475 checkForCancellation();
476 hits.add(processHit(solrObjectId, caseDatabase, aCase));
479 checkForCancellation();
480 String nextCursorMark = response.getNextCursorMark();
481 if (cursorMark.equals(nextCursorMark)) {
482 allResultsProcessed =
true;
484 cursorMark = nextCursorMark;
496 private static Long parseSolrObjectId(String solrDocumentId) {
503 final String[] solrDocumentIdParts = solrDocumentId.split(SOLR_DOC_ID_PARTS_SEPARATOR);
504 if (1 == solrDocumentIdParts.length) {
505 return Long.parseLong(solrDocumentId);
507 return Long.parseLong(solrDocumentIdParts[0]);
524 "# {0} - Solr document id",
525 "# {1} - case database name",
526 "# {2} - case directory",
527 "MultiCaseSearcher.exceptionMessage.hitProcessingError=Failed to query case database for processing of Solr object id {0} of case {1} in {2}"
530 private static SearchHit processHit(Long solrObjectId, SleuthkitCase caseDatabase, MultiCaseMetadata caseInfo)
throws MultiCaseSearcherException {
532 final long objectId = getObjectIdForSolrObjectId(solrObjectId, caseDatabase);
533 final CaseMetadata caseMetadata = caseInfo.getCaseMetadata();
534 final String caseDisplayName = caseMetadata.getCaseDisplayName();
535 final String caseDirectoryPath = caseMetadata.getCaseDirectory();
536 final Content content = caseDatabase.getContentById(objectId);
537 final Content dataSource = content.getDataSource();
538 final String dataSourceName = (dataSource == null) ?
"" : dataSource.getName();
539 SearchHit.SourceType sourceType = SearchHit.SourceType.FILE;
540 String sourceName =
"";
541 String sourcePath =
"";
542 if (content instanceof AbstractFile) {
543 AbstractFile sourceFile = (AbstractFile) content;
544 sourceName = sourceFile.getName();
545 sourcePath = sourceFile.getLocalAbsPath();
546 if (null == sourcePath) {
547 sourceType = SearchHit.SourceType.FILE;
548 sourcePath = sourceFile.getUniquePath();
550 sourceType = SearchHit.SourceType.LOCAL_FILE;
551 sourceName = sourceFile.getName();
553 }
else if (content instanceof BlackboardArtifact) {
554 BlackboardArtifact sourceArtifact = (BlackboardArtifact) content;
555 sourceType = SearchHit.SourceType.ARTIFACT;
556 BlackboardArtifact.Type artifactType = caseDatabase.getArtifactType(sourceArtifact.getArtifactTypeName());
557 sourceName = artifactType.getDisplayName();
558 Content source = sourceArtifact.getParent();
559 if (source instanceof AbstractFile) {
560 AbstractFile sourceFile = (AbstractFile) source;
561 sourcePath = sourceFile.getLocalAbsPath();
562 if (null == sourcePath) {
563 sourcePath = sourceFile.getUniquePath();
566 sourcePath = source.getUniquePath();
568 }
else if (content instanceof Report) {
569 Report report = (Report) content;
570 sourceType = SearchHit.SourceType.REPORT;
571 sourceName = report.getReportName();
572 sourcePath = report.getUniquePath();
575 return new SearchHit(caseDisplayName, caseDirectoryPath, dataSourceName, sourceType, sourceName, sourcePath);
576 }
catch (SQLException | TskCoreException ex) {
577 throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_hitProcessingError(solrObjectId, caseInfo.getCaseMetadata().getCaseName(), caseInfo.getCaseMetadata().getCaseDirectory()), ex);
595 private static long getObjectIdForSolrObjectId(
long solrObjectId, SleuthkitCase caseDatabase)
throws MultiCaseSearcherException, TskCoreException, SQLException {
596 if (0 < solrObjectId) {
599 try (SleuthkitCase.CaseDbQuery databaseQuery = caseDatabase.executeQuery(
"SELECT artifact_obj_id FROM blackboard_artifacts WHERE artifact_id = " + solrObjectId)) {
600 final ResultSet resultSet = databaseQuery.getResultSet();
601 if (resultSet.next()) {
602 return resultSet.getLong(
"artifact_obj_id");
604 throw new TskCoreException(
"Empty result set getting obj_id for artifact with artifact_id =" + solrObjectId);
616 private void checkForCancellation() throws InterruptedException {
617 if (Thread.currentThread().isInterrupted() || searchStopped) {
618 throw new InterruptedException(
"Search Cancelled");
638 this.textIndexMetadata = textIndexMetaData;
679 private TextIndexMetadata(Path caseDirectoryPath, String solrCoreName, String textIndexUNCPath) {
718 static final class MultiCaseSearcherException
extends Exception {
720 private static final long serialVersionUID = 1L;
728 private MultiCaseSearcherException(String message) {
739 private MultiCaseSearcherException(String message, Throwable cause) {
740 super(message, cause);
749 void stopMultiCaseSearch() {
752 searchStopped =
true;
761 void registerWithEventBus(Object
object) {
762 eventBus.register(
object);
771 void unregisterWithEventBus(Object
object) {
772 eventBus.unregister(
object);
TextIndexMetadata(Path caseDirectoryPath, String solrCoreName, String textIndexUNCPath)
String getTextIndexPath()
final Path caseDirectoryPath
final String solrCoreName
Path getCaseDirectoryPath()
final String textIndexUNCPath