Mercurial > dive4elements > river
diff flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java @ 3318:dbe2f85bf160
merged flys-artifacts/2.8
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:35 +0200 |
parents | 1b41dc00b1f7 |
children | 0d63581c5df1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java Fri Sep 28 12:14:35 2012 +0200 @@ -0,0 +1,1469 @@ +package de.intevation.flys.artifacts; + +import de.intevation.artifactdatabase.ArtifactDatabaseImpl; +import de.intevation.artifactdatabase.DefaultArtifact; +import de.intevation.artifactdatabase.ProtocolUtils; +import de.intevation.artifactdatabase.data.DefaultStateData; +import de.intevation.artifactdatabase.data.StateData; +import de.intevation.artifactdatabase.state.DefaultFacet; +import de.intevation.artifactdatabase.state.DefaultOutput; +import de.intevation.artifactdatabase.state.Facet; +import de.intevation.artifactdatabase.state.Output; +import de.intevation.artifactdatabase.state.State; +import de.intevation.artifactdatabase.state.StateEngine; +import de.intevation.artifactdatabase.transition.TransitionEngine; +import de.intevation.artifacts.Artifact; +import de.intevation.artifacts.ArtifactDatabase; +import de.intevation.artifacts.ArtifactDatabaseException; +import de.intevation.artifacts.ArtifactFactory; +import de.intevation.artifacts.CallContext; +import de.intevation.artifacts.CallMeta; +import de.intevation.artifacts.Message; +import de.intevation.artifacts.common.ArtifactNamespaceContext; +import de.intevation.artifacts.common.utils.XMLUtils; +import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator; +import de.intevation.flys.artifacts.cache.CacheFactory; +import de.intevation.flys.artifacts.context.FLYSContext; +import de.intevation.flys.artifacts.model.CalculationMessage; +import de.intevation.flys.artifacts.states.DefaultState; +import de.intevation.flys.artifacts.states.DefaultState.ComputeType; +import de.intevation.flys.utils.FLYSUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.xml.xpath.XPathConstants; + +import net.sf.ehcache.Cache; + +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * The default FLYS artifact with convenience added. + * (Subclass to get fully functional artifacts). + * + * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> + */ +public abstract class FLYSArtifact extends DefaultArtifact { + + /** The logger that is used in this artifact. */ + private static Logger logger = Logger.getLogger(FLYSArtifact.class); + + public static final String COMPUTING_CACHE = "computed.values"; + + /** The XPath that points to the input data elements of the FEED document. */ + public static final String XPATH_FEED_INPUT = + "/art:action/art:data/art:input"; + + /** The XPath that points to the name of the target state of ADVANCE. */ + public static final String XPATH_ADVANCE_TARGET = + "/art:action/art:target/@art:name"; + + public static final String XPATH_MODEL_ARTIFACT = + "/art:action/art:template/@uuid"; + + public static final String XPATH_FILTER = + "/art:action/art:filter/art:out"; + + /** The constant string that shows that an operation was successful. */ + public static final String OPERATION_SUCCESSFUL = "SUCCESS"; + + /** The constant string that shows that an operation failed. */ + public static final String OPERATION_FAILED = "FAILURE"; + + /** The identifier of the current state. */ + protected String currentStateId; + + /** The identifiers of previous states on a stack. */ + protected List<String> previousStateIds; + + /** The name of the artifact. */ + protected String name; + + /** The data that have been inserted into this artifact. */ + protected Map<String, StateData> data; + + /** Mapping of state names to created facets. */ + protected Map<String, List<Facet>> facets; + + /** + * Used to generates "view" on the facets (hides facets not matching the + * filter in output of collection); out -> facets. + */ + protected Map<String, List<Facet>> filterFacets; + + + /** + * The default constructor that creates an empty FLYSArtifact. + */ + public FLYSArtifact() { + data = new TreeMap<String, StateData>(); + previousStateIds = new ArrayList<String>(); + facets = new HashMap<String, List<Facet>>(); + } + + /** + * This method appends the static data - that has already been inserted by + * the user - to the static node of the DESCRIBE document. + * + * @param doc The document. + * @param ui The root node. + * @param context The CallContext. + * @param uuid The identifier of the artifact. + */ + protected void appendStaticUI( + Document doc, + Node ui, + CallContext context, + String uuid) + { + List<String> stateIds = getPreviousStateIds(); + + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + StateEngine engine = (StateEngine) flysContext.get( + FLYSContext.STATE_ENGINE_KEY); + + for (String stateId: stateIds) { + logger.debug("Append static data for state: " + stateId); + DefaultState state = (DefaultState) engine.getState(stateId); + + ui.appendChild(state.describeStatic(this, doc, ui, context, uuid)); + } + } + + + /** + * Returns the name of the concrete artifact. + * + * @return the name of the concrete artifact. + */ + public String getName() { + return name; + } + + + /** + * Initialize the artifact and insert new data if <code>data</code> contains + * information necessary for this artifact. + * + * @param identifier The UUID. + * @param factory The factory that is used to create this artifact. + * @param context The CallContext. + * @param data Some optional data. + */ + @Override + public void setup( + String identifier, + ArtifactFactory factory, + Object context, + CallMeta callMeta, + Document data) + { + logger.debug("Setup this artifact with the uuid: " + identifier); + + super.setup(identifier, factory, context, callMeta, data); + + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + + List<State> states = getStates(context); + + String name = getName(); + logger.debug("Set initial state for artifact '" + name + "'"); + + if (states == null) { + logger.error("No states found from which an initial " + + "state could be picked."); + } + setCurrentState(states.get(0)); + + String model = XMLUtils.xpathString( + data, + XPATH_MODEL_ARTIFACT, + ArtifactNamespaceContext.INSTANCE); + + if (model != null && model.length() > 0) { + ArtifactDatabase db = (ArtifactDatabase) flysContext.get( + ArtifactDatabaseImpl.GLOBAL_CONTEXT_KEY); + + try { + initialize(db.getRawArtifact(model), context, callMeta); + } + catch (ArtifactDatabaseException adbe) { + logger.error(adbe, adbe); + } + } + + filterFacets = buildFilterFacets(data); + } + + + /** Get copy of previous state ids as Strings in list. */ + protected List<String> clonePreviousStateIds() { + return new ArrayList<String>(previousStateIds); + } + + + /** + * Copies data item from other artifact to this artifact. + * + * @param other Artifact from which to get data. + * @param name Name of data. + */ + protected void importData(FLYSArtifact other, final String name) { + if (other == null) { + logger.error("No other art. to import data " + name + " from."); + return; + } + + StateData sd = other.getData(name); + + if (sd == null) { + logger.warn("Other artifact has no data " + name + "."); + return; + } + + this.addData(name, sd); + } + + + protected Map<String, StateData> cloneData() { + Map<String, StateData> copy = new TreeMap<String, StateData>(); + + for (Map.Entry<String, StateData> entry: data.entrySet()) { + copy.put(entry.getKey(), entry.getValue().deepCopy()); + } + + return copy; + } + + /** + * Return a copy of the facet mapping. + * @return Mapping of state-ids to facets. + */ + protected Map<String, List<Facet>> cloneFacets() { + Map<String, List<Facet>> copy = new HashMap<String, List<Facet>>(); + + for (Map.Entry<String, List<Facet>> entry: facets.entrySet()) { + List<Facet> facets = entry.getValue(); + List<Facet> facetCopies = new ArrayList<Facet>(facets.size()); + for (Facet facet: facets) { + facetCopies.add(facet.deepCopy()); + } + copy.put(entry.getKey(), facetCopies); + } + + return copy; + } + + + /** + * (called from setup). + * @param artifact master-artifact (if any, otherwise initialize is not called). + */ + protected void initialize( + Artifact artifact, + Object context, + CallMeta callMeta) + { + if (!(artifact instanceof FLYSArtifact)) { + return; + } + + FLYSArtifact flys = (FLYSArtifact)artifact; + + currentStateId = flys.currentStateId; + previousStateIds = flys.clonePreviousStateIds(); + name = flys.name; + data = flys.cloneData(); + facets = flys.cloneFacets(); + // Do not clone filter facets! + + ArrayList<String> stateIds = (ArrayList<String>) getPreviousStateIds(); + ArrayList<String> toInitialize = (ArrayList<String>) stateIds.clone(); + + toInitialize.add(getCurrentStateId()); + + for (String stateId: toInitialize) { + State state = getState(context, stateId); + + if (state != null) { + state.initialize(artifact, this, context, callMeta); + } + } + } + + + /** + * Builds filter facets from document. + * @see filterFacets + */ + protected Map<String, List<Facet>> buildFilterFacets(Document document) { + + NodeList nodes = (NodeList)XMLUtils.xpath( + document, + XPATH_FILTER, + XPathConstants.NODESET, + ArtifactNamespaceContext.INSTANCE); + + if (nodes == null || nodes.getLength() == 0) { + return null; + } + + Map<String, List<Facet>> result = new HashMap<String, List<Facet>>(); + + for (int i = 0, N = nodes.getLength(); i < N; ++i) { + Element element = (Element)nodes.item(i); + String oName = element.getAttribute("name"); + if (oName.length() == 0) { + continue; + } + + List<Facet> facets = new ArrayList<Facet>(); + + NodeList facetNodes = element.getElementsByTagNameNS( + ArtifactNamespaceContext.NAMESPACE_URI, + "facet"); + + for (int j = 0, M = facetNodes.getLength(); j < M; ++j) { + Element facetElement = (Element)facetNodes.item(j); + + String fName = facetElement.getAttribute("name"); + + int index; + try { + index = Integer.parseInt(facetElement.getAttribute("index")); + } + catch (NumberFormatException nfe) { + logger.warn(nfe); + index = 0; + } + facets.add(new DefaultFacet(index, fName, "")); + } + + if (!facets.isEmpty()) { + result.put(oName, facets); + } + } + + return result; + } + + + /** + * Insert new data included in <code>input</code> into the current state. + * + * @param target XML document that contains new data. + * @param context The CallContext. + * + * @return a document that contains a SUCCESS or FAILURE message. + */ + @Override + public Document feed(Document target, CallContext context) { + logger.info("FLYSArtifact.feed()"); + + Document doc = XMLUtils.newDocument(); + + XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator( + doc, + ArtifactNamespaceContext.NAMESPACE_URI, + ArtifactNamespaceContext.NAMESPACE_PREFIX); + + Element result = creator.create("result"); + doc.appendChild(result); + + try { + saveData(target, XPATH_FEED_INPUT, context); + + compute(context, ComputeType.FEED, true); + + return describe(target, context); + } + catch (IllegalArgumentException iae) { + // do not store state if validation fails. + context.afterCall(CallContext.NOTHING); + creator.addAttr(result, "type", OPERATION_FAILED, true); + + result.setTextContent(iae.getMessage()); + } + + return doc; + } + + /** + * This method returns a description of this artifact. + * + * @param data Some data. + * @param context The CallContext. + * + * @return the description of this artifact. + */ + @Override + public Document describe(Document data, CallContext context) { + logger.debug("Describe: the current state is: " + getCurrentStateId()); + + if (logger.isDebugEnabled()) { + dumpArtifact(); + } + + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + + StateEngine stateEngine = (StateEngine) flysContext.get( + FLYSContext.STATE_ENGINE_KEY); + + TransitionEngine transitionEngine = (TransitionEngine) flysContext.get( + FLYSContext.TRANSITION_ENGINE_KEY); + + List<State> reachable = transitionEngine.getReachableStates( + this, getCurrentState(context), stateEngine); + + Document description = XMLUtils.newDocument(); + XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator( + description, + ArtifactNamespaceContext.NAMESPACE_URI, + ArtifactNamespaceContext.NAMESPACE_PREFIX); + + Element root = ProtocolUtils.createRootNode(creator); + description.appendChild(root); + + State current = getCurrentState(context); + + ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash()); + ProtocolUtils.appendState(creator, root, current); + ProtocolUtils.appendReachableStates(creator, root, reachable); + + appendBackgroundActivity(creator, root, context); + + Element ui = ProtocolUtils.createArtNode( + creator, "ui", null, null); + + Element staticUI = ProtocolUtils.createArtNode( + creator, "static", null, null); + + Element outs = ProtocolUtils.createArtNode( + creator, "outputmodes", null, null); + appendOutputModes(description, outs, context, identifier()); + + appendStaticUI(description, staticUI, context, identifier()); + + Element name = ProtocolUtils.createArtNode( + creator, "name", + new String[] { "value" }, + new String[] { getName() }); + + Element dynamic = current.describe( + this, + description, + root, + context, + identifier()); + + if (dynamic != null) { + ui.appendChild(dynamic); + } + + ui.appendChild(staticUI); + + root.appendChild(name); + root.appendChild(ui); + root.appendChild(outs); + + return description; + } + + /** Override me! */ + + protected void appendBackgroundActivity( + ElementCreator cr, + Element root, + CallContext context + ) { + LinkedList<Message> messages = context.getBackgroundMessages(); + + if (messages == null) { + return; + } + + Element inBackground = cr.create("background-processing"); + root.appendChild(inBackground); + + cr.addAttr( + inBackground, + "value", + String.valueOf(context.isInBackground()), + true); + + CalculationMessage message = (CalculationMessage) messages.getLast(); + cr.addAttr( + inBackground, + "steps", + String.valueOf(message.getSteps()), + true); + + cr.addAttr( + inBackground, + "currentStep", + String.valueOf(message.getCurrentStep()), + true); + + inBackground.setTextContent(message.getMessage()); + } + + /** + * Append output mode nodes to a document. + */ + protected void appendOutputModes( + Document doc, + Element outs, + CallContext context, + String uuid) + { + List<Output> generated = getOutputs(context); + logger.debug("This Artifact has " + generated.size() + " Outputs."); + + ProtocolUtils.appendOutputModes(doc, outs, generated); + } + + + /** + * This method handles request for changing the current state of an + * artifact. It is possible to step forward or backward. + * + * @param target The incoming ADVANCE document. + * @param context The CallContext. + * + * @return a document that contains a SUCCESS or FAILURE message. + */ + @Override + public Document advance(Document target, CallContext context) { + Document doc = XMLUtils.newDocument(); + + XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( + doc, + ArtifactNamespaceContext.NAMESPACE_URI, + ArtifactNamespaceContext.NAMESPACE_PREFIX); + + Element result = ec.create("result"); + + String currentStateId = getCurrentStateId(); + String targetState = XMLUtils.xpathString( + target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE); + + logger.info("FLYSArtifact.advance() to '" + targetState + "'"); + + if (!currentStateId.equals(targetState) + && isStateReachable(targetState, context)) + { + logger.info("Advance: Step forward"); + + List<String> prev = getPreviousStateIds(); + prev.add(currentStateId); + + setCurrentStateId(targetState); + + logger.debug("Compute data for state: " + targetState); + compute(context, ComputeType.ADVANCE, true); + + return describe(target, context); + } + else if (isPreviousState(targetState, context)) { + logger.info("Advance: Step back to"); + + List<String> prevs = getPreviousStateIds(); + int targetIdx = prevs.indexOf(targetState); + int start = prevs.size() - 1; + + destroyStates(prevs, context); + + for (int i = start; i >= targetIdx; i--) { + String prev = prevs.get(i); + logger.debug("Remove state id '" + prev + "'"); + + prevs.remove(prev); + facets.remove(prev); + } + + destroyState(getCurrentStateId(), context); + setCurrentStateId(targetState); + + return describe(target, context); + } + + logger.warn("Advance: Cannot advance to '" + targetState + "'"); + ec.addAttr(result, "type", OPERATION_FAILED, true); + + doc.appendChild(result); + + return doc; + } + + + /** + * Returns the identifier of the current state. + * + * @return the identifier of the current state. + */ + public String getCurrentStateId() { + return currentStateId; + } + + + /** + * Sets the identifier of the current state. + * + * @param id the identifier of a state. + */ + protected void setCurrentStateId(String id) { + currentStateId = id; + } + + + /** + * Set the current state of this artifact. <b>NOTE</b>We don't store the + * State object itself - which is not necessary - but its identifier. So + * this method will just call the setCurrentStateId() method with the + * identifier of <i>state</i>. + * + * @param state The new current state. + */ + protected void setCurrentState(State state) { + setCurrentStateId(state.getID()); + } + + + /** + * Returns the current state of the artifact. + * + * @return the current State of the artifact. + */ + public State getCurrentState(Object context) { + return getState(context, getCurrentStateId()); + } + + + /** + * Get list of existant states for this Artifact. + * @param context Contex to get StateEngine from. + * @return list of states. + */ + protected List<State> getStates(Object context) { + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + StateEngine engine = (StateEngine) flysContext.get( + FLYSContext.STATE_ENGINE_KEY); + return engine.getStates(getName()); + } + + + /** + * Get state with given ID. + * @param context Context to get StateEngine from. + * @param stateID ID of state to get. + * @return state with given ID. + */ + protected State getState(Object context, String stateID) { + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + StateEngine engine = (StateEngine) flysContext.get( + FLYSContext.STATE_ENGINE_KEY); + return engine.getState(stateID); + } + + + /** + * Returns the vector of previous state identifiers. + * + * @return the vector of previous state identifiers. + */ + protected List<String> getPreviousStateIds() { + return previousStateIds; + } + + + /** + * Get all previous and the current state id. + * @return #getPreviousStateIds() + #getCurrentStateId() + */ + public List<String> getStateHistoryIds() { + ArrayList<String> prevIds = (ArrayList) getPreviousStateIds(); + ArrayList<String> allIds = (ArrayList) prevIds.clone(); + + allIds.add(getCurrentStateId()); + return allIds; + } + + + /** + * Adds a new StateData item to the data pool of this artifact. + * + * @param name the name of the data object. + * @param data the data object itself. + */ + protected void addData(String name, StateData data) { + this.data.put(name, data); + } + + + protected StateData removeData(String name) { + return this.data.remove(name); + } + + + /** + * This method returns a specific StateData object that is stored in the + * data pool of this artifact. + * + * @param name The name of the data object. + * + * @return the StateData object if existing, otherwise null. + */ + public StateData getData(String name) { + return data.get(name); + } + + + /** Return named data item, null if not found. */ + public String getDataAsString(String name) { + StateData data = getData(name); + return data != null ? (String) data.getValue() : null; + } + + + /** + * This method returns the value of a StateData object stored in the data + * pool of this Artifact as Integer. + * + * @param name The name of the StateData object. + * + * @return an Integer representing the value of the data object or null if + * no object was found for <i>name</i>. + * + * @throws NumberFormatException if the value of the data object could not + * be transformed into an Integer. + */ + public Integer getDataAsInteger(String name) + throws NumberFormatException + { + String value = getDataAsString(name); + + if (value != null && value.length() > 0) { + return Integer.parseInt(value); + } + + return null; + } + + + /** + * This method returns the value of a StateData object stored in the data + * pool of this Artifact as Double. + * + * @param name The name of the StateData object. + * + * @return an Double representing the value of the data object or null if + * no object was found for <i>name</i>. + * + * @throws NumberFormatException if the value of the data object could not + * be transformed into a Double. + */ + public Double getDataAsDouble(String name) + throws NumberFormatException + { + String value = getDataAsString(name); + + if (value != null && value.length() > 0) { + return Double.parseDouble(value); + } + + return null; + } + + + /** + * This method returns the value of a StateData object stored in the data + * pool of this Artifact as Long. + * + * @param name The name of the StateData object. + * + * @return a Long representing the value of the data object or null if + * no object was found for <i>name</i>. + * + * @throws NumberFormatException if the value of the data object could not + * be transformed into a Long. + */ + public Long getDataAsLong(String name) + throws NumberFormatException + { + String value = getDataAsString(name); + + if (value != null && value.length() > 0) { + return Long.parseLong(value); + } + + return null; + } + + + /** + * This method returns the value of a StateData object stored in the data + * pool of this Artifact is Boolean using Boolean.valueOf(). + * + * @param name The name of the StateData object. + * + * @return a Boolean representing the value of the data object or null if no + * such object is existing. + */ + public Boolean getDataAsBoolean(String name) { + String value = getDataAsString(name); + + if (value == null || value.length() == 0) { + return null; + } + + return Boolean.valueOf(value); + } + + + /** + * Add StateData containing a given string. + * @param name Name of the data object. + * @param value String to store. + */ + public void addStringData(String name, String value) { + addData(name, new DefaultStateData(name, null, null, value)); + } + + + public Collection<StateData> getAllData() { + return data.values(); + } + + + public List<Facet> getFacets() { + List<Facet> all = new ArrayList<Facet>(); + + Set<Map.Entry<String, List<Facet>>> entries = facets.entrySet(); + for (Map.Entry<String, List<Facet>> entry: entries) { + List<Facet> fs = entry.getValue(); + for (Facet f: fs) { + all.add(f); + } + } + + return all; + } + + + /** + * Get facet as stored internally, with equalling name and index than given + * facet. + * @param facet that defines index and name of facet searched. + * @return facet instance or null if not found. + */ + public Facet getNativeFacet(Facet facet) { + String name = facet.getName(); + int index = facet.getIndex(); + + for (Map.Entry<String, List<Facet>> facetList: facets.entrySet()) { + for (Facet f: facetList.getValue()) { + if (f.getIndex() == index && f.getName().equals(name)) { + return f; + } + } + } + + logger.warn("Could not find facet: " + name + " at " + index); + return null; + } + + + /** + * This method stores the data that is contained in the FEED document. + * + * @param feed The FEED document. + * @param xpath The XPath that points to the data nodes. + */ + public void saveData(Document feed, String xpath, CallContext context) + throws IllegalArgumentException + { + if (feed == null || xpath == null || xpath.length() == 0) { + throw new IllegalArgumentException("error_feed_no_data"); + } + + NodeList nodes = (NodeList) XMLUtils.xpath( + feed, + xpath, + XPathConstants.NODESET, + ArtifactNamespaceContext.INSTANCE); + + if (nodes == null || nodes.getLength() == 0) { + throw new IllegalArgumentException("error_feed_no_data"); + } + + int count = nodes.getLength(); + logger.debug("Try to save " + count + " data items."); + + String uri = ArtifactNamespaceContext.NAMESPACE_URI; + + DefaultState current = (DefaultState) getCurrentState(context); + + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + StateEngine engine = (StateEngine) flysContext.get( + FLYSContext.STATE_ENGINE_KEY); + + for (int i = 0; i < count; i++) { + Element node = (Element)nodes.item(i); + + String name = node.getAttributeNS(uri, "name"); + String value = node.getAttributeNS(uri, "value"); + + if (name.length() > 0 && value.length() > 0) { + logger.debug("Save data item for '" + name + "' : " + value); + + StateData model = engine.getStateData(getName(), name); + + StateData sd = model != null + ? model.deepCopy() + : new DefaultStateData(name, null, null, value); + + addData( + name, current.transform(this, context, sd, name, value)); + } + else if (name.length() > 0 && value.length() == 0) { + if (removeData(name) != null) { + logger.debug("Removed data '" + name + "' successfully."); + } + } + } + + current.validate(this); + } + + + /** + * Determines if the state with the identifier <i>stateId</i> is reachable + * from the current state. The determination itself takes place in the + * TransitionEngine. + * + * @param stateId The identifier of a state. + * @param context The context object. + * + * @return true, if the state specified by <i>stateId</i> is reacahble, + * otherwise false. + */ + protected boolean isStateReachable(String stateId, Object context) { + logger.debug("Determine if the state '" + stateId + "' is reachable."); + + FLYSContext flysContext = FLYSUtils.getFlysContext(context); + + State currentState = getCurrentState(context); + StateEngine sEngine = (StateEngine) flysContext.get( + FLYSContext.STATE_ENGINE_KEY); + + TransitionEngine tEngine = (TransitionEngine) flysContext.get( + FLYSContext.TRANSITION_ENGINE_KEY); + + return tEngine.isStateReachable(this, stateId, currentState, sEngine); + } + + + /** + * Determines if the state with the identifier <i>stateId</i> is a previous + * state of the current state. + * + * @param stateId The target state identifier. + * @param context The context object. + */ + protected boolean isPreviousState(String stateId, Object context) { + logger.debug("Determine if the state '" + stateId + "' is old."); + + List<String> prevs = getPreviousStateIds(); + if (prevs.contains(stateId)) { + return true; + } + + return false; + } + + + /** + * Computes the hash code of the entered values. + * + * @return a hash code. + */ + @Override + public String hash() { + Set<Map.Entry<String, StateData>> entries = data.entrySet(); + + long hash = 0L; + int shift = 3; + + for (Map.Entry<String, StateData> entry: entries) { + String key = entry.getKey(); + Object value = entry.getValue().getValue(); + + hash ^= ((long)key.hashCode() << shift) + | ((long)value.hashCode() << (shift + 3)); + shift += 2; + } + + return getCurrentStateId() + hash; + } + + + /** + * Return List of outputs, where combinations of outputname and filtername + * that match content in filterFacets is left out. + * @return filtered Outputlist. + */ + protected List<Output> filterOutputs(List<Output> outs) { + if (filterFacets == null || filterFacets.isEmpty()) { + logger.debug("No filter for Outputs."); + return outs; + } + + logger.debug("Filter Facets with " + filterFacets.size() + " filters."); + + List<Output> filtered = new ArrayList<Output>(); + + for (Output out: outs) { + String outName = out.getName(); + + logger.debug(" filter Facets for Output: " + outName); + + List<Facet> fFacets = filterFacets.get(outName); + if (fFacets != null) { + logger.debug("" + fFacets.size() + " filters for: " + outName); + + if (logger.isDebugEnabled()) { + for (Facet tmp: fFacets) { + logger.debug(" filter = '" + tmp.getName() + "'"); + } + } + + List<Facet> resultFacets = new ArrayList<Facet>(); + + for (Facet facet: out.getFacets()) { + for (Facet fFacet: fFacets) { + if (facet.getIndex() == fFacet.getIndex() + && facet.getName().equals(fFacet.getName())) { + resultFacets.add(facet); + break; + } + } + } + + logger.debug("Facets after filtering = " + resultFacets.size()); + + if (!resultFacets.isEmpty()) { + DefaultOutput nout = new DefaultOutput( + out.getName(), + out.getDescription(), + out.getMimeType(), + resultFacets); + filtered.add(nout); + } + } + } + + logger.debug("All Facets after filtering = " + filtered.size()); + + return filtered; + } + + + /** + * Get all outputs that the Artifact can do in this state (which includes + * all previous states). + * + * @return list of outputs + */ + public List<Output> getOutputs(Object context) { + logger.debug("##### Get Outputs for: " + identifier() + " #####"); + + dumpArtifact(); + + List<String> stateIds = getPreviousStateIds(); + List<Output> generated = new ArrayList<Output>(); + + for (String stateId: stateIds) { + DefaultState state = (DefaultState) getState(context, stateId); + generated.addAll(getOutputForState(state)); + } + + generated.addAll(getCurrentOutputs(context)); + + return filterOutputs(generated); + } + + + /** + * Get output(s) for current state. + * @return list of outputs for current state. + */ + public List<Output> getCurrentOutputs(Object context) { + DefaultState cur = (DefaultState) getCurrentState(context); + + try { + if (cur.validate(this)) { + return getOutputForState(cur); + } + } + catch (IllegalArgumentException iae) { } + + return new ArrayList<Output>(); + } + + + /** + * Get output(s) for a specific state. + * @param state State of interest + * @return list of output(s) for given state. + */ + protected List<Output> getOutputForState(DefaultState state) { + logger.debug("Find Outputs for State: " + state.getID()); + + List<Output> list = state.getOutputs(); + if (list == null || list.size() == 0) { + logger.debug("-> No output modes for this state."); + return new ArrayList<Output>(); + } + + String stateId = state.getID(); + + List<Facet> fs = facets.get(stateId); + + if (fs == null || fs.size() == 0) { + logger.debug("No facets found."); + return new ArrayList<Output>(); + } + + List<Output> gen = generateOutputs(list, fs); + + logger.debug("State '" + stateId + "' has " + gen.size() + " outs"); + + return gen; + } + + + /** + * Generate a list of outputs with facets from fs if type is found in list + * of output. + * + * @param list List of outputs + * @param fs List of facets + */ + protected List<Output> generateOutputs(List<Output> list, List<Facet> fs) { + List<Output> generated = new ArrayList<Output>(); + + boolean debug = logger.isDebugEnabled(); + + for (Output out: list) { + Output o = new DefaultOutput( + out.getName(), + out.getDescription(), + out.getMimeType(), + out.getType()); + + Set<String> outTypes = new HashSet<String>(); + + for (Facet f: out.getFacets()) { + if (outTypes.add(f.getName()) && debug) { + logger.debug("configured facet " + f); + } + } + + boolean facetAdded = false; + for (Facet f: fs) { + String type = f.getName(); + + if (outTypes.contains(type)) { + if (debug) { + logger.debug("Add facet " + f); + } + facetAdded = true; + o.addFacet(f); + } + } + + if (facetAdded) { + generated.add(o); + } + } + + return generated; + } + + + /** + * Dispatches the computation request to compute(CallContext context, String + * hash) with the current hash value of the artifact which is provided by + * hash(). + * + * @param context The CallContext. + */ + public Object compute( + CallContext context, + ComputeType type, + boolean generateFacets + ) { + return compute(context, hash(), type, generateFacets); + } + + + /** + * Dispatches computation requests to the current state which needs to + * implement a createComputeCallback(String hash, FLYSArtifact artifact) + * method. + * + * @param context The CallContext. + * @param hash The hash value which is used to fetch computed data from + * cache. + * + * @return the computed data. + */ + public Object compute( + CallContext context, + String hash, + ComputeType type, + boolean generateFacets + ) { + DefaultState current = (DefaultState) getCurrentState(context); + return compute(context, hash, current, type, generateFacets); + } + + + /** + * Like compute, but identify State by it id (string). + */ + public Object compute( + CallContext context, + String hash, + String stateID, + ComputeType type, + boolean generateFacets + ) { + DefaultState current = + (stateID == null) + ? (DefaultState)getCurrentState(context) + : (DefaultState)getState(context, stateID); + + if (hash == null) { + hash = hash(); + } + + return compute(context, hash, current, type, generateFacets); + } + + + /** + * Let current state compute and register facets. + * + * @param key key of state + * @param state state + * @param type Type of compute + * @param generateFacets Whether new facets shall be generated. + */ + public Object compute( + CallContext context, + String key, + DefaultState state, + ComputeType type, + boolean generateFacets + ) { + String stateID = state.getID(); + + List<Facet> fs = (generateFacets) ? new ArrayList<Facet>() : null; + + try { + Cache cache = CacheFactory.getCache(COMPUTING_CACHE); + + Object old = null; + + if (cache != null) { + net.sf.ehcache.Element element = cache.get(key); + if (element != null) { + logger.debug("Got computation result from cache."); + old = element.getValue(); + } + } + else { + logger.debug("cache not configured."); + } + + Object res; + switch (type) { + case FEED: + res = state.computeFeed(this, key, context, fs, old); + break; + case ADVANCE: + res = state.computeAdvance(this, key, context, fs, old); + break; + case INIT: + res = state.computeInit(this, key, context, context.getMeta(), fs); + default: + res = null; + } + + if (cache != null && old != res && res != null) { + logger.debug("Store computation result to cache."); + net.sf.ehcache.Element element = + new net.sf.ehcache.Element(key, res); + cache.put(element); + } + + return res; + } + finally { + if (generateFacets) { + if (fs.isEmpty()) { + facets.remove(stateID); + } + else { + facets.put(stateID, fs); + } + } + } + } + + + /** + * Method to dump the artifacts state/data. + */ + protected void dumpArtifact() { + if (logger.isDebugEnabled()) { + logger.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++"); + // Include uuid, type, name + + logger.debug("------ DUMP DATA ------"); + Collection<StateData> allData = data.values(); + + for (StateData d: allData) { + String name = d.getName(); + String value = (String) d.getValue(); + + logger.debug("- " + name + ": " + value); + } + + logger.debug("------ DUMP PREVIOUS STATES ------"); + List<String> stateIds = getPreviousStateIds(); + + for (String id: stateIds) { + logger.debug("- State: " + id); + } + + logger.debug("CURRENT STATE: " + getCurrentStateId()); + + debugFacets(); + dumpFilterFacets(); + + logger.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++"); + } + } + + + protected void debugFacets() { + logger.debug("######### FACETS #########"); + Set<Map.Entry<String, List<Facet>>> entries = facets.entrySet(); + + for (Map.Entry<String, List<Facet>> entry: entries) { + String out = entry.getKey(); + List<Facet> fs = entry.getValue(); + for (Facet f: fs) { + logger.debug(" # " + out + " : " + f.getName()); + } + } + + logger.debug("######## FACETS END ########"); + } + + + protected void dumpFilterFacets() { + logger.debug("######## FILTER FACETS ########"); + + if (filterFacets == null || filterFacets.isEmpty()) { + logger.debug("No Filter Facets defined."); + return; + } + + Set<Map.Entry<String, List<Facet>>> entries = filterFacets.entrySet(); + for (Map.Entry<String, List<Facet>> entry: entries) { + String out = entry.getKey(); + List<Facet> filters = entry.getValue(); + + logger.debug("There are " + filters.size() + " filters for: " +out); + + for (Facet filter: filters) { + logger.debug(" filter: " + filter.getName()); + } + } + + logger.debug("######## FILTER FACETS END ########"); + } + + + protected void destroyState(String id, Object context) { + State s = getState(context, id); + s.endOfLife(this, context); + } + + + /** + * Calls endOfLife() for each state in the list <i>ids</i>. + * + * @param ids The State IDs that should be destroyed. + * @param context The FLYSContext. + */ + protected void destroyStates(List<String> ids, Object context) { + for (int i = 0, num = ids.size(); i < num; i++) { + destroyState(ids.get(i), context); + } + } + + + /** + * Destroy the states. + */ + @Override + public void endOfLife(Object context) { + logger.info("FLYSArtifact.endOfLife: " + identifier()); + + ArrayList<String> ids = (ArrayList<String>) getPreviousStateIds(); + ArrayList<String> toDestroy = (ArrayList<String>) ids.clone(); + + toDestroy.add(getCurrentStateId()); + + destroyStates(toDestroy, context); + } + + + /** + * Determines Facets initial disposition regarding activity (think of + * selection in Client ThemeList GUI). This will be checked one time + * when the facet enters a collections describe document. + * + * @param facetName name of the facet. + * @param outputName name of the output. + * @param index index of the facet. + * + * @return 1 if wished to be initally active, 0 if not. FLYSArtifact + * defaults to "1". + */ + public int getInitialFacetActivity( + String outputName, + String facetName, + int index + ) + { + return 1; + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :