Mercurial > dive4elements > river
diff flys-artifacts/src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java @ 3468:f37e7e8907cb
merged flys-artifacts/2.8.1
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:39 +0200 |
parents | 200e70f31f6f |
children | b1912514e0f5 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java Fri Sep 28 12:14:39 2012 +0200 @@ -0,0 +1,608 @@ +package de.intevation.flys.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 java.util.Set; + +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.XMLUtils; + +import de.intevation.artifactdatabase.Backend; +import de.intevation.artifactdatabase.Backend.PersistentArtifact; +import de.intevation.artifactdatabase.DefaultArtifactCollection; +import de.intevation.artifactdatabase.state.Output; +import de.intevation.artifactdatabase.state.Settings; +import de.intevation.artifactdatabase.state.StateEngine; + +import de.intevation.flys.artifacts.context.FLYSContext; +import de.intevation.flys.artifacts.FLYSArtifact; +import de.intevation.flys.exports.OutGenerator; +import de.intevation.flys.exports.OutputHelper; + +import de.intevation.flys.utils.FLYSUtils; + +/** + * @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"; + + + /** + * 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); + + 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.size() == 0) { + log.debug("No Output Settings check necessary."); + return modified; + } + + Set<Map.Entry<String, Output>> entries = outputMap.entrySet(); + + for (Map.Entry<String, Output> entry: entries) { + 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("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 = output.getSettings(); + + 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) + { + Document doc = XMLUtils.newDocument(); + + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + StateEngine engine = (StateEngine) flysContext.get( + FLYSContext.STATE_ENGINE_KEY); + + FLYSArtifact masterArtifact = getMasterArtifact(context); + + XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( + doc, + ArtifactNamespaceContext.NAMESPACE_URI, + ArtifactNamespaceContext.NAMESPACE_PREFIX); + + 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(); + + return new AttributeWriter( + db, + aParser.getCollectionAttribute(), + aParser.getOuts(), + aParser.getFacets(), + oParser.getOuts(), + oParser.getFacets(), + engine.getCompatibleFacets(masterArtifact.getStateHistoryIds()) + ).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 :