ingo@119: package de.intevation.flys.artifacts;
ingo@119:
christian@3883: import java.util.ArrayList;
christian@3883: import java.util.Collection;
christian@3883: import java.util.HashMap;
christian@3883: import java.util.HashSet;
christian@3883: import java.util.LinkedList;
christian@3883: import java.util.List;
christian@3883: import java.util.Map;
christian@3883: import java.util.Set;
christian@3883: import java.util.TreeMap;
christian@3883:
christian@3883: import javax.xml.xpath.XPathConstants;
christian@3883:
christian@3883: import net.sf.ehcache.Cache;
christian@3883:
christian@3883: import org.apache.log4j.Logger;
christian@3883: import org.w3c.dom.Document;
christian@3883: import org.w3c.dom.Element;
christian@3883: import org.w3c.dom.Node;
christian@3883: import org.w3c.dom.NodeList;
christian@3883:
sascha@1055: import de.intevation.artifactdatabase.ArtifactDatabaseImpl;
sascha@1055: import de.intevation.artifactdatabase.DefaultArtifact;
christian@3306: import de.intevation.artifactdatabase.ProtocolUtils;
felix@1724: import de.intevation.artifactdatabase.data.DefaultStateData;
sascha@1055: import de.intevation.artifactdatabase.data.StateData;
sascha@1057: import de.intevation.artifactdatabase.state.DefaultFacet;
sascha@1055: import de.intevation.artifactdatabase.state.DefaultOutput;
sascha@1055: import de.intevation.artifactdatabase.state.Facet;
sascha@1055: import de.intevation.artifactdatabase.state.Output;
sascha@1055: import de.intevation.artifactdatabase.state.State;
sascha@1055: import de.intevation.artifactdatabase.state.StateEngine;
sascha@1055: import de.intevation.artifactdatabase.transition.TransitionEngine;
ingo@940: import de.intevation.artifacts.Artifact;
ingo@940: import de.intevation.artifacts.ArtifactDatabase;
ingo@940: import de.intevation.artifacts.ArtifactDatabaseException;
ingo@119: import de.intevation.artifacts.ArtifactFactory;
ingo@119: import de.intevation.artifacts.CallContext;
ingo@952: import de.intevation.artifacts.CallMeta;
christian@3306: import de.intevation.artifacts.Message;
ingo@119: import de.intevation.artifacts.common.ArtifactNamespaceContext;
ingo@119: import de.intevation.artifacts.common.utils.XMLUtils;
christian@3306: import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
sascha@1055: import de.intevation.flys.artifacts.cache.CacheFactory;
ingo@119: import de.intevation.flys.artifacts.context.FLYSContext;
christian@3306: import de.intevation.flys.artifacts.model.CalculationMessage;
felix@1777: import de.intevation.flys.artifacts.states.DefaultState;
sascha@1055: import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
felix@1777: import de.intevation.flys.utils.FLYSUtils;
ingo@119:
ingo@119: /**
felix@2169: * The default FLYS artifact with convenience added.
felix@2169: * (Subclass to get fully functional artifacts).
ingo@119: *
ingo@119: * @author Ingo Weinzierl
ingo@119: */
ingo@119: public abstract class FLYSArtifact extends DefaultArtifact {
ingo@119:
felix@1704: /** The logger that is used in this artifact. */
sascha@3554: private static Logger log = Logger.getLogger(FLYSArtifact.class);
ingo@119:
ingo@686: public static final String COMPUTING_CACHE = "computed.values";
ingo@686:
felix@1704: /** The XPath that points to the input data elements of the FEED document. */
ingo@121: public static final String XPATH_FEED_INPUT =
ingo@121: "/art:action/art:data/art:input";
ingo@119:
felix@1704: /** The XPath that points to the name of the target state of ADVANCE. */
ingo@122: public static final String XPATH_ADVANCE_TARGET =
ingo@122: "/art:action/art:target/@art:name";
ingo@122:
ingo@940: public static final String XPATH_MODEL_ARTIFACT =
ingo@941: "/art:action/art:template/@uuid";
ingo@940:
sascha@1057: public static final String XPATH_FILTER =
sascha@1057: "/art:action/art:filter/art:out";
sascha@1057:
felix@1704: /** The constant string that shows that an operation was successful. */
ingo@122: public static final String OPERATION_SUCCESSFUL = "SUCCESS";
ingo@122:
felix@1704: /** The constant string that shows that an operation failed. */
ingo@122: public static final String OPERATION_FAILED = "FAILURE";
ingo@122:
ingo@119: /** The identifier of the current state. */
ingo@119: protected String currentStateId;
ingo@119:
felix@1704: /** The identifiers of previous states on a stack. */
sascha@661: protected List previousStateIds;
ingo@122:
felix@1704: /** The name of the artifact. */
ingo@119: protected String name;
ingo@119:
felix@1704: /** The data that have been inserted into this artifact. */
bjoern@3920: private Map data;
ingo@121:
felix@1771: /** Mapping of state names to created facets. */
felix@1771: protected Map> facets;
ingo@687:
felix@1704: /**
felix@1704: * Used to generates "view" on the facets (hides facets not matching the
felix@1704: * filter in output of collection); out -> facets.
felix@1704: */
sascha@1056: protected Map> filterFacets;
sascha@1056:
ingo@121:
ingo@121: /**
ingo@121: * The default constructor that creates an empty FLYSArtifact.
ingo@121: */
ingo@121: public FLYSArtifact() {
ingo@686: data = new TreeMap();
sascha@661: previousStateIds = new ArrayList();
felix@1771: facets = new HashMap>();
ingo@121: }
ingo@121:
felix@4147:
sascha@3193: /**
sascha@3193: * This method appends the static data - that has already been inserted by
sascha@3193: * the user - to the static node of the DESCRIBE document.
sascha@3193: *
sascha@3193: * @param doc The document.
sascha@3193: * @param ui The root node.
sascha@3193: * @param context The CallContext.
sascha@3193: * @param uuid The identifier of the artifact.
sascha@3193: */
sascha@3193: protected void appendStaticUI(
sascha@3193: Document doc,
sascha@3193: Node ui,
sascha@3193: CallContext context,
sascha@3193: String uuid)
sascha@3193: {
sascha@3193: List stateIds = getPreviousStateIds();
sascha@3193:
sascha@3193: FLYSContext flysContext = FLYSUtils.getFlysContext(context);
sascha@3193: StateEngine engine = (StateEngine) flysContext.get(
sascha@3193: FLYSContext.STATE_ENGINE_KEY);
sascha@3193:
sascha@3554: boolean debug = log.isDebugEnabled();
sascha@3554:
sascha@3193: for (String stateId: stateIds) {
sascha@3554: if (debug) {
sascha@3554: log.debug("Append static data for state: " + stateId);
sascha@3554: }
sascha@3193: DefaultState state = (DefaultState) engine.getState(stateId);
sascha@3193:
sascha@3193: ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
sascha@3193: }
sascha@3193: }
sascha@3193:
ingo@121:
ingo@121: /**
ingo@121: * Returns the name of the concrete artifact.
ingo@121: *
ingo@121: * @return the name of the concrete artifact.
ingo@121: */
sascha@1059: public String getName() {
sascha@1059: return name;
sascha@1059: }
ingo@121:
ingo@119:
ingo@119: /**
ingo@119: * Initialize the artifact and insert new data if data
contains
ingo@119: * information necessary for this artifact.
ingo@119: *
ingo@119: * @param identifier The UUID.
ingo@119: * @param factory The factory that is used to create this artifact.
ingo@119: * @param context The CallContext.
ingo@119: * @param data Some optional data.
ingo@119: */
ingo@119: @Override
ingo@119: public void setup(
ingo@119: String identifier,
ingo@119: ArtifactFactory factory,
ingo@119: Object context,
ingo@952: CallMeta callMeta,
ingo@119: Document data)
ingo@119: {
sascha@3554: boolean debug = log.isDebugEnabled();
sascha@3554:
sascha@3554: if (debug) {
sascha@3554: log.debug("Setup this artifact with the uuid: " + identifier);
sascha@3554: }
ingo@119:
ingo@952: super.setup(identifier, factory, context, callMeta, data);
ingo@119:
felix@1777: FLYSContext flysContext = FLYSUtils.getFlysContext(context);
felix@1067:
felix@1067: List states = getStates(context);
ingo@119:
ingo@121: String name = getName();
sascha@3554:
sascha@3554: if (debug) {
christian@3883: log.debug("setup(): Set initial state for artifact '" + name + "'");
sascha@3554: }
ingo@119:
felix@2169: if (states == null) {
sascha@3554: log.error("No states found from which an initial "
felix@2766: + "state could be picked.");
felix@2169: }
ingo@119: setCurrentState(states.get(0));
ingo@940:
ingo@940: String model = XMLUtils.xpathString(
ingo@940: data,
ingo@940: XPATH_MODEL_ARTIFACT,
ingo@940: ArtifactNamespaceContext.INSTANCE);
ingo@940:
ingo@940: if (model != null && model.length() > 0) {
ingo@940: ArtifactDatabase db = (ArtifactDatabase) flysContext.get(
ingo@940: ArtifactDatabaseImpl.GLOBAL_CONTEXT_KEY);
ingo@940:
ingo@940: try {
ingo@952: initialize(db.getRawArtifact(model), context, callMeta);
ingo@940: }
ingo@940: catch (ArtifactDatabaseException adbe) {
sascha@3554: log.error(adbe, adbe);
ingo@940: }
ingo@940: }
sascha@1057:
sascha@1057: filterFacets = buildFilterFacets(data);
ingo@940: }
ingo@940:
felix@1763:
felix@2141: /** Get copy of previous state ids as Strings in list. */
sascha@1059: protected List clonePreviousStateIds() {
sascha@1059: return new ArrayList(previousStateIds);
sascha@1059: }
sascha@1059:
felix@2141:
felix@1895: /**
felix@1895: * Copies data item from other artifact to this artifact.
felix@2111: *
felix@1895: * @param other Artifact from which to get data.
felix@1895: * @param name Name of data.
felix@1895: */
felix@1895: protected void importData(FLYSArtifact other, final String name) {
felix@1895: if (other == null) {
sascha@3554: log.error("No other art. to import data " + name + " from.");
felix@2111: return;
felix@1895: }
felix@1895:
felix@1895: StateData sd = other.getData(name);
felix@1895:
felix@1895: if (sd == null) {
sascha@3554: log.warn("Other artifact has no data " + name + ".");
felix@1895: return;
felix@1895: }
felix@1895:
felix@1895: this.addData(name, sd);
felix@1895: }
felix@1763:
felix@2169:
felix@4206: /** Clone the internal map of map of state-name to state-data. */
sascha@1059: protected Map cloneData() {
sascha@1059: Map copy = new TreeMap();
sascha@1059:
sascha@1059: for (Map.Entry entry: data.entrySet()) {
sascha@1059: copy.put(entry.getKey(), entry.getValue().deepCopy());
sascha@1059: }
sascha@1059:
sascha@1059: return copy;
sascha@1059: }
sascha@1059:
felix@4147:
felix@1763: /**
felix@1763: * Return a copy of the facet mapping.
felix@1771: * @return Mapping of state-ids to facets.
felix@1763: */
felix@1771: protected Map> cloneFacets() {
christian@3306: Map> copy = new HashMap>();
sascha@1059:
felix@1771: for (Map.Entry> entry: facets.entrySet()) {
felix@1771: List facets = entry.getValue();
felix@1771: List facetCopies = new ArrayList(facets.size());
felix@1771: for (Facet facet: facets) {
felix@1771: facetCopies.add(facet.deepCopy());
felix@1771: }
felix@1771: copy.put(entry.getKey(), facetCopies);
sascha@1059: }
sascha@1059:
sascha@1059: return copy;
sascha@1059: }
ingo@940:
felix@1763:
felix@1765: /**
felix@1765: * (called from setup).
felix@1771: * @param artifact master-artifact (if any, otherwise initialize is not called).
felix@1765: */
ingo@952: protected void initialize(
ingo@952: Artifact artifact,
ingo@952: Object context,
ingo@952: CallMeta callMeta)
ingo@952: {
sascha@1059: if (!(artifact instanceof FLYSArtifact)) {
sascha@1059: return;
sascha@1059: }
sascha@1059:
sascha@1059: FLYSArtifact flys = (FLYSArtifact)artifact;
sascha@1059:
sascha@1059: currentStateId = flys.currentStateId;
sascha@1059: previousStateIds = flys.clonePreviousStateIds();
sascha@1059: name = flys.name;
sascha@1059: data = flys.cloneData();
sascha@1059: facets = flys.cloneFacets();
sascha@1059: // Do not clone filter facets!
ingo@2093:
ingo@2606: ArrayList stateIds = (ArrayList) getPreviousStateIds();
ingo@2606: ArrayList toInitialize = (ArrayList) stateIds.clone();
ingo@2093:
ingo@2606: toInitialize.add(getCurrentStateId());
ingo@2606:
ingo@2606: for (String stateId: toInitialize) {
ingo@2093: State state = getState(context, stateId);
ingo@2093:
ingo@2093: if (state != null) {
ingo@2095: state.initialize(artifact, this, context, callMeta);
ingo@2093: }
ingo@2093: }
ingo@119: }
ingo@119:
felix@1704:
felix@1704: /**
felix@1704: * Builds filter facets from document.
felix@1704: * @see filterFacets
felix@1704: */
sascha@1057: protected Map> buildFilterFacets(Document document) {
bjoern@4403: if (log.isDebugEnabled()) {
bjoern@4403: log.debug("Building filter factes for artifact " + this.getName());
bjoern@4403: }
sascha@1057:
sascha@1057: NodeList nodes = (NodeList)XMLUtils.xpath(
sascha@1057: document,
sascha@1057: XPATH_FILTER,
sascha@1057: XPathConstants.NODESET,
sascha@1057: ArtifactNamespaceContext.INSTANCE);
sascha@1057:
sascha@1057: if (nodes == null || nodes.getLength() == 0) {
sascha@1057: return null;
sascha@1057: }
sascha@1057:
sascha@1057: Map> result = new HashMap>();
sascha@1057:
sascha@1057: for (int i = 0, N = nodes.getLength(); i < N; ++i) {
sascha@1057: Element element = (Element)nodes.item(i);
sascha@1057: String oName = element.getAttribute("name");
bjoern@4403: if (oName == null || oName.isEmpty()) {
sascha@1057: continue;
sascha@1057: }
sascha@1057:
sascha@1057: List facets = new ArrayList();
sascha@1057:
sascha@1057: NodeList facetNodes = element.getElementsByTagNameNS(
sascha@1057: ArtifactNamespaceContext.NAMESPACE_URI,
sascha@1057: "facet");
sascha@1057:
sascha@1057: for (int j = 0, M = facetNodes.getLength(); j < M; ++j) {
sascha@1057: Element facetElement = (Element)facetNodes.item(j);
sascha@1057:
sascha@1057: String fName = facetElement.getAttribute("name");
sascha@1057:
sascha@1057: int index;
sascha@1057: try {
sascha@1057: index = Integer.parseInt(facetElement.getAttribute("index"));
sascha@1057: }
sascha@1057: catch (NumberFormatException nfe) {
sascha@3554: log.warn(nfe);
sascha@1057: index = 0;
sascha@1057: }
bjoern@4403: if (log.isDebugEnabled()) {
bjoern@4403: log.debug("Creating filter facet " + fName + " with index " + index +
bjoern@4403: " for out " + oName);
bjoern@4403: }
sascha@1057: facets.add(new DefaultFacet(index, fName, ""));
sascha@1057: }
sascha@1057:
sascha@1057: if (!facets.isEmpty()) {
sascha@1057: result.put(oName, facets);
sascha@1057: }
sascha@1057: }
sascha@1057:
sascha@1057: return result;
sascha@1057: }
sascha@1057:
ingo@119:
ingo@119: /**
ingo@119: * Insert new data included in input
into the current state.
ingo@119: *
ingo@119: * @param target XML document that contains new data.
ingo@119: * @param context The CallContext.
ingo@119: *
ingo@119: * @return a document that contains a SUCCESS or FAILURE message.
ingo@119: */
ingo@119: @Override
ingo@119: public Document feed(Document target, CallContext context) {
sascha@3554: log.debug("FLYSArtifact.feed()");
ingo@123:
ingo@121: Document doc = XMLUtils.newDocument();
ingo@119:
ingo@121: XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
ingo@121: doc,
ingo@121: ArtifactNamespaceContext.NAMESPACE_URI,
ingo@121: ArtifactNamespaceContext.NAMESPACE_PREFIX);
ingo@119:
ingo@121: Element result = creator.create("result");
ingo@121: doc.appendChild(result);
ingo@119:
ingo@121: try {
sascha@3553: saveData(target, context);
ingo@689:
sascha@705: compute(context, ComputeType.FEED, true);
ingo@689:
ingo@123: return describe(target, context);
ingo@121: }
ingo@121: catch (IllegalArgumentException iae) {
sascha@658: // do not store state if validation fails.
sascha@658: context.afterCall(CallContext.NOTHING);
ingo@122: creator.addAttr(result, "type", OPERATION_FAILED, true);
ingo@121:
ingo@121: result.setTextContent(iae.getMessage());
ingo@121: }
ingo@121:
ingo@121: return doc;
ingo@119: }
ingo@119:
felix@4147:
sascha@3193: /**
sascha@3193: * This method returns a description of this artifact.
sascha@3193: *
sascha@3193: * @param data Some data.
sascha@3193: * @param context The CallContext.
sascha@3193: *
sascha@3193: * @return the description of this artifact.
sascha@3193: */
christian@3306: @Override
sascha@3193: public Document describe(Document data, CallContext context) {
sascha@3193:
sascha@3554: if (log.isDebugEnabled()) {
sascha@3554: log.debug(
sascha@3554: "Describe: the current state is: " + getCurrentStateId());
sascha@3193: dumpArtifact();
sascha@3193: }
sascha@3193:
sascha@3193: FLYSContext flysContext = FLYSUtils.getFlysContext(context);
sascha@3193:
sascha@3193: StateEngine stateEngine = (StateEngine) flysContext.get(
sascha@3193: FLYSContext.STATE_ENGINE_KEY);
sascha@3193:
sascha@3193: TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
sascha@3193: FLYSContext.TRANSITION_ENGINE_KEY);
sascha@3193:
sascha@3193: List reachable = transitionEngine.getReachableStates(
sascha@3193: this, getCurrentState(context), stateEngine);
sascha@3193:
sascha@3193: Document description = XMLUtils.newDocument();
sascha@3193: XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
sascha@3193: description,
sascha@3193: ArtifactNamespaceContext.NAMESPACE_URI,
sascha@3193: ArtifactNamespaceContext.NAMESPACE_PREFIX);
sascha@3193:
sascha@3193: Element root = ProtocolUtils.createRootNode(creator);
sascha@3193: description.appendChild(root);
sascha@3193:
sascha@3193: State current = getCurrentState(context);
sascha@3193:
sascha@3193: ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
sascha@3193: ProtocolUtils.appendState(creator, root, current);
sascha@3193: ProtocolUtils.appendReachableStates(creator, root, reachable);
sascha@3193:
sascha@3193: appendBackgroundActivity(creator, root, context);
sascha@3193:
sascha@3193: Element ui = ProtocolUtils.createArtNode(
sascha@3193: creator, "ui", null, null);
sascha@3193:
sascha@3193: Element staticUI = ProtocolUtils.createArtNode(
sascha@3193: creator, "static", null, null);
sascha@3193:
sascha@3193: Element outs = ProtocolUtils.createArtNode(
sascha@3193: creator, "outputmodes", null, null);
sascha@3193: appendOutputModes(description, outs, context, identifier());
sascha@3193:
sascha@3193: appendStaticUI(description, staticUI, context, identifier());
sascha@3193:
sascha@3193: Element name = ProtocolUtils.createArtNode(
sascha@3193: creator, "name",
sascha@3193: new String[] { "value" },
sascha@3193: new String[] { getName() });
sascha@3193:
sascha@3193: Element dynamic = current.describe(
sascha@3193: this,
sascha@3193: description,
sascha@3193: root,
sascha@3193: context,
sascha@3193: identifier());
sascha@3193:
sascha@3193: if (dynamic != null) {
sascha@3193: ui.appendChild(dynamic);
sascha@3193: }
sascha@3193:
sascha@3193: ui.appendChild(staticUI);
sascha@3193:
sascha@3193: root.appendChild(name);
sascha@3193: root.appendChild(ui);
sascha@3193: root.appendChild(outs);
sascha@3193:
sascha@3193: return description;
sascha@3193: }
sascha@3193:
sascha@3193: /** Override me! */
sascha@3193:
sascha@3193: protected void appendBackgroundActivity(
sascha@3193: ElementCreator cr,
sascha@3193: Element root,
sascha@3193: CallContext context
sascha@3193: ) {
sascha@3193: LinkedList messages = context.getBackgroundMessages();
sascha@3193:
sascha@3193: if (messages == null) {
sascha@3193: return;
sascha@3193: }
sascha@3193:
sascha@3193: Element inBackground = cr.create("background-processing");
sascha@3193: root.appendChild(inBackground);
sascha@3193:
sascha@3193: cr.addAttr(
sascha@3193: inBackground,
sascha@3193: "value",
sascha@3193: String.valueOf(context.isInBackground()),
sascha@3193: true);
sascha@3193:
felix@4206: CalculationMessage message = (CalculationMessage) messages.getLast();
sascha@3193: cr.addAttr(
sascha@3193: inBackground,
sascha@3193: "steps",
sascha@3193: String.valueOf(message.getSteps()),
sascha@3193: true);
sascha@3193:
sascha@3193: cr.addAttr(
sascha@3193: inBackground,
sascha@3193: "currentStep",
sascha@3193: String.valueOf(message.getCurrentStep()),
sascha@3193: true);
sascha@3193:
sascha@3193: inBackground.setTextContent(message.getMessage());
sascha@3193: }
sascha@3193:
sascha@3193: /**
sascha@3193: * Append output mode nodes to a document.
sascha@3193: */
sascha@3193: protected void appendOutputModes(
sascha@3193: Document doc,
sascha@3193: Element outs,
sascha@3193: CallContext context,
sascha@3193: String uuid)
sascha@3193: {
sascha@3193: List