19 package org.sleuthkit.autopsy.keywordsearch;
21 import com.google.common.base.Predicate;
22 import com.google.common.collect.Iterators;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.LinkedHashMap;
29 import java.util.List;
31 import java.util.TreeMap;
32 import java.util.TreeSet;
33 import java.util.logging.Level;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 import javax.annotation.concurrent.GuardedBy;
37 import org.apache.commons.lang.StringUtils;
38 import org.apache.solr.client.solrj.SolrQuery;
39 import org.apache.solr.client.solrj.SolrRequest.METHOD;
40 import org.apache.solr.client.solrj.response.QueryResponse;
41 import org.openide.util.NbBundle;
57 class AccountsText
implements IndexedText {
62 private static final String HIGHLIGHT_PRE =
"<span style='background:yellow'>";
63 private static final String ANCHOR_NAME_PREFIX = AccountsText.class.getName() +
"_";
65 private static final String INSERT_PREFIX =
"<a name='" + ANCHOR_NAME_PREFIX;
66 private static final String INSERT_POSTFIX =
"'></a>$0";
67 private static final Pattern ANCHOR_DETECTION_PATTERN = Pattern.compile(HIGHLIGHT_PRE);
73 private static final String FIELD =
Server.
Schema.CONTENT_STR.toString();
77 private final long solrObjectId;
78 private final Collection<? extends BlackboardArtifact> artifacts;
79 private final Set<String> accountNumbers =
new HashSet<>();
80 private final String displayName;
83 private boolean isPageInfoLoaded =
false;
84 private int numberPagesForFile = 0;
85 private Integer currentPage = 0;
90 private final TreeMap<Integer, Integer> numberOfHitsPerPage =
new TreeMap<>();
94 private final Set<Integer> pages = numberOfHitsPerPage.keySet();
98 private final HashMap<Integer, Integer> currentHitPerPage =
new HashMap<>();
101 this(objectID, Arrays.asList(artifact));
106 "AccountsText.creditCardNumber=Credit Card Number",
107 "AccountsText.creditCardNumbers=Credit Card Numbers"})
108 AccountsText(
long objectID, Collection<? extends BlackboardArtifact> artifacts) {
109 this.solrObjectId = objectID;
110 this.artifacts = artifacts;
111 displayName = artifacts.size() == 1
112 ? Bundle.AccountsText_creditCardNumber()
113 : Bundle.AccountsText_creditCardNumbers();
117 return this.solrObjectId;
121 public int getNumberPages() {
122 return this.numberPagesForFile;
126 public int getCurrentPage() {
127 return this.currentPage;
131 public boolean hasNextPage() {
132 return getIndexOfCurrentPage() < pages.size() - 1;
137 public boolean hasPreviousPage() {
138 return getIndexOfCurrentPage() > 0;
142 @NbBundle.Messages(
"AccountsText.nextPage.exception.msg=No next page.")
143 public int nextPage() {
145 currentPage =Iterators.get(pages.iterator(),getIndexOfCurrentPage() + 1);
148 throw new IllegalStateException(Bundle.AccountsText_nextPage_exception_msg());
153 @NbBundle.Messages(
"AccountsText.previousPage.exception.msg=No previous page.")
154 public int previousPage() {
155 if (hasPreviousPage()) {
156 currentPage = Iterators.get(pages.iterator(),getIndexOfCurrentPage() - 1);
159 throw new IllegalStateException(Bundle.AccountsText_previousPage_exception_msg());
163 private int getIndexOfCurrentPage() {
164 return Iterators.indexOf(pages.iterator(), this.currentPage::equals);
168 public boolean hasNextItem() {
169 if (this.currentHitPerPage.containsKey(currentPage)) {
170 return this.currentHitPerPage.get(currentPage) < this.numberOfHitsPerPage.get(currentPage);
177 public boolean hasPreviousItem() {
178 if (this.currentHitPerPage.containsKey(currentPage)) {
179 return this.currentHitPerPage.get(currentPage) > 1;
186 @NbBundle.Messages(
"AccountsText.nextItem.exception.msg=No next item.")
187 public int nextItem() {
189 return currentHitPerPage.merge(currentPage, 1, Integer::sum);
191 throw new IllegalStateException(Bundle.AccountsText_nextItem_exception_msg());
196 @NbBundle.Messages(
"AccountsText.previousItem.exception.msg=No previous item.")
197 public int previousItem() {
198 if (hasPreviousItem()) {
199 return currentHitPerPage.merge(currentPage, -1, Integer::sum);
201 throw new IllegalStateException(Bundle.AccountsText_previousItem_exception_msg());
206 public int currentItem() {
207 if (this.currentHitPerPage.containsKey(currentPage)) {
208 return currentHitPerPage.get(currentPage);
218 synchronized private void loadPageInfo()
throws IllegalStateException,
TskCoreException {
219 if (isPageInfoLoaded) {
226 logger.log(Level.WARNING,
"Could not get number pages for content " +
this.solrObjectId, ex);
231 addToPagingInfo(artifact);
234 this.currentPage = pages.stream().findFirst().orElse(1);
236 isPageInfoLoaded =
true;
241 throw new IllegalStateException(
"not all artifacts are from the same object!");
246 if (keywordAttribute != null) {
249 List<String> rawDocIDs =
new ArrayList<>();
253 rawDocIDs.add(docID.getValueString());
259 .map(Integer::valueOf)
260 .forEach(chunkID -> {
261 numberOfHitsPerPage.put(chunkID, 0);
262 currentHitPerPage.put(chunkID, 0);
267 @NbBundle.Messages({
"AccountsText.getMarkup.noMatchMsg="
268 +
"<html><pre><span style\\\\='background\\\\:yellow'>There were no keyword hits on this page. <br />"
269 +
"The keyword could have been in the file name."
270 +
" <br />Advance to another page if present, or to view the original text, choose File Text"
271 +
" <br />in the drop down menu to the right...</span></pre></html>",
272 "AccountsText.getMarkup.queryFailedMsg="
273 +
"<html><pre><span style\\\\='background\\\\:yellow'>Failed to retrieve keyword hit results."
274 +
" <br />Confirm that Autopsy can connect to the Solr server. "
275 +
"<br /></span></pre></html>"})
276 public String getText() {
280 SolrQuery q =
new SolrQuery();
281 q.setShowDebugInfo(DEBUG);
284 final String filterQuery =
Server.
Schema.ID.toString() +
":" + contentIdStr;
286 q.setQuery(filterQuery);
289 QueryResponse queryResponse = solrServer.
query(q, METHOD.POST);
291 String highlightedText
292 = HighlightedText.attemptManualHighlighting(
293 queryResponse.getResults(),
298 highlightedText = insertAnchors(highlightedText);
301 return "<html><pre>" + highlightedText +
"</pre></html>";
302 }
catch (Exception ex) {
303 logger.log(Level.WARNING,
"Error getting highlighted text for " + solrObjectId, ex);
304 return Bundle.AccountsText_getMarkup_queryFailedMsg();
316 private String insertAnchors(String searchableContent) {
321 Matcher m = ANCHOR_DETECTION_PATTERN.matcher(searchableContent);
322 StringBuffer sb =
new StringBuffer(searchableContent.length());
326 m.appendReplacement(sb, INSERT_PREFIX + count + INSERT_POSTFIX);
330 this.numberOfHitsPerPage.put(this.currentPage, count);
331 if (this.currentItem() == 0 && this.hasNextItem()) {
334 return sb.toString();
338 public String toString() {
343 public boolean isSearchable() {
348 public String getAnchorPrefix() {
349 return ANCHOR_NAME_PREFIX;
353 public int getNumberHits() {
354 if (!this.numberOfHitsPerPage.containsKey(
this.currentPage)) {
357 return this.numberOfHitsPerPage.get(this.currentPage);
static Version.Type getBuildType()
static synchronized Server getServer()
TSK_KEYWORD_SEARCH_DOCUMENT_ID
BlackboardAttribute getAttribute(BlackboardAttribute.Type attributeType)
static final String CHUNK_ID_SEPARATOR
QueryResponse query(SolrQuery sq)
synchronized static Logger getLogger(String name)
int queryNumFileChunks(long fileID)