org.sleuthkit.autopsy.keywordsearch.ExtractedContentViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.sleuthkit.autopsy.keywordsearch.ExtractedContentViewer.java

Source

/*
 * Autopsy Forensic Browser
 *
 * Copyright 2011 Basis Technology Corp.
 * Contact: carrier <at> sleuthkit <dot> org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sleuthkit.autopsy.keywordsearch;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.solr.client.solrj.SolrServerException;
import org.openide.nodes.Node;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.apache.commons.lang.StringEscapeUtils;
import org.sleuthkit.autopsy.datamodel.HighlightLookup;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.Directory;

/**
 * Displays marked-up (HTML) content for a Node. The sources are all the 
 * MarkupSource items in the selected Node's lookup, plus the content that
 * Solr extracted (if there is any).
 */
@ServiceProvider(service = DataContentViewer.class, position = 4)
public class ExtractedContentViewer implements DataContentViewer {

    private static final Logger logger = Logger.getLogger(ExtractedContentViewer.class.getName());
    private ExtractedContentPanel panel;
    private Node currentNode = null;
    private MarkupSource currentSource = null;

    //keep last content cached
    private String curContent;
    private long curContentId;
    private int curContentChunk;

    public ExtractedContentViewer() {
    }

    @Override
    public void setNode(final Node selectedNode) {
        //TODO why setNode() is called twice for the same node each time

        // to clear the viewer
        if (selectedNode == null) {
            currentNode = null;
            resetComponent();
            return;
        }

        this.currentNode = selectedNode;

        // sources are custom markup from the node (if available) and default
        // markup is fetched from solr
        List<MarkupSource> sources = new ArrayList<MarkupSource>();

        //add additional registered sources for this node
        sources.addAll(selectedNode.getLookup().lookupAll(MarkupSource.class));

        if (solrHasContent(selectedNode)) {
            Content content = selectedNode.getLookup().lookup(Content.class);
            if (content == null) {
                return;
            }

            //add to page tracking if not there yet
            final long contentID = content.getId();

            MarkupSource newSource = new MarkupSource() {

                private boolean inited = false;
                private int numPages = 0;
                private int currentPage = 0;
                private boolean hasChunks = false;

                @Override
                public int getCurrentPage() {
                    return this.currentPage;
                }

                @Override
                public boolean hasNextPage() {
                    return currentPage < numPages;
                }

                @Override
                public boolean hasPreviousPage() {
                    return currentPage > 1;
                }

                @Override
                public int nextPage() {
                    if (!hasNextPage()) {
                        throw new IllegalStateException("No next page.");
                    }
                    ++currentPage;
                    return currentPage;
                }

                @Override
                public int previousPage() {
                    if (!hasPreviousPage()) {
                        throw new IllegalStateException("No previous page.");
                    }
                    --currentPage;
                    return currentPage;
                }

                @Override
                public boolean hasNextItem() {
                    throw new UnsupportedOperationException("Not supported, not a searchable source.");
                }

                @Override
                public boolean hasPreviousItem() {
                    throw new UnsupportedOperationException("Not supported, not a searchable source.");
                }

                @Override
                public int nextItem() {
                    throw new UnsupportedOperationException("Not supported, not a searchable source.");
                }

                @Override
                public int previousItem() {
                    throw new UnsupportedOperationException("Not supported, not a searchable source.");
                }

                @Override
                public int currentItem() {
                    throw new UnsupportedOperationException("Not supported, not a searchable source.");
                }

                @Override
                public String getMarkup() {
                    try {
                        curContent = StringEscapeUtils
                                .escapeHtml(getSolrContent(selectedNode, currentPage, hasChunks));
                        curContent = "<pre>" + curContent.trim() + "</pre>";
                        return curContent;
                    } catch (SolrServerException ex) {
                        logger.log(Level.WARNING, "Couldn't get extracted content.", ex);
                        return "";
                    }
                }

                @Override
                public String toString() {
                    return "Extracted Content";
                }

                @Override
                public boolean isSearchable() {
                    return false;
                }

                @Override
                public String getAnchorPrefix() {
                    return "";
                }

                @Override
                public int getNumberHits() {
                    return 0;
                }

                @Override
                public LinkedHashMap<Integer, Integer> getHitsPages() {
                    return null;
                }

                @Override
                public int getNumberPages() {
                    if (inited) {
                        return this.numPages;
                    }

                    final Server solrServer = KeywordSearch.getServer();

                    try {
                        numPages = solrServer.queryNumFileChunks(contentID);
                        if (numPages == 0) {
                            numPages = 1;
                            hasChunks = false;
                        } else {
                            hasChunks = true;
                        }
                        inited = true;
                    } catch (SolrServerException ex) {
                        logger.log(Level.WARNING, "Could not get number of chunks: ", ex);

                    } catch (NoOpenCoreException ex) {
                        logger.log(Level.WARNING, "Could not get number of chunks: ", ex);
                    }
                    return numPages;
                }
            };

            currentSource = newSource;
            sources.add(newSource);

            //init pages
            final int totalPages = currentSource.getNumberPages();
            int currentPage = currentSource.getCurrentPage();
            if (currentPage == 0 && currentSource.hasNextPage()) {
                currentSource.nextPage();
            }

            updatePageControls();
        }

        // first source will be the default displayed
        setPanel(sources);
        // If node has been selected before, return to the previous position
        scrollToCurrentHit();
    }

    private void scrollToCurrentHit() {
        final MarkupSource source = panel.getSelectedSource();
        if (source == null || !source.isSearchable()) {
            return;
        }

        // using invokeLater to wait for ComboBox selection to complete
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                panel.scrollToAnchor(source.getAnchorPrefix() + Integer.toString(source.currentItem()));
            }
        });
    }

    @Override
    public String getTitle() {
        return "Text View";
    }

    @Override
    public String getToolTip() {
        return "Displays extracted text and keyword-search results.";
    }

    @Override
    public DataContentViewer getInstance() {
        return new ExtractedContentViewer();
    }

    @Override
    public Component getComponent() {
        if (panel == null) {
            panel = new ExtractedContentPanel();
            panel.addPrevMatchControlListener(new PrevFindActionListener());
            panel.addNextMatchControlListener(new NextFindActionListener());
            panel.addPrevPageControlListener(new PrevPageActionListener());
            panel.addNextPageControlListener(new NextPageActionListener());
            panel.addSourceComboControlListener(new SourceChangeActionListener());
        }
        return panel;
    }

    @Override
    public void resetComponent() {
        setPanel(new ArrayList<MarkupSource>());
        panel.resetDisplay();
    }

    @Override
    public boolean isSupported(Node node) {
        if (node == null) {
            return false;
        }

        Collection<? extends MarkupSource> sources = node.getLookup().lookupAll(MarkupSource.class);
        HighlightLookup highlight = node.getLookup().lookup(HighlightLookup.class);

        return !sources.isEmpty() || highlight != null || solrHasContent(node);
    }

    @Override
    public int isPreferred(Node node, boolean isSupported) {
        BlackboardArtifact art = node.getLookup().lookup(BlackboardArtifact.class);
        if (isSupported) {
            if (art == null) {
                return 4;
            } else if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
                return 6;
            } else {
                return 4;
            }
        } else {
            return 0;
        }
    }

    /**
     * Set the MarkupSources for the panel to display (safe to call even if the
     * panel hasn't been created yet)
     * @param sources 
     */
    private void setPanel(List<MarkupSource> sources) {
        if (panel != null) {
            panel.setSources(sources);
        }
    }

    //get current node content id, or 0 if not available
    private long getNodeContentId() {
        Content content = currentNode.getLookup().lookup(Content.class);
        if (content == null) {
            return 0;
        }

        return content.getId();
    }

    private class IsDirVisitor extends ContentVisitor.Default<Boolean> {

        @Override
        protected Boolean defaultVisit(Content cntnt) {
            return false;
        }

        @Override
        public Boolean visit(Directory d) {
            return true;
        }
    }

    /**
     * Check if Solr has extracted content for a given node
     * @param node
     * @return true if Solr has content, else false
     */
    private boolean solrHasContent(Node node) {
        Content content = node.getLookup().lookup(Content.class);
        if (content == null) {
            return false;
        }

        if (content.getSize() == 0)
            return false;

        final Server solrServer = KeywordSearch.getServer();

        boolean isDir = content.accept(new IsDirVisitor());
        if (isDir) {
            return false;
        }

        final long contentID = content.getId();

        try {
            return solrServer.queryIsIndexed(contentID);
        } catch (NoOpenCoreException ex) {
            logger.log(Level.WARNING, "Couldn't determine whether content is supported.", ex);
            return false;
        } catch (SolrServerException ex) {
            logger.log(Level.WARNING, "Couldn't determine whether content is supported.", ex);
            return false;
        }
    }

    /**
     * Get extracted content for a node from Solr
     * @param node a node that has extracted content in Solr (check with
     * solrHasContent(ContentNode))
     * @param currentPage currently used page 
     * @param hasChunks true if the content behind the node has multiple chunks. This means we need to address the content pages specially.
     * @return the extracted content
     * @throws SolrServerException if something goes wrong
     */
    private String getSolrContent(Node node, int currentPage, boolean hasChunks) throws SolrServerException {
        Content contentObj = node.getLookup().lookup(Content.class);

        final Server solrServer = KeywordSearch.getServer();

        int chunkId = 0;
        if (hasChunks) {
            chunkId = currentPage;
        }

        //check if cached
        long contentId = getNodeContentId();
        if (curContent != null) {
            if (contentId == curContentId && curContentChunk == chunkId) {
                return curContent;
            }
        }

        //not cached
        try {
            curContent = solrServer.getSolrContent(contentObj, chunkId);
            curContentId = contentId;
            curContentChunk = chunkId;
        } catch (NoOpenCoreException ex) {
            logger.log(Level.WARNING, "Couldn't get text content.", ex);
            return "";
        }
        return curContent;
    }

    private class NextFindActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            MarkupSource source = panel.getSelectedSource();
            final boolean hasNextItem = source.hasNextItem();
            final boolean hasNextPage = source.hasNextPage();
            int indexVal = 0;
            if (hasNextItem || hasNextPage) {
                if (!hasNextItem) {
                    //flip the page
                    nextPage();
                    indexVal = source.currentItem();
                } else {
                    indexVal = source.nextItem();
                }

                //scroll
                panel.scrollToAnchor(source.getAnchorPrefix() + Integer.toString(indexVal));

                //update display
                panel.updateCurrentMatchDisplay(source.currentItem());
                panel.updateTotaMatcheslDisplay(source.getNumberHits());

                //update controls if needed
                if (!source.hasNextItem() && !source.hasNextPage()) {
                    panel.enableNextMatchControl(false);
                }
                if (source.hasPreviousItem() || source.hasPreviousPage()) {
                    panel.enablePrevMatchControl(true);
                }
            }
        }
    }

    private class PrevFindActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            MarkupSource source = panel.getSelectedSource();
            final boolean hasPreviousItem = source.hasPreviousItem();
            final boolean hasPreviousPage = source.hasPreviousPage();
            int indexVal = 0;
            if (hasPreviousItem || hasPreviousPage) {
                if (!hasPreviousItem) {
                    //flip the page
                    previousPage();
                    indexVal = source.currentItem();
                } else {
                    indexVal = source.previousItem();
                }

                //scroll
                panel.scrollToAnchor(source.getAnchorPrefix() + Integer.toString(indexVal));

                //update display
                panel.updateCurrentMatchDisplay(source.currentItem());
                panel.updateTotaMatcheslDisplay(source.getNumberHits());

                //update controls if needed
                if (!source.hasPreviousItem() && !source.hasPreviousPage()) {
                    panel.enablePrevMatchControl(false);
                }
                if (source.hasNextItem() || source.hasNextPage()) {
                    panel.enableNextMatchControl(true);
                }
            }
        }
    }

    private class SourceChangeActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            currentSource = panel.getSelectedSource();

            if (currentSource == null) {
                //TODO might need to reset something
                return;
            }

            final int totalPages = currentSource.getNumberPages();
            final int currentPage = currentSource.getCurrentPage();

            updatePageControls();
            updateSearchControls();

        }
    }

    private void updateSearchControls() {
        //setup search controls
        if (currentSource != null && currentSource.isSearchable()) {

            panel.updateCurrentMatchDisplay(currentSource.currentItem());
            panel.updateTotaMatcheslDisplay(currentSource.getNumberHits());

            if (currentSource.hasNextItem() || currentSource.hasNextPage()) {
                panel.enableNextMatchControl(true);
            } else {
                panel.enableNextMatchControl(false);
            }

            if (currentSource.hasPreviousItem() || currentSource.hasPreviousPage()) {
                panel.enablePrevMatchControl(true);
            } else {
                panel.enablePrevMatchControl(false);
            }

        } else {
            panel.enableNextMatchControl(false);
            panel.enablePrevMatchControl(false);
            panel.updateCurrentMatchDisplay(0);
            panel.updateTotaMatcheslDisplay(0);
        }
    }

    private void updatePageControls() {
        if (currentSource == null) {
            return;
        }

        final int currentPage = currentSource.getCurrentPage();
        final int totalPages = currentSource.getNumberPages();
        panel.updateTotalPageslDisplay(totalPages);
        panel.updateCurrentPageDisplay(currentPage);

        if (totalPages == 1) {
            panel.enableNextPageControl(false);
            panel.enablePrevPageControl(false);
        } else {
            if (currentSource.hasNextPage()) {
                panel.enableNextPageControl(true);
            } else {
                panel.enableNextPageControl(false);
            }

            if (currentSource.hasPreviousPage()) {
                panel.enablePrevPageControl(true);
            } else {
                panel.enablePrevPageControl(false);
            }
        }

    }

    private void nextPage() {
        if (currentSource.hasNextPage()) {
            currentSource.nextPage();

            //set new text
            panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            panel.refreshCurrentMarkup();
            panel.setCursor(null);

            //update display
            panel.updateCurrentPageDisplay(currentSource.getCurrentPage());

            //scroll to current selection
            ExtractedContentViewer.this.scrollToCurrentHit();

            //update controls if needed
            if (!currentSource.hasNextPage()) {
                panel.enableNextPageControl(false);
            }
            if (currentSource.hasPreviousPage()) {
                panel.enablePrevPageControl(true);
            }

            updateSearchControls();
        }
    }

    private void previousPage() {
        if (currentSource.hasPreviousPage()) {
            currentSource.previousPage();

            //set new text
            panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            panel.refreshCurrentMarkup();
            panel.setCursor(null);

            //update display
            panel.updateCurrentPageDisplay(currentSource.getCurrentPage());

            //scroll to current selection
            ExtractedContentViewer.this.scrollToCurrentHit();

            //update controls if needed
            if (!currentSource.hasPreviousPage()) {
                panel.enablePrevPageControl(false);
            }
            if (currentSource.hasNextPage()) {
                panel.enableNextPageControl(true);
            }

            updateSearchControls();

        }
    }

    class NextPageActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            nextPage();
        }
    }

    private class PrevPageActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            previousPage();
        }
    }
}