annis.libgui.AnnisBaseUI.java Source code

Java tutorial

Introduction

Here is the source code for annis.libgui.AnnisBaseUI.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.libgui;

import annis.VersionInfo;
import annis.libgui.media.MediaController;
import annis.libgui.visualizers.VisualizerPlugin;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.google.common.base.Charsets;
import com.google.common.eventbus.EventBus;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.vaadin.annotations.Theme;
import com.vaadin.sass.internal.ScssStylesheet;
import com.vaadin.server.ClassResource;
import com.vaadin.server.Page;
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.ui.AbstractComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.HasComponents;
import com.vaadin.ui.UI;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.xeoh.plugins.base.Plugin;
import net.xeoh.plugins.base.PluginManager;
import net.xeoh.plugins.base.impl.PluginManagerFactory;
import net.xeoh.plugins.base.util.PluginManagerUtil;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.AnnotationIntrospector;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.xc.JaxbAnnotationIntrospector;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

/**
 * Basic UI functionality.
 * 
 * This class allows to out source some common tasks like initialization of 
 * the logging framework or the plugin loading to this base class.
 */
@Theme("annis")
public class AnnisBaseUI extends UI implements PluginSystem, Serializable {

    static {
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();
    }

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

    public final static String USER_KEY = "annis.gui.AnnisBaseUI:USER_KEY";
    public final static String USER_LOGIN_ERROR = "annis.gui.AnnisBaseUI:USER_LOGIN_ERROR";
    public final static String CONTEXT_PATH = "annis.gui.AnnisBaseUI:CONTEXT_PATH";
    public final static String WEBSERVICEURL_KEY = "annis.gui.AnnisBaseUI:WEBSERVICEURL_KEY";

    public final static String CITATION_KEY = "annis.gui.AnnisBaseUI:CITATION_KEY";

    private transient PluginManager pluginManager;

    private static final Map<String, VisualizerPlugin> visualizerRegistry = Collections
            .synchronizedMap(new HashMap<String, VisualizerPlugin>());

    private static final Map<String, Date> resourceAddedDate = Collections
            .synchronizedMap(new HashMap<String, Date>());

    private transient MediaController mediaController;

    private transient ObjectMapper jsonMapper;

    private TreeSet<String> alreadyAddedCSS = new TreeSet<String>();

    private transient EventBus loginDataLostBus;

    @Override
    protected void init(VaadinRequest request) {

        initLogging();

        // store the webservice URL property explicitly in the session in order to 
        // access it from the "external" servlets
        getSession().getSession().setAttribute(WEBSERVICEURL_KEY,
                getSession().getAttribute(Helper.KEY_WEB_SERVICE_URL));

        getSession().setAttribute(CONTEXT_PATH, request.getContextPath());
        alreadyAddedCSS.clear();

        initPlugins();

        checkIfRemoteLoggedIn(request);
        getSession().addRequestHandler(new RemoteUserRequestHandler());

    }

    @Override
    public void attach() {

        super.attach();
        alreadyAddedCSS.clear();
    }

    @Override
    public void close() {
        if (pluginManager != null) {
            pluginManager.shutdown();
        }
        super.close();
    }

    /**
     * Given an configuration file name (might include directory) this function
     * returns all locations for this file in the "ANNIS configuration system".
     *
     * The files in the result list do not necessarily exist.
     *
     * These locations are the
     * - base installation: WEB-INF/conf/ folder of the deployment.
     * - global configuration: $ANNIS_CFG environment variable value or /etc/annis/ if not set
     * - user configuration: ~/.annis/
     * @param configFile The file path of the configuration file relative to the base config folder.
     * @return list of files or directories in the order in which they should be processed (most important is last)
     */
    public static List<File> getAllConfigLocations(String configFile) {
        LinkedList<File> locations = new LinkedList<File>();

        // first load everything from the base application
        locations.add(new File(VaadinService.getCurrent().getBaseDirectory(), "/WEB-INF/conf/" + configFile));

        // next everything from the global config
        // When ANNIS_CFG environment variable is set use this value or default to
        // "/etc/annis/
        String globalConfigDir = System.getenv("ANNIS_CFG");
        if (globalConfigDir == null) {
            globalConfigDir = "/etc/annis";
        }
        locations.add(new File(globalConfigDir + "/" + configFile));

        // the final and most specific user configuration is in the users home directory
        locations.add(new File(System.getProperty("user.home") + "/.annis/" + configFile));

        return locations;
    }

    protected Map<String, InstanceConfig> loadInstanceConfig() {
        TreeMap<String, InstanceConfig> result = new TreeMap<>();

        // get a list of all directories that contain instance informations
        List<File> locations = getAllConfigLocations("instances");
        for (File root : locations) {
            if (root.isDirectory()) {
                // get all sub-files ending on ".json"
                File[] instanceFiles = root.listFiles((FilenameFilter) new SuffixFileFilter(".json"));
                if (instanceFiles != null) {
                    for (File i : instanceFiles) {
                        if (i.isFile() && i.canRead()) {
                            try {
                                InstanceConfig config = getJsonMapper().readValue(i, InstanceConfig.class);
                                String name = StringUtils.removeEnd(i.getName(), ".json");
                                config.setInstanceName(name);
                                result.put(name, config);
                            } catch (IOException ex) {
                                log.warn("could not parse instance config: " + ex.getMessage());
                            }
                        }
                    }
                }
            }
        }

        // always provide a default instance
        if (!result.containsKey("default")) {
            InstanceConfig cfgDefault = new InstanceConfig();
            cfgDefault.setInstanceDisplayName("ANNIS");
            result.put("default", cfgDefault);
        }

        return result;
    }

    protected final void initLogging() {
        try {

            List<File> logbackFiles = getAllConfigLocations("gui-logback.xml");

            InputStream inStream = null;
            if (!logbackFiles.isEmpty()) {
                try {
                    inStream = new FileInputStream(logbackFiles.get(logbackFiles.size() - 1));
                } catch (FileNotFoundException ex) {
                    // well no logging no error...
                }
            }
            if (inStream == null) {
                ClassResource res = new ClassResource(AnnisBaseUI.class, "logback.xml");
                inStream = res.getStream().getStream();
            }

            if (inStream != null) {
                LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
                JoranConfigurator jc = new JoranConfigurator();
                jc.setContext(context);
                context.reset();

                context.putProperty("webappHome", VaadinService.getCurrent().getBaseDirectory().getAbsolutePath());

                // load config file
                jc.doConfigure(inStream);
            }
        } catch (JoranException ex) {
            log.error("init logging failed", ex);
        }

    }

    /**
     * Override this method to append additional plugins to the internal {@link PluginManager}.
     * 
     * The default implementation is empty 
     * (thus you don't need to call {@code super.addCustomUIPlugins(...)}).
     * @param pluginManager 
     */
    protected void addCustomUIPlugins(PluginManager pluginManager) {
        // default: do nothing
    }

    private void initPlugins() {

        log.info("Adding plugins");
        pluginManager = PluginManagerFactory.createPluginManager();
        addCustomUIPlugins(pluginManager);

        File baseDir = VaadinService.getCurrent().getBaseDirectory();

        File builtin = new File(baseDir, "WEB-INF/lib/annis-visualizers-" + VersionInfo.getReleaseName() + ".jar");
        if (builtin.canRead()) {
            pluginManager.addPluginsFrom(builtin.toURI());
            log.info("added built-in plugins from  {}", builtin.getPath());
        } else {
            log.warn("could not find built-in plugin file {}", builtin.getPath());
        }
        File basicPlugins = new File(baseDir, "WEB-INF/plugins");
        if (basicPlugins.isDirectory()) {
            pluginManager.addPluginsFrom(basicPlugins.toURI());
            log.info("added plugins from {}", basicPlugins.getPath());
        }

        String globalPlugins = System.getenv("ANNIS_PLUGINS");
        if (globalPlugins != null) {
            pluginManager.addPluginsFrom(new File(globalPlugins).toURI());
            log.info("added plugins from {}", globalPlugins);
        }

        StringBuilder listOfPlugins = new StringBuilder();
        listOfPlugins.append("loaded plugins:\n");
        PluginManagerUtil util = new PluginManagerUtil(pluginManager);
        for (Plugin p : util.getPlugins()) {
            listOfPlugins.append(p.getClass().getName()).append("\n");
        }
        log.info(listOfPlugins.toString());

        Collection<VisualizerPlugin> visualizers = util.getPlugins(VisualizerPlugin.class);
        for (VisualizerPlugin vis : visualizers) {
            visualizerRegistry.put(vis.getShortName(), vis);
            resourceAddedDate.put(vis.getShortName(), new Date());
        }
    }

    private static void checkIfRemoteLoggedIn(VaadinRequest request) {
        // check if we are logged in using an external authentification mechanism
        // like Schibboleth
        String remoteUser = request.getRemoteUser();
        if (remoteUser != null) {
            Helper.setUser(new AnnisUser(remoteUser, null, true));
        }
    }

    /**
     * Inject CSS into the UI. 
     * This function will not add multiple style-elements if the
     * exact CSS string was already added.
     * @param cssContent 
     */
    public void injectUniqueCSS(String cssContent) {
        injectUniqueCSS(cssContent, null);
    }

    /**
     * Inject CSS into the UI. 
     * This function will not add multiple style-elements if the
     * exact CSS string was already added.
     * @param cssContent 
     * @param wrapperClass Name of the wrapper class (a CSS class that is applied to a parent element)
     */
    public void injectUniqueCSS(String cssContent, String wrapperClass) {
        if (alreadyAddedCSS == null) {
            alreadyAddedCSS = new TreeSet<String>();
        }

        if (wrapperClass != null) {
            cssContent = wrapCSS(cssContent, wrapperClass);
        }

        String hashForCssContent = Hashing.md5().hashString(cssContent, Charsets.UTF_8).toString();
        if (!alreadyAddedCSS.contains(hashForCssContent)) {
            //      CSSInject cssInject = new CSSInject(UI.getCurrent());
            //      cssInject.setStyles(cssContent);
            Page.getCurrent().getStyles().add(cssContent);
            alreadyAddedCSS.add(hashForCssContent);
        }
    }

    private String wrapCSS(String cssContent, String wrapperClass) {
        try {

            String wrappedContent = wrapperClass == null ? cssContent
                    : "." + wrapperClass + "{\n" + cssContent + "\n}";

            File tmpFile = File.createTempFile("annis-stylesheet", ".scss");
            Files.write(wrappedContent, tmpFile, Charsets.UTF_8);
            ScssStylesheet styleSheet = ScssStylesheet.get(tmpFile.getCanonicalPath());
            styleSheet.compile();

            return styleSheet.printState();

        } catch (IOException ex) {
            log.error("IOException when compiling wrapped CSS", ex);
        } catch (Exception ex) {
            log.error("Could not compile wrapped CSS", ex);
        }
        return null;
    }

    @Override
    public PluginManager getPluginManager() {
        if (pluginManager == null) {
            initPlugins();
        }
        return pluginManager;
    }

    @Override
    public VisualizerPlugin getVisualizer(String shortName) {
        return visualizerRegistry.get(shortName);
    }

    public ObjectMapper getJsonMapper() {
        if (jsonMapper == null) {
            jsonMapper = new ObjectMapper();
            // configure json object mapper
            AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
            jsonMapper.setAnnotationIntrospector(introspector);
            // the json should be human readable
            jsonMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
            jsonMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        }
        return jsonMapper;
    }

    private static class RemoteUserRequestHandler implements RequestHandler {

        @Override
        public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response)
                throws IOException {
            checkIfRemoteLoggedIn(request);
            // we never write any information in this handler
            return false;
        }
    }

    public EventBus getLoginDataLostBus() {
        if (loginDataLostBus == null) {
            loginDataLostBus = new EventBus();
        }
        return loginDataLostBus;
    }

}