diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java index 87ddac3d22..e51c1c2620 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java @@ -18,71 +18,99 @@ */ package org.sleuthkit.autopsy.keywordsearch; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.Comparator; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Stores the fact that file or an artifact associated with a file had a keyword - * hit. All instances make both the document id of the Solr document where the - * keyword was found and the object Id available to clients. Artifact keyword - * hits also make the artifact available to clients. + * Represents the fact that a file or an artifact associated with a file had a + * keyword hit. All instances make both the document id of the Solr document + * where the keyword was found and the object id of the file available to + * clients. Keyword hits on the indexed text of an artifact also make the + * artifact available to clients. */ class KeywordHit implements Comparable { + private static final String GET_CONTENT_ID_FROM_ARTIFACT_ID = "SELECT obj_id FROM blackboard_artifacts WHERE artifact_id = "; + private final String solrDocumentId; private final long solrObjectId; private final int chunkId; private final String snippet; private final long contentID; - private final BlackboardArtifact artifact; + private final boolean hitOnArtifact; private final String hit; - public String getHit() { - return hit; - } - + /** + * Constructor + * + * @param solrDocumentId The id of the document this hit is in. + * @param snippet A small amount of text from the document containing + * the hit. + * @param hit The exact text from the document that was the hit. + * For some searches (ie substring, regex) this will be + * different than the search term. + * + * @throws TskCoreException If there is a problem getting the underlying + * content associated with a hit on the text of an + * artifact. + */ KeywordHit(String solrDocumentId, String snippet, String hit) throws TskCoreException { this.snippet = StringUtils.stripToEmpty(snippet); this.hit = hit; this.solrDocumentId = solrDocumentId; - /** + /* * Parse the Solr document id to get the Solr object id and chunk id. - * The Solr object id will either be a file id or an artifact id from - * the case database. + * The Solr object id will either be the object id of a file id or an + * artifact id from the case database. * * For every object (file or artifact) there will at least two Solr * documents. One contains object metadata (chunk #1) and the second and * subsequent documents contain chunks of the text. */ - final int separatorIndex = solrDocumentId.indexOf(Server.CHUNK_ID_SEPARATOR); - if (-1 != separatorIndex) { - this.solrObjectId = Long.parseLong(solrDocumentId.substring(0, separatorIndex)); - this.chunkId = Integer.parseInt(solrDocumentId.substring(separatorIndex + 1)); - } else { + String[] split = solrDocumentId.split(Server.CHUNK_ID_SEPARATOR); + if (split.length == 1) { + //chunk 0 has only the bare document id without the chunk id. this.solrObjectId = Long.parseLong(solrDocumentId); this.chunkId = 0; + } else { + this.solrObjectId = Long.parseLong(split[0]); + this.chunkId = Integer.parseInt(split[1]); } - /* - * If the high order bit of the object id is set (ie, it is negative), - * the hit was in an artifact, look up the artifact. - */ - if (this.solrObjectId < 0) { + //artifacts have negative obj ids + hitOnArtifact = this.solrObjectId < 0; + + if (hitOnArtifact) { + // If the hit was in an artifact, look up the source content for the artifact. SleuthkitCase caseDb = Case.getCurrentCase().getSleuthkitCase(); - this.artifact = caseDb.getBlackboardArtifact(this.solrObjectId); - contentID = artifact.getObjectID(); + try (SleuthkitCase.CaseDbQuery executeQuery = + caseDb.executeQuery(GET_CONTENT_ID_FROM_ARTIFACT_ID + this.solrObjectId); + ResultSet resultSet = executeQuery.getResultSet();) { + if (resultSet.next()) { + contentID = resultSet.getLong("obj_id"); + } else { + throw new TskCoreException("Failed to get obj_id for artifact with artifact_id =" + this.solrObjectId + ". No matching artifact was found."); + } + } catch (SQLException ex) { + throw new TskCoreException("Error getting obj_id for artifact with artifact_id =" + this.solrObjectId, ex); + } } else { //else the object id is for content. - this.artifact = null; contentID = this.solrObjectId; } } + String getHit() { + return hit; + } + String getSolrDocumentId() { return this.solrDocumentId; } @@ -113,17 +141,20 @@ class KeywordHit implements Comparable { * @return */ boolean isArtifactHit() { - return (null != this.artifact); + return hitOnArtifact; } /** * If this hit is in the indexed text of an artifact, get that artifact. * - * @return The artifact whose indexed text this hit is in, or null if it is - * not an artifacts hit. + * @return The artifact whose indexed text this hit is in. */ - BlackboardArtifact getArtifact() { - return this.artifact; + Optional getArtifactID() { + if (hitOnArtifact) { + return Optional.of(solrObjectId); + } else { + return Optional.empty(); + } } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java index b603f7b68c..ac7cfae557 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java @@ -186,12 +186,20 @@ class KeywordSearchResultFactory extends ChildFactory { properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet()); } - String hitName = hit.isArtifactHit() - ? hit.getArtifact().getDisplayName() + " Artifact" //NON-NLS - : contentName; - + String hitName; + if (hit.isArtifactHit()) { + try { + hitName = tskCase.getBlackboardArtifact(hit.getArtifactID().get()).getDisplayName() + " Artifact"; //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex); + return false; + } + } else { + hitName = contentName; + } hitNumber++; tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, queryRequest, queryResults)); + } // Add all the nodes to toPopulate at once. Minimizes node creation diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index 93a9e40480..4d3f9cc5b9 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -225,9 +225,11 @@ class LuceneQuery implements KeywordSearchQuery { } } - if (hit.isArtifactHit()) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID())); - } + + hit.getArtifactID().ifPresent(artifactID + -> attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) + ); + try { bba.addAttributes(attributes); //write out to bb diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 6b91c9c2fb..a422d9f9c3 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -417,7 +417,7 @@ final class RegexQuery implements KeywordSearchQuery { final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) { if (hit.isArtifactHit()) { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifact().getArtifactID())); //NON-NLS + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS } else { LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContentID())); //NON-NLS } @@ -480,9 +480,10 @@ final class RegexQuery implements KeywordSearchQuery { if (snippet != null) { attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); } - if (hit.isArtifactHit()) { - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID())); - } + + hit.getArtifactID().ifPresent(artifactID + -> attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) + ); attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal())); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index 62c7dd601d..f37be9952e 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -359,7 +359,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) { if (hit.isArtifactHit()) { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifact().getArtifactID())); //NON-NLS + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS } else { LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), hit.getContentID())); //NON-NLS } @@ -398,7 +398,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { * hit. */ if (content instanceof AbstractFile) { - AbstractFile file = (AbstractFile)content; + AbstractFile file = (AbstractFile) content; if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); @@ -422,9 +422,10 @@ final class TermsComponentQuery implements KeywordSearchQuery { if (snippet != null) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); } - if (hit.isArtifactHit()) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID())); - } + + hit.getArtifactID().ifPresent( + artifactID -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) + ); // TermsComponentQuery is now being used exclusively for substring searches. attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal())); @@ -472,12 +473,12 @@ final class TermsComponentQuery implements KeywordSearchQuery { * Creates an attribute of the the given type to the given artifact with a * value parsed from the snippet for a credit account number hit. * - * @param attributesMap A map of artifact attribute objects, used to avoid - * creating duplicate attributes. - * @param attrType The type of attribute to create. - * @param groupName The group name of the regular expression that was - * used to parse the attribute data. - * @param matcher A matcher for the snippet. + * @param attributeMap A map of artifact attribute objects, used to avoid + * creating duplicate attributes. + * @param attrType The type of attribute to create. + * @param groupName The group name of the regular expression that was + * used to parse the attribute data. + * @param matcher A matcher for the snippet. */ static private void addAttributeIfNotAlreadyCaptured(Map attributeMap, ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) { BlackboardAttribute.Type type = new BlackboardAttribute.Type(attrType);