annis.gui.SearchUI.java Source code

Java tutorial

Introduction

Here is the source code for annis.gui.SearchUI.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 Licsense 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.components.ExceptionDialog;
import annis.libgui.InstanceConfig;
import annis.libgui.Helper;
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.TextExporter;
import annis.gui.exporter.WekaExporter;
import annis.libgui.media.MediaController;
import annis.libgui.media.MimeTypeErrorListener;
import annis.libgui.media.MediaControllerImpl;
import annis.gui.objects.PagedResultQuery;
import annis.gui.objects.Query;
import annis.gui.querybuilder.TigerQueryBuilderPlugin;
import annis.gui.flatquerybuilder.FlatQueryBuilderPlugin;
import annis.gui.frequency.FrequencyQueryPanel;
import annis.gui.objects.QueryUIState;
import annis.gui.resultview.ResultViewPanel;
import annis.gui.servlets.ResourceServlet;
import static annis.libgui.Helper.*;
import annis.libgui.media.PDFController;
import annis.libgui.media.PDFControllerImpl;
import annis.service.objects.AnnisCorpus;
import annis.service.objects.CorpusConfig;
import annis.service.objects.OrderType;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
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.GenericType;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.vaadin.annotations.Theme;
import com.vaadin.event.ShortcutListener;
import com.vaadin.server.ErrorHandler;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.Page;
import com.vaadin.server.Page.UriFragmentChangedEvent;
import com.vaadin.server.RequestHandler;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import com.vaadin.server.WebBrowser;

import com.vaadin.ui.*;
import com.vaadin.ui.TabSheet.Tab;
import com.vaadin.ui.themes.ValoTheme;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.Cookie;
import net.xeoh.plugins.base.PluginManager;
import net.xeoh.plugins.base.util.uri.ClassURI;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;

/**
 * GUI for searching in corpora.
 *
 * @author Thomas Krause <krauseto@hu-berlin.de>
 */
@Theme("annis")
public class SearchUI extends CommonUI implements MimeTypeErrorListener, Page.UriFragmentChangedListener,
        ErrorHandler, TabSheet.CloseHandler, LoginListener, Sidebar, TabSheet.SelectedTabChangeListener {

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

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

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

    private transient Cache<String, CorpusConfig> corpusConfigCache;

    // 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 MainToolbar toolbar;

    private ControlPanel controlPanel;

    private TabSheet mainTab;

    private final QueryController queryController;

    private String lastQueriedFragment;

    private InstanceConfig instanceConfig;

    private DocBrowserController docBrowserController;

    private Set<Component> selectedTabHistory;

    public final static int CONTROL_PANEL_WIDTH = 360;

    private final QueryUIState queryState = new QueryUIState();

    private void initTransients() {
        corpusConfigCache = CacheBuilder.newBuilder().maximumSize(250).build();
    }

    public SearchUI() {
        initTransients();
        queryController = new QueryController(this);
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        initTransients();
    }

    @Override
    protected void init(VaadinRequest request) {
        super.init(request);
        setErrorHandler(this);

        this.selectedTabHistory = new LinkedHashSet<>();
        this.instanceConfig = getInstanceConfig(request);

        getPage().setTitle(instanceConfig.getInstanceDisplayName() + " (ANNIS Corpus Search)");

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

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

        GridLayout mainLayout = new GridLayout(2, 2);
        setContent(mainLayout);

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

        toolbar = new MainToolbar(SearchUI.this);
        toolbar.addLoginListener(SearchUI.this);
        addExtension(toolbar.getScreenshotExtension());

        mainLayout.addComponent(toolbar, 0, 0, 1, 0);

        final HelpPanel help = new HelpPanel(this);

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

        Tab helpTab = mainTab.addTab(help, "Help/Examples");
        helpTab.setIcon(FontAwesome.QUESTION_CIRCLE);
        helpTab.setClosable(false);
        controlPanel = new ControlPanel(queryController, instanceConfig, help.getExamples(), this);

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

        mainLayout.addComponent(controlPanel, 0, 1);
        mainLayout.addComponent(mainTab, 1, 1);

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

        getPage().addUriFragmentChangedListener(this);

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

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

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

        loadInstanceFonts();

        checkCitation();
        lastQueriedFragment = "";

        checkServiceVersion();
        evaluateFragment(getPage().getUriFragment());

    }

    private void checkServiceVersion() {
        try {
            WebResource resRelease = Helper.getAnnisWebResource().path("version").path("release");
            String releaseService = resRelease.get(String.class);
            String releaseGUI = VersionInfo.getReleaseName();

            // 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
                WebResource resRevision = Helper.getAnnisWebResource().path("version").path("revision");
                String revisionService = resRevision.get(String.class);
                String revisionGUI = VersionInfo.getBuildRevision();

                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);
        }
    }

    @Override
    public void error(com.vaadin.server.ErrorEvent event) {
        log.error("Unknown error in some component: " + event.getThrowable().getLocalizedMessage(),
                event.getThrowable());
        // get the source throwable (thus the one that triggered the error)
        Throwable source = event.getThrowable();
        if (source != null) {
            while (source.getCause() != null) {
                source = source.getCause();
            }
            ExceptionDialog.show(source);
        }
    }

    public boolean canReportBugs() {
        if (toolbar != null) {
            return toolbar.canReportBugs();
        }
        return false;
    }

    public void reportBug() {
        toolbar.reportBug();
    }

    public void reportBug(Throwable cause) {
        toolbar.reportBug(cause);
    }

    private void loadInstanceFonts() {
        if (instanceConfig != null && instanceConfig.getFont() != null) {
            FontConfig cfg = instanceConfig.getFont();

            String url = cfg.getUrl() == null || cfg.getUrl().isEmpty() ? ""
                    : "@import url(" + cfg.getUrl() + ");\n";

            if (cfg.getSize() == null || cfg.getSize().isEmpty()) {
                injectUniqueCSS(url + "." + CORPUS_FONT_FORCE + " {font-family: '" + cfg.getName()
                        + "', monospace !important; }\n" + "." + CORPUS_FONT + " {font-family: '" + cfg.getName()
                        + "', monospace; }\n" + "div." + CORPUS_FONT + " .CodeMirror pre {font-family: '"
                        + cfg.getName() + "', monospace; }\n"
                        // this one is for the virtual keyboard
                        + "#keyboardInputMaster tbody tr td table tbody tr td {\n" + "  font-family: '"
                        + cfg.getName() + "', 'Lucida Console','Arial Unicode MS',monospace; " + "}");
            } else {
                injectUniqueCSS(url + "." + CORPUS_FONT_FORCE + " {\n" + "  font-family: '" + cfg.getName()
                        + "', monospace !important;\n" + "  font-size: " + cfg.getSize() + " !important;\n" + "}\n"
                        + "." + CORPUS_FONT + " {\n" + "  font-family: '" + cfg.getName() + "', monospace;\n"
                        + "  font-size: " + cfg.getSize() + ";\n" + "}\n" + "div." + CORPUS_FONT
                        + " .CodeMirror pre" + " {\n" + "  font-family: '" + cfg.getName() + "', monospace;\n"
                        + "  font-size: " + cfg.getSize() + ";\n" + "}\n" + "." + CORPUS_FONT
                        + " .v-table-table {\n" + "    font-size: " + cfg.getSize() + ";\n" + "}\n"
                        // this one is for the virtual keyboard
                        + "#keyboardInputMaster tbody tr td table tbody tr td {\n" + "  font-family: '"
                        + cfg.getName() + "', 'Lucida Console','Arial Unicode MS',monospace; " + "}");
            }
        } else {
            injectUniqueCSS(
                    // use original font definition from keyboard.css if no font given
                    "#keyboardInputMaster tbody tr td table tbody tr td {\n"
                            + "  font-family: 'Lucida Console','Arial Unicode MS',monospace;" + "}");
        }
    }

    private InstanceConfig getInstanceConfig(VaadinRequest request) {
        String instance = null;
        String pathInfo = request.getPathInfo();

        if (pathInfo != null && pathInfo.startsWith("/")) {
            pathInfo = pathInfo.substring(1);
        }
        if (pathInfo != null && pathInfo.endsWith("/")) {
            pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
        }

        Map<String, InstanceConfig> allConfigs = loadInstanceConfig();

        if (pathInfo != null && !pathInfo.isEmpty()) {
            instance = pathInfo;
        }

        if (instance != null && allConfigs.containsKey(instance)) {
            // return the config that matches the parsed name
            return allConfigs.get(instance);
        } else if (allConfigs.containsKey("default")) {
            // return the default config
            return allConfigs.get("default");
        } else if (allConfigs.size() > 0) {
            // just return any existing config as a fallback
            log.warn("Instance config {} not found or null and default config is not available.", instance);
            return allConfigs.values().iterator().next();
        }

        // default to an empty instance config
        return new InstanceConfig();
    }

    /**
     * Get a cached version of the {@link CorpusConfig} for a corpus.
     *
     * @param corpus
     * @return
     */
    public CorpusConfig getCorpusConfigWithCache(String corpus) {
        CorpusConfig config = new CorpusConfig();
        if (corpusConfigCache != null) {
            config = corpusConfigCache.getIfPresent(corpus);
            if (config == null) {
                if (corpus.equals(DEFAULT_CONFIG)) {
                    config = Helper.getDefaultCorpusConfig();
                } else {
                    config = Helper.getCorpusConfig(corpus);
                }

                corpusConfigCache.put(corpus, config);
            }
        }

        return config;
    }

    public void clearCorpusConfigCache() {
        if (corpusConfigCache != null) {
            corpusConfigCache.invalidateAll();
        }
    }

    @Override
    protected void addCustomUIPlugins(PluginManager pluginManager) {
        pluginManager.addPluginsFrom(new ClassURI(TigerQueryBuilderPlugin.class).toURI());
        pluginManager.addPluginsFrom(new ClassURI(FlatQueryBuilderPlugin.class).toURI());
        pluginManager.addPluginsFrom(new ClassURI(ResourceServlet.class).toURI());
    }

    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);
                }
                queryController.setQuery(new PagedResultQuery(cleft, cright, 0, 10, null, aql, selectedCorpora));
            } else {
                queryController.setQuery(new Query(aql, selectedCorpora));
            }

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

    }

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

            // set cookie
            Cookie c = new Cookie("annis-sidebar-state", state.name());
            c.setMaxAge(30 * 24 * 60 * 60); // 30 days
            c.setPath(Helper.getContext());
            VaadinService.getCurrentResponse().addCookie(c);
        }
    }

    @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 InstanceConfig getInstanceConfig() {
        return instanceConfig;
    }

    public QueryController getQueryController() {
        return queryController;
    }

    public TabSheet getMainTab() {
        return mainTab;
    }

    @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 = getPage().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() {
        toolbar.notifiyQueryStarted();
    }

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

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

    @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(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()) {
                    // When we did not get any answer for this corpus we might not have
                    // the rights to access it yet. Since we want to preserve the "c"
                    // parameter in the string we should still remember it.
                    // See https://github.com/korpling/ANNIS/issues/330
                    mappedNames.add(selectedCorpusName);
                } else {
                    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(lastQueriedFragment)) {
            return;
        }

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

        Set<String> corpora = new TreeSet<>();

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

        if (args.containsKey("c") && args.size() == 1) {
            // special case: we were called from outside and should only select,
            // but not query, the selected corpora
            if (corpora.isEmpty()) {
                // show a warning message that the corpus was not imported yet
                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>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());
            } else {
                getControlPanel().getCorpusList().selectCorpora(corpora);
            }

        } else if (args.get("cl") != null && args.get("cr") != null) {
            // do not change the manually selected search options
            //String a = args.get("cl");
            //String b = args.get("cr");
            //new Notification("hello zangsir", "<div><ul><li>cl and cr: "+ a + b + "</li></ul></div>", Notification.Type.WARNING_MESSAGE, true).show(Page.getCurrent());

            controlPanel.getSearchOptions().setOptionsManuallyChanged(true);

            PagedResultQuery query = new PagedResultQuery(Integer.parseInt(args.get("cl")),
                    Integer.parseInt(args.get("cr")), Integer.parseInt(args.get("s")),
                    Integer.parseInt(args.get("l")), args.get("seg"), args.get("q"), corpora);

            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
            queryController.setQuery(query);
            queryController.executeSearch(true, false);
        } else if (args.get("q") != null) {
            // do not change the manually selected search options
            controlPanel.getSearchOptions().setOptionsManuallyChanged(true);

            // use default context
            queryController.setQuery(new Query(args.get("q"), corpora));
            queryController.executeSearch(true, true);
        }
    }

    /**
     * Updates the browser address bar with the current query paramaters 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(PagedResultQuery q) {
        List<String> args = Helper.citationFragment(q.getQuery(), q.getCorpora(), q.getLeftContext(),
                q.getRightContext(), q.getSegmentation(), q.getOffset(), q.getLimit(), q.getOrder());

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

        // reset title
        getPage().setTitle(instanceConfig.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;
    }

    public QueryUIState getQueryState() {
        return queryState;
    }

    public FontConfig getInstanceFont() {
        if (instanceConfig != null && instanceConfig.getFont() != null) {
            return instanceConfig.getFont();
        }
        return null;
    }

}