ingo@119: package de.intevation.flys.artifacts; ingo@119: sascha@1055: import de.intevation.artifactdatabase.ArtifactDatabaseImpl; sascha@1055: import de.intevation.artifactdatabase.DefaultArtifact; ingo@121: felix@1724: import de.intevation.artifactdatabase.data.DefaultStateData; sascha@1055: import de.intevation.artifactdatabase.data.StateData; ingo@379: 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; ingo@686: sascha@1055: import de.intevation.artifactdatabase.transition.TransitionEngine; ingo@119: 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; ingo@119: ingo@119: import de.intevation.artifacts.common.ArtifactNamespaceContext; sascha@1055: ingo@119: import de.intevation.artifacts.common.utils.XMLUtils; ingo@119: sascha@1055: import de.intevation.flys.artifacts.cache.CacheFactory; ingo@317: ingo@119: import de.intevation.flys.artifacts.context.FLYSContext; sascha@650: felix@1777: import de.intevation.flys.artifacts.states.DefaultState; sascha@1055: import de.intevation.flys.artifacts.states.DefaultState.ComputeType; sascha@650: felix@1777: import de.intevation.flys.utils.FLYSUtils; ingo@119: sascha@1055: import java.util.ArrayList; sascha@1055: import java.util.Collection; sascha@1055: import java.util.HashMap; sascha@1055: import java.util.HashSet; sascha@1055: import java.util.List; sascha@1055: import java.util.Map; sascha@1055: import java.util.Set; sascha@1055: import java.util.TreeMap; sascha@1055: sascha@1055: import javax.xml.xpath.XPathConstants; sascha@1055: sascha@1055: import net.sf.ehcache.Cache; sascha@1055: sascha@1055: import org.apache.log4j.Logger; sascha@1055: sascha@1055: import org.w3c.dom.Document; sascha@1055: import org.w3c.dom.Element; sascha@1055: import org.w3c.dom.NodeList; 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: felix@1704: /** The logger that is used in this artifact. */ ingo@119: private static Logger logger = Logger.getLogger(FLYSArtifact.class); ingo@119: ingo@686: public static final String COMPUTING_CACHE = "computed.values"; ingo@686: felix@1704: /** The XPath that points to the input data elements of the FEED document. */ ingo@121: public static final String XPATH_FEED_INPUT = ingo@121: "/art:action/art:data/art:input"; ingo@119: felix@1704: /** The XPath that points to the name of the target state of ADVANCE. */ ingo@122: public static final String XPATH_ADVANCE_TARGET = ingo@122: "/art:action/art:target/@art:name"; ingo@122: ingo@940: public static final String XPATH_MODEL_ARTIFACT = ingo@941: "/art:action/art:template/@uuid"; ingo@940: sascha@1057: public static final String XPATH_FILTER = sascha@1057: "/art:action/art:filter/art:out"; sascha@1057: felix@1704: /** The constant string that shows that an operation was successful. */ ingo@122: public static final String OPERATION_SUCCESSFUL = "SUCCESS"; ingo@122: felix@1704: /** The constant string that shows that an operation failed. */ ingo@122: public static final String OPERATION_FAILED = "FAILURE"; ingo@122: ingo@119: /** The identifier of the current state. */ ingo@119: protected String currentStateId; ingo@119: felix@1704: /** The identifiers of previous states on a stack. */ sascha@661: protected List previousStateIds; ingo@122: felix@1704: /** The name of the artifact. */ ingo@119: protected String name; ingo@119: felix@1704: /** The data that have been inserted into this artifact. */ ingo@121: protected Map data; ingo@121: felix@1771: /** Mapping of state names to created facets. */ felix@1771: protected Map> facets; ingo@687: felix@1704: /** felix@1704: * Used to generates "view" on the facets (hides facets not matching the felix@1704: * filter in output of collection); out -> facets. felix@1704: */ sascha@1056: protected Map> filterFacets; sascha@1056: ingo@121: ingo@121: /** ingo@121: * The default constructor that creates an empty FLYSArtifact. ingo@121: */ ingo@121: public FLYSArtifact() { ingo@686: data = new TreeMap(); sascha@661: previousStateIds = new ArrayList(); felix@1771: facets = new HashMap>(); ingo@121: } ingo@121: ingo@121: ingo@121: /** ingo@121: * Returns the name of the concrete artifact. ingo@121: * ingo@121: * @return the name of the concrete artifact. ingo@121: */ sascha@1059: public String getName() { sascha@1059: return name; sascha@1059: } ingo@121: ingo@119: ingo@119: /** ingo@119: * Initialize the artifact and insert new data if data contains ingo@119: * information necessary for this artifact. ingo@119: * ingo@119: * @param identifier The UUID. ingo@119: * @param factory The factory that is used to create this artifact. ingo@119: * @param context The CallContext. ingo@119: * @param data Some optional data. ingo@119: */ ingo@119: @Override ingo@119: public void setup( ingo@119: String identifier, ingo@119: ArtifactFactory factory, ingo@119: Object context, ingo@952: CallMeta callMeta, ingo@119: Document data) ingo@119: { ingo@119: logger.debug("Setup this artifact with the uuid: " + identifier); ingo@119: ingo@952: super.setup(identifier, factory, context, callMeta, data); ingo@119: felix@1777: FLYSContext flysContext = FLYSUtils.getFlysContext(context); felix@1067: felix@1067: List states = getStates(context); ingo@119: ingo@121: String name = getName(); ingo@121: logger.debug("Set initial state for artifact '" + name + "'"); ingo@119: 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) { ingo@940: logger.error(adbe, adbe); ingo@940: } ingo@940: } sascha@1057: sascha@1057: filterFacets = buildFilterFacets(data); ingo@940: } ingo@940: felix@1763: sascha@1059: protected List clonePreviousStateIds() { sascha@1059: return new ArrayList(previousStateIds); sascha@1059: } sascha@1059: 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) { felix@1895: logger.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) { felix@1895: logger.warn("Other artifact has no data " + name + "."); felix@1895: return; felix@1895: } felix@1895: felix@1895: this.addData(name, sd); felix@1895: } felix@1763: sascha@1059: protected Map cloneData() { sascha@1059: Map copy = new TreeMap(); sascha@1059: sascha@1059: for (Map.Entry entry: data.entrySet()) { sascha@1059: copy.put(entry.getKey(), entry.getValue().deepCopy()); sascha@1059: } sascha@1059: sascha@1059: return copy; sascha@1059: } sascha@1059: felix@1763: /** felix@1763: * Return a copy of the facet mapping. felix@1771: * @return Mapping of state-ids to facets. felix@1763: */ felix@1771: protected Map> cloneFacets() { felix@1771: Map copy = new HashMap>(); sascha@1059: felix@1771: for (Map.Entry> entry: facets.entrySet()) { felix@1771: List facets = entry.getValue(); felix@1771: List facetCopies = new ArrayList(facets.size()); felix@1771: for (Facet facet: facets) { felix@1771: facetCopies.add(facet.deepCopy()); felix@1771: } felix@1771: copy.put(entry.getKey(), facetCopies); sascha@1059: } sascha@1059: sascha@1059: return copy; sascha@1059: } ingo@940: felix@1763: felix@1765: /** felix@1765: * (called from setup). felix@1771: * @param artifact master-artifact (if any, otherwise initialize is not called). felix@1765: */ ingo@952: protected void initialize( ingo@952: Artifact artifact, ingo@952: Object context, ingo@952: CallMeta callMeta) ingo@952: { sascha@1059: if (!(artifact instanceof FLYSArtifact)) { sascha@1059: return; sascha@1059: } sascha@1059: sascha@1059: FLYSArtifact flys = (FLYSArtifact)artifact; sascha@1059: sascha@1059: currentStateId = flys.currentStateId; sascha@1059: previousStateIds = flys.clonePreviousStateIds(); sascha@1059: name = flys.name; sascha@1059: data = flys.cloneData(); sascha@1059: facets = flys.cloneFacets(); sascha@1059: // Do not clone filter facets! ingo@2093: ingo@2093: List stateIds = getPreviousStateIds(); ingo@2093: stateIds.add(getCurrentStateId()); ingo@2093: ingo@2093: for (String stateId: stateIds) { ingo@2093: State state = getState(context, stateId); ingo@2093: ingo@2093: if (state != null) { ingo@2095: state.initialize(artifact, this, context, callMeta); ingo@2093: } ingo@2093: } ingo@119: } ingo@119: felix@1704: felix@1704: /** felix@1704: * Builds filter facets from document. felix@1704: * @see filterFacets felix@1704: */ sascha@1057: protected Map> buildFilterFacets(Document document) { sascha@1057: sascha@1057: NodeList nodes = (NodeList)XMLUtils.xpath( sascha@1057: document, sascha@1057: XPATH_FILTER, sascha@1057: XPathConstants.NODESET, sascha@1057: ArtifactNamespaceContext.INSTANCE); sascha@1057: sascha@1057: if (nodes == null || nodes.getLength() == 0) { sascha@1057: return null; sascha@1057: } sascha@1057: sascha@1057: Map> result = new HashMap>(); sascha@1057: sascha@1057: for (int i = 0, N = nodes.getLength(); i < N; ++i) { sascha@1057: Element element = (Element)nodes.item(i); sascha@1057: String oName = element.getAttribute("name"); sascha@1057: if (oName.length() == 0) { sascha@1057: continue; sascha@1057: } sascha@1057: sascha@1057: List facets = new ArrayList(); sascha@1057: sascha@1057: NodeList facetNodes = element.getElementsByTagNameNS( sascha@1057: ArtifactNamespaceContext.NAMESPACE_URI, sascha@1057: "facet"); sascha@1057: sascha@1057: for (int j = 0, M = facetNodes.getLength(); j < M; ++j) { sascha@1057: Element facetElement = (Element)facetNodes.item(j); sascha@1057: sascha@1057: String fName = facetElement.getAttribute("name"); sascha@1057: sascha@1057: int index; sascha@1057: try { sascha@1057: index = Integer.parseInt(facetElement.getAttribute("index")); sascha@1057: } sascha@1057: catch (NumberFormatException nfe) { sascha@1057: logger.warn(nfe); sascha@1057: index = 0; sascha@1057: } sascha@1057: facets.add(new DefaultFacet(index, fName, "")); sascha@1057: } sascha@1057: sascha@1057: if (!facets.isEmpty()) { sascha@1057: result.put(oName, facets); sascha@1057: } sascha@1057: } sascha@1057: sascha@1057: return result; sascha@1057: } sascha@1057: ingo@119: ingo@119: /** ingo@119: * Insert new data included in input into the current state. ingo@119: * ingo@119: * @param target XML document that contains new data. ingo@119: * @param context The CallContext. ingo@119: * ingo@119: * @return a document that contains a SUCCESS or FAILURE message. ingo@119: */ ingo@119: @Override ingo@119: public Document feed(Document target, CallContext context) { 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@322: saveData(target, XPATH_FEED_INPUT, 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: 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: sascha@661: List prev = getPreviousStateIds(); ingo@122: prev.add(getCurrentStateId()); ingo@122: ingo@122: setCurrentStateId(targetState); ingo@122: ingo@693: logger.debug("Compute data for state: " + targetState); sascha@705: compute(context, ComputeType.ADVANCE, true); ingo@693: 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@1086: List 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); ingo@140: logger.debug("Remove state id '" + prev + "'"); 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: 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: */ 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. 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@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 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 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 getStateHistoryIds() { felix@1783: List allIds = getPreviousStateIds(); 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: 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: ingo@122: 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 name. 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@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 name. 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: felix@1763: sascha@960: public Collection getAllData() { sascha@960: return data.values(); sascha@960: } sascha@960: ingo@937: ingo@2095: public List getFacets() { ingo@2095: List all = new ArrayList(); ingo@2095: ingo@2095: Set>> entries = facets.entrySet(); ingo@2095: for (Map.Entry> entry: entries) { ingo@2095: List fs = entry.getValue(); ingo@2095: for (Facet f: fs) { ingo@2095: all.add(f); ingo@2095: } 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: felix@1771: for (Map.Entry> facetList: facets.entrySet()) { felix@1771: for (Facet f: facetList.getValue()) { felix@1771: if (f.getIndex() == index && f.getName().equals(name)) { felix@1771: return f; felix@1771: } ingo@696: } ingo@696: } ingo@696: ingo@696: logger.warn("Could not find facet: " + name + " at " + index); 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: */ ingo@322: public void saveData(Document feed, String xpath, CallContext context) ingo@121: throws IllegalArgumentException ingo@121: { ingo@121: if (feed == null || xpath == null || xpath.length() == 0) { ingo@325: throw new IllegalArgumentException("error_feed_no_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@325: throw new IllegalArgumentException("error_feed_no_data"); ingo@121: } ingo@121: ingo@121: int count = nodes.getLength(); ingo@121: logger.debug("Try to save " + count + " data items."); ingo@121: sascha@708: String uri = ArtifactNamespaceContext.NAMESPACE_URI; ingo@121: ingo@1176: DefaultState current = (DefaultState) getCurrentState(context); ingo@1176: 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) { ingo@121: logger.debug("Save data item for '" + name + "' : " + value); ingo@121: ingo@1176: addData(name, current.transform(this, context, name, value)); ingo@121: } ingo@1656: else if (name.length() > 0 && value.length() == 0) { ingo@1656: if (removeData(name) != null) { ingo@1656: logger.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 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: 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 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: sascha@661: List prevs = getPreviousStateIds(); ingo@140: if (prevs.contains(stateId)) { ingo@140: return true; ingo@140: } ingo@140: ingo@140: return false; 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: Set> entries = data.entrySet(); ingo@686: sascha@705: long hash = 0L; sascha@705: int shift = 3; ingo@686: ingo@686: for (Map.Entry entry: entries) { 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 filterOutputs(List outs) { sascha@1056: if (filterFacets == null || filterFacets.isEmpty()) { felix@1704: logger.debug("No filter for Outputs."); sascha@1056: return outs; sascha@1056: } sascha@1056: ingo@2095: logger.debug("Filter Facets with " + filterFacets.size() + " filters."); ingo@2095: sascha@1056: List filtered = new ArrayList(); sascha@1056: sascha@1056: for (Output out: outs) { ingo@2095: String outName = out.getName(); sascha@1056: ingo@2095: logger.debug(" filter Facets for Output: " + outName); ingo@2095: ingo@2095: List fFacets = filterFacets.get(outName); sascha@1056: if (fFacets != null) { ingo@2095: logger.debug("" + fFacets.size() + " filters for: " + outName); ingo@2095: ingo@2095: if (logger.isDebugEnabled()) { ingo@2095: for (Facet tmp: fFacets) { ingo@2095: logger.debug(" filter = '" + tmp.getName() + "'"); ingo@2095: } ingo@2095: } sascha@1056: sascha@1056: List resultFacets = new ArrayList(); 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: ingo@2095: logger.debug("Facets after filtering = " + resultFacets.size()); 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: } sascha@1056: } sascha@1056: ingo@2095: logger.debug("All Facets after filtering = " + filtered.size()); 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 getOutputs(Object context) { ingo@2095: logger.debug("##### Get Outputs for: " + identifier() + " #####"); ingo@2095: ingo@965: List stateIds = getPreviousStateIds(); ingo@944: List generated = new ArrayList(); 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 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(); 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 getOutputForState(DefaultState state) { ingo@2095: logger.debug("Find Outputs for State: " + state.getID()); ingo@2095: ingo@965: List list = state.getOutputs(); ingo@965: if (list == null || list.size() == 0) { ingo@965: logger.debug("-> No output modes for this state."); ingo@965: return new ArrayList(); ingo@965: } ingo@965: ingo@2095: String stateId = state.getID(); ingo@2095: ingo@2095: List fs = facets.get(stateId); felix@1771: felix@1771: if (fs == null || fs.size() == 0) { ingo@965: logger.debug("No facets found."); ingo@965: return new ArrayList(); ingo@965: } ingo@965: ingo@2095: List gen = generateOutputs(list, fs); ingo@2095: ingo@2095: logger.debug("State '" + stateId + "' has " + gen.size() + " outs"); 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 generateOutputs(List list, List fs) { ingo@937: List generated = new ArrayList(); ingo@937: ingo@937: boolean debug = logger.isDebugEnabled(); ingo@937: ingo@937: for (Output out: list) { 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 outTypes = new HashSet(); ingo@937: ingo@937: for (Facet f: out.getFacets()) { ingo@937: if (outTypes.add(f.getName()) && debug) { ingo@937: logger.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) { ingo@937: logger.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: ) { sascha@722: DefaultState current = 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 fs = (generateFacets) ? new ArrayList() : 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@697: logger.debug("Got computation result from cache."); sascha@697: old = element.getValue(); sascha@697: } sascha@697: } 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@697: logger.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 { felix@1771: facets.put(stateID, fs); sascha@705: } ingo@690: } ingo@686: } ingo@686: } ingo@686: ingo@686: ingo@623: /** ingo@623: * Method to dump the artifacts state/data. ingo@623: */ ingo@623: protected void dumpArtifact() { ingo@623: if (logger.isDebugEnabled()) { ingo@623: logger.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++"); ingo@623: ingo@623: logger.debug("------ DUMP DATA ------"); ingo@623: Collection allData = data.values(); ingo@623: ingo@623: for (StateData d: allData) { ingo@623: String name = d.getName(); ingo@623: String value = (String) d.getValue(); ingo@623: ingo@623: logger.debug("- " + name + ": " + value); ingo@623: } ingo@623: ingo@623: logger.debug("------ DUMP PREVIOUS STATES ------"); sascha@661: List stateIds = getPreviousStateIds(); ingo@623: ingo@623: for (String id: stateIds) { ingo@623: logger.debug("- State: " + id); ingo@623: } ingo@623: ingo@623: logger.debug("CURRENT STATE: " + getCurrentStateId()); ingo@623: ingo@2095: debugFacets(); ingo@2095: dumpFilterFacets(); ingo@2095: ingo@623: logger.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++"); ingo@623: } ingo@623: } ingo@1086: ingo@1086: ingo@2095: protected void debugFacets() { ingo@2095: logger.debug("######### FACETS #########"); ingo@2095: Set>> entries = facets.entrySet(); ingo@2095: ingo@2095: for (Map.Entry> entry: entries) { ingo@2095: String out = entry.getKey(); ingo@2095: List fs = entry.getValue(); ingo@2095: for (Facet f: fs) { ingo@2095: logger.debug(" # " + out + " : " + f.getName()); ingo@2095: } ingo@2095: } ingo@2095: ingo@2095: logger.debug("######## FACETS END ########"); ingo@2095: } ingo@2095: ingo@2095: ingo@2095: protected void dumpFilterFacets() { ingo@2095: logger.debug("######## FILTER FACETS ########"); ingo@2095: ingo@2095: if (filterFacets == null || filterFacets.isEmpty()) { ingo@2095: logger.debug("No Filter Facets defined."); ingo@2095: return; ingo@2095: } ingo@2095: ingo@2095: Set>> entries = filterFacets.entrySet(); ingo@2095: for (Map.Entry> entry: entries) { ingo@2095: String out = entry.getKey(); ingo@2095: List filters = entry.getValue(); ingo@2095: ingo@2095: logger.debug("There are " + filters.size() + " filters for: " +out); ingo@2095: ingo@2095: for (Facet filter: filters) { ingo@2095: logger.debug(" filter: " + filter.getName()); ingo@2095: } ingo@2095: } ingo@2095: ingo@2095: logger.debug("######## FILTER FACETS END ########"); ingo@2095: } ingo@2095: ingo@2095: 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 ids. 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 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) { ingo@1086: logger.info("FLYSArtifact.endOfLife: " + identifier()); ingo@1086: ingo@1086: List ids = getPreviousStateIds(); ingo@1086: ids.add(getCurrentStateId()); ingo@1086: ingo@1086: destroyStates(ids, context); ingo@1086: } felix@1709: felix@1709: felix@1709: /** felix@1709: * Determines Facets initial disposition regarding activity (think of felix@1709: * selection in Client ThemeList GUI). This will be checked one time felix@1709: * when the facet enters a collections describe document. felix@1709: * felix@1809: * @param facetName name of the facet. felix@1809: * @param outputName name of the output. felix@1809: * @param index index of the facet. felix@1709: * felix@1709: * @return 1 if wished to be initally active, 0 if not. FLYSArtifact felix@1709: * defaults to "1". felix@1709: */ felix@1809: public int getInitialFacetActivity( felix@1809: String outputName, felix@1809: String facetName, felix@1809: int index) felix@1809: { felix@1709: return 1; felix@1709: } ingo@119: } ingo@119: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :