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)