ingo@119: package de.intevation.flys.artifacts; ingo@119: ingo@121: import java.util.HashMap; ingo@119: import java.util.List; ingo@121: import java.util.Map; ingo@122: import java.util.Set; ingo@122: import java.util.Vector; ingo@121: ingo@121: import javax.xml.xpath.XPathConstants; ingo@119: ingo@119: import org.apache.log4j.Logger; ingo@119: ingo@119: import org.w3c.dom.Document; ingo@121: import org.w3c.dom.Element; ingo@121: import org.w3c.dom.Node; ingo@121: import org.w3c.dom.NodeList; ingo@119: ingo@119: import de.intevation.artifacts.ArtifactFactory; ingo@119: import de.intevation.artifacts.CallContext; ingo@119: ingo@119: import de.intevation.artifacts.common.ArtifactNamespaceContext; ingo@119: import de.intevation.artifacts.common.utils.XMLUtils; ingo@119: ingo@119: import de.intevation.artifactdatabase.DefaultArtifact; ingo@121: import de.intevation.artifactdatabase.data.DefaultStateData; ingo@119: import de.intevation.artifactdatabase.data.StateData; ingo@119: import de.intevation.artifactdatabase.state.State; ingo@119: import de.intevation.artifactdatabase.state.StateEngine; ingo@122: import de.intevation.artifactdatabase.transition.TransitionEngine; ingo@119: ingo@119: import de.intevation.flys.artifacts.context.FLYSContext; ingo@119: ingo@119: ingo@119: /** ingo@119: * The defaul FLYS artifact. ingo@119: * ingo@119: * @author Ingo Weinzierl ingo@119: */ ingo@119: public abstract class FLYSArtifact extends DefaultArtifact { ingo@119: ingo@119: /** The logger that is used in this artifact.*/ ingo@119: private static Logger logger = Logger.getLogger(FLYSArtifact.class); ingo@119: ingo@119: ingo@121: /** 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: ingo@122: /** 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@122: /** The constant string that shows that an operation was successful.*/ ingo@122: public static final String OPERATION_SUCCESSFUL = "SUCCESS"; ingo@122: ingo@122: /** The constant string that shows that an operation failed.*/ ingo@122: public static final String OPERATION_FAILED = "FAILURE"; ingo@122: ingo@119: ingo@119: /** The identifier of the current state. */ ingo@119: protected String currentStateId; ingo@119: ingo@122: /** The identifiers of previous states on a stack.*/ ingo@122: protected Vector previousStateIds; ingo@122: ingo@119: /** The name of the artifact.*/ ingo@119: protected String name; ingo@119: ingo@121: /** The data that have been inserted into this artifact.*/ ingo@121: protected Map data; ingo@121: ingo@121: ingo@121: /** ingo@121: * The default constructor that creates an empty FLYSArtifact. ingo@121: */ ingo@121: public FLYSArtifact() { ingo@122: data = new HashMap(); ingo@122: previousStateIds = new Vector(); ingo@121: } ingo@121: 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: */ ingo@121: public abstract String getName(); ingo@121: ingo@121: ingo@121: /** ingo@121: * Returns the FLYSContext from context object. ingo@121: * ingo@121: * @param context The CallContext or the FLYSContext. ingo@121: * ingo@121: * @return the FLYSContext. ingo@121: */ ingo@121: protected FLYSContext getFlysContext(Object context) { ingo@121: return context instanceof FLYSContext ingo@121: ? (FLYSContext) context ingo@121: : (FLYSContext) ((CallContext) context).globalContext(); ingo@121: } 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@119: Document data) ingo@119: { ingo@119: logger.debug("Setup this artifact with the uuid: " + identifier); ingo@119: ingo@119: super.setup(identifier, factory, context, data); ingo@119: ingo@119: FLYSContext flysContext = (FLYSContext) context; ingo@119: StateEngine engine = (StateEngine) flysContext.get( ingo@119: FLYSContext.STATE_ENGINE_KEY); ingo@119: ingo@121: String name = getName(); ingo@121: ingo@121: logger.debug("Set initial state for artifact '" + name + "'"); ingo@119: List states = engine.getStates(name); ingo@119: ingo@119: setCurrentState(states.get(0)); ingo@119: } ingo@119: 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) { ingo@123: logger.info("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 { ingo@121: saveData(target, XPATH_FEED_INPUT); ingo@123: return describe(target, context); ingo@121: } ingo@121: catch (IllegalArgumentException iae) { ingo@122: creator.addAttr(result, "type", OPERATION_FAILED, true); ingo@121: ingo@121: // TODO I18N this message - getMessage() returns a lookup string, no ingo@121: // human readable error message ingo@121: result.setTextContent(iae.getMessage()); ingo@121: } ingo@121: ingo@121: return doc; ingo@119: } ingo@119: 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: */ ingo@122: public Document advance(Document target, CallContext context) { 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@122: String targetState = XMLUtils.xpathString( ingo@122: target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE); ingo@122: ingo@140: logger.info("FLYSArtifact.advance() to '" + targetState + "'"); ingo@140: ingo@122: if (isStateReachable(targetState, context)) { ingo@140: logger.info("Advance: Step forward"); ingo@122: ingo@122: Vector prev = getPreviousStateIds(); ingo@122: prev.add(getCurrentStateId()); ingo@122: ingo@122: setCurrentStateId(targetState); ingo@122: ingo@123: return describe(target, context); ingo@122: } ingo@140: else if (isPreviousState(targetState, context)) { ingo@140: logger.info("Advance: Step back to"); ingo@122: ingo@140: Vector prevs = getPreviousStateIds(); ingo@140: int targetIdx = prevs.indexOf(targetState); ingo@140: int start = prevs.size() - 1; ingo@140: ingo@140: for (int i = start; i >= targetIdx; i--) { ingo@140: String prev = prevs.get(i); ingo@140: logger.debug("Remove state id '" + prev + "'"); ingo@140: prevs.remove(prev); ingo@140: } ingo@140: ingo@140: setCurrentStateId(targetState); ingo@140: ingo@140: return describe(target, context); ingo@140: } ingo@122: ingo@122: logger.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: */ ingo@119: protected 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. NOTEWe 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 state. 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@119: protected State getCurrentState(Object context) { ingo@121: FLYSContext flysContext = getFlysContext(context); ingo@119: StateEngine engine = (StateEngine) flysContext.get( ingo@119: FLYSContext.STATE_ENGINE_KEY); ingo@119: ingo@119: return engine.getState(getCurrentStateId()); ingo@119: } ingo@119: 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: */ ingo@122: protected Vector getPreviousStateIds() { ingo@122: return previousStateIds; ingo@122: } ingo@122: ingo@122: ingo@122: /** 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: 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@122: protected StateData getData(String name) { ingo@122: return data.get(name); ingo@122: } ingo@122: ingo@122: 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: */ ingo@121: public void saveData(Document feed, String xpath) ingo@121: throws IllegalArgumentException ingo@121: { ingo@121: if (feed == null || xpath == null || xpath.length() == 0) { ingo@121: throw new IllegalArgumentException("feed.no.input.data"); ingo@121: } ingo@119: ingo@121: NodeList nodes = (NodeList) XMLUtils.xpath( ingo@121: feed, ingo@121: xpath, ingo@121: XPathConstants.NODESET, ingo@121: ArtifactNamespaceContext.INSTANCE); ingo@121: ingo@121: if (nodes == null || nodes.getLength() == 0) { ingo@121: throw new IllegalArgumentException("feed.no.input.data"); ingo@121: } ingo@121: ingo@121: int count = nodes.getLength(); ingo@121: logger.debug("Try to save " + count + " data items."); ingo@121: ingo@121: for (int i = 0; i < count; i++) { ingo@121: Node node = nodes.item(i); ingo@121: ingo@121: String name = XMLUtils.xpathString( ingo@121: node, "@art:name", ArtifactNamespaceContext.INSTANCE); ingo@121: String value = XMLUtils.xpathString( ingo@121: node, "@art:value", ArtifactNamespaceContext.INSTANCE); ingo@121: ingo@121: if (name != null && value != null) { ingo@121: logger.debug("Save data item for '" + name + "' : " + value); ingo@121: ingo@121: // TODO ADD INPUT VALIDATION! ingo@121: addData(name, new DefaultStateData(name, null, null, value)); ingo@121: } ingo@121: } ingo@119: } ingo@122: ingo@122: ingo@122: /** ingo@122: * This method fills a state object with the data that have been inserted to ingo@122: * this artifact. This is necessary to use the isStateReachable() method, ingo@122: * because the Transitions need to know about the inserted data. ingo@122: * ingo@122: * @param state The state that needs to be filled with data. ingo@122: * ingo@122: * @return the filled state. ingo@122: */ ingo@122: protected State fillState(State state) { ingo@122: Map stateData = state.getData(); ingo@140: ingo@140: if (stateData == null) { ingo@140: return state; ingo@140: } ingo@140: ingo@122: Set keys = stateData.keySet(); ingo@122: ingo@122: for (String key: keys) { ingo@122: StateData tmp = getData(key); ingo@122: ingo@122: if (tmp != null) { ingo@122: StateData data = stateData.get(key); ingo@122: data.setValue(tmp.getValue()); ingo@122: } ingo@122: } ingo@122: ingo@122: return state; ingo@122: } ingo@122: ingo@122: ingo@122: /** ingo@122: * Determines if the state with the identifier stateId 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 stateId is reacahble, ingo@122: * otherwise false. ingo@122: */ ingo@122: protected boolean isStateReachable(String stateId, Object context) { ingo@122: logger.debug("Determine if the state '" + stateId + "' is reachable."); ingo@122: ingo@122: FLYSContext flysContext = getFlysContext(context); ingo@122: ingo@122: State currentState = fillState(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@122: return tEngine.isStateReachable(stateId, currentState, sEngine); ingo@122: } ingo@140: ingo@140: ingo@140: /** ingo@140: * Determines if the state with the identifier stateId 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) { ingo@140: logger.debug("Determine if the state '" + stateId + "' is old."); ingo@140: ingo@140: Vector prevs = getPreviousStateIds(); ingo@140: if (prevs.contains(stateId)) { ingo@140: return true; ingo@140: } ingo@140: ingo@140: return false; ingo@140: } ingo@119: } ingo@119: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :