annis.gui.SearchView.java Source code

Java tutorial

Introduction

Here is the source code for annis.gui.SearchView.java

Source

/*
 * Copyright 2015 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;

import annis.VersionInfo;
import annis.gui.controlpanel.ControlPanel;
import annis.gui.docbrowser.DocBrowserController;
import annis.gui.exporter.CSVExporter;
import annis.gui.exporter.Exporter;
import annis.gui.exporter.GridExporter;
import annis.gui.exporter.SimpleTextExporter;
import annis.gui.exporter.TokenExporter;
import annis.gui.exporter.WekaExporter;
import annis.gui.frequency.FrequencyQueryPanel;
import annis.gui.objects.DisplayedResultQuery;
import annis.gui.objects.PagedResultQuery;
import annis.gui.objects.Query;
import annis.gui.objects.QueryGenerator;
import annis.gui.resultview.ResultViewPanel;
import annis.libgui.Background;
import annis.libgui.Helper;
import annis.libgui.InstanceConfig;
import annis.libgui.media.MediaController;
import annis.libgui.media.MediaControllerImpl;
import annis.libgui.media.MimeTypeErrorListener;
import annis.libgui.media.PDFController;
import annis.libgui.media.PDFControllerImpl;
import annis.service.objects.AnnisCorpus;
import annis.service.objects.OrderType;
import com.google.common.base.Strings;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.gwt.thirdparty.guava.common.base.Splitter;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.vaadin.event.ShortcutListener;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.Page;
import com.vaadin.server.RequestHandler;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinSession;
import com.vaadin.server.WebBrowser;
import com.vaadin.ui.Component;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.UI;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.ValoTheme;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;

/**
 * The view which shows the search interface.
 *
 * @author Thomas Krause <krauseto@hu-berlin.de>
 */
public class SearchView extends GridLayout implements View, MimeTypeErrorListener, Page.UriFragmentChangedListener,
        TabSheet.CloseHandler, LoginListener, Sidebar, TabSheet.SelectedTabChangeListener {

    private static final org.slf4j.Logger log = LoggerFactory.getLogger(SearchView.class);

    public static final String NAME = "";

    static final Exporter[] EXPORTER = new Exporter[] { new WekaExporter(), new CSVExporter(), new TokenExporter(),
            new GridExporter(), new SimpleTextExporter() };

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

    // regular expression matching, CLEFT and CRIGHT are optional
    // indexes: AQL=1, CIDS=2, CLEFT=4, CRIGHT=6
    private final Pattern citationPattern = Pattern.compile(
            "AQL\\((.*)\\),CIDS\\(([^)]*)\\)(,CLEFT\\(([^)]*)\\),)?(CRIGHT\\(([^)]*)\\))?",
            Pattern.MULTILINE | Pattern.DOTALL);

    private ControlPanel controlPanel;

    private TabSheet mainTab;

    private String lastEvaluatedFragment;

    private DocBrowserController docBrowserController;

    private Set<Component> selectedTabHistory;

    public final static int CONTROL_PANEL_WIDTH = 360;

    private final AnnisUI ui;

    private MainToolbar toolbar;

    public SearchView(AnnisUI ui) {
        super(2, 2);
        this.ui = ui;
        this.selectedTabHistory = new LinkedHashSet<>();

        // init a doc browser controller
        this.docBrowserController = new DocBrowserController(ui);

        // always get the resize events directly
        setImmediate(true);

        setSizeFull();
        setMargin(false);
        setRowExpandRatio(1, 1.0f);
        setColumnExpandRatio(1, 1.0f);

        final HelpPanel help = new HelpPanel(ui);

        mainTab = new TabSheet();
        mainTab.setSizeFull();
        mainTab.setCloseHandler(SearchView.this);
        mainTab.addStyleName(ValoTheme.TABSHEET_FRAMED);
        mainTab.addSelectedTabChangeListener(SearchView.this);

        TabSheet.Tab helpTab = mainTab.addTab(help, "Help/Examples");
        helpTab.setIcon(FontAwesome.QUESTION_CIRCLE);
        helpTab.setClosable(false);

        controlPanel = new ControlPanel(ui.getInstanceConfig(), help.getExamples(), ui);

        controlPanel.setWidth(CONTROL_PANEL_WIDTH, Layout.Unit.PIXELS);
        controlPanel.setHeight(100f, Layout.Unit.PERCENTAGE);

        ui.addAction(new ShortcutListener("Tutor^eial") {
            @Override
            public void handleAction(Object sender, Object target) {
                mainTab.setSelectedTab(help);
            }
        });

        addComponent(controlPanel, 0, 1);
        addComponent(mainTab, 1, 1);
    }

    @Override
    public void enter(ViewChangeListener.ViewChangeEvent event) {
        if (event.getOldView() == event.getNewView()) {
            return;
        }

        InstanceConfig config = ui.getInstanceConfig();

        Page.getCurrent().setTitle(config.getInstanceDisplayName() + " (ANNIS Corpus Search)");

        Page.getCurrent().addUriFragmentChangedListener(this);

        getSession().addRequestHandler(new CitationRequestHandler());

        getSession().setAttribute(MediaController.class, new MediaControllerImpl());

        getSession().setAttribute(PDFController.class, new PDFControllerImpl());

        // the following shoul
        checkCitation();
        lastEvaluatedFragment = "";
        Background.run(new VersionChecker());
        evaluateFragment(Page.getCurrent().getUriFragment());

        if (config.isLoginOnStart() && toolbar != null && Helper.getUser() == null) {
            toolbar.showLoginWindow(false);
        }

    }

    public void setToolbar(MainToolbar newToolbar) {
        // remove old one if necessary
        if (this.toolbar != null) {
            removeComponent(this.toolbar);
            this.toolbar = null;
        }

        // add new toolbar
        if (newToolbar != null) {
            this.toolbar = newToolbar;
            addComponent(this.toolbar, 0, 0, 1, 0);
        }
    }

    public void checkCitation() {
        if (VaadinSession.getCurrent() == null || VaadinSession.getCurrent().getSession() == null) {
            return;
        }
        Object origURLRaw = VaadinSession.getCurrent().getSession().getAttribute("citation");
        if (origURLRaw == null || !(origURLRaw instanceof String)) {
            return;
        }
        String origURL = (String) origURLRaw;
        String parameters = origURL.replaceAll(".*?/Cite(/)?", "");
        if (!"".equals(parameters) && !origURL.equals(parameters)) {
            try {
                String decoded = URLDecoder.decode(parameters, "UTF-8");
                evaluateCitation(decoded);
            } catch (UnsupportedEncodingException ex) {
                log.error(null, ex);
            }
        }

    }

    public void evaluateCitation(String relativeUri) {
        Matcher m = citationPattern.matcher(relativeUri);
        if (m.matches()) {
            // AQL
            String aql = "";
            if (m.group(1) != null) {
                aql = m.group(1);
            }

            // CIDS
            Set<String> selectedCorpora = new HashSet<>();
            if (m.group(2) != null) {
                String[] cids = m.group(2).split(",");
                selectedCorpora.addAll(Arrays.asList(cids));
            }

            // filter by actually avaible user corpora in order not to get any exception later
            WebResource res = Helper.getAnnisWebResource();
            List<AnnisCorpus> userCorpora = res.path("query").path("corpora").get(new AnnisCorpusListType());

            LinkedList<String> userCorporaStrings = new LinkedList<>();
            for (AnnisCorpus c : userCorpora) {
                userCorporaStrings.add(c.getName());
            }

            selectedCorpora.retainAll(userCorporaStrings);

            // CLEFT and CRIGHT
            if (m.group(4) != null && m.group(6) != null) {
                int cleft = 0;
                int cright = 0;
                try {
                    cleft = Integer.parseInt(m.group(4));
                    cright = Integer.parseInt(m.group(6));
                } catch (NumberFormatException ex) {
                    log.error("could not parse context value", ex);
                }
                ui.getQueryController()
                        .setQuery(new PagedResultQuery(cleft, cright, 0, 10, null, aql, selectedCorpora));
            } else {
                ui.getQueryController().setQuery(new Query(aql, selectedCorpora));
            }

            // remove all currently openend sub-windows
            Set<Window> all = new HashSet<>(ui.getWindows());
            for (Window w : all) {
                ui.removeWindow(w);
            }
        } else {
            Notification.show("Invalid citation", Notification.Type.WARNING_MESSAGE);
        }

    }

    @Override
    public void updateSidebarState(SidebarState state) {
        if (controlPanel != null && state != null) {
            controlPanel.setVisible(state.isSidebarVisible());

            // set cookie
            ui.getSettings().set("annis-sidebar-state", state.name(), 30);
        }
    }

    @Override
    public void onTabClose(TabSheet tabsheet, Component tabContent) {
        // select the tab that was selected before
        if (tabsheet == mainTab) {
            selectedTabHistory.remove(tabContent);

            if (!selectedTabHistory.isEmpty()) {
                // get the last selected tab
                Component[] asArray = selectedTabHistory.toArray(new Component[selectedTabHistory.size()]);
                mainTab.setSelectedTab(asArray[asArray.length - 1]);
            }
        }

        tabsheet.removeComponent(tabContent);
        if (tabContent instanceof FrequencyQueryPanel) {
            controlPanel.getQueryPanel().notifyFrequencyTabClose();
        }

    }

    public void closeTab(Component c) {
        selectedTabHistory.remove(c);
        mainTab.removeComponent(c);
    }

    @Override
    public void selectedTabChange(TabSheet.SelectedTabChangeEvent event) {
        Component tab = event.getTabSheet().getSelectedTab();
        if (tab != null) {
            // first remove the old element to make sure it is added at the end
            selectedTabHistory.remove(tab);
            selectedTabHistory.add(tab);
        }
    }

    public ResultViewPanel getLastSelectedResultView() {
        for (Component c : selectedTabHistory) {
            if (c instanceof ResultViewPanel && mainTab.getTab(c) != null) {
                return (ResultViewPanel) c;
            }
        }
        return null;
    }

    public ControlPanel getControlPanel() {
        return controlPanel;
    }

    public TabSheet getMainTab() {
        return mainTab;
    }

    public MainToolbar getMainToolbar() {
        return toolbar;
    }

    @Override
    public void notifyCannotPlayMimeType(String mimeType) {
        if (mimeType == null) {
            return;
        }

        if (mimeType.startsWith("audio/ogg") || mimeType.startsWith("video/web")) {
            String browserList = "<ul>"
                    + "<li>Mozilla Firefox: <a href=\"http://www.mozilla.org/firefox\" target=\"_blank\">http://www.mozilla.org/firefox</a></li>"
                    + "<li>Google Chrome: <a href=\"http://www.google.com/chrome\" target=\"_blank\">http://www.google.com/chrome</a></li>"
                    + "</ul>";

            WebBrowser browser = Page.getCurrent().getWebBrowser();

            // IE9 users can install a plugin
            Set<String> supportedByIE9Plugin = new HashSet<>();
            supportedByIE9Plugin.add("video/webm");
            supportedByIE9Plugin.add("audio/ogg");
            supportedByIE9Plugin.add("video/ogg");

            if (browser.isIE() && browser.getBrowserMajorVersion() >= 9
                    && supportedByIE9Plugin.contains(mimeType)) {
                Notification n = new Notification("Media file type unsupported by your browser",
                        "Please install the WebM plugin for Internet Explorer 9 from "
                                + "<a target=\"_blank\" href=\"https://tools.google.com/dlpage/webmmf\">https://tools.google.com/dlpage/webmmf</a> "
                                + " or use a browser from the following list "
                                + "(these are known to work with WebM or OGG files)<br/>" + browserList
                                + "<br/><br /><strong>Click on this message to hide it</strong>",
                        Notification.Type.WARNING_MESSAGE, true);
                n.setDelayMsec(15000);

                n.show(Page.getCurrent());
            } else {
                Notification n = new Notification("Media file type unsupported by your browser",
                        "Please use a browser from the following list "
                                + "(these are known to work with WebM or OGG files)<br/>" + browserList
                                + "<br/><br /><strong>Click on this message to hide it</strong>",
                        Notification.Type.WARNING_MESSAGE, true);
                n.setDelayMsec(15000);
                n.show(Page.getCurrent());
            }
        } else {
            Notification.show("Media file type \"" + mimeType + "\" unsupported by your browser!",
                    "Try to check your browsers documentation how to enable "
                            + "support for the media type or inform the corpus creator about this problem.",
                    Notification.Type.WARNING_MESSAGE);
        }

    }

    public void notifiyQueryStarted() {
        if (toolbar != null) {
            toolbar.notifiyQueryStarted();
        }
    }

    @Override
    public void onLogin() {
        getControlPanel().getCorpusList().updateCorpusSetList(true);
        // re-evaluate the fragment in case a corpus is now accessible
        evaluateFragment(Page.getCurrent().getUriFragment());
    }

    @Override
    public void onLogout() {
        getControlPanel().getCorpusList().updateCorpusSetList(false);
    }

    @Override
    public void notifyMightNotPlayMimeType(String mimeType) {
        /*
         if(!warnedAboutPossibleMediaFormatProblem)
         {
         Notification notify = new Notification("Media file type \"" + mimeType  + "\" might be unsupported by your browser!",
         "This means you might get errors playing this file.<br/><br /> "
         + "<em>If you have problems with this media file:</em><br /> Try to check your browsers "
         + "documentation how to enable "
         + "support for the media type or inform the corpus creator about this problem.",
         Notification.Type.TRAY_NOTIFICATION, true);
         notify.setDelayMsec(15000);
         showNotification(notify);
         warnedAboutPossibleMediaFormatProblem = true;
         }
         */
    }

    @Override
    public void uriFragmentChanged(Page.UriFragmentChangedEvent event) {
        evaluateFragment(event.getUriFragment());
    }

    /**
     * Takes a list of raw corpus names as given by the #c parameter and returns a
     * list of corpus names that are known to exist. It also replaces alias names
     * with the real corpus names.
     *
     * @param originalNames
     * @return
     */
    private Set<String> getMappedCorpora(List<String> originalNames) {
        WebResource rootRes = Helper.getAnnisWebResource();
        Set<String> mappedNames = new HashSet<>();
        // iterate over given corpora and map names if necessary
        for (String selectedCorpusName : originalNames) {
            // get the real corpus descriptions by the name (which could be an alias)
            try {
                List<AnnisCorpus> corporaByName = rootRes.path("query").path("corpora")
                        .path(urlPathEscape.escape(selectedCorpusName)).get(new GenericType<List<AnnisCorpus>>() {
                        });

                if (corporaByName != null && !corporaByName.isEmpty()) {
                    for (AnnisCorpus c : corporaByName) {
                        mappedNames.add(c.getName());
                    }
                }
            } catch (ClientHandlerException ex) {
                String msg = "alias mapping does not work for alias: " + selectedCorpusName;
                log.error(msg, ex);
                Notification.show(msg, Notification.Type.TRAY_NOTIFICATION);
            }
        }
        return mappedNames;
    }

    private void evaluateFragment(String fragment) {
        // do nothing if not changed
        if (fragment == null || fragment.isEmpty() || fragment.equals(lastEvaluatedFragment)) {
            return;
        }

        Map<String, String> args = Helper.parseFragment(fragment);

        if (args.containsKey("c")) {
            String[] originalCorpusNames = args.get("c").split("\\s*,\\s*");
            Set<String> corpora = getMappedCorpora(Arrays.asList(originalCorpusNames));

            if (corpora.isEmpty()) {
                if (Helper.getUser() == null && toolbar != null) {
                    // not logged in, show login window
                    boolean onlyCorpusSelected = args.containsKey("c") && args.size() == 1;
                    toolbar.showLoginWindow(!onlyCorpusSelected);
                } else {
                    // already logged in or no login system available, just display a message
                    new Notification("Linked corpus does not exist",
                            "<div><p>The corpus you wanted to access unfortunally does not (yet) exist"
                                    + " in ANNIS.</p>" + "<h2>possible reasons are:</h2>" + "<ul>"
                                    + "<li>that it has not been imported yet,</li>"
                                    + "<li>you don't have the access rights to see this corpus,</li>"
                                    + "<li>or the ANNIS service is not running.</li>" + "</ul>"
                                    + "<p>Please ask the responsible person of the site that contained "
                                    + "the link to import the corpus.</p></div>",
                            Notification.Type.WARNING_MESSAGE, true).show(Page.getCurrent());

                }
            } // end if corpus list returned from service is empty
            else {
                if (args.containsKey("c") && args.size() == 1) {
                    // special case: we were called from outside and should only select,
                    // but not query, the selected corpora
                    ui.getQueryState().getSelectedCorpora().setValue(corpora);
                } else if (args.get("cl") != null && args.get("cr") != null) {
                    // make sure the properties are not overwritten by the background process
                    getControlPanel().getSearchOptions().setUpdateStateFromConfig(false);

                    DisplayedResultQuery query = QueryGenerator.displayed().left(Integer.parseInt(args.get("cl")))
                            .right(Integer.parseInt(args.get("cr"))).offset(Integer.parseInt(args.get("s")))
                            .limit(Integer.parseInt(args.get("l"))).segmentation(args.get("seg"))
                            .baseText(args.get("bt")).query(args.get("q")).corpora(corpora).build();

                    if (query.getBaseText() == null && query.getSegmentation() != null) {
                        // if no explicit visible segmentation was given use the same as the context
                        query.setBaseText(query.getSegmentation());
                    }
                    if (query.getBaseText() != null && query.getBaseText().isEmpty()) {
                        // empty string means "null"
                        query.setBaseText(null);
                    }

                    String matchSelectionRaw = args.get("m");
                    if (matchSelectionRaw != null) {
                        for (String selectedMatchNr : Splitter.on(',').omitEmptyStrings().trimResults()
                                .split(matchSelectionRaw)) {
                            try {
                                long nr = Long.parseLong(selectedMatchNr);
                                query.getSelectedMatches().add(nr);
                            } catch (NumberFormatException ex) {
                                log.warn("Invalid long provided as selected match", ex);
                            }
                        }
                    }

                    if (args.get("o") != null) {
                        try {
                            query.setOrder(OrderType.valueOf(args.get("o").toLowerCase()));
                        } catch (IllegalArgumentException ex) {
                            log.warn("Could not parse query fragment argument for order", ex);
                        }
                    }

                    // full query with given context
                    ui.getQueryController().setQuery(query);
                    ui.getQueryController().executeSearch(true, false);
                } else if (args.get("q") != null) {
                    // use default context
                    ui.getQueryController().setQuery(new Query(args.get("q"), corpora));
                    ui.getQueryController().executeSearch(true, true);
                }

                getControlPanel().getCorpusList().scrollToSelectedCorpus();

            } // end if corpus list from server was non-empty
        } // end if there is a corpus definition
    }

    /**
     * Updates the browser address bar with the current query parameters and the
     * query itself.
     *
     * This is for convenient reloading the vaadin app and easy copying citation
     * links.
     *
     * @param q The query where the parameters are extracted from.
     */
    public void updateFragment(DisplayedResultQuery q) {
        List<String> args = Helper.citationFragment(q.getQuery(), q.getCorpora(), q.getLeftContext(),
                q.getRightContext(), q.getSegmentation(), q.getBaseText(), q.getOffset(), q.getLimit(),
                q.getOrder(), q.getSelectedMatches());

        // set our fragment
        lastEvaluatedFragment = StringUtils.join(args, "&");
        UI.getCurrent().getPage().setUriFragment(lastEvaluatedFragment, false);

        // reset title
        Page.getCurrent().setTitle(ui.getInstanceConfig().getInstanceDisplayName() + " (ANNIS Corpus Search)");
    }

    /**
     * Adds the _c fragement to the URL in the browser adress bar when a corpus is
     * selected.
     *
     * @param corpora A list of corpora, which are add to the fragment.
     */
    public void updateFragementWithSelectedCorpus(Set<String> corpora) {
        if (corpora != null && !corpora.isEmpty()) {
            String fragment = "_c=" + Helper.encodeBase64URL(StringUtils.join(corpora, ","));
            UI.getCurrent().getPage().setUriFragment(fragment);
        } else {
            UI.getCurrent().getPage().setUriFragment("");
        }
    }

    private class CitationRequestHandler implements RequestHandler {

        @Override
        public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response)
                throws IOException {
            checkCitation();
            return false;
        }
    }

    private static class AnnisCorpusListType extends GenericType<List<AnnisCorpus>> {

        public AnnisCorpusListType() {
        }
    }

    public TabSheet getTabSheet() {
        return mainTab;
    }

    public DocBrowserController getDocBrowserController() {
        return docBrowserController;
    }

    private class VersionChecker implements Runnable {

        @Override
        public void run() {
            try {
                WebResource resRelease = Helper.getAnnisWebResource().path("version").path("release");
                final String releaseService = resRelease.get(String.class);
                final String releaseGUI = VersionInfo.getReleaseName();

                WebResource resRevision = Helper.getAnnisWebResource().path("version").path("revision");
                final String revisionService = resRevision.get(String.class);
                final String revisionGUI = VersionInfo.getBuildRevision();

                // GUI update
                ui.access(new Runnable() {

                    @Override
                    public void run() {
                        // check if the release version differs and show a big warning
                        if (!releaseGUI.equals(releaseService)) {
                            Notification.show("Different service version",
                                    "The service uses version " + releaseService
                                            + " but the user interface is using version  " + releaseGUI
                                            + ". This can produce unwanted errors.",
                                    Notification.Type.WARNING_MESSAGE);
                        } else {
                            // show a smaller warning if the revisions are not the same

                            if (!revisionService.equals(revisionGUI)) {
                                // shorten the strings
                                String commonPrefix = Strings.commonPrefix(revisionService, revisionGUI);
                                int outputLength = Math.max(6, commonPrefix.length() + 2);
                                String revisionServiceShort = revisionService.substring(0,
                                        Math.min(revisionService.length() - 1, outputLength));
                                String revisionGUIShort = revisionGUI.substring(0,
                                        Math.min(revisionGUI.length() - 1, outputLength));

                                Notification n = new Notification("Different service revision",
                                        "The service uses revision <code title=\"" + revisionGUI + "\">"
                                                + revisionServiceShort
                                                + "</code> but the user interface is using revision  <code title=\""
                                                + revisionGUI + "\">" + revisionGUIShort + "</code>.",
                                        Notification.Type.TRAY_NOTIFICATION);
                                n.setHtmlContentAllowed(true);
                                n.setDelayMsec(3000);
                                n.show(Page.getCurrent());
                            }
                        }
                    }
                });

            } catch (UniformInterfaceException ex) {
                log.warn("Could not get the version of the service", ex);
            } catch (ClientHandlerException ex) {
                log.warn("Could not get the version of the service because service is not running", ex);
            }

        }

    }

}