diff artifacts/src/main/java/org/dive4elements/river/collections/FLYSArtifactCollection.java @ 5838:5aa05a7a34b7

Rename modules to more fitting names.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 15:23:37 +0200
parents flys-artifacts/src/main/java/org/dive4elements/river/collections/FLYSArtifactCollection.java@bd047b71ab37
children 4897a58c8746
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/collections/FLYSArtifactCollection.java	Thu Apr 25 15:23:37 2013 +0200
@@ -0,0 +1,625 @@
+package org.dive4elements.river.collections;
+
+import java.io.IOException;
+import java.io.OutputStream;
+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;
+
+import org.dive4elements.artifactdatabase.Backend;
+import org.dive4elements.artifactdatabase.Backend.PersistentArtifact;
+import org.dive4elements.artifactdatabase.DefaultArtifactCollection;
+import org.dive4elements.artifactdatabase.state.Facet;
+import org.dive4elements.artifactdatabase.state.Output;
+import org.dive4elements.artifactdatabase.state.Settings;
+import org.dive4elements.artifactdatabase.state.StateEngine;
+import org.dive4elements.artifacts.Artifact;
+import org.dive4elements.artifacts.ArtifactDatabase;
+import org.dive4elements.artifacts.ArtifactDatabaseException;
+import org.dive4elements.artifacts.ArtifactNamespaceContext;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.artifacts.CallMeta;
+import org.dive4elements.artifacts.common.utils.XMLUtils;
+import org.dive4elements.river.artifacts.FLYSArtifact;
+import org.dive4elements.river.artifacts.context.FLYSContext;
+import org.dive4elements.river.exports.OutGenerator;
+import org.dive4elements.river.exports.OutputHelper;
+import org.dive4elements.river.utils.FLYSUtils;
+
+/**
+ * Collection of artifacts, can do outs, describe.
+ * Lots of stuff done in AttributeParser and AttributeWriter.
+ * Critical out and facet merging.
+ * @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_ARTIFACT_STATE_DATA =
+        "/art:result/art:ui/art:static/art:state/art:data";
+
+    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";
+
+    /** Xpath to master artifacts uuid. */
+    public static final String XPATH_MASTER_UUID =
+        "/art:artifact-collection/art:artifact/@art:uuid";
+
+    public static final String XPATH_LOADED_RECOMMENDATIONS =
+        "/art:attribute/art:loaded-recommendations";
+
+
+    /**
+     * Create and return description Document for this collection.
+     */
+    @Override
+    public Document describe(CallContext context) {
+        log.debug("FLYSArtifactCollection.describe: " + identifier);
+
+        CollectionDescriptionHelper helper = new CollectionDescriptionHelper(
+            getName(), identifier(), getCreationTime(), getTTL(),
+            context);
+
+        ArtifactDatabase db = context.getDatabase();
+
+        Document        oldAttrs = getAttribute();
+        AttributeParser parser   = new AttributeParser(oldAttrs);
+
+        try {
+            String[] aUUIDs  = getArtifactUUIDs(context);
+
+            oldAttrs = removeAttributes(oldAttrs, context);
+            parser   = new AttributeParser(oldAttrs);
+
+            CollectionAttribute newAttr = mergeAttributes(
+                db, context, parser, aUUIDs);
+
+            if (checkOutputSettings(newAttr, context)) {
+                saveCollectionAttribute(db, context, newAttr);
+            }
+
+            helper.setAttribute(newAttr);
+
+            if (aUUIDs != null) {
+                for (String uuid: aUUIDs) {
+                    helper.addArtifact(uuid);
+                }
+            }
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error("Error while merging attribute documents.", ade);
+
+            helper.setAttribute(parser.getCollectionAttribute());
+        }
+
+        return helper.toXML();
+    }
+
+
+    /**
+     * Merge the current art:outputs nodes with the the outputs provided by the
+     * artifacts in the Collection.
+     *
+     * @param uuids Artifact uuids.
+     */
+    protected CollectionAttribute mergeAttributes(
+        ArtifactDatabase db,
+        CallContext      context,
+        AttributeParser  oldParser,
+        String[]         uuids
+    ) {
+        CollectionAttribute cAttribute =
+            buildOutAttributes(db, context, oldParser, uuids);
+
+        if (cAttribute == null) {
+            log.warn("mergeAttributes: cAttribute == null");
+            return null;
+        }
+
+        cAttribute.setLoadedRecommendations(
+            getLoadedRecommendations(oldParser.getAttributeDocument()));
+
+        saveCollectionAttribute(db, context, cAttribute);
+
+        return cAttribute;
+    }
+
+
+    protected Document removeAttributes(Document attrs, CallContext context) {
+        Node outs = (Node) XMLUtils.xpath(
+            attrs,
+            "/art:attribute/art:outputs",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        NodeList nodes = (NodeList) XMLUtils.xpath(
+            attrs,
+            "/art:attribute/art:outputs/art:output",
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (nodes != null) {
+            for (int i = 0; i < nodes.getLength(); i++) {
+                Element e = (Element)nodes.item(i);
+                if(!outputExists(e.getAttribute("name"), context)) {
+                    outs.removeChild(e);
+                }
+            }
+        }
+        return attrs;
+    }
+
+
+    /**
+     * True if current MasterArtifact has given output.
+     * @param name Name of the output of interest.
+     * @param context current context
+     * @return true if current master artifact has given output.
+     */
+    protected boolean outputExists(String name, CallContext context) {
+        FLYSArtifact master = getMasterArtifact(context);
+        List<Output> outList = master.getOutputs(context);
+
+        for (Output o : outList) {
+            if (name.equals(o.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param db The ArtifactDatabase which is required to save the attribute
+     * into.
+     * @param attribute The CollectionAttribute that should be stored in the
+     * database.
+     *
+     * @return true, if the transaction was successful, otherwise false.
+     */
+    protected boolean saveCollectionAttribute(
+        ArtifactDatabase    db,
+        CallContext         context,
+        CollectionAttribute attribute
+    ) {
+        log.info("Save new CollectionAttribute into database.");
+
+        Document doc = attribute.toXML();
+
+        try {
+            // Save the merged document into database.
+            db.setCollectionAttribute(identifier(), context.getMeta(), doc);
+
+            log.info("Saving CollectionAttribute was successful.");
+
+            return true;
+        }
+        catch (ArtifactDatabaseException adb) {
+            log.error(adb, adb);
+        }
+
+        return false;
+    }
+
+
+    /**
+     * 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 skipped.
+     */
+    protected Node getLoadedRecommendations(Document oldAttrs) {
+        Element loadedRecoms = (Element) XMLUtils.xpath(
+            oldAttrs,
+            XPATH_LOADED_RECOMMENDATIONS,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        return loadedRecoms;
+    }
+
+
+    /**
+     * Evaluates the Output settings. If an Output has no Settings set, the
+     * relevant OutGenerator is used to initialize a default Settings object.
+     *
+     * @param attribute The CollectionAttribute.
+     * @param cc The CallContext.
+     *
+     * @return true, if the CollectionAttribute was modified, otherwise false.
+     */
+    protected boolean checkOutputSettings(
+        CollectionAttribute attribute,
+        CallContext         cc
+    ) {
+        boolean modified = false;
+
+        Map<String, Output> outputMap = attribute != null
+            ? attribute.getOutputs()
+            : null;
+
+        if (outputMap == null || outputMap.isEmpty()) {
+            log.debug("No Output Settings check necessary.");
+            return modified;
+        }
+
+
+        for (Map.Entry<String, Output> entry: outputMap.entrySet()) {
+            String outName = entry.getKey();
+            Output output  = entry.getValue();
+
+            if (outName.equals("sq_overview")) {
+                continue;
+            }
+            Settings settings = output.getSettings();
+
+            if (settings == null) {
+                log.debug("No Settings set for Output '" + outName + "'.");
+                output.setSettings(
+                    createInitialOutputSettings(cc, attribute, outName));
+
+                modified = true;
+            }
+        }
+
+        return modified;
+    }
+
+
+    /**
+     * This method uses the the OutGenerator for the specified Output
+     * <i>out</i> to create an initial Settings object.
+     *
+     * @param cc The CallContext object.
+     * @param attr The CollectionAttribute.
+     * @param out The name of the output.
+     *
+     * @return a default Settings object for the specified Output.
+     */
+    protected Settings createInitialOutputSettings(
+        CallContext         cc,
+        CollectionAttribute attr,
+        String              out
+    ) {
+        OutGenerator outGen = FLYSContext.getOutGenerator(cc, out, null);
+
+        if (outGen == null) {
+            return null;
+        }
+
+        // XXX NOTE: the outGen is not able to process its generate() operation,
+        // because it has no OutputStream set!
+        outGen.init(XMLUtils.newDocument(), null, cc);
+        prepareMasterArtifact(outGen, cc);
+
+        try {
+            Document outAttr = getAttribute(cc, attr, out);
+            OutputHelper helper = new OutputHelper(identifier());
+            helper.doOut(outGen, out, out, outAttr, cc);
+        }
+        catch (ArtifactDatabaseException adbe) {
+            log.error(adbe, adbe);
+        }
+        catch (IOException ioe) {
+            log.error(ioe, ioe);
+        }
+
+        return outGen.getSettings();
+    }
+
+
+    @Override
+    public void out(
+        String       type,
+        Document     format,
+        OutputStream out,
+        CallContext  context)
+    throws IOException
+    {
+        boolean debug = log.isDebugEnabled();
+
+        long reqBegin = System.currentTimeMillis();
+
+        if (debug) {
+            log.debug(XMLUtils.toString(format));
+            log.debug("FLYSArtifactCollection.out");
+        }
+
+        String name = XMLUtils.xpathString(
+            format, XPATH_OUT_NAME, ArtifactNamespaceContext.INSTANCE);
+
+        String subtype = XMLUtils.xpathString(
+            format, XPATH_OUT_TYPE, ArtifactNamespaceContext.INSTANCE);
+
+        if (debug) {
+            log.debug("-> Output name = " + name);
+            log.debug("-> Output type = " + type);
+            log.debug("-> Output subtype = " + subtype);
+        }
+
+        OutGenerator generator = null;
+        if (type != null
+             && type.length() > 0
+             && type.indexOf("chartinfo") > 0)
+        {
+            generator = FLYSContext.getOutGenerator(context, type, subtype);
+        }
+        else {
+            generator = FLYSContext.getOutGenerator(context, name, subtype);
+        }
+
+        if (generator == null) {
+            log.error("There is no generator specified for output: " + name);
+            // TODO Throw an exception.
+
+            return;
+        }
+
+        Document        oldAttrs  = getAttribute();
+        AttributeParser parser    = new AttributeParser(oldAttrs);
+        CollectionAttribute cAttr = parser.getCollectionAttribute();
+
+        Output output = cAttr.getOutput(name);
+        Settings settings = null;
+        if (output != null) {
+            settings = output.getSettings();
+
+            if (debug) {
+                List<Facet> facets = output.getFacets();
+                for(Facet facet: facets) {
+                    log.debug("  -- Facet " + facet.getName());
+                }
+            }
+        }
+
+        generator.init(format, out, context);
+        generator.setSettings(settings);
+        generator.setCollection(this);
+        prepareMasterArtifact(generator, context);
+
+        try {
+            Document attr = getAttribute(context, cAttr, name);
+            OutputHelper helper = new OutputHelper(identifier());
+            if (name.equals("sq_overview")) {
+                helper.doOut(generator, name, subtype, format, context);
+            }
+            helper.doOut(generator, name, subtype, attr, context);
+            generator.generate();
+        }
+        catch (ArtifactDatabaseException adbe) {
+            log.error(adbe, adbe);
+        }
+
+        if (debug) {
+            long duration = System.currentTimeMillis() -reqBegin;
+            log.info("Processing out(" + name + ") took " + duration + " ms.");
+        }
+    }
+
+
+    /**
+     * Sets the master Artifact at the given <i>generator</i>.
+     *
+     * @param generator The generator that gets a master Artifact.
+     * @param cc The CallContext.
+     */
+    protected void prepareMasterArtifact(OutGenerator generator, CallContext cc
+    ) {
+        // Get master artifact.
+        FLYSArtifact master = getMasterArtifact(cc);
+        if (master != null) {
+            log.debug("Set master Artifact to uuid: " + master.identifier());
+            generator.setMasterArtifact(master);
+        }
+        else {
+            log.warn("Could not set master artifact.");
+        }
+    }
+
+
+    /**
+     * @return masterartifact or null if exception/not found.
+     */
+    protected FLYSArtifact getMasterArtifact(CallContext context)
+    {
+        try {
+            ArtifactDatabase db = context.getDatabase();
+            CallMeta callMeta   = context.getMeta();
+            Document document   = db.getCollectionsMasterArtifact(
+                identifier(), callMeta);
+
+            String masterUUID   = XMLUtils.xpathString(
+                document, XPATH_MASTER_UUID, ArtifactNamespaceContext.INSTANCE);
+            FLYSArtifact masterArtifact =
+                (FLYSArtifact) getArtifact(masterUUID, context);
+            return masterArtifact;
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error(ade, ade);
+        }
+        return null;
+    }
+
+
+    /**
+     * Return merged output document.
+     * @param uuids List of artifact uuids.
+     */
+    protected CollectionAttribute buildOutAttributes(
+        ArtifactDatabase db,
+        CallContext      context,
+        AttributeParser  aParser,
+        String[]         uuids)
+    {
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        if (engine == null) {
+            log.error("buildOutAttributes: engine == null");
+            return null;
+        }
+
+        FLYSArtifact masterArtifact = getMasterArtifact(context);
+
+        if (masterArtifact == null) {
+            log.debug("buildOutAttributes: masterArtifact == null");
+            return null;
+        }
+
+        OutputParser oParser = new OutputParser(db, context);
+
+        if (uuids != null) {
+            for (String uuid: uuids) {
+                try {
+                    oParser.parse(uuid);
+                }
+                catch (ArtifactDatabaseException ade) {
+                    log.warn(ade, ade);
+                }
+            }
+        }
+
+        aParser.parse();
+
+        AttributeWriter aWriter = new AttributeWriter(
+            db,
+            aParser.getCollectionAttribute(),
+            aParser.getOuts(),
+            aParser.getFacets(),
+            oParser.getOuts(),
+            oParser.getFacets(),
+            engine.getCompatibleFacets(masterArtifact.getStateHistoryIds())
+            );
+        return aWriter.write();
+    }
+
+
+    /**
+     * Returns the "attribute" (part of description document) for a specific
+     * output type.
+     *
+     * @param context The CallContext object.
+     * @param cAttr The CollectionAttribute.
+     * @param output The name of the desired output type.
+     *
+     * @return the attribute for the desired output type.
+     */
+    protected Document getAttribute(
+        CallContext         context,
+        CollectionAttribute cAttr,
+        String              output)
+    throws    ArtifactDatabaseException
+    {
+        Document attr = cAttr.toXML();
+
+        Map<String, String> vars = new HashMap<String, String>();
+        vars.put("output", output);
+
+        Node out = (Node) XMLUtils.xpath(
+            attr,
+            "art:attribute/art:outputs/art:output[@name=$output]",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE,
+            vars);
+
+
+        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 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;
+    }
+
+
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org