Mercurial > dive4elements > river
view flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java @ 4124:c03639e30cc5
Improvements for issue949 (q and w facets for official lines).
author | Felix Wolfsteller <felix.wolfsteller@intevation.de> |
---|---|
date | Fri, 12 Oct 2012 23:30:09 +0200 |
parents | 975f608dd254 |
children | db0b9cff8496 |
line wrap: on
line source
package de.intevation.flys.artifacts; 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; 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; /** * 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 log = 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. */ private 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); boolean debug = log.isDebugEnabled(); for (String stateId: stateIds) { if (debug) { log.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) { boolean debug = log.isDebugEnabled(); if (debug) { log.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(); if (debug) { log.debug("setup(): Set initial state for artifact '" + name + "'"); } if (states == null) { log.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) { log.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) { log.error("No other art. to import data " + name + " from."); return; } StateData sd = other.getData(name); if (sd == null) { log.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) { log.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) { log.debug("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, 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) { if (log.isDebugEnabled()) { log.debug( "Describe: the current state is: " + getCurrentStateId()); 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); if (log.isDebugEnabled()) { log.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) { boolean debug = log.isDebugEnabled(); 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); if (debug) { log.debug("FLYSArtifact.advance() to '" + targetState + "'"); } if (!currentStateId.equals(targetState) && isStateReachable(targetState, context)) { if (debug) { log.debug("Advance: Step forward"); } List<String> prev = getPreviousStateIds(); prev.add(currentStateId); setCurrentStateId(targetState); if (debug) { log.debug("Compute data for state: " + targetState); } compute(context, ComputeType.ADVANCE, true); return describe(target, context); } else if (isPreviousState(targetState, context)) { if (debug) { log.debug("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); if (debug) { log.debug("Remove state id '" + prev + "'"); } prevs.remove(prev); facets.remove(prev); } destroyState(getCurrentStateId(), context); setCurrentStateId(targetState); return describe(target, context); } log.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); } /** * A derived Artifact class can use this method to set the data */ protected void setData(Map<String, StateData> data) { this.data = data; } /** 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)); } /** * This method returns all stored StateData in this artifact as a Collection * @return a Collection of all StateData objects in this artifact */ public Collection<StateData> getAllData() { return data.values(); } public List<Facet> getFacets() { List<Facet> all = new ArrayList<Facet>(); for (List<Facet> fs: facets.values()) { all.addAll(fs); } 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 (List<Facet> fs: facets.values()) { for (Facet f: fs) { if (f.getIndex() == index && f.getName().equals(name)) { return f; } } } log.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, CallContext context) throws IllegalArgumentException { if (feed == null) { throw new IllegalArgumentException("error_feed_no_data"); } NodeList nodes = (NodeList) XMLUtils.xpath( feed, XPATH_FEED_INPUT, XPathConstants.NODESET, ArtifactNamespaceContext.INSTANCE); if (nodes == null || nodes.getLength() == 0) { throw new IllegalArgumentException("error_feed_no_data"); } boolean debug = log.isDebugEnabled(); int count = nodes.getLength(); if (debug) { log.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) { if (debug) { log.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 && debug) { log.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) { if (log.isDebugEnabled()) { log.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) { if (log.isDebugEnabled()) { log.debug("Determine if the state '" + stateId + "' is old."); } return getPreviousStateIds().contains(stateId); } /** * Computes the hash code of the entered values. * * @return a hash code. */ @Override public String hash() { long hash = 0L; int shift = 3; for (Map.Entry<String, StateData> entry: data.entrySet()) { 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()) { log.debug("No filter for Outputs."); return outs; } boolean debug = log.isDebugEnabled(); if (debug) { log.debug( "Filter Facets with " + filterFacets.size() + " filters."); } List<Output> filtered = new ArrayList<Output>(); for (Output out: outs) { String outName = out.getName(); if (debug) { log.debug(" filter Facets for Output: " + outName); } List<Facet> fFacets = filterFacets.get(outName); if (fFacets != null) { if (debug) { log.debug("" + fFacets.size() + " filters for: " + outName); for (Facet tmp: fFacets) { log.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; } } } if (debug) { log.debug( "Facets after filtering = " + resultFacets.size()); } if (!resultFacets.isEmpty()) { DefaultOutput nout = new DefaultOutput( out.getName(), out.getDescription(), out.getMimeType(), resultFacets); filtered.add(nout); } } } if (debug) { log.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) { if (log.isDebugEnabled()) { log.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) { if (state == null) { log.error("state == null: This should not happen!"); return new ArrayList<Output>(); } boolean debug = log.isDebugEnabled(); if (debug) { log.debug("Find Outputs for State: " + state.getID()); } List<Output> list = state.getOutputs(); if (list == null || list.isEmpty()) { if (debug) { log.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.isEmpty()) { if (debug) { log.debug("No facets found."); } return new ArrayList<Output>(); } List<Output> gen = generateOutputs(list, fs); if (debug) { log.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 = log.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) { log.debug("configured facet " + f); } } boolean facetAdded = false; for (Facet f: fs) { String type = f.getName(); if (outTypes.contains(type)) { if (debug) { log.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) { log.debug("Got computation result from cache."); old = element.getValue(); } } else { log.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) { log.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 { addFacets(stateID, fs); } } } } /** * Sets the facets for an ID * * Normally the id is a state ID. * * @param id ID to map the facets to * @param facets List of facets to be stored */ protected void addFacets(String id, List<Facet> facets) { this.facets.put(id, facets); } /** * Method to dump the artifacts state/data. */ protected void dumpArtifact() { log.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++"); // Include uuid, type, name log.debug("------ DUMP DATA ------"); Collection<StateData> allData = data.values(); for (StateData d: allData) { String name = d.getName(); String value = (String) d.getValue(); log.debug("- " + name + ": " + value); } log.debug("------ DUMP PREVIOUS STATES ------"); List<String> stateIds = getPreviousStateIds(); for (String id: stateIds) { log.debug("- State: " + id); } log.debug("CURRENT STATE: " + getCurrentStateId()); debugFacets(); dumpFilterFacets(); log.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++"); } protected void debugFacets() { log.debug("######### FACETS #########"); for (Map.Entry<String, List<Facet>> entry: facets.entrySet()) { String out = entry.getKey(); List<Facet> fs = entry.getValue(); for (Facet f: fs) { log.debug(" # " + out + " : " + f.getName()); } } log.debug("######## FACETS END ########"); } protected void dumpFilterFacets() { log.debug("######## FILTER FACETS ########"); if (filterFacets == null || filterFacets.isEmpty()) { log.debug("No Filter Facets defined."); return; } for (Map.Entry<String, List<Facet>> entry: filterFacets.entrySet()) { String out = entry.getKey(); List<Facet> filters = entry.getValue(); log.debug("There are " + filters.size() + " filters for: " +out); for (Facet filter: filters) { log.debug(" filter: " + filter.getName()); } } log.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) { if (log.isDebugEnabled()) { log.debug("FLYSArtifact.endOfLife: " + identifier()); } ArrayList<String> ids = (ArrayList<String>) getPreviousStateIds(); ArrayList<String> toDestroy = (ArrayList<String>) ids.clone(); toDestroy.add(getCurrentStateId()); destroyStates(toDestroy, context); } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :