annis.gui.resultview.VisualizerPanel.java Source code

Java tutorial

Introduction

Here is the source code for annis.gui.resultview.VisualizerPanel.java

Source

/*
 * Copyright 2011 Corpuslinguistic working group Humboldt University Berlin.
 *
 * 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 annis.gui.resultview;

import annis.CommonHelper;
import annis.libgui.Background;
import annis.libgui.Helper;
import annis.libgui.InstanceConfig;
import annis.libgui.PluginSystem;
import annis.libgui.VisualizationToggle;
import annis.libgui.media.MediaController;
import annis.libgui.media.MediaPlayer;
import annis.libgui.media.PDFViewer;
import annis.libgui.visualizers.FilteringVisualizerPlugin;
import annis.libgui.visualizers.VisualizerInput;
import annis.libgui.visualizers.VisualizerPlugin;
import annis.resolver.ResolverEntry;
import annis.visualizers.LoadableVisualizer;
import com.google.common.base.Joiner;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.Resource;
import com.vaadin.server.StreamResource;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Component;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.UI;
import com.vaadin.ui.themes.ChameleonTheme;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.corpus_tools.salt.common.SDocument;
import org.corpus_tools.salt.common.SDocumentGraph;
import org.corpus_tools.salt.common.SToken;
import org.corpus_tools.salt.common.SaltProject;
import org.corpus_tools.salt.core.SFeature;
import org.corpus_tools.salt.core.SNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Controls the visibility of visualizer plugins and provides some control
 * methods for the media visualizers.
 *
 * @author Thomas Krause <krauseto@hu-berlin.de>
 * @author Benjamin Weienfels <b.pixeldrama@gmail.com>
 *
 */
public class VisualizerPanel extends CssLayout implements Button.ClickListener, VisualizationToggle {
    public static final long serialVersionUID = 2L;

    private final Logger log = LoggerFactory.getLogger(VisualizerPanel.class);

    public static final Resource ICON_COLLAPSE = FontAwesome.MINUS_SQUARE_O;

    public static final Resource ICON_EXPAND = FontAwesome.PLUS_SQUARE_O;

    private String corpusName;

    private String documentName;

    private Component vis;

    private SDocument result;

    private PluginSystem ps;

    private ResolverEntry entry;

    private Map<String, Long> markedAndCovered;

    private Map<String, String> markersExact;

    private Map<String, String> markersCovered;

    private Button btEntry;

    private String htmlID;

    private String resultID;

    private VisualizerPlugin visPlugin;

    private Set<String> visibleTokenAnnos;

    private String segmentationName;

    private final String PERMANENT = "permanent";

    private final String ISVISIBLE = "visible";

    private final String HIDDEN = "hidden";

    private final String PRELOADED = "preloaded";

    private ProgressBar progress;

    private InstanceConfig instanceConfig;

    private VisualizerContextChanger visCtxChanger;

    private final static Escaper urlPathEscape = UrlEscapers.urlPathSegmentEscaper();

    /**
     * This Constructor should be used for {@link ComponentVisualizerPlugin}
     * Visualizer.
     *
     */
    public VisualizerPanel(final ResolverEntry entry, SDocument result, String corpusName, String documentName,
            Set<String> visibleTokenAnnos, Map<String, Long> markedAndCovered,
            @Deprecated Map<String, String> markedAndCoveredMap, @Deprecated Map<String, String> markedExactMap,
            String htmlID, String resultID, SingleResultPanel parent, String segmentationName, PluginSystem ps,
            InstanceConfig instanceConfig) throws IOException {

        this.ps = ps;
        this.instanceConfig = instanceConfig;
        this.entry = entry;
        this.markersExact = markedExactMap;
        this.markersCovered = markedAndCoveredMap;

        this.visCtxChanger = parent;

        this.result = result;
        this.corpusName = corpusName;
        this.documentName = documentName;
        this.visibleTokenAnnos = visibleTokenAnnos;
        this.markedAndCovered = markedAndCovered;
        this.segmentationName = segmentationName;
        this.htmlID = htmlID;
        this.resultID = resultID;

        this.progress = new ProgressBar();
        this.progress.setIndeterminate(true);
        this.progress.setVisible(false);
        this.progress.setEnabled(false);

        this.addStyleName(ChameleonTheme.PANEL_BORDERLESS);
        this.setWidth("100%");

        if (entry != null && ps != null) {
            visPlugin = ps.getVisualizer(entry.getVisType());
            if (visPlugin == null) {
                // fallback to default visualizer if original vis type was not found
                entry.setVisType(PluginSystem.DEFAULT_VISUALIZER);
                visPlugin = ps.getVisualizer(entry.getVisType());
            }

            if (HIDDEN.equalsIgnoreCase(entry.getVisibility())) {
                // build button for visualizer
                btEntry = new Button(entry.getDisplayName());
                btEntry.setIcon(ICON_EXPAND);
                btEntry.setStyleName(ChameleonTheme.BUTTON_BORDERLESS + " " + ChameleonTheme.BUTTON_SMALL);
                btEntry.addClickListener((Button.ClickListener) this);
                btEntry.setDisableOnClick(true);

                addComponent(btEntry);
                addComponent(progress);
            } else {

                if (ISVISIBLE.equalsIgnoreCase(entry.getVisibility())
                        || PRELOADED.equalsIgnoreCase(entry.getVisibility())) {
                    // build button for visualizer
                    btEntry = new Button(entry.getDisplayName());
                    btEntry.setIcon(ICON_COLLAPSE);
                    btEntry.setStyleName(ChameleonTheme.BUTTON_BORDERLESS + " " + ChameleonTheme.BUTTON_SMALL);
                    btEntry.addClickListener((Button.ClickListener) this);
                    addComponent(btEntry);
                }

                addComponent(progress);

                // create the visualizer and calc input
                try {
                    vis = createComponent();
                    if (vis != null) {
                        vis.setVisible(true);
                        addComponent(vis);
                    }
                } catch (Exception ex) {
                    Notification.show("Could not create visualizer " + visPlugin.getShortName(), ex.toString(),
                            Notification.Type.TRAY_NOTIFICATION);
                    log.error("Could not create visualizer " + visPlugin.getShortName(), ex);
                }

                if (btEntry != null && PRELOADED.equalsIgnoreCase(entry.getVisibility())) {
                    btEntry.setIcon(ICON_EXPAND);
                    if (vis != null) {
                        vis.setVisible(false);
                    }
                }

            }

        } // end if entry not null

    }

    private List<SToken> createTokenList(List<String> tokenIDs, SDocumentGraph graph) {
        if (tokenIDs == null || graph == null) {
            return new LinkedList<>();
        }
        ArrayList<SToken> r = new ArrayList<>(tokenIDs.size());
        for (String t : tokenIDs) {
            SNode n = graph.getNode(t);
            if (n instanceof SToken) {
                r.add((SToken) n);
            }
        }
        return r;
    }

    private Component createComponent() {
        if (visPlugin == null) {
            return null;
        }

        final VisualizerInput input = createInput();

        Component c = visPlugin.createComponent(input, this);
        c.setVisible(false);
        c.addStyleName(Helper.CORPUS_FONT);
        c.addStyleName("vis-content");

        return c;
    }

    private VisualizerInput createInput() {
        VisualizerInput input = new VisualizerInput();
        input.setAnnisWebServiceURL((String) VaadinSession.getCurrent().getAttribute("AnnisWebService.URL"));
        input.setContextPath(Helper.getContext());
        input.setDotPath((String) VaadinSession.getCurrent().getAttribute("DotPath"));

        input.setId(resultID);

        input.setMarkableExactMap(markersExact);
        input.setMarkableMap(markersCovered);
        input.setMarkedAndCovered(markedAndCovered);

        input.setResult(result);
        input.setVisibleTokenAnnos(visibleTokenAnnos);
        input.setSegmentationName(segmentationName);
        if (instanceConfig != null && instanceConfig.getFont() != null) {
            input.setFont(instanceConfig.getFont());
        }

        if (entry != null) {
            input.setMappings(entry.getMappings());
            input.setNamespace(entry.getNamespace());
            String template = Helper.getContext() + "/Resource/" + entry.getVisType() + "/%s";
            input.setResourcePathTemplate(template);
        }

        // getting the whole document, when plugin is using text
        if (visPlugin != null && visPlugin.isUsingText() && result != null
                && result.getDocumentGraph().getNodes().size() > 0) {
            List<String> nodeAnnoFilter = null;
            if (visPlugin instanceof FilteringVisualizerPlugin) {
                nodeAnnoFilter = ((FilteringVisualizerPlugin) visPlugin).getFilteredNodeAnnotationNames(corpusName,
                        documentName, input.getMappings());
            }
            SaltProject p = getDocument(result.getGraph().getRoots().get(0).getName(), result.getName(),
                    nodeAnnoFilter);

            SDocument wholeDocument = p.getCorpusGraphs().get(0).getDocuments().get(0);

            input.setDocument(wholeDocument);
        } else {
            input.setDocument(result);
        }

        // getting the raw text, when the visualizer wants to have it
        if (visPlugin != null && visPlugin.isUsingRawText()) {
            input.setRawText(Helper.getRawText(corpusName, documentName));
        }

        return input;
    }

    public void setVisibleTokenAnnosVisible(SortedSet<String> annos) {
        this.visibleTokenAnnos = annos;
        if (visPlugin != null && vis != null) {
            visPlugin.setVisibleTokenAnnosVisible(vis, annos);
        }
    }

    public void setSegmentationLayer(String segmentationName, Map<String, Long> markedAndCovered) {
        this.segmentationName = segmentationName;
        this.markedAndCovered = markedAndCovered;

        if (visPlugin != null && vis != null) {
            visPlugin.setSegmentationLayer(vis, segmentationName, markedAndCovered);
        }
    }

    private SaltProject getDocument(String toplevelCorpusName, String documentName, List<String> nodeAnnoFilter) {
        SaltProject txt = null;
        try {
            toplevelCorpusName = urlPathEscape.escape(toplevelCorpusName);
            documentName = urlPathEscape.escape(documentName);
            WebResource res = Helper.getAnnisWebResource().path("query").path("graph").path(toplevelCorpusName)
                    .path(documentName);
            if (nodeAnnoFilter != null) {
                res = res.queryParam("filternodeanno", Joiner.on(",").join(nodeAnnoFilter));
            }
            txt = res.get(SaltProject.class);
        } catch (ClientHandlerException | UniformInterfaceException e) {
            log.error("General remote service exception", e);
        }
        return txt;
    }

    @Override
    public void buttonClick(ClickEvent event) {

        boolean isVisible = !visualizerIsVisible();

        // register new state by the parent SingleResultPanel, so the state will be
        // still available, after a reload
        visCtxChanger.registerVisibilityStatus(entry.getId(), isVisible);

        // start the toogle process.
        toggleVisualizer(isVisible, null);
    }

    @Override
    public boolean visualizerIsVisible() {
        if (vis == null || !vis.isVisible()) {
            return false;
        }
        return true;
    }

    private void loadVisualizer(final LoadableVisualizer.Callback callback) {
        if (visPlugin != null) {
            btEntry.setIcon(ICON_COLLAPSE);
            progress.setIndeterminate(true);
            progress.setVisible(true);
            progress.setEnabled(true);
            progress.setDescription("Loading visualizer" + visPlugin.getShortName());

            ExecutorService execService = Executors.newSingleThreadExecutor();

            final Future<Component> future = execService.submit(new LoadComponentTask());

            // run the actual code to load the visualizer
            Background.run(new BackgroundJob(future, callback, UI.getCurrent()));

        } // end if create input was needed

    } // end loadVisualizer

    private void updateGUIAfterLoadingVisualizer(LoadableVisualizer.Callback callback) {
        if (callback != null && vis instanceof LoadableVisualizer) {
            LoadableVisualizer loadableVis = (LoadableVisualizer) vis;
            if (loadableVis.isLoaded()) {
                // direct call callback since the visualizer is already ready
                callback.visualizerLoaded(loadableVis);
            } else {
                loadableVis.clearCallbacks();
                // add listener when player was fully loaded
                loadableVis.addOnLoadCallBack(callback);
            }
        }

        progress.setEnabled(false);
        progress.setVisible(false);

        if (vis != null) {
            btEntry.setEnabled(true);
            vis.setVisible(true);
            if (vis instanceof PDFViewer) {
                ((PDFViewer) vis).openPDFPage("-1");
            }
            if (vis instanceof MediaPlayer) {
                // if this is a media player visualizer, close all other media players
                // since some browsers (e.g. Chrome) have problems if there are multiple
                // audio/video elements on one page
                MediaController mediaController = VaadinSession.getCurrent().getAttribute(MediaController.class);
                mediaController.closeOtherPlayers((MediaPlayer) vis);

            }
            // add if not already added
            if (getComponentIndex(vis) < 0) {
                addComponent(vis);
            }
        }
    }

    @Override
    public void toggleVisualizer(boolean visible, LoadableVisualizer.Callback callback) {
        if (visible) {
            loadVisualizer(callback);
        } else {
            // hide
            btEntry.setEnabled(true);

            if (vis != null) {
                vis.setVisible(false);
                if (vis instanceof MediaPlayer) {
                    removeComponent(vis);
                }

            }

            btEntry.setIcon(ICON_EXPAND);

        }
    }

    public String getHtmlID() {
        return htmlID;
    }

    private class BackgroundJob implements Runnable {

        private final Future<Component> future;
        private final LoadableVisualizer.Callback callback;
        private final UI ui;

        public BackgroundJob(Future<Component> future, LoadableVisualizer.Callback callback, UI ui) {
            this.future = future;
            this.callback = callback;
            this.ui = ui;
        }

        @Override
        public void run() {

            Throwable exception = null;
            try {
                final Component result = future.get(60, TimeUnit.SECONDS);

                ui.accessSynchronously(new Runnable() {
                    @Override
                    public void run() {
                        vis = result;
                        updateGUIAfterLoadingVisualizer(callback);
                    }
                });
            } catch (InterruptedException ex) {
                log.error(null, ex);
                exception = ex;
            } catch (ExecutionException ex) {
                log.error(null, ex);
                exception = ex;
            } catch (TimeoutException ex) {
                future.cancel(true);
                log.error("Could create visualizer " + visPlugin.getShortName() + " in 60 seconds: Timeout", ex);
                exception = ex;
            }

            if (exception != null) {
                final Throwable finalException = exception;
                ui.accessSynchronously(new Runnable() {
                    @Override
                    public void run() {
                        Notification.show("Error when creating visualizer " + visPlugin.getShortName(),
                                finalException.toString(), Notification.Type.WARNING_MESSAGE);
                    }
                });
            }

        }
    }

    public class LoadComponentTask implements Callable<Component> {

        @Override
        public Component call() throws Exception {
            // only create component if not already created
            if (vis == null) {
                return createComponent();
            } else {
                return vis;
            }
        }
    }

    public static class ByteArrayOutputStreamSource implements StreamResource.StreamSource {

        private static final Logger log = LoggerFactory.getLogger(ByteArrayOutputStreamSource.class);

        private transient ByteArrayOutputStream byteStream;

        public ByteArrayOutputStreamSource(ByteArrayOutputStream byteStream) {
            this.byteStream = byteStream;
        }

        @Override
        public InputStream getStream() {
            if (byteStream == null) {
                log.error("byte stream was null");
                return null;
            }
            return new ByteArrayInputStream(byteStream.toByteArray());
        }
    }

    public String getVisualizerShortName() {
        if (visPlugin != null) {
            return visPlugin.getShortName();
        }

        else {
            return null;
        }
    }

    protected SDocument getResult() {
        return result;
    }

}