view flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java @ 2089:0da8874bd378

Added initial state to map artifact to be able to advance and step back. The map artifact overrides describe() to have the complete UI information in the describe response document. flys-artifacts/trunk@3613 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Raimund Renkert <raimund.renkert@intevation.de>
date Fri, 06 Jan 2012 12:02:10 +0000
parents f73036b991e2
children ebc2aa64c1be
line wrap: on
line source
package de.intevation.flys.artifacts;

import de.intevation.artifactdatabase.ArtifactDatabaseImpl;
import de.intevation.artifactdatabase.DefaultArtifact;

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.common.ArtifactNamespaceContext;

import de.intevation.artifacts.common.utils.XMLUtils;

import de.intevation.flys.artifacts.cache.CacheFactory;

import de.intevation.flys.artifacts.context.FLYSContext;

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.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.NodeList;

/**
 * The defaul FLYS artifact.
 *
 * @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 -&gt; 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>>();
    }


    /**
     * 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 + "'");

        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);
    }


    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.");
        }

        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 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!
    }


    /**
     * 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 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.
     */
    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 targetState = XMLUtils.xpathString(
            target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE);

        logger.info("FLYSArtifact.advance() to '" + targetState + "'");

        if (isStateReachable(targetState, context)) {
            logger.info("Advance: Step forward");

            List<String> prev = getPreviousStateIds();
            prev.add(getCurrentStateId());

            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() {
        List<String> allIds = getPreviousStateIds();
        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);
    }


    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 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();
    }


    /**
     * 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);

        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);

                addData(name, current.transform(this, context, 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;
        }

        List<Output> filtered = new ArrayList<Output>();

        for (Output out: outs) {

            List<Facet> fFacets = filterFacets.get(out.getName());
            if (fFacets != null) {

                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 (!resultFacets.isEmpty()) {
                    DefaultOutput nout = new DefaultOutput(
                        out.getName(),
                        out.getDescription(),
                        out.getMimeType(),
                        resultFacets);
                    filtered.add(nout);
                }
            }
        }

        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) {
        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) {
        List<Output> list = state.getOutputs();
        if (list == null || list.size() == 0) {
            logger.debug("-> No output modes for this state.");
            return new ArrayList<Output>();
        }

        List<Facet> fs = facets.get(state.getID());

        if (fs == null || fs.size() == 0) {
            logger.debug("No facets found.");
            return new ArrayList<Output>();
        }

        return generateOutputs(list, fs);
    }


    /**
     * 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();
                }
            }

            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 +++++++++++++++++");

            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());

            logger.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++");
        }
    }


    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());

        List<String> ids = getPreviousStateIds();
        ids.add(getCurrentStateId());

        destroyStates(ids, 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 :

http://dive4elements.wald.intevation.org