view artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContextFactory.java @ 9555:ef5754ba5573

Implemented legend aggregation based on type of themes. Added theme-editor style configuration for aggregated legend entries. Only configured themes get aggregated.
author gernotbelger
date Tue, 23 Oct 2018 16:26:48 +0200
parents c26fb37899ca
children
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.artifacts.context;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.xpath.XPathConstants;

import org.apache.log4j.Logger;
import org.dive4elements.artifactdatabase.state.State;
import org.dive4elements.artifactdatabase.state.StateEngine;
import org.dive4elements.artifactdatabase.transition.Transition;
import org.dive4elements.artifactdatabase.transition.TransitionEngine;
import org.dive4elements.artifacts.ArtifactContextFactory;
import org.dive4elements.artifacts.ContextInjector;
import org.dive4elements.artifacts.GlobalContext;
import org.dive4elements.artifacts.common.utils.Config;
import org.dive4elements.artifacts.common.utils.ElementConverter;
import org.dive4elements.artifacts.common.utils.XMLUtils;
import org.dive4elements.river.artifacts.model.Module;
import org.dive4elements.river.artifacts.model.RiverFactory;
import org.dive4elements.river.artifacts.model.ZoomScale;
import org.dive4elements.river.artifacts.states.StateFactory;
import org.dive4elements.river.artifacts.transitions.TransitionFactory;
import org.dive4elements.river.exports.GeneratorLookup;
import org.dive4elements.river.exports.OutGenerator;
import org.dive4elements.river.model.River;
import org.dive4elements.river.themes.Theme;
import org.dive4elements.river.themes.ThemeFactory;
import org.dive4elements.river.themes.ThemeGroup;
import org.dive4elements.river.themes.ThemeMapping;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * The ArtifactContextFactory is used to initialize basic components and put
 * them into the global context of the application.
 *
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class RiverContextFactory implements ArtifactContextFactory {

    /** The log that is used in this class. */
    private static Logger log = Logger.getLogger(RiverContextFactory.class);

    /** The XPath to the artifacts configured in the configuration. */
    private static final String XPATH_ARTIFACTS = "/artifact-database/artifacts/artifact";

    /** The XPath to the name of the artifact. */
    private static final String XPATH_ARTIFACT_NAME = "/artifact/@name";

    /** The XPath to the xlink ref in an artifact configuration. */
    private static final String XPATH_XLINK = "xlink:href";

    /** The XPath to the transitions configured in the artifact config. */
    private static final String XPATH_TRANSITIONS = "/artifact/states/transition";

    /** The XPath to the states configured in the artifact config. */
    private static final String XPATH_STATES = "/artifact/states/state";

    private static final String XPATH_OUTPUT_GENERATORS = "/artifact-database/output-generators//output-generator";

    private static final String XPATH_THEME_CONFIG = "/artifact-database/flys/themes/configuration/text()";

    private static final String XPATH_THEMES = "theme";

    private static final String XPATH_LEGEND_GROUP = "/themes/legendgroup";

    private static final String XPATH_THEME_GROUPS = "/themes/themegroup";

    private static final String XPATH_THEME_MAPPINGS = "/themes/mappings/mapping";

    private static final String XPATH_RIVER_WMS = "/artifact-database/floodmap/river";

    private static final String XPATH_MODULES = "/artifact-database/modules/module";

    private static final String XPATH_ZOOM_SCALES = "/artifact-database/options/zoom-scales/zoom-scale";

    private static final String XPATH_DGM_PATH = "/artifact-database/options/dgm-path/text()";

    private static GlobalContext GLOBAL_CONTEXT_INSTANCE;

    /**
     * Creates a new D4EArtifactContext object and initialize all
     * components required by the application.
     *
     * @param config
     *            The artifact server configuration.
     * @return a D4EArtifactContext.
     */
    @Override
    public GlobalContext createArtifactContext(final Document config) {
        final RiverContext context = new RiverContext(config);

        configureTransitions(config, context);
        configureStates(config, context);
        configureOutGenerators(config, context);
        configureThemes(config, context);
        configureThemesMappings(config, context);
        configureLegend(config, context);
        configureFloodmapWMS(config, context);
        configureModules(config, context);
        configureZoomScales(config, context);
        configureDGMPath(config, context);

        synchronized (RiverContextFactory.class) {
            GLOBAL_CONTEXT_INSTANCE = context;
        }

        return context;
    }

    public static synchronized GlobalContext getGlobalContext() {
        return GLOBAL_CONTEXT_INSTANCE;
    }

    private void configureDGMPath(final Document config, final RiverContext context) {
        final String dgmPath = (String) XMLUtils.xpath(config, XPATH_DGM_PATH, XPathConstants.STRING);

        context.put("dgm-path", dgmPath);
    }

    private void configureZoomScales(final Document config, final RiverContext context) {
        final NodeList list = (NodeList) XMLUtils.xpath(config, XPATH_ZOOM_SCALES, XPathConstants.NODESET);
        final ZoomScale scale = new ZoomScale();
        for (int i = 0; i < list.getLength(); i++) {
            final Element element = (Element) list.item(i);
            String river = "default";
            double range = 0d;
            double radius = 10d;
            if (element.hasAttribute("river")) {
                river = element.getAttribute("river");
            }
            if (!element.hasAttribute("range")) {
                continue;
            } else {
                final String r = element.getAttribute("range");
                try {
                    range = Double.parseDouble(r);
                }
                catch (final NumberFormatException nfe) {
                    continue;
                }
            }
            if (!element.hasAttribute("radius")) {
                continue;
            } else {
                final String r = element.getAttribute("radius");
                try {
                    radius = Double.parseDouble(r);
                }
                catch (final NumberFormatException nfe) {
                    continue;
                }
            }
            scale.addRange(river, range, radius);
        }
        context.put("zoomscale", scale);
    }

    /**
     * This method initializes the transition configuration.
     *
     * @param config
     *            the config document.
     * @param context
     *            the RiverContext.
     */
    private void configureTransitions(final Document config, final RiverContext context) {
        final TransitionEngine engine = new TransitionEngine();

        final List<Document> artifacts = getArtifactConfigurations(config);
        log.info("Found " + artifacts.size() + " artifacts in the config.");

        for (final Document doc : artifacts) {

            final String artName = (String) XMLUtils.xpath(doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);

            final NodeList list = (NodeList) XMLUtils.xpath(doc, XPATH_TRANSITIONS, XPathConstants.NODESET);

            if (list == null) {
                log.warn("The artifact " + artName + " has no transitions configured.");
                continue;
            }

            final int trans = list.getLength();

            log.info("Artifact '" + artName + "' has " + trans + " transitions.");

            for (int i = 0; i < trans; i++) {
                final Transition t = TransitionFactory.createTransition(list.item(i));
                final String s = t.getFrom();
                engine.addTransition(s, t);
            }
        }

        context.put(RiverContext.TRANSITION_ENGINE_KEY, engine);
    }

    /**
     * This method returns all artifact documents defined in
     * <code>config</code>. <br>
     * NOTE: The artifact configurations need to be
     * stored in own files referenced by an xlink.
     *
     * @param config
     *            The global configuration.
     *
     * @return an array of Artifact configurations.
     */
    private List<Document> getArtifactConfigurations(final Document config) {
        final NodeList artifacts = (NodeList) XMLUtils.xpath(config, XPATH_ARTIFACTS, XPathConstants.NODESET);

        final int count = artifacts.getLength();

        final ArrayList<Document> docs = new ArrayList<>(count);

        for (int i = 0; i < count; i++) {
            final Element tmp = (Element) artifacts.item(i);

            String xlink = tmp.getAttribute(XPATH_XLINK);
            xlink = Config.replaceConfigDir(xlink);

            if (!xlink.isEmpty()) {
                final File file = new File(xlink);
                if (!file.isFile() || !file.canRead()) {
                    log.warn("Artifact configuration '" + file + "' not found.");
                } else {
                    final Document doc = XMLUtils.parseDocument(file);
                    if (doc != null) {
                        docs.add(doc);
                    }
                }
                continue;
            }
            final Document doc = XMLUtils.newDocument();
            final Node copy = doc.adoptNode(tmp.cloneNode(true));
            doc.appendChild(copy);
            docs.add(doc);
        }
        return docs;
    }

    /**
     * This method initializes the transition configuration.
     *
     * @param config
     *            the config document.
     * @param context
     *            the RiverContext.
     */
    private void configureStates(final Document config, final RiverContext context) {
        final StateEngine engine = new StateEngine();

        final List<Document> artifacts = getArtifactConfigurations(config);
        log.info("Found " + artifacts.size() + " artifacts in the config.");

        for (final Document doc : artifacts) {
            final List<State> states = new ArrayList<>();

            final String artName = (String) XMLUtils.xpath(doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);

            final NodeList stateList = (NodeList) XMLUtils.xpath(doc, XPATH_STATES, XPathConstants.NODESET);

            if (stateList == null) {
                log.warn("The artifact " + artName + " has no states configured.");
                continue;
            }

            final int count = stateList.getLength();

            log.info("Artifact '" + artName + "' has " + count + " states.");

            for (int i = 0; i < count; i++) {
                states.add(StateFactory.createState(stateList.item(i)));
            }

            engine.addStates(artName, states);
        }

        context.put(RiverContext.STATE_ENGINE_KEY, engine);
    }

    /**
     * This method intializes the provided output generators.
     *
     * @param config
     *            the config document.
     * @param context
     *            the RiverContext.
     */
    private void configureOutGenerators(final Document config, final RiverContext context) {
        final NodeList outGenerators = (NodeList) XMLUtils.xpath(config, XPATH_OUTPUT_GENERATORS, XPathConstants.NODESET);

        final int num = outGenerators == null ? 0 : outGenerators.getLength();

        if (num == 0) {
            log.warn("No output generators configured in this application.");
            return;
        }

        log.info("Found " + num + " configured output generators.");

        final GeneratorLookup generators = new GeneratorLookup();

        int idx = 0;

        for (int i = 0; i < num; i++) {
            final Element item = (Element) outGenerators.item(i);

            final String names = item.getAttribute("names").trim();
            final String clazz = item.getAttribute("class").trim();
            final String converter = item.getAttribute("converter").trim();
            final String injectors = item.getAttribute("injectors").trim();

            if (names.isEmpty() || clazz.isEmpty()) {
                continue;
            }

            Class<OutGenerator> generatorClass = null;

            try {
                generatorClass = (Class<OutGenerator>) Class.forName(clazz);
            }
            catch (final ClassNotFoundException cnfe) {
                log.error(cnfe, cnfe);
                continue;
            }

            Object cfg = null;

            if (!converter.isEmpty()) {
                try {
                    final ElementConverter ec = (ElementConverter) Class.forName(converter).newInstance();
                    cfg = ec.convert(item);
                }
                catch (final ClassNotFoundException cnfe) {
                    log.error(cnfe, cnfe);
                }
                catch (final InstantiationException ie) {
                    log.error(ie);
                }
                catch (final IllegalAccessException iae) {
                    log.error(iae);
                }
            }

            List<ContextInjector> cis = null;

            if (!injectors.isEmpty()) {
                cis = new ArrayList<>();
                for (final String injector : injectors.split("[\\s,]+")) {
                    try {
                        final ContextInjector ci = (ContextInjector) Class.forName(injector).newInstance();
                        ci.setup(item);
                        cis.add(ci);
                    }
                    catch (final ClassNotFoundException cnfe) {
                        log.error(cnfe, cnfe);
                    }
                    catch (final InstantiationException ie) {
                        log.error(ie);
                    }
                    catch (final IllegalAccessException iae) {
                        log.error(iae);
                    }
                }
            }

            for (String key : names.split("[\\s,]+")) {
                if (!(key = key.trim()).isEmpty()) {
                    generators.putGenerator(key, generatorClass, cfg, cis);
                    idx++;
                }
            }
        }

        log.info("Successfully loaded " + idx + " output generators.");
        context.put(RiverContext.OUTGENERATORS_KEY, generators);
        context.put(RiverContext.FACETFILTER_KEY, generators);
    }

    /**
     * This methods reads the configured themes and puts them into the
     * RiverContext.
     *
     * @param config
     *            The global configuration.
     * @param context
     *            The RiverContext.
     */
    private void configureThemes(final Document config, final RiverContext context) {
        log.debug("RiverContextFactory.configureThemes");

        final Document cfg = getThemeConfig(config);

        final NodeList themeGroups = (NodeList) XMLUtils.xpath(cfg, XPATH_THEME_GROUPS, XPathConstants.NODESET);

        final int groupNum = themeGroups != null ? themeGroups.getLength() : 0;

        if (groupNum == 0) {
            log.warn("There are no theme groups configured!");
        }

        log.info("Found " + groupNum + " theme groups in configuration");

        final List<ThemeGroup> groups = new ArrayList<>();

        for (int g = 0; g < groupNum; g++) {
            final Element themeGroup = (Element) themeGroups.item(g);

            final Map<String, Theme> theThemes = readThemes(cfg, themeGroup);

            if (theThemes.size() == 0) {
                log.warn("There are no themes configured!");
                return;
            }

            final String gName = themeGroup.getAttribute("name");
            groups.add(new ThemeGroup(gName, theThemes));

            log.info("Initialized " + theThemes.size() + "/" + theThemes.size() + " themes " + "of theme-group '" + gName + "'");
        }
        context.put(RiverContext.THEMES, groups);
    }

    /**
     * This methods reads the configured themes and puts them into the
     * RiverContext.
     *
     * @param config
     *            The global configuration.
     * @param context
     *            The RiverContext.
     */
    private void configureLegend(final Document config, final RiverContext context) {
        log.debug("RiverContextFactory.configureLegend");

        final Map<String, Theme> legendGroup = readLegend(config);
        context.put(RiverContext.LEGEND, legendGroup);
    }

    private Map<String, Theme> readLegend(final Document config) {

        final Document cfg = getThemeConfig(config);

        final Node legendGroup = (Node) XMLUtils.xpath(cfg, XPATH_LEGEND_GROUP, XPathConstants.NODE);
        if (legendGroup == null) {
            log.warn("There is no legend group configured");
            return Collections.emptyMap();
        }

        return readThemes(cfg, legendGroup);
    }

    private Map<String, Theme> readThemes(final Document cfg, final Node themeGroup) {
        final NodeList themes = (NodeList) XMLUtils.xpath(themeGroup, XPATH_THEMES, XPathConstants.NODESET);
        if (themes == null)
            return Collections.emptyMap();

        final int num = themes.getLength();
        log.info("Theme group has " + num + " themes.");
        final Map<String, Theme> theThemes = new HashMap<>();

        for (int i = 0; i < num; i++) {
            final Node theme = themes.item(i);

            final Theme theTheme = ThemeFactory.createTheme(cfg, theme);
            theThemes.put(theTheme.getName(), theTheme);
        }

        return theThemes;
    }

    /**
     * This method is used to retrieve the theme configuration document.
     *
     * @param config
     *            The global configuration.
     *
     * @return the theme configuration.
     */
    private Document getThemeConfig(final Document config) {
        String themeConfig = (String) XMLUtils.xpath(config, XPATH_THEME_CONFIG, XPathConstants.STRING);

        themeConfig = Config.replaceConfigDir(themeConfig);

        log.debug("Parse theme cfg: " + themeConfig);

        return XMLUtils.parseDocument(new File(themeConfig), true, XMLUtils.CONF_RESOLVER);
    }

    private void configureThemesMappings(final Document cfg, final RiverContext context) {
        log.debug("RiverContextFactory.configureThemesMappings");

        final Document config = getThemeConfig(cfg);

        final NodeList mappings = (NodeList) XMLUtils.xpath(config, XPATH_THEME_MAPPINGS, XPathConstants.NODESET);

        final int num = mappings != null ? mappings.getLength() : 0;

        if (num == 0) {
            log.warn("No theme <--> facet mappins found!");
            return;
        }

        final Map<String, List<ThemeMapping>> mapping = new HashMap<>();

        for (int i = 0; i < num; i++) {
            final Element node = (Element) mappings.item(i);

            final String from = node.getAttribute("from");
            final String to = node.getAttribute("to");
            final String pattern = node.getAttribute("pattern");
            final String masterAttrPattern = node.getAttribute("masterAttr");
            final String outputPattern = node.getAttribute("output");

            if (from.length() > 0 && to.length() > 0) {
                List<ThemeMapping> tm = mapping.get(from);

                if (tm == null) {
                    tm = new ArrayList<>();
                    mapping.put(from, tm);
                }

                tm.add(new ThemeMapping(from, to, pattern, masterAttrPattern, outputPattern));
            }
        }

        log.debug("Found " + mapping.size() + " theme mappings.");

        context.put(RiverContext.THEME_MAPPING, mapping);
    }

    /**
     * Reads configured floodmap river WMSs from floodmap.xml and
     * loads them into the given RiverContext.
     *
     * @param cfg
     * @param context
     */
    private void configureFloodmapWMS(final Document cfg, final RiverContext context) {
        final Map<String, String> riverWMS = new HashMap<>();

        final NodeList rivers = (NodeList) XMLUtils.xpath(cfg, XPATH_RIVER_WMS, XPathConstants.NODESET);

        final int num = rivers != null ? rivers.getLength() : 0;

        for (int i = 0; i < num; i++) {
            final Element e = (Element) rivers.item(i);

            final String river = e.getAttribute("name");
            final String url = XMLUtils.xpathString(e, "river-wms/@url", null);

            if (river != null && url != null) {
                riverWMS.put(river, url);
            }
        }

        log.debug("Found " + riverWMS.size() + " river WMS.");

        context.put(RiverContext.RIVER_WMS, riverWMS);
    }

    /**
     * This method initializes the modules configuration.
     *
     * @param config
     *            the config document.
     * @param context
     *            the RiverContext.
     */
    private void configureModules(final Document cfg, final RiverContext context) {
        final NodeList modulenodes = (NodeList) XMLUtils.xpath(cfg, XPATH_MODULES, XPathConstants.NODESET);

        final int num = modulenodes != null ? modulenodes.getLength() : 0;

        final List<Module> modules = new ArrayList<>(num);

        for (int i = 0; i < num; i++) {
            final Element e = (Element) modulenodes.item(i);
            final String modulename = e.getAttribute("name");
            final String attrselected = e.getAttribute("selected");
            final boolean selected = Boolean.parseBoolean(attrselected);
            final String group = e.getAttribute("group");

            log.debug("Loaded module " + modulename);

            final NodeList children = e.getChildNodes();
            final List<String> rivers = new ArrayList<>(children.getLength());
            for (int j = 0; j < children.getLength(); j++) {
                if (children.item(j).getNodeType() != Node.ELEMENT_NODE) {
                    continue;
                }

                final Element ce = (Element) children.item(j);
                if (ce.hasAttribute("uuid")) {
                    rivers.add(ce.getAttribute("uuid"));
                } else if (ce.hasAttribute("name")) {
                    final List<River> allRivers = RiverFactory.getRivers();
                    final String name = ce.getAttribute("name");
                    for (final River r : allRivers) {
                        if (name.equals(r.getName())) {
                            rivers.add(r.getModelUuid());
                            break;
                        }
                    }
                }
            }
            modules.add(new Module(modulename, selected, group, rivers));
        }
        context.put(RiverContext.MODULES, modules);
    }
}

http://dive4elements.wald.intevation.org