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 <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
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<String> 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<String, StateData> data;
ingo@121: 
felix@1771:     /** Mapping of state names to created facets. */
bjoern@4497:     private Map<String, List<Facet>> 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 -&gt; facets.
felix@1704:      */
sascha@1056:     protected Map<String, List<Facet>> 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<String, StateData>();
sascha@661:         previousStateIds = new ArrayList<String>();
felix@1771:         facets           = new HashMap<String, List<Facet>>();
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<String> 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 <code>data</code> 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<State> 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<String> clonePreviousStateIds() {
sascha@1059:         return new ArrayList<String>(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<String, StateData> cloneData() {
sascha@1059:         Map<String, StateData> copy = new TreeMap<String, StateData>();
sascha@1059: 
sascha@1059:         for (Map.Entry<String, StateData> 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<String, List<Facet>> cloneFacets() {
christian@3306:         Map<String, List<Facet>> copy = new HashMap<String, List<Facet>>();
sascha@1059: 
felix@1771:         for (Map.Entry<String, List<Facet>> entry: facets.entrySet()) {
felix@1771:             List<Facet> facets      = entry.getValue();
felix@1771:             List<Facet> facetCopies = new ArrayList<Facet>(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<String> stateIds     = (ArrayList<String>) getPreviousStateIds();
ingo@2606:         ArrayList<String> toInitialize = (ArrayList<String>) 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<String, List<Facet>> 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<String, List<Facet>> result = new HashMap<String, List<Facet>>();
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<Facet> facets = new ArrayList<Facet>();
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 <code>input</code> 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<State> 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<Message> 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<Output> generated = getOutputs(context);
sascha@3554: 
sascha@3554:         if (log.isDebugEnabled()) {
sascha@3554:             log.debug("This Artifact has " + generated.size() + " Outputs.");
sascha@3554:         }
sascha@3193: 
sascha@3193:         ProtocolUtils.appendOutputModes(doc, outs, generated);
sascha@3193:     }
sascha@3193: 
ingo@119: 
ingo@119:     /**
ingo@122:      * This method handles request for changing the current state of an
ingo@122:      * artifact. It is possible to step forward or backward.
ingo@122:      *
ingo@122:      * @param target The incoming ADVANCE document.
ingo@122:      * @param context The CallContext.
ingo@122:      *
ingo@122:      * @return a document that contains a SUCCESS or FAILURE message.
ingo@122:      */
christian@3306:     @Override
ingo@122:     public Document advance(Document target, CallContext context) {
sascha@3554: 
sascha@3554:         boolean debug = log.isDebugEnabled();
sascha@3554: 
ingo@122:         Document doc = XMLUtils.newDocument();
ingo@122: 
ingo@122:         XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
ingo@122:             doc,
ingo@122:             ArtifactNamespaceContext.NAMESPACE_URI,
ingo@122:             ArtifactNamespaceContext.NAMESPACE_PREFIX);
ingo@122: 
ingo@122:         Element result = ec.create("result");
ingo@122: 
ingo@2606:         String currentStateId = getCurrentStateId();
ingo@2606:         String targetState    = XMLUtils.xpathString(
ingo@122:             target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE);
ingo@122: 
sascha@3554:         if (debug) {
sascha@3554:             log.debug("FLYSArtifact.advance() to '" + targetState + "'");
sascha@3554:         }
ingo@140: 
ingo@2606:         if (!currentStateId.equals(targetState)
ingo@2606:             && isStateReachable(targetState, context))
ingo@2606:         {
sascha@3554:             if (debug) {
sascha@3554:                 log.debug("Advance: Step forward");
sascha@3554:             }
ingo@122: 
sascha@661:             List<String> prev = getPreviousStateIds();
ingo@2606:             prev.add(currentStateId);
ingo@122: 
ingo@122:             setCurrentStateId(targetState);
ingo@122: 
sascha@3554:             if (debug) {
sascha@3554:                 log.debug("Compute data for state: " + targetState);
sascha@3554:             }
sascha@705:             compute(context, ComputeType.ADVANCE, true);
ingo@693: 
ingo@123:             return describe(target, context);
ingo@122:         }
ingo@140:         else if (isPreviousState(targetState, context)) {
sascha@3554:             if (debug) {
sascha@3554:                 log.debug("Advance: Step back to");
sascha@3554:             }
ingo@122: 
ingo@1086:             List<String> prevs   = getPreviousStateIds();
ingo@140:             int targetIdx        = prevs.indexOf(targetState);
ingo@140:             int start            = prevs.size() - 1;
ingo@140: 
ingo@1086:             destroyStates(prevs, context);
ingo@1084: 
ingo@140:             for (int i = start; i >= targetIdx; i--) {
ingo@140:                 String prev = prevs.get(i);
sascha@3554:                 if (debug) {
sascha@3554:                     log.debug("Remove state id '" + prev + "'");
sascha@3554:                 }
ingo@1084: 
ingo@140:                 prevs.remove(prev);
ingo@691:                 facets.remove(prev);
ingo@140:             }
ingo@140: 
ingo@1094:             destroyState(getCurrentStateId(), context);
ingo@140:             setCurrentStateId(targetState);
ingo@140: 
ingo@140:             return describe(target, context);
ingo@140:         }
ingo@122: 
sascha@3554:         log.warn("Advance: Cannot advance to '" + targetState + "'");
ingo@123:         ec.addAttr(result, "type", OPERATION_FAILED, true);
ingo@122: 
ingo@123:         doc.appendChild(result);
ingo@122: 
ingo@122:         return doc;
ingo@122:     }
ingo@122: 
ingo@122: 
ingo@122:     /**
ingo@119:      * Returns the identifier of the current state.
ingo@119:      *
ingo@119:      * @return the identifier of the current state.
ingo@119:      */
sascha@722:     public String getCurrentStateId() {
ingo@119:         return currentStateId;
ingo@119:     }
ingo@119: 
ingo@119: 
ingo@119:     /**
ingo@119:      * Sets the identifier of the current state.
ingo@119:      *
ingo@119:      * @param id the identifier of a state.
ingo@119:      */
ingo@119:     protected void setCurrentStateId(String id) {
ingo@119:         currentStateId = id;
ingo@119:     }
ingo@119: 
ingo@119: 
ingo@119:     /**
ingo@119:      * Set the current state of this artifact. <b>NOTE</b>We don't store the
ingo@119:      * State object itself - which is not necessary - but its identifier. So
ingo@119:      * this method will just call the setCurrentStateId() method with the
ingo@119:      * identifier of <i>state</i>.
ingo@119:      *
ingo@119:      * @param state The new current state.
ingo@119:      */
ingo@119:     protected void setCurrentState(State state) {
ingo@119:         setCurrentStateId(state.getID());
ingo@119:     }
ingo@119: 
ingo@119: 
ingo@119:     /**
ingo@119:      * Returns the current state of the artifact.
ingo@119:      *
ingo@119:      * @return the current State of the artifact.
ingo@119:      */
ingo@1654:     public State getCurrentState(Object context) {
felix@1067:         return getState(context, getCurrentStateId());
felix@1067:     }
felix@1067: 
felix@1067: 
felix@1067:     /**
felix@1067:      * Get list of existant states for this Artifact.
felix@1067:      * @param context Contex to get StateEngine from.
felix@1067:      * @return list of states.
felix@1067:      */
felix@1067:     protected List<State> getStates(Object context) {
felix@1777:         FLYSContext flysContext = FLYSUtils.getFlysContext(context);
ingo@119:         StateEngine engine      = (StateEngine) flysContext.get(
ingo@119:             FLYSContext.STATE_ENGINE_KEY);
felix@1067:         return engine.getStates(getName());
ingo@119:     }
ingo@119: 
felix@1067: 
felix@1067:     /**
felix@1067:      * Get state with given ID.
felix@1067:      * @param context Context to get StateEngine from.
felix@1067:      * @param stateID ID of state to get.
felix@1067:      * @return state with given ID.
felix@1067:      */
sascha@722:     protected State getState(Object context, String stateID) {
felix@1777:         FLYSContext flysContext = FLYSUtils.getFlysContext(context);
sascha@722:         StateEngine engine      = (StateEngine) flysContext.get(
sascha@722:             FLYSContext.STATE_ENGINE_KEY);
sascha@722:         return engine.getState(stateID);
sascha@722:     }
sascha@722: 
ingo@119: 
ingo@119:     /**
ingo@122:      * Returns the vector of previous state identifiers.
ingo@122:      *
ingo@122:      * @return the vector of previous state identifiers.
ingo@122:      */
sascha@661:     protected List<String> getPreviousStateIds() {
ingo@122:         return previousStateIds;
ingo@122:     }
ingo@122: 
ingo@122: 
ingo@122:     /**
felix@1783:      * Get all previous and the current state id.
felix@1783:      * @return #getPreviousStateIds() + #getCurrentStateId()
felix@1783:      */
felix@1783:     public List<String> getStateHistoryIds() {
ingo@2606:         ArrayList<String> prevIds = (ArrayList) getPreviousStateIds();
ingo@2606:         ArrayList<String> allIds  = (ArrayList) prevIds.clone();
ingo@2606: 
felix@1783:         allIds.add(getCurrentStateId());
felix@1783:         return allIds;
felix@1783:     }
felix@1783: 
felix@1783: 
felix@1783:     /**
ingo@121:      * Adds a new StateData item to the data pool of this artifact.
ingo@121:      *
ingo@121:      * @param name the name of the data object.
ingo@121:      * @param data the data object itself.
ingo@121:      */
ingo@121:     protected void addData(String name, StateData data) {
ingo@121:         this.data.put(name, data);
ingo@121:     }
ingo@121: 
ingo@121: 
felix@4206:     /** Remove and return statedata associated to name. */
ingo@1656:     protected StateData removeData(String name) {
ingo@1656:         return this.data.remove(name);
ingo@1656:     }
ingo@1656: 
ingo@1656: 
ingo@121:     /**
ingo@122:      * This method returns a specific StateData object that is stored in the
ingo@122:      * data pool of this artifact.
ingo@122:      *
ingo@122:      * @param name The name of the data object.
ingo@122:      *
ingo@122:      * @return the StateData object if existing, otherwise null.
ingo@122:      */
ingo@298:     public StateData getData(String name) {
ingo@122:         return data.get(name);
ingo@122:     }
ingo@122: 
felix@4206: 
bjoern@3920:     /**
bjoern@3920:      * A derived Artifact class can use this method to set the data
bjoern@3920:      */
bjoern@3920:     protected void setData(Map<String, StateData> data) {
bjoern@3920:         this.data = data;
bjoern@3920:     }
ingo@122: 
felix@4206: 
felix@2169:     /** Return named data item, null if not found. */
ingo@937:     public String getDataAsString(String name) {
ingo@937:         StateData data = getData(name);
ingo@937:         return data != null ? (String) data.getValue() : null;
ingo@937:     }
ingo@937: 
felix@1763: 
felix@1763:     /**
ingo@2013:      * This method returns the value of a StateData object stored in the data
ingo@2013:      * pool of this Artifact as Integer.
ingo@2013:      *
ingo@2013:      * @param name The name of the StateData object.
ingo@2013:      *
ingo@2013:      * @return an Integer representing the value of the data object or null if
ingo@2013:      * no object was found for <i>name</i>.
ingo@2013:      *
ingo@2013:      * @throws NumberFormatException if the value of the data object could not
ingo@2013:      * be transformed into an Integer.
ingo@2013:      */
ingo@2013:     public Integer getDataAsInteger(String name)
ingo@2013:     throws NumberFormatException
ingo@2013:     {
ingo@2013:         String value = getDataAsString(name);
ingo@2013: 
ingo@2013:         if (value != null && value.length() > 0) {
ingo@2013:             return Integer.parseInt(value);
ingo@2013:         }
ingo@2013: 
ingo@2013:         return null;
ingo@2013:     }
ingo@2013: 
ingo@2013: 
ingo@2013:     /**
ingo@2038:      * This method returns the value of a StateData object stored in the data
ingo@2701:      * pool of this Artifact as Double.
ingo@2701:      *
ingo@2701:      * @param name The name of the StateData object.
ingo@2701:      *
ingo@2701:      * @return an Double representing the value of the data object or null if
ingo@2701:      * no object was found for <i>name</i>.
ingo@2701:      *
ingo@2701:      * @throws NumberFormatException if the value of the data object could not
ingo@2701:      * be transformed into a Double.
ingo@2701:      */
ingo@2701:     public Double getDataAsDouble(String name)
ingo@2701:     throws NumberFormatException
ingo@2701:     {
ingo@2701:         String value = getDataAsString(name);
ingo@2701: 
ingo@2701:         if (value != null && value.length() > 0) {
ingo@2701:             return Double.parseDouble(value);
ingo@2701:         }
ingo@2701: 
ingo@2701:         return null;
ingo@2701:     }
ingo@2701: 
ingo@2701: 
ingo@2701:     /**
ingo@2701:      * This method returns the value of a StateData object stored in the data
ingo@2128:      * pool of this Artifact as Long.
ingo@2128:      *
ingo@2128:      * @param name The name of the StateData object.
ingo@2128:      *
ingo@2128:      * @return a Long representing the value of the data object or null if
ingo@2128:      * no object was found for <i>name</i>.
ingo@2128:      *
ingo@2128:      * @throws NumberFormatException if the value of the data object could not
ingo@2128:      * be transformed into a Long.
ingo@2128:      */
ingo@2128:     public Long getDataAsLong(String name)
ingo@2128:     throws NumberFormatException
ingo@2128:     {
ingo@2128:         String value = getDataAsString(name);
ingo@2128: 
ingo@2128:         if (value != null && value.length() > 0) {
ingo@2128:             return Long.parseLong(value);
ingo@2128:         }
ingo@2128: 
ingo@2128:         return null;
ingo@2128:     }
ingo@2128: 
ingo@2128: 
ingo@2128:     /**
ingo@2128:      * This method returns the value of a StateData object stored in the data
ingo@2038:      * pool of this Artifact is Boolean using Boolean.valueOf().
ingo@2038:      *
ingo@2038:      * @param name The name of the StateData object.
ingo@2038:      *
ingo@2038:      * @return a Boolean representing the value of the data object or null if no
ingo@2038:      * such object is existing.
ingo@2038:      */
ingo@2038:     public Boolean getDataAsBoolean(String name) {
ingo@2038:         String value = getDataAsString(name);
ingo@2038: 
ingo@2038:         if (value == null || value.length() == 0) {
ingo@2038:             return null;
ingo@2038:         }
ingo@2038: 
ingo@2038:         return Boolean.valueOf(value);
ingo@2038:     }
ingo@2038: 
ingo@2038: 
ingo@2038:     /**
felix@1763:      * Add StateData containing a given string.
felix@1763:      * @param name Name of the data object.
felix@1763:      * @param value String to store.
felix@1763:      */
felix@1724:     public void addStringData(String name, String value) {
felix@1724:         addData(name, new DefaultStateData(name, null, null, value));
felix@1724:     }
felix@1724: 
bjoern@3920:     /**
bjoern@3920:      * This method returns all stored StateData in this artifact as a Collection
bjoern@3920:      * @return a Collection of all StateData objects in this artifact
bjoern@3920:      */
sascha@960:     public Collection<StateData> getAllData() {
sascha@960:         return data.values();
sascha@960:     }
sascha@960: 
ingo@937: 
felix@4206:     /** Return all produced facets. */
ingo@2095:     public List<Facet> getFacets() {
ingo@2095:         List<Facet> all = new ArrayList<Facet>();
ingo@2095: 
felix@4206:         // Iterate over facets of each state.
sascha@3553:         for (List<Facet> fs: facets.values()) {
sascha@3553:             all.addAll(fs);
ingo@2095:         }
ingo@2095: 
ingo@2095:         return all;
ingo@2095:     }
ingo@2095: 
ingo@2095: 
felix@1771:     /**
felix@1771:      * Get facet as stored internally, with equalling name and index than given
felix@1771:      * facet.
felix@1771:      * @param facet that defines index and name of facet searched.
felix@1771:      * @return facet instance or null if not found.
felix@1771:      */
ingo@696:     public Facet getNativeFacet(Facet facet) {
ingo@696:         String name  = facet.getName();
ingo@696:         int    index = facet.getIndex();
ingo@696: 
sascha@3553:         for (List<Facet> fs: facets.values()) {
sascha@3553:             for (Facet f: fs) {
felix@1771:                 if (f.getIndex() == index && f.getName().equals(name)) {
felix@1771:                     return f;
felix@1771:                 }
ingo@696:             }
ingo@696:         }
ingo@696: 
sascha@3554:         log.warn("Could not find facet: " + name + " at " + index);
bjoern@4495:         log.warn("Available facets for : " + getName() + " " + identifier() +
bjoern@4495:                 ": " + facets.values());
ingo@696:         return null;
ingo@696:     }
ingo@696: 
ingo@696: 
ingo@122:     /**
ingo@121:      * This method stores the data that is contained in the FEED document.
ingo@119:      *
ingo@119:      * @param feed The FEED document.
ingo@119:      * @param xpath The XPath that points to the data nodes.
ingo@119:      */
sascha@3553:     public void saveData(Document feed, CallContext context)
ingo@121:     throws IllegalArgumentException
ingo@121:     {
sascha@3553:         if (feed == null) {
ingo@325:             throw new IllegalArgumentException("error_feed_no_data");
ingo@121:         }
ingo@119: 
ingo@121:         NodeList nodes = (NodeList) XMLUtils.xpath(
ingo@121:             feed,
sascha@3553:             XPATH_FEED_INPUT,
ingo@121:             XPathConstants.NODESET,
ingo@121:             ArtifactNamespaceContext.INSTANCE);
ingo@121: 
ingo@121:         if (nodes == null || nodes.getLength() == 0) {
ingo@325:             throw new IllegalArgumentException("error_feed_no_data");
ingo@121:         }
ingo@121: 
sascha@3554:         boolean debug = log.isDebugEnabled();
sascha@3554: 
ingo@121:         int count = nodes.getLength();
sascha@3554: 
sascha@3554:         if (debug) {
sascha@3554:             log.debug("Try to save " + count + " data items.");
sascha@3554:         }
ingo@121: 
sascha@708:         String uri = ArtifactNamespaceContext.NAMESPACE_URI;
ingo@121: 
ingo@1176:         DefaultState current = (DefaultState) getCurrentState(context);
ingo@1176: 
ingo@2205:         FLYSContext flysContext = FLYSUtils.getFlysContext(context);
ingo@2205:         StateEngine engine      = (StateEngine) flysContext.get(
ingo@2205:             FLYSContext.STATE_ENGINE_KEY);
ingo@2205: 
sascha@708:         for (int i = 0; i < count; i++) {
sascha@708:             Element node = (Element)nodes.item(i);
ingo@121: 
sascha@708:             String name  = node.getAttributeNS(uri, "name");
sascha@708:             String value = node.getAttributeNS(uri, "value");
sascha@708: 
sascha@708:             if (name.length() > 0 && value.length() > 0) {
sascha@3554:                 if (debug) {
sascha@3554:                     log.debug("Save data item for '" + name + "' : " + value);
sascha@3554:                 }
ingo@121: 
ingo@2205:                 StateData model = engine.getStateData(getName(), name);
ingo@2205: 
ingo@2205:                 StateData sd = model != null
ingo@2205:                     ? model.deepCopy()
ingo@2205:                     : new DefaultStateData(name, null, null, value);
ingo@2205: 
ingo@2205:                 addData(
ingo@2205:                     name, current.transform(this, context, sd, name, value));
ingo@121:             }
ingo@1656:             else if (name.length() > 0 && value.length() == 0) {
sascha@3554:                 if (removeData(name) != null && debug) {
sascha@3554:                     log.debug("Removed data '" + name + "' successfully.");
ingo@1656:                 }
ingo@1656:             }
ingo@121:         }
ingo@322: 
sascha@1050:         current.validate(this);
ingo@122:     }
ingo@122: 
ingo@122: 
ingo@122:     /**
ingo@122:      * Determines if the state with the identifier <i>stateId</i> is reachable
ingo@122:      * from the current state. The determination itself takes place in the
ingo@122:      * TransitionEngine.
ingo@122:      *
ingo@122:      * @param stateId The identifier of a state.
ingo@122:      * @param context The context object.
ingo@122:      *
ingo@122:      * @return true, if the state specified by <i>stateId</i> is reacahble,
ingo@122:      * otherwise false.
ingo@122:      */
ingo@122:     protected boolean isStateReachable(String stateId, Object context) {
sascha@3554: 
sascha@3554:         if (log.isDebugEnabled()) {
sascha@3554:             log.debug("Determine if the state '" + stateId + "' is reachable.");
sascha@3554:         }
ingo@122: 
felix@1777:         FLYSContext flysContext = FLYSUtils.getFlysContext(context);
ingo@122: 
ingo@624:         State currentState  = getCurrentState(context);
ingo@122:         StateEngine sEngine = (StateEngine) flysContext.get(
ingo@122:             FLYSContext.STATE_ENGINE_KEY);
ingo@122: 
ingo@122:         TransitionEngine tEngine = (TransitionEngine) flysContext.get(
ingo@122:             FLYSContext.TRANSITION_ENGINE_KEY);
ingo@122: 
ingo@355:         return tEngine.isStateReachable(this, stateId, currentState, sEngine);
ingo@122:     }
ingo@140: 
ingo@140: 
ingo@140:     /**
ingo@140:      * Determines if the state with the identifier <i>stateId</i> is a previous
ingo@140:      * state of the current state.
ingo@140:      *
ingo@140:      * @param stateId The target state identifier.
ingo@140:      * @param context The context object.
ingo@140:      */
ingo@140:     protected boolean isPreviousState(String stateId, Object context) {
sascha@3554:         if (log.isDebugEnabled()) {
sascha@3554:             log.debug("Determine if the state '" + stateId + "' is old.");
sascha@3554:         }
ingo@140: 
sascha@3553:         return getPreviousStateIds().contains(stateId);
ingo@140:     }
ingo@317: 
ingo@317: 
ingo@317:     /**
ingo@686:      * Computes the hash code of the entered values.
ingo@686:      *
ingo@686:      * @return a hash code.
ingo@686:      */
ingo@686:     @Override
ingo@686:     public String hash() {
ingo@686: 
sascha@705:         long hash  = 0L;
sascha@705:         int  shift = 3;
ingo@686: 
teichmann@4050:         for (Map.Entry<String, StateData> entry: data.entrySet()) {
ingo@686:             String key   = entry.getKey();
ingo@686:             Object value = entry.getValue().getValue();
ingo@686: 
sascha@742:             hash ^= ((long)key.hashCode() << shift)
sascha@722:                  |  ((long)value.hashCode() << (shift + 3));
ingo@686:             shift += 2;
ingo@686:         }
ingo@686: 
ingo@686:         return getCurrentStateId() + hash;
ingo@686:     }
ingo@686: 
felix@1628: 
felix@1628:     /**
felix@1763:      * Return List of outputs, where combinations of outputname and filtername
felix@1763:      * that match content in filterFacets is left out.
felix@1763:      * @return filtered Outputlist.
felix@1628:      */
sascha@1056:     protected List<Output> filterOutputs(List<Output> outs) {
sascha@1056:         if (filterFacets == null || filterFacets.isEmpty()) {
sascha@3554:             log.debug("No filter for Outputs.");
sascha@1056:             return outs;
sascha@1056:         }
sascha@1056: 
sascha@3554:         boolean debug = log.isDebugEnabled();
sascha@3436: 
sascha@3436:         if (debug) {
sascha@3554:             log.debug(
sascha@3436:                 "Filter Facets with " + filterFacets.size() + " filters.");
sascha@3436:         }
ingo@2095: 
sascha@1056:         List<Output> filtered = new ArrayList<Output>();
sascha@1056: 
sascha@1056:         for (Output out: outs) {
ingo@2095:             String outName = out.getName();
sascha@1056: 
sascha@3436:             if (debug) {
sascha@3554:                 log.debug("  filter Facets for Output: " + outName);
sascha@3436:             }
ingo@2095: 
ingo@2095:             List<Facet> fFacets = filterFacets.get(outName);
sascha@1056:             if (fFacets != null) {
sascha@3436:                 if (debug) {
sascha@3554:                     log.debug("" + fFacets.size() + " filters for: " + outName);
ingo@2095:                     for (Facet tmp: fFacets) {
sascha@3554:                         log.debug("   filter = '" + tmp.getName() + "'");
ingo@2095:                     }
ingo@2095:                 }
sascha@1056: 
sascha@1056:                 List<Facet> resultFacets = new ArrayList<Facet>();
sascha@1056: 
sascha@1056:                 for (Facet facet: out.getFacets()) {
sascha@1056:                     for (Facet fFacet: fFacets) {
sascha@1056:                         if (facet.getIndex() == fFacet.getIndex()
sascha@1056:                         &&  facet.getName().equals(fFacet.getName())) {
sascha@1056:                             resultFacets.add(facet);
sascha@1056:                             break;
sascha@1056:                         }
sascha@1056:                     }
sascha@1056:                 }
sascha@1056: 
sascha@3436:                 if (debug) {
sascha@3554:                     log.debug(
sascha@3436:                         "Facets after filtering = " + resultFacets.size());
sascha@3436:                 }
ingo@2095: 
sascha@1056:                 if (!resultFacets.isEmpty()) {
sascha@1056:                     DefaultOutput nout = new DefaultOutput(
sascha@1056:                         out.getName(),
sascha@1056:                         out.getDescription(),
sascha@1056:                         out.getMimeType(),
sascha@1056:                         resultFacets);
sascha@1056:                     filtered.add(nout);
sascha@1056:                 }
sascha@1056:             }
bjoern@4403:             else if (debug) {
bjoern@4403:                 log.debug("No filter Factes for Output: " + outName);
bjoern@4403:             }
sascha@1056:         }
sascha@1056: 
sascha@3436:         if (debug) {
bjoern@4403:             log.debug("Number of outs after filtering = " + filtered.size());
sascha@3436:         }
ingo@2095: 
sascha@1056:         return filtered;
sascha@1056:     }
sascha@1056: 
ingo@686: 
felix@1765:     /**
felix@1765:      * Get all outputs that the Artifact can do in this state (which includes
felix@1765:      * all previous states).
felix@1765:      *
felix@1765:      * @return list of outputs
felix@1765:      */
sascha@1050:     public List<Output> getOutputs(Object context) {
sascha@3554:         if (log.isDebugEnabled()) {
sascha@3554:             log.debug("##### Get Outputs for: " + identifier() + " #####");
sascha@3554:             dumpArtifact();
sascha@3436:         }
ingo@2095: 
ingo@965:         List<String> stateIds  = getPreviousStateIds();
ingo@944:         List<Output> generated = new ArrayList<Output>();
ingo@944: 
ingo@944:         for (String stateId: stateIds) {
felix@1067:             DefaultState state = (DefaultState) getState(context, stateId);
sascha@981:             generated.addAll(getOutputForState(state));
ingo@944:         }
ingo@944: 
ingo@965:         generated.addAll(getCurrentOutputs(context));
ingo@944: 
sascha@1056:         return filterOutputs(generated);
ingo@944:     }
ingo@944: 
ingo@944: 
felix@1765:     /**
felix@1765:      * Get output(s) for current state.
felix@1765:      * @return list of outputs for current state.
felix@1765:      */
sascha@1050:     public List<Output> getCurrentOutputs(Object context) {
ingo@965:         DefaultState cur = (DefaultState) getCurrentState(context);
ingo@965: 
ingo@965:         try {
sascha@1050:             if (cur.validate(this)) {
sascha@981:                 return getOutputForState(cur);
ingo@965:             }
ingo@965:         }
ingo@965:         catch (IllegalArgumentException iae) { }
ingo@965: 
ingo@965:         return new ArrayList<Output>();
ingo@965:     }
ingo@965: 
felix@1137: 
felix@1765:     /**
felix@1765:      * Get output(s) for a specific state.
felix@1765:      * @param state State of interest
felix@1765:      * @return list of output(s) for given state.
felix@1765:      */
sascha@981:     protected List<Output> getOutputForState(DefaultState state) {
sascha@3436: 
sascha@3436:         if (state == null) {
sascha@3554:             log.error("state == null: This should not happen!");
sascha@3436:             return new ArrayList<Output>();
sascha@3436:         }
sascha@3436: 
sascha@3554:         boolean debug = log.isDebugEnabled();
sascha@3436: 
sascha@3436:         if (debug) {
sascha@3554:             log.debug("Find Outputs for State: " + state.getID());
sascha@3436:         }
ingo@2095: 
ingo@965:         List<Output> list = state.getOutputs();
sascha@3555:         if (list == null || list.isEmpty()) {
sascha@3436:             if (debug) {
sascha@3554:                 log.debug("-> No output modes for this state.");
sascha@3436:             }
ingo@965:             return new ArrayList<Output>();
ingo@965:         }
ingo@965: 
ingo@2095:         String stateId = state.getID();
ingo@2095: 
bjoern@4497:         List<Facet> fs = getFacets(stateId);
felix@1771: 
sascha@3555:         if (fs == null || fs.isEmpty()) {
sascha@3436:             if (debug) {
sascha@3554:                 log.debug("No facets found.");
sascha@3436:             }
ingo@965:             return new ArrayList<Output>();
ingo@965:         }
bjoern@4495:         if (debug) {
bjoern@4495:             log.debug("State '" + stateId + "' has facets " + fs);
bjoern@4495:         }
ingo@965: 
ingo@2095:         List<Output> gen = generateOutputs(list, fs);
ingo@2095: 
sascha@3436:         if (debug) {
sascha@3554:             log.debug("State '" + stateId + "' has " + gen.size() + " outs");
sascha@3436:         }
ingo@2095: 
ingo@2095:         return gen;
ingo@965:     }
ingo@965: 
ingo@965: 
felix@1763:     /**
felix@1763:      * Generate a list of outputs with facets from fs if type is found in list
felix@1763:      * of output.
felix@1763:      *
felix@1763:      * @param list List of outputs
felix@1763:      * @param fs List of facets
felix@1763:      */
ingo@937:     protected List<Output> generateOutputs(List<Output> list, List<Facet> fs) {
ingo@937:         List<Output> generated = new ArrayList<Output>();
bjoern@4495:         log.debug("generateOutputs for Artifact " + getName() + " "
bjoern@4495:                 + identifier());
ingo@937: 
sascha@3554:         boolean debug = log.isDebugEnabled();
ingo@937: 
ingo@937:         for (Output out: list) {
bjoern@4495:             log.debug("check facets for output: " + out.getName());
ingo@937:             Output o = new DefaultOutput(
ingo@937:                 out.getName(),
ingo@937:                 out.getDescription(),
ingo@937:                 out.getMimeType(),
ingo@937:                 out.getType());
ingo@937: 
ingo@937:             Set<String> outTypes = new HashSet<String>();
ingo@937: 
ingo@937:             for (Facet f: out.getFacets()) {
ingo@937:                 if (outTypes.add(f.getName()) && debug) {
sascha@3554:                     log.debug("configured facet " + f);
ingo@937:                 }
ingo@937:             }
ingo@937: 
ingo@937:             boolean facetAdded = false;
ingo@937:             for (Facet f: fs) {
ingo@937:                 String type = f.getName();
ingo@937: 
ingo@937:                 if (outTypes.contains(type)) {
ingo@937:                     if (debug) {
sascha@3554:                         log.debug("Add facet " + f);
ingo@937:                     }
ingo@937:                     facetAdded = true;
ingo@937:                     o.addFacet(f);
ingo@937:                 }
ingo@937:             }
ingo@937: 
ingo@937:             if (facetAdded) {
ingo@937:                 generated.add(o);
ingo@937:             }
ingo@937:         }
ingo@937: 
ingo@937:         return generated;
ingo@937:     }
ingo@937: 
ingo@937: 
ingo@689:     /**
ingo@689:      * Dispatches the computation request to compute(CallContext context, String
ingo@689:      * hash) with the current hash value of the artifact which is provided by
ingo@689:      * hash().
ingo@689:      *
ingo@689:      * @param context The CallContext.
ingo@689:      */
sascha@705:     public Object compute(
sascha@742:         CallContext context,
sascha@742:         ComputeType type,
sascha@705:         boolean     generateFacets
sascha@705:     ) {
sascha@705:         return compute(context, hash(), type, generateFacets);
ingo@689:     }
ingo@689: 
ingo@689: 
ingo@689:     /**
ingo@689:      * Dispatches computation requests to the current state which needs to
ingo@689:      * implement a createComputeCallback(String hash, FLYSArtifact artifact)
ingo@689:      * method.
ingo@689:      *
ingo@689:      * @param context The CallContext.
ingo@689:      * @param hash The hash value which is used to fetch computed data from
ingo@689:      * cache.
ingo@689:      *
ingo@689:      * @return the computed data.
ingo@689:      */
sascha@705:     public Object compute(
sascha@705:         CallContext context,
sascha@705:         String      hash,
sascha@742:         ComputeType type,
sascha@705:         boolean     generateFacets
sascha@705:     ) {
ingo@693:         DefaultState current = (DefaultState) getCurrentState(context);
sascha@722:         return compute(context, hash, current, type, generateFacets);
sascha@722:     }
ingo@689: 
felix@1704: 
felix@1704:     /**
felix@1704:      * Like compute, but identify State by it id (string).
felix@1704:      */
sascha@722:     public Object compute(
sascha@722:         CallContext context,
ingo@1117:         String      hash,
sascha@722:         String      stateID,
sascha@742:         ComputeType type,
sascha@722:         boolean     generateFacets
sascha@722:     ) {
felix@2141:         DefaultState current =
felix@2141:             (stateID == null)
sascha@722:             ? (DefaultState)getCurrentState(context)
sascha@722:             : (DefaultState)getState(context, stateID);
sascha@722: 
sascha@722:         if (hash == null) {
sascha@722:             hash = hash();
sascha@722:         }
ingo@689: 
sascha@705:         return compute(context, hash, current, type, generateFacets);
ingo@689:     }
ingo@689: 
felix@1137: 
felix@1137:     /**
felix@1765:      * Let current state compute and register facets.
felix@1765:      *
felix@1137:      * @param key key of state
felix@1137:      * @param state state
felix@1137:      * @param type Type of compute
felix@1137:      * @param generateFacets Whether new facets shall be generated.
felix@1137:      */
ingo@690:     public Object compute(
sascha@697:         CallContext   context,
sascha@697:         String        key,
sascha@697:         DefaultState  state,
sascha@705:         ComputeType   type,
sascha@705:         boolean       generateFacets
sascha@697:     ) {
sascha@697:         String stateID = state.getID();
ingo@691: 
felix@1137:         List<Facet> fs = (generateFacets) ? new ArrayList<Facet>() : null;
sascha@697: 
sascha@697:         try {
sascha@697:             Cache cache = CacheFactory.getCache(COMPUTING_CACHE);
sascha@697: 
sascha@697:             Object old = null;
sascha@697: 
sascha@697:             if (cache != null) {
sascha@697:                 net.sf.ehcache.Element element = cache.get(key);
sascha@697:                 if (element != null) {
sascha@3554:                     log.debug("Got computation result from cache.");
sascha@697:                     old = element.getValue();
sascha@697:                 }
sascha@697:             }
sascha@3203:             else {
sascha@3554:                 log.debug("cache not configured.");
sascha@3203:             }
sascha@697: 
sascha@697:             Object res;
ingo@690:             switch (type) {
ingo@690:                 case FEED:
sascha@697:                     res = state.computeFeed(this, key, context, fs, old);
sascha@697:                     break;
sascha@697:                 case ADVANCE:
sascha@700:                     res = state.computeAdvance(this, key, context, fs, old);
sascha@697:                     break;
ingo@942:                 case INIT:
ingo@958:                     res = state.computeInit(this, key, context, context.getMeta(), fs);
sascha@697:                 default:
sascha@697:                     res = null;
sascha@697:             }
ingo@690: 
sascha@697:             if (cache != null && old != res && res != null) {
sascha@3554:                 log.debug("Store computation result to cache.");
sascha@697:                 net.sf.ehcache.Element element =
sascha@697:                     new net.sf.ehcache.Element(key, res);
sascha@697:                 cache.put(element);
sascha@697:             }
sascha@697: 
sascha@697:             return res;
sascha@697:         }
sascha@697:         finally {
sascha@705:             if (generateFacets) {
sascha@705:                 if (fs.isEmpty()) {
felix@1771:                     facets.remove(stateID);
sascha@705:                 }
sascha@705:                 else {
bjoern@3934:                     addFacets(stateID, fs);
sascha@705:                 }
ingo@690:             }
ingo@686:         }
ingo@686:     }
ingo@686: 
bjoern@3934:     /**
bjoern@3934:      * Sets the facets for an ID
bjoern@3934:      *
bjoern@3934:      * Normally the id is a state ID.
bjoern@3934:      *
bjoern@3934:      * @param id ID to map the facets to
bjoern@3934:      * @param facets List of facets to be stored
bjoern@3934:      */
bjoern@3934:     protected void addFacets(String id, List<Facet> facets) {
bjoern@3934:         this.facets.put(id, facets);
bjoern@3934:     }
bjoern@3934: 
ingo@686: 
ingo@623:     /**
ingo@623:      * Method to dump the artifacts state/data.
ingo@623:      */
ingo@623:     protected void dumpArtifact() {
sascha@3554:         log.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++");
sascha@3554:         // Include uuid, type, name
bjoern@4495:         log.debug(" - Name: " + getName());
bjoern@4495:         log.debug(" - UUID: " + identifier());
bjoern@4495:         log.debug(" - Class: " + this.getClass().getName());
ingo@623: 
sascha@3554:         log.debug("------ DUMP DATA ------");
sascha@3554:         Collection<StateData> allData = data.values();
ingo@623: 
sascha@3554:         for (StateData d: allData) {
sascha@3554:             String name  = d.getName();
sascha@3554:             String value = (String) d.getValue();
ingo@623: 
sascha@3554:             log.debug("- " + name + ": " + value);
sascha@3554:         }
ingo@2095: 
sascha@3554:         log.debug("------ DUMP PREVIOUS STATES ------");
sascha@3554:         List<String> stateIds = getPreviousStateIds();
sascha@3554: 
sascha@3554:         for (String id: stateIds) {
sascha@3554:             log.debug("- State: " + id);
ingo@623:         }
sascha@3554: 
sascha@3554:         log.debug("CURRENT STATE: " + getCurrentStateId());
sascha@3554: 
sascha@3554:         debugFacets();
sascha@3554:         dumpFilterFacets();
sascha@3554: 
sascha@3554:         log.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++");
ingo@623:     }
ingo@1086: 
ingo@1086: 
ingo@2095:     protected void debugFacets() {
sascha@3554:         log.debug("######### FACETS #########");
ingo@2095: 
teichmann@4050:         for (Map.Entry<String, List<Facet>> entry: facets.entrySet()) {
ingo@2095:             String out = entry.getKey();
ingo@2095:             List<Facet> fs = entry.getValue();
ingo@2095:             for (Facet f: fs) {
sascha@3554:                 log.debug("  # " + out + " : " + f.getName());
ingo@2095:             }
ingo@2095:         }
ingo@2095: 
sascha@3554:         log.debug("######## FACETS END ########");
ingo@2095:     }
ingo@2095: 
ingo@2095: 
ingo@2095:     protected void dumpFilterFacets() {
sascha@3554:         log.debug("######## FILTER FACETS ########");
ingo@2095: 
ingo@2095:         if (filterFacets == null || filterFacets.isEmpty()) {
sascha@3554:             log.debug("No Filter Facets defined.");
ingo@2095:             return;
ingo@2095:         }
ingo@2095: 
teichmann@4050:         for (Map.Entry<String, List<Facet>> entry: filterFacets.entrySet()) {
ingo@2095:             String      out     = entry.getKey();
ingo@2095:             List<Facet> filters = entry.getValue();
ingo@2095: 
sascha@3554:             log.debug("There are " + filters.size() + " filters for: " +out);
ingo@2095: 
ingo@2095:             for (Facet filter: filters) {
sascha@3554:                 log.debug("  filter: " + filter.getName());
ingo@2095:             }
ingo@2095:         }
ingo@2095: 
sascha@3554:         log.debug("######## FILTER FACETS END ########");
ingo@2095:     }
ingo@2095: 
ingo@2095: 
felix@4206:     /** Destroy and clean up state with given id. */
ingo@1094:     protected void destroyState(String id, Object context) {
ingo@1094:         State s = getState(context, id);
ingo@1094:         s.endOfLife(this, context);
ingo@1094:     }
ingo@1094: 
ingo@1094: 
ingo@1086:     /**
ingo@1086:      * Calls endOfLife() for each state in the list <i>ids</i>.
ingo@1086:      *
ingo@1086:      * @param ids The State IDs that should be destroyed.
ingo@1086:      * @param context The FLYSContext.
ingo@1086:      */
ingo@1086:     protected void destroyStates(List<String> ids, Object context) {
ingo@1086:         for (int i = 0, num = ids.size(); i < num; i++) {
ingo@1094:             destroyState(ids.get(i), context);
ingo@1086:         }
ingo@1086:     }
ingo@1086: 
ingo@1086: 
felix@1763:     /**
felix@1763:      * Destroy the states.
felix@1763:      */
ingo@1086:     @Override
ingo@1086:     public void endOfLife(Object context) {
sascha@3554:         if (log.isDebugEnabled()) {
sascha@3554:             log.debug("FLYSArtifact.endOfLife: " + identifier());
sascha@3436:         }
ingo@1086: 
ingo@2606:         ArrayList<String> ids       = (ArrayList<String>) getPreviousStateIds();
ingo@2606:         ArrayList<String> toDestroy = (ArrayList<String>) ids.clone();
ingo@1086: 
ingo@2606:         toDestroy.add(getCurrentStateId());
ingo@2606: 
ingo@2606:         destroyStates(toDestroy, context);
ingo@1086:     }
bjoern@4496: 
bjoern@4496:     /**
bjoern@4496:      * Return the Facets which a state provides
bjoern@4496:      * @param stateid String that identifies the state
bjoern@4496:      * @return List of Facets belonging to the state identifier
bjoern@4496:      */
bjoern@4496:     protected List<Facet> getFacets(String stateid) {
bjoern@4496:         return this.facets.get(stateid);
bjoern@4496:     }
ingo@119: }
ingo@119: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :