view artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContextFactory.java @ 7073:beb9e94f04d3 generator-refactoring

Allow comma separated names in "names" attribute in out generators to allow registering for more than one name. Old "name" attribute is removed.
author Sascha L. Teichmann <teichmann@intevation.de>
date Fri, 20 Sep 2013 15:42:21 +0200
parents 4e597d435398
children a2abd87c0d3b
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 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.GlobalContext;
import org.dive4elements.artifacts.common.utils.Config;
import org.dive4elements.artifacts.common.utils.XMLUtils;
import org.dive4elements.river.artifacts.model.Module;
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.themes.Theme;
import org.dive4elements.river.themes.ThemeFactory;
import org.dive4elements.river.themes.ThemeGroup;
import org.dive4elements.river.themes.ThemeMapping;
import org.dive4elements.river.utils.Pair;
import org.dive4elements.river.exports.OutGenerator;

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

import javax.xml.xpath.XPathConstants;

import org.apache.log4j.Logger;
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 logger that is used in this class. */
    private static Logger logger = Logger.getLogger(RiverContextFactory.class);

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

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

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

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

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

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

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

    public static final String XPATH_THEMES =
        "theme";

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

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

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

    public 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(Document config) {
        RiverContext context = new RiverContext(config);

        configureTransitions(config, context);
        configureStates(config, context);
        configureOutGenerators(config, context);
        configureThemes(config, context);
        configureThemesMappings(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(Document config, RiverContext context) {
        String dgmPath = (String) XMLUtils.xpath(
            config,
            XPATH_DGM_PATH,
            XPathConstants.STRING);

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


    protected void configureZoomScales(Document config, RiverContext context) {
        NodeList list = (NodeList)XMLUtils.xpath(
            config,
            XPATH_ZOOM_SCALES,
            XPathConstants.NODESET);
        ZoomScale scale = new ZoomScale();
        for (int i = 0; i < list.getLength(); i++) {
            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 {
                String r = element.getAttribute("range");
                try {
                    range = Double.parseDouble(r);
                }
                catch (NumberFormatException nfe) {
                    continue;
                }
            }
            if (!element.hasAttribute("radius")) {
                continue;
            }
            else {
                String r = element.getAttribute("radius");
                try {
                    radius = Double.parseDouble(r);
                }
                catch (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.
     */
    protected void configureTransitions(Document config, RiverContext context) {
        TransitionEngine engine = new TransitionEngine();

        Document[] artifacts = getArtifactConfigurations(config);
        logger.info("Found " + artifacts.length + " artifacts in the config.");

        for (Document doc: artifacts) {

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

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

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

            int trans = list.getLength();

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

            for (int i = 0; i < trans; i++) {
                Transition t = TransitionFactory.createTransition(list.item(i));
                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.
     */
    protected Document[] getArtifactConfigurations(Document config) {
        NodeList artifacts = (NodeList) XMLUtils.xpath(
            config, XPATH_ARTIFACTS, XPathConstants.NODESET);

        int count = artifacts.getLength();

        Document[] artifactDocs = new Document[count];

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

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

            File artifactFile = new File(xlink);
            artifactDocs[i]   = XMLUtils.parseDocument(artifactFile);
        }

        return artifactDocs;
    }


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

        Document[] artifacts = getArtifactConfigurations(config);
        logger.info("Found " + artifacts.length + " artifacts in the config.");

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

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

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

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

            int count = stateList.getLength();

            logger.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.
     */
    protected void configureOutGenerators(Document config, RiverContext context){

        NodeList outGenerators = (NodeList) XMLUtils.xpath(
            config,
            XPATH_OUTPUT_GENERATORS,
            XPathConstants.NODESET);

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

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

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

        Map<String, Pair<Class<OutGenerator>, Element>> generators =
            new HashMap<String, Pair<Class<OutGenerator>, Element>>();

        int idx = 0;

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

            String name  = item.getAttribute("names");
            String clazz = item.getAttribute("class");

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

            Class<OutGenerator> generatorClass = null;

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

            Pair<Class<OutGenerator>, Element> pair =
                new Pair<Class<OutGenerator>, Element>(generatorClass, item);

            for (String key: name.split("[\\s,]")) {
                if (!(key = key.trim()).isEmpty()) {
                    generators.put(key, pair);
                    idx++;
                }
            }
        }

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


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

        Document cfg = getThemeConfig(config);

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

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

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

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

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

        for (int g = 0; g < groupNum; g++) {
            Element themeGroup = (Element) themeGroups.item(g);
            NodeList themes = (NodeList) XMLUtils.xpath(
                themeGroup, XPATH_THEMES, XPathConstants.NODESET);

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

            if (num == 0) {
                logger.warn("There are no themes configured!");
                return;
            }

            logger.info("Theme group has " + num + " themes.");

            Map<String, Theme> theThemes = new HashMap<String, Theme>();

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

                Theme theTheme = ThemeFactory.createTheme(cfg, theme);

                if (theme != null) {
                    theThemes.put(theTheme.getName(), theTheme);
                }
            }
            String gName = themeGroup.getAttribute("name");
            groups.add(new ThemeGroup(gName, theThemes));

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

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

        themeConfig = Config.replaceConfigDir(themeConfig);

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

        return XMLUtils.parseDocument(new File(themeConfig));
    }


    protected void configureThemesMappings(Document cfg, RiverContext context) {
        logger.debug("RiverContextFactory.configureThemesMappings");

        Document config = getThemeConfig(cfg);

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

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

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

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

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

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

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

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

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

        logger.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
     */
    protected void configureFloodmapWMS(Document cfg, RiverContext context) {
        Map<String, String> riverWMS = new HashMap<String, String>();

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

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

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

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

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

        logger.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.
     */
    protected void configureModules(Document cfg, RiverContext context) {
        NodeList modulenodes = (NodeList) XMLUtils.xpath(
            cfg, XPATH_MODULES, XPathConstants.NODESET);

        int num = modulenodes != null ? modulenodes.getLength() : 0;
        ArrayList<Module> modules = new ArrayList<Module>(num);

        for (int i = 0; i < num; i++) {
            Element e = (Element) modulenodes.item(i);
            String modulename = e.getAttribute("name");
            String attrselected = e.getAttribute("selected");
            boolean selected = attrselected == null ? false :
                attrselected.equalsIgnoreCase("true");
            logger.debug("Loaded module " + modulename);
            modules.add(new Module(modulename, selected));
        }
        context.put(RiverContext.MODULES, modules);
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org