view flys-artifacts/src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java @ 1135:c8c753931514

Added a fallback mechanism to style mapserver layers as long as the theme mechanism is not implemented for maps (as it is for charts). flys-artifacts/trunk@2656 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Tue, 06 Sep 2011 17:41:07 +0000
parents 2d60a875e28c
children e6b513495281
line wrap: on
line source
package de.intevation.flys.collections;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
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;

import de.intevation.artifacts.Artifact;
import de.intevation.artifacts.ArtifactDatabase;
import de.intevation.artifacts.ArtifactDatabaseException;
import de.intevation.artifacts.ArtifactNamespaceContext;
import de.intevation.artifacts.CallContext;
import de.intevation.artifacts.CallMeta;

import de.intevation.artifacts.common.utils.ClientProtocolUtils;
import de.intevation.artifacts.common.utils.XMLUtils;

import de.intevation.artifactdatabase.Backend;
import de.intevation.artifactdatabase.Backend.PersistentArtifact;
import de.intevation.artifactdatabase.DefaultArtifactCollection;

import de.intevation.flys.artifacts.context.FLYSContext;
import de.intevation.flys.artifacts.model.ManagedFacet;
import de.intevation.flys.exports.OutGenerator;
import de.intevation.flys.themes.Theme;
import de.intevation.flys.themes.ThemeFactory;


/**
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class FLYSArtifactCollection extends DefaultArtifactCollection {

    /** The logger used in this class. */
    private static Logger log = Logger.getLogger(FLYSArtifactCollection.class);


    /** Constant XPath that points to the outputmodes of an artifact. */
    public static final String XPATH_ARTIFACT_OUTPUTMODES =
        "/art:result/art:outputmodes";

    public static final String XPATH_COLLECTION_ITEMS =
        "/art:result/art:artifact-collection/art:collection-item";

    public static final String XPATH_OUT_NAME = "/art:action/@art:name";

    public static final String XPATH_OUT_TYPE = "/art:action/@art:type";

    public static final String XPATH_LOADED_RECOMMENDATIONS =
        "/art:attribute/art:loaded-recommendations";



    @Override
    public Document describe(CallContext context) {
        log.debug("FLYSArtifactCollection.describe: " + identifier);

        Document doc = XMLUtils.newDocument();

        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
            doc,
            ArtifactNamespaceContext.NAMESPACE_URI,
            ArtifactNamespaceContext.NAMESPACE_PREFIX);

        Date creationTime = getCreationTime();
        String creation   = creationTime != null
            ? Long.toString(creationTime.getTime())
            : "";

        Element collection = ec.create("artifact-collection");
        Element artifacts  = ec.create("artifacts");

        ec.addAttr(collection, "name", getName(), true);
        ec.addAttr(collection, "uuid", identifier(), true);
        ec.addAttr(collection, "creation", creation,  true);
        ec.addAttr(collection, "ttl", String.valueOf(getTTL()), true);

        collection.appendChild(artifacts);
        doc.appendChild(collection);

        ArtifactDatabase db = context.getDatabase();
        Document oldAttrs   = getAttribute();

        try {
            String[] aUUIDs = getArtifactUUIDs(context);
            Node newAttr    = mergeAttributes(db, context, oldAttrs, aUUIDs);

            collection.appendChild(doc.importNode(newAttr, true));

            for (String uuid: aUUIDs) {
                try {
                    artifacts.appendChild(
                        buildArtifactNode(db, uuid, context, ec));
                }
                catch (ArtifactDatabaseException dbe) {
                    log.warn(dbe, dbe);
                }
            }
        }
        catch (ArtifactDatabaseException ade) {
            log.error(ade, ade);

            collection.appendChild(
                doc.importNode(oldAttrs.getFirstChild(), true));
        }

        return doc;
    }


    /**
     * Merge the current art:outputs nodes with the the outputs provided by the
     * artifacts in the Collection.
     */
    protected Node mergeAttributes(
        ArtifactDatabase db,
        CallContext      context,
        Document         oldAttrs,
        String[]         uuids)
    {
        Document doc = buildOutAttributes(db, context, oldAttrs, uuids);
        Node newAttr = doc.getFirstChild();

        newAttr = mergeLoadedRecommendations(oldAttrs, newAttr);

        try {
            // Save the merged document into database.
            db.setCollectionAttribute(identifier(), context.getMeta(), doc);
        }
        catch (ArtifactDatabaseException adb) {
            log.error(adb, adb);
        }

        return newAttr;
    }


    /**
     * Merge the recommendations which have already been loaded from the old
     * attribute document into the new attribute document. This is necessary,
     * because mergeAttributes() only merges the art:outputs nodes - all
     * other nodes are skiped.
     */
    protected Node mergeLoadedRecommendations(Document oldAttrs, Node newAttrs){
        Element loadedRecoms = (Element) XMLUtils.xpath(
            oldAttrs,
            XPATH_LOADED_RECOMMENDATIONS,
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE);

        if (loadedRecoms != null) {
            Document doc = newAttrs.getOwnerDocument();
            newAttrs.appendChild(doc.importNode(loadedRecoms, true));
        }

        return newAttrs;
    }


    @Override
    public void out(
        String       type,
        Document     format,
        OutputStream out,
        CallContext  context)
    throws IOException
    {
        log.info("FLYSArtifactCollection.out");

        String name = XMLUtils.xpathString(
            format, XPATH_OUT_NAME, ArtifactNamespaceContext.INSTANCE);

        String subtype = XMLUtils.xpathString(
            format, XPATH_OUT_TYPE, ArtifactNamespaceContext.INSTANCE);

        log.info("-> Output name = " + name);
        log.info("-> Output type = " + type);
        log.info("-> Output subtype = " + subtype);

        OutGenerator generator = null;
        if (type != null && type.length() > 0) {
            if (type.indexOf("chartinfo") > 0) {
                generator = getOutGenerator(context, type, subtype);
            }
            else {
                generator = getOutGenerator(context, name, subtype);
            }
        }
        else {
            generator = getOutGenerator(context, name, subtype);
        }

        if (generator == null) {
            log.error("There is no generator specified for output: " + name);
            // TODO Throw an exception.

            return;
        }

        generator.init(format, out, context);

        // TODO Determine the correct master artifact here!

        try {
            Document attr = getAttribute(context, name);
            doOut(generator, name, subtype, attr, context);
        }
        catch (ArtifactDatabaseException adbe) {
            log.error(adbe, adbe);
        }
    }


    /**
     * Creates the concrete output.
     *
     * @param generator The OutGenerator that creates the output.
     * @param outputName The name of the requested output.
     * @param attributes The collection's attributes for this concrete output
     * type.
     * @param context The context object.
     */
    protected void doOut(
        OutGenerator generator,
        String       outName,
        String       facet,
        Document     attributes,
        CallContext  context)
    throws IOException
    {
        log.info("FLYSArtifactCollection.doOut: " + outName);

        ThemeList themeList = new ThemeList(attributes);

        int size = themeList.size();
        log.debug("Output will contain " + size + " elements.");

        try {
            for (int i = 0; i < size; i++) {
                ManagedFacet theme = themeList.get(i);

                if (theme == null) {
                    log.debug("Theme is empty - no output is generated.");
                    continue;
                }

                String art = theme.getArtifact();

                if (log.isDebugEnabled()) {
                    log.debug("Do output for...");
                    log.debug("... artifact: " + art);
                    log.debug("... facet: " + theme.getName());
                }

                String facetName = theme.getName();

                if (outName.equals("export") && !facetName.equals(facet)) {
                    continue;
                }

                Artifact artifact = getArtifact(art, context);
                if (i == 0) {
                    generator.setMasterArtifact(artifact);
                }

                if (theme.getActive() == 0) {
                    continue;
                }

                generator.doOut(
                    artifact,
                    theme,
                    getFacetThemeFromAttribute(
                        art,
                        outName,
                        facetName,
                        context));
            }
        }
        catch (ArtifactDatabaseException ade) {
            log.error(ade, ade);
        }

        generator.generate();
    }


    protected Document buildOutAttributes(
        ArtifactDatabase db,
        CallContext      context,
        Document         oldAttr,
        String[]         items)
    {
        Document doc = XMLUtils.newDocument();

        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
            doc,
            ArtifactNamespaceContext.NAMESPACE_URI,
            ArtifactNamespaceContext.NAMESPACE_PREFIX);

        AttributeParser aParser = new AttributeParser();
        OutputParser    oParser = new OutputParser(db, context);

        for (String uuid: items) {
            try {
                oParser.parse(uuid);
            }
            catch (ArtifactDatabaseException ade) {
                log.warn(ade, ade);
            }
        }

        aParser.parse(oldAttr);

        return new AttributeWriter(aParser.getOuts(), oParser.getOuts()).write();
    }


    /**
     * Returns the attribute for a specific output type.
     *
     * @param output The name of the desired output type.
     *
     * @return the attribute for the desired output type.
     */
    protected Document getAttribute(CallContext context, String output)
    throws    ArtifactDatabaseException
    {
        Document attr = buildOutAttributes(
            context.getDatabase(),
            context,
            getAttribute(),
            getArtifactUUIDs(context));

        Node out = (Node) XMLUtils.xpath(
            attr,
            "art:attribute/art:outputs/art:output[@name='" + output + "']",
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE);


        if (out != null) {
            Document o = XMLUtils.newDocument();

            o.appendChild(o.importNode(out, true));

            return o;
        }

        return null;
    }


    /**
     * This method returns the list of artifact UUIDs that this collections
     * contains.
     *
     * @param context The CallContext that is necessary to get information about
     * the ArtifactDatabase.
     *
     * @return a list of uuids.
     */
    protected String[] getArtifactUUIDs(CallContext context)
    throws    ArtifactDatabaseException
    {
        log.debug("FLYSArtifactCollection.getArtifactUUIDs");

        ArtifactDatabase db = context.getDatabase();
        CallMeta meta       = context.getMeta();

        Document itemList = db.listCollectionArtifacts(identifier(), meta);
        NodeList items    = (NodeList) XMLUtils.xpath(
            itemList,
            XPATH_COLLECTION_ITEMS,
            XPathConstants.NODESET,
            ArtifactNamespaceContext.INSTANCE);

        if (items == null || items.getLength() == 0) {
            log.debug("No artifacts found in this collection.");
            return null;
        }

        int num = items.getLength();

        List<String> uuids = new ArrayList<String>(num);

        for (int i = 0; i < num; i++) {
            String uuid = XMLUtils.xpathString(
                items.item(i),
                "@art:uuid",
                ArtifactNamespaceContext.INSTANCE);

            if (uuid != null && uuid.trim().length() != 0) {
                uuids.add(uuid);
            }
        }

        return (String[]) uuids.toArray(new String[uuids.size()]);
    }


    /**
     * Returns a concrete Artifact of this collection specified by its uuid.
     *
     * @param uuid The Artifact's uuid.
     * @param context The CallContext.
     *
     * @return an Artifact.
     */
    protected Artifact getArtifact(String uuid, CallContext context)
    throws    ArtifactDatabaseException
    {
        log.debug("FLYSArtifactCollection.getArtifact");

        Backend backend               = Backend.getInstance();
        PersistentArtifact persistent = backend.getArtifact(uuid);

        return persistent != null ? persistent.getArtifact() : null;
    }


    /**
     * Returns the attribute that belongs to an artifact stored in this
     * collection.
     *
     * @param uuid The Artifact's uuid.
     * @param outname The name of the requested output.
     * @param facet The name of the requested facet.
     * @param context The CallContext.
     *
     * @return an attribute in form of a document.
     */
    protected Document getFacetThemeFromAttribute(
        String      uuid,
        String      outName,
        String      facet,
        CallContext context)
    throws    ArtifactDatabaseException
    {
        log.debug("FLYSArtifactCollection.getFacetThemeFromAttribute");

        ArtifactDatabase db = context.getDatabase();
        CallMeta       meta = context.getMeta();

        FLYSContext flysContext = context instanceof FLYSContext
            ? (FLYSContext) context
            : (FLYSContext) context.globalContext();

        Map<String, String> mappings = (Map<String, String>)
            flysContext.get(FLYSContext.THEME_MAPPING);

        String themeName = mappings.get(facet);

        Document attr = db.getCollectionItemAttribute(identifier(), uuid, meta);

        if (attr == null) {
            attr = initItemAttribute(uuid, facet, context);

            if (attr == null) {
                return null;
            }
        }

        log.debug("Search attribute of collection item: " + uuid);

        Node tmp = (Node) XMLUtils.xpath(
            attr,
            "/art:attribute",
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE);

        if (tmp == null) {
            log.warn("No attribute found. Operation failed.");
            return null;
        }

        log.debug("Search theme '" + themeName + "' in attribute.");

        Node theme = (Node) XMLUtils.xpath(
            tmp,
            "art:themes/theme[@name='" + themeName + "']",
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE);

        if (theme == null) {
            log.warn("Could not find the theme in attribute of: " + uuid);

            Theme t = getThemeForFacet(uuid, facet, context);

            if (t == null) {
                log.warn("No theme found for facet: " + facet);
                return null;
            }

            addThemeToAttribute(uuid, attr, t, context);
            theme   = t.toXML().getFirstChild();
        }

        Document doc = XMLUtils.newDocument();
        doc.appendChild(doc.importNode(theme, true));

        return doc;
    }


    /**
     * Adds the theme of a facet to a CollectionItem's attribute.
     *
     * @param uuid The uuid of the artifact.
     * @param attr The current attribute of an artifact.
     * @param t The theme to add.
     * @param context The CallContext.
     */
    protected void addThemeToAttribute(
        String      uuid,
        Document    attr,
        Theme       t,
        CallContext context)
    {
        log.debug("FLYSArtifactCollection.addThemeToAttribute: " + uuid);

        if (t == null) {
            log.warn("Theme is empty - cancel adding it to attribute!");
            return;
        }

        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
            attr,
            ArtifactNamespaceContext.NAMESPACE_URI,
            ArtifactNamespaceContext.NAMESPACE_PREFIX);

        Node tmp = (Node) XMLUtils.xpath(
            attr,
            "/art:attribute",
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE);

        if (tmp == null) {
            tmp = ec.create("attribute");
            attr.appendChild(tmp);
        }

        Node themes = (Node) XMLUtils.xpath(
            tmp,
            "art:themes",
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE);

        if (themes == null) {
            themes = ec.create("themes");
            tmp.appendChild(themes);
        }

        themes.appendChild(attr.importNode(t.toXML().getFirstChild(), true));

        try {
            setCollectionItemAttribute(uuid, attr, context);

            log.debug("Successfully added theme to item attribute.");
        }
        catch (ArtifactDatabaseException e) {
            // do nothing
            log.warn("Cannot set attribute of item: " + uuid);
        }
    }


    /**
     * Initializes the attribute of an collection item with the theme of a
     * specific facet.
     *
     * @param uuid The uuid of an artifact.
     * @param facet The name of a facet.
     * @param context The CallContext.
     *
     * @param the new attribute.
     */
    protected Document initItemAttribute(
        String      uuid,
        String      facet,
        CallContext context)
    {
        log.info("FLYSArtifactCollection.initItemAttribute");

        Theme t = getThemeForFacet(uuid, facet, context);

        if (t == null) {
            log.info("Could not find theme for facet. Cancel initialization.");
            return null;
        }

        Document attr = XMLUtils.newDocument();

        addThemeToAttribute(uuid, attr, t, context);

        return attr;
    }


    /**
     * Sets the attribute of a CollectionItem specified by <i>uuid</i> to a new
     * value <i>attr</i>.
     *
     * @param uuid The uuid of the CollectionItem.
     * @param attr The new attribute for the CollectionItem.
     * @param context The CallContext.
     */
    public void setCollectionItemAttribute(
        String      uuid,
        Document    attr,
        CallContext context)
    throws ArtifactDatabaseException
    {
        Document doc = ClientProtocolUtils.newSetItemAttributeDocument(
            uuid,
            attr);

        if (doc == null) {
            log.warn("Cannot set item attribute: No attribute found.");
            return;
        }

        ArtifactDatabase db = context.getDatabase();
        CallMeta       meta = context.getMeta();

        db.setCollectionItemAttribute(identifier(), uuid, doc, meta);
    }


    /**
     * Returns the theme of a specific facet.
     *
     * @param uuid The uuid of an artifact.
     * @param facet The name of the facet.
     * @param context The CallContext object.
     *
     * @return the desired theme.
     */
    protected Theme getThemeForFacet(
        String uuid,
        String facet,
        CallContext context)
    {
        log.info("FLYSArtifactCollection.getThemeForFacet: " + facet);

        FLYSContext flysContext = context instanceof FLYSContext
            ? (FLYSContext) context
            : (FLYSContext) context.globalContext();

        return ThemeFactory.getTheme(flysContext, facet);
    }


    /**
     * Returns the OutGenerator for a specified <i>type</i>.
     *
     * @param name The name of the output type.
     * @param type Defines the type of the desired OutGenerator.
     *
     * @return Instance of an OutGenerator for specified <i>type</i>.
     */
    protected OutGenerator getOutGenerator(
        CallContext context,
        String      name,
        String      type)
    {
        FLYSContext flysContext = context instanceof FLYSContext
            ? (FLYSContext) context
            : (FLYSContext) context.globalContext();

        Map<String, Class> generators = (Map<String, Class>)
            flysContext.get(FLYSContext.OUTGENERATORS_KEY);

        if (generators == null) {
            log.error("No output generators found in the running application!");
            return null;
        }

        Class clazz = generators.get(name);

        try {
            return clazz != null ? (OutGenerator) clazz.newInstance() : null;
        }
        catch (InstantiationException ie) {
            log.error(ie, ie);
        }
        catch (IllegalAccessException iae) {
            log.error(iae, iae);
        }

        return null;
    }


    protected Element buildArtifactNode(
        ArtifactDatabase        database,
        String                  uuid,
        CallContext             context,
        XMLUtils.ElementCreator ec)
    throws ArtifactDatabaseException
    {
        log.debug("Append artifact '" + uuid + "' to collection description");

        // TODO
        String hash = "MYHASH";

        Element ci   = ec.create("artifact");
        ec.addAttr(ci, "uuid", uuid, true);
        ec.addAttr(ci, "hash", hash, true);

        // XXX I am not sure if it works well every time with an empty document
        // in the describe operation of an artifact.
        Document description = database.describe(uuid, null, context.getMeta());

        Node outputModes = (Node) XMLUtils.xpath(
            description,
            XPATH_ARTIFACT_OUTPUTMODES,
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE);

        if (outputModes != null) {
            Document doc = ci.getOwnerDocument();
            ci.appendChild(doc.importNode(outputModes, true));
        }

        return ci;
    }


    /**
     * Inner class to structure/order the themes of a chart.
     */
    private class ThemeList {
        private Logger logger = Logger.getLogger(ThemeList.class);
        protected Map<Integer, ManagedFacet> themes;

        public ThemeList(Document output) {
            themes = new HashMap<Integer, ManagedFacet>();
            parse(output);
        }

        protected void parse(Document output) {
            NodeList themes = (NodeList) XMLUtils.xpath(
                output,
                "art:output/art:facet",
                XPathConstants.NODESET,
                ArtifactNamespaceContext.INSTANCE);

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

            logger.debug("Output has " +  num + " elements.");

            if (num == 0) {
                return;
            }

            String uri = ArtifactNamespaceContext.NAMESPACE_URI;

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

                String name   = theme.getAttributeNS(uri, "facet");
                String uuid   = theme.getAttributeNS(uri, "artifact");
                String pos    = theme.getAttributeNS(uri, "pos");
                String active = theme.getAttributeNS(uri, "active");
                String idx    = theme.getAttributeNS(uri, "index");
                String desc   = theme.getAttributeNS(uri, "description");

                addTheme(uuid, name, idx, pos, active, desc);
            }
        }

        protected void addTheme(
            String uuid,
            String name,
            String index,
            String position,
            String active,
            String description)
        {
            if (logger.isDebugEnabled()) {
                logger.debug("Add theme: ");
                logger.debug(".. name: " + name);
                logger.debug(".. uuid: " + uuid);
                logger.debug(".. position: " + position);
                logger.debug(".. active: " + active);
            }

            try {
                int pos = Integer.parseInt(position);
                int act = Integer.parseInt(active);
                int idx = Integer.parseInt(index);

                themes.put(
                    new Integer(pos-1),
                    new ManagedFacet(name, idx, description, uuid, pos, act));
            }
            catch (NumberFormatException nfe) {
                logger.warn(nfe, nfe);
            }
        }

        public ManagedFacet get(int idx) {
            return themes.get(new Integer(idx));
        }

        public int size() {
            return themes.size();
        }
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org