view gnv-artifacts/src/main/java/de/intevation/gnv/state/StateBase.java @ 1129:ccfa07b88476

merged geo-backend
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:01 +0200
parents dec4257ad570
children
line wrap: on
line source
/*
 * Copyright (c) 2010 by Intevation GmbH
 *
 * This program is free software under the LGPL (>=v2.1)
 * Read the file LGPL.txt coming with the software for details
 * or visit http://www.gnu.org/licenses/ if it does not exist.
 */

package de.intevation.gnv.state;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
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.artifacts.common.utils.Config;
import de.intevation.artifacts.common.utils.XMLUtils;
import de.intevation.artifacts.ArtifactNamespaceContext;
import de.intevation.artifacts.CallContext;
import de.intevation.artifacts.CallMeta;
import de.intevation.gnv.artifacts.cache.CacheFactory;
import de.intevation.gnv.artifacts.ressource.RessourceFactory;
import de.intevation.gnv.geobackend.base.Result;
import de.intevation.gnv.geobackend.base.query.QueryExecutor;
import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory;
import de.intevation.gnv.geobackend.base.query.exception.QueryException;
import de.intevation.gnv.geobackend.util.DateUtils;
import de.intevation.gnv.state.describedata.DefaultKeyValueDescribeData;
import de.intevation.gnv.state.describedata.KeyValueDescibeData;
import de.intevation.gnv.state.describedata.MinMaxDescribeData;
import de.intevation.gnv.state.describedata.NamedArrayList;
import de.intevation.gnv.state.describedata.NamedCollection;
import de.intevation.gnv.state.describedata.SingleValueDescribeData;
import de.intevation.gnv.state.exception.StateException;
import de.intevation.gnv.utils.ArtifactXMLUtilities;
import de.intevation.gnv.utils.InputValidator;

/**
 * This is the major implementation of <code>State</code>. Nearly every other
 * state is derived by this class.
 *
 * @author <a href="mailto:tim.englich@intevation.de">Tim Englich</a>
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
 */
public abstract class StateBase implements State {

    /**
     * The UID of this Class
     */
    private static final long serialVersionUID = 2411169179001645426L;

    /**
     * the logger, used to log exceptions and additonaly information
     */
    private static Logger log = Logger.getLogger(StateBase.class);

    protected final static String MINVALUEFIELDNAME = "minvalue";

    protected final static String MAXVALUEFIELDNAME = "maxvalue";

    private final static String NODATASELECTIONKEY = "n/n";

    public final static String DESCRIBEDATAKEY = "_DESCRIBEDATA";

    public final static String XPATH_STATIC_UI  = "art:static";

    public final static String XPATH_DYNAMIC_UI = "art:dynamic";

    public static final String EXCEPTION_NO_INPUT      = "no.input.data";

    public static final String EXCEPTION_INVALID_INPUT =
        "input.is.not.valid";

    public final static String HASH_ID_SEPARATOR = "#";

    /** input value names which should not be rendered from State itself */
    public final static String[] BLACKLIST = {"sourceid", "fisname"};

    private String id = null;

    protected String hash;

    private String description = null;

    protected String dataName = null;

    protected String preSettingsName = null;

    protected boolean dataMultiSelect = false;

    protected boolean dataNoSelect = false;

    protected String queryID = null;

    protected Collection<String> inputValueNames = null;

    protected Map<String, InputValue> inputValues = null;

    protected State parent = null;

    protected Map<String, InputData> inputData = null;

    protected Map<String, InputData> preSettings = null;


    /**
     * The source date format as string.
     */
    public static String srcDateFormat = "yyyy.MM.dd hh:mm:ss";


    /**
     * The source date format used to read string represented strings.
     */
    public static DateFormat srcFormat;


    static {
        srcFormat = new SimpleDateFormat(srcDateFormat);
    }


    /**
     * Constructor
     */
    public StateBase() {
        super();
    }


    public String getID() {
        return this.id;
    }


    public String getDescription() {
        return this.description;
    }


    public Collection<InputValue> getRequiredInputValues() {
        return this.inputValues.values();
    }


    public void reset(String uuid) {
        inputData.remove(dataName);
    }


    public void setup(Node configuration) {
        this.id = ((Element)configuration).getAttribute("id");
        this.description = ((Element)configuration).getAttribute("description");

        log.info("State-ID = " + this.id);

        NodeList inputValuesNodes = Config.getNodeSetXPath(configuration,
                "inputvalues/inputvalue");
        this.inputValues = new HashMap<String, InputValue>(inputValuesNodes
                .getLength());
        this.inputValueNames = new ArrayList<String>(inputValuesNodes
                .getLength());
        for (int i = 0; i < inputValuesNodes.getLength(); i++) {
            Element inputValueNode = (Element)inputValuesNodes.item(i);
            String usedinQueryValue = inputValueNode.getAttribute("usedinquery");
            int usedinQuery = 1;
            if (usedinQueryValue != null) {
                try {
                    usedinQuery = Integer.parseInt(usedinQueryValue);
                } catch (NumberFormatException e) {
                    log
                            .warn("Used in Query Value cannot be transformed into a Number");
                }
            }
            InputValue inputValue = new DefaultInputValue(inputValueNode.getAttribute("name"),
                                                          inputValueNode.getAttribute("type"),
                                                          Boolean.parseBoolean(inputValueNode.
                                                          getAttribute("multiselect")), usedinQuery);
            this.inputValues.put(inputValue.getName(), inputValue);
            this.inputValueNames.add(inputValue.getName());
        }

        this.queryID = Config.getStringXPath(configuration, "queryID");
        log.info("QueryID ==> " + this.queryID);

        this.dataName = Config.getStringXPath(configuration, "dataname");

        String dataMultiSelectValue = Config.getStringXPath(configuration,
                                                           "data-multiselect");
        if (dataMultiSelectValue != null) {
            this.dataMultiSelect = Boolean.parseBoolean(dataMultiSelectValue);
        }

        String dataNoSelectValue =Config.getStringXPath(configuration,
                                                        "data-noselect");
        if (dataNoSelectValue != null) {
            this. dataNoSelect = Boolean.parseBoolean(dataNoSelectValue);
        }

        this.preSettingsName = Config.getStringXPath(configuration, "presettings-name");

    }


    public State getParent() {
        return this.parent;
    }


    public void setParent(State state) {
        this.parent = state;
    }


    public Document feed(
        CallContext           context,
        Collection<InputData> inputData,
        String                uuid)
    throws StateException
    {
        RessourceFactory resFactory = RessourceFactory.getInstance();
        Locale[] serverLocales      = resFactory.getLocales();
        Locale locale               = context.getMeta().getPreferredLocale(
            serverLocales);

        if (inputData != null) {
            Iterator<InputData> it = inputData.iterator();
            InputValidator iv = new InputValidator();
            while (it.hasNext()) {
                InputData tmpItem = it.next();
                InputValue inputValue = this.inputValues.get(tmpItem.getName());
                if (inputValue != null) {
                    if (this.inputData == null) {
                        this.inputData = new TreeMap<String, InputData>();
                    }

                    boolean valid = InputValidator.isInputValid(tmpItem.getValue(),
                            inputValue.getType());
                    if (valid) {

                        if (tmpItem.getName().equals(this.dataName)){
                            String[] desc = getDescriptionForInputData(
                                tmpItem, context, uuid);
                            tmpItem.setDescription(desc);
                        }
                        this.inputData.put(tmpItem.getName(), tmpItem);
                    } else {
                        String msg = resFactory.getRessource(
                            locale,
                            EXCEPTION_INVALID_INPUT,
                            EXCEPTION_INVALID_INPUT);
                        log.warn(msg);
                        return feedFailure(msg);
                    }

                } else {
                    String msg = resFactory.getRessource(
                        locale,
                        EXCEPTION_INVALID_INPUT,
                        EXCEPTION_INVALID_INPUT);
                    log.warn(msg);
                    return feedFailure(msg);

                }
            }

            return feedSuccess();
        } else {
            String msg = resFactory.getRessource(
                locale,
                EXCEPTION_NO_INPUT,
                EXCEPTION_NO_INPUT);
            log.warn(msg);
            return feedFailure(msg);
        }
    }


    protected Document feedSuccess() {
        return ArtifactXMLUtilities.createSuccessReport(
            "Initialize success", XMLUtils.newDocument());
    }


    protected Document feedFailure(String msg) {
        return ArtifactXMLUtilities.createInputExceptionReport(
            msg, XMLUtils.newDocument());
    }


    protected String[] getDescriptionForInputData(
        InputData data, CallContext context, String uuid)
    {
        // there is only one element in the list, so take the first
        Object obj = getDescibeData(uuid).get(0);
        List descs = new ArrayList();

        if (obj instanceof NamedArrayList) {
            NamedArrayList list = (NamedArrayList) obj;
            List       selected = Arrays.asList(data.splitValue());
            int            size = list.size();

            for (int i = 0; i < size; i++) {
                KeyValueDescibeData kv = (KeyValueDescibeData) list.get(i);

                // values are concatinated in InputData, so one InputData object can
                // contain many input
                String key = kv.getKey();
                int idx = selected.indexOf(key);
                if (idx >= 0) {
                    descs.add(kv.getValue());

                    // XXX Workarround: I just wanted to remove the element at
                    // 'idx' from selected, but for any reason this is not
                    // possible (throws an exception) (iw)
                    List tmp = new ArrayList();
                    for (int j = 0; j < selected.size(); j++) {
                        if (j != idx)
                            tmp.add(selected.get(j));
                    }

                    selected = tmp;
                }
            }
        }

        return (String[]) descs.toArray(new String[descs.size()]);
    }


    public void putInputData(Collection<InputData> inputData, String uuid)
    throws StateException {
        if (inputData != null) {
            Iterator<InputData> it = inputData.iterator();
            InputValidator iv = new InputValidator();
            while (it.hasNext()) {
                InputData tmpItem = it.next();
                InputValue inputValue = this.inputValues.get(tmpItem.getName());
                if (inputValue != null) {
                    if (this.inputData == null) {
                        this.inputData = new TreeMap<String, InputData>();
                    }

                    boolean valid = InputValidator.isInputValid(tmpItem.getValue(),
                            inputValue.getType());
                    if (valid) {
                        if (tmpItem.getName().equals(MINVALUEFIELDNAME)){
                            String minValue = tmpItem.getValue();
                            String maxValue = this.getInputValue4ID(inputData, MAXVALUEFIELDNAME);
                            valid = InputValidator.isInputValid(maxValue,inputValue.getType());
                            if (!valid){
                                String errMsg = "Wrong input for " + tmpItem.getValue()
                                                + " is not an " + inputValue.getType()
                                                + " Value.";
                                log.warn(errMsg);
                                throw new StateException(errMsg);
                            }

                            valid = InputValidator.isInputValid(minValue,
                                    maxValue,
                                    inputValue.getType());
                            if (!valid){
                                String errMsg = "MaxValue-Input is less than MinValue-Input ";
                                log.warn(errMsg);
                                throw new StateException(errMsg);
                            }
                        }else if (tmpItem.getName().equals(MAXVALUEFIELDNAME)){
                            String minValue = this.getInputValue4ID(inputData, MINVALUEFIELDNAME);
                            String maxValue = tmpItem.getValue();
                            valid = InputValidator.isInputValid(minValue,inputValue.getType());
                            if (!valid){
                                String errMsg = "Wrong input for " + tmpItem.getValue()
                                                + " is not an " + inputValue.getType()
                                                + " Value.";
                                log.warn(errMsg);
                                throw new StateException(errMsg);
                            }

                            valid = InputValidator.isInputValid(minValue,
                                                    maxValue,
                                                    inputValue.getType());
                            if (!valid){
                                String errMsg = "MaxValue-Input is less than MinValue-Input ";
                                log.warn(errMsg);
                                throw new StateException(errMsg);
                            }
                        }
                        this.inputData.put(tmpItem.getName(), tmpItem);
                    } else {
                        String errMsg = "Wrong input for " + tmpItem.getValue()
                                        + " is not an " + inputValue.getType()
                                        + " Value.";
                        log.warn(errMsg);
                        throw new StateException(errMsg);
                    }

                } else {
                    String errMsg = "No Inputvalue given for Inputdata "
                                    + tmpItem.getName();
                    log.warn(errMsg + "Value will be ignored");

                }
            }
        } else {
            log.warn("No Inputdata given");
        }

        setHash(uuid);
    }


    public void setPreSettings(Map<String, InputData> preSettings) {
        this.preSettings = preSettings;
    }


    public Map<String, InputData> getPreSettings() {
        return this.preSettings;
    }


    protected String getInputValue4ID(Collection<InputData> inputData, String inputName){
        Iterator<InputData> it = inputData.iterator();
        while (it.hasNext()) {
            InputData tmpItem = it.next();
            if (tmpItem.getName().equals(inputName)){
                return tmpItem.getValue();
            }
        }
        return null;
    }


    public void advance(String uuid, CallContext context)
    throws StateException
    {
    }


    public void initialize(String uuid, CallContext context)
    throws StateException
    {
    }


    protected String[] generateFilterValuesFromInputData() {
        List<String> list = new ArrayList<String>();
        Iterator<String> it = this.inputValueNames.iterator();
        while (it.hasNext()) {
            String value = it.next();
            InputData data = this.inputData.get(value);
            if (data != null
                && this.inputValues.containsKey(data.getName())) {

                int size = this.inputValues.get(data.getName())
                        .usedInQueries();
                String type = this.inputValues.get(data.getName())
                        .getType();
                String requestValue = data.getValue();
                if (type.equalsIgnoreCase("string")) {
                    requestValue = this
                            .prepareInputData4DBQuery(requestValue);
                } else if (type.equalsIgnoreCase("date")) {
                    requestValue = this
                            .prepareInputData4DateDBQuery(requestValue);
                } else if (type.equalsIgnoreCase("coordinate") || (type.equalsIgnoreCase("geometry") && requestValue.toLowerCase().startsWith("point"))){
                    requestValue = this
                    .prepareInputData4RegionDBQuery(requestValue);
                }
                for (int j = 0; j < size; j++) {
                    list.add(requestValue);
                }
            }
        }
        String[] filterValues = list.toArray(new String[list.size()]);
        return filterValues;
    }


    protected String prepareInputData4RegionDBQuery(String value){
        return value;
    }

    private String prepareInputData4DateDBQuery(String value) {
        if (value != null) {
            String[] values = value.split(",");
            String newValue = "";
            for (int i = 0; i < values.length; i++) {
                if (newValue.length() > 0) {
                    newValue = newValue + " , ";
                }
                // TODO JUST HACK FIND A BETTER RESOLUTION
                newValue = newValue + "to_date('" + values[i].trim()
                           + "', 'YYYY.MM.DD HH24:MI:SS')";
            }
            return newValue;
        }

        return value;
    }

    private String prepareInputData4DBQuery(String value) {
        if (value != null) {
            String[] values = value.split(",");
            String newValue = "";
            for (int i = 0; i < values.length; i++) {
                if (newValue.length() > 0) {
                    newValue = newValue + " , ";
                }
                newValue = newValue + "'" + values[i].trim() + "'";
            }
            return newValue;
        }

        return value;

    }


    protected List<Object> purifyResult(Collection<Result> result, String uuid) {
        List<Object> describeData = new ArrayList<Object>();

        NamedCollection<KeyValueDescibeData> keyValueDescibeData =
            extractKVP(result, "KEY", "VALUE");

        describeData.add(keyValueDescibeData);

        return describeData;
    }


    protected NamedCollection<KeyValueDescibeData> extractKVP(Collection<Result> result,
                                                              String keyid,
                                                              String valueid) {
        Iterator<Result> rit = result.iterator();
        int dataSize = (this.dataNoSelect ? result.size()+1 : result.size());

        NamedCollection<KeyValueDescibeData> keyValueDescibeData = new NamedArrayList<KeyValueDescibeData>(
                this.dataName, dataSize);
        keyValueDescibeData.setMultiSelect(this.dataMultiSelect);

        if (this.dataNoSelect){
            keyValueDescibeData.add(new DefaultKeyValueDescribeData(
                NODATASELECTIONKEY,
                "No Selection",
                getID()
            ));
        }

        boolean initialized = false;
        int     keyPos      = 0;
        int     valuePos    = 1;
        String  previousKey = null;
        InputData preSettingsData =
            (this.preSettings != null && this.preSettingsName != null)
                ? this.preSettings.get(this.preSettingsName)
                : null;
        boolean filterWithPresettings = preSettingsData != null;

        List<String> preSettingValues = null;
        if(filterWithPresettings){
            preSettingValues = Arrays.asList(preSettingsData.splitValue());
        }
        while (rit.hasNext()) {
            Result resultValue = rit.next();
            if (!initialized){
                keyPos = resultValue.getResultDescriptor().getColumnIndex(keyid);
                valuePos = resultValue.getResultDescriptor().getColumnIndex(valueid);
                if (valuePos < 0){
                    valuePos = 1;
                }
                initialized = true;
            }
            String tmpKey = resultValue.getString(keyPos);

            // TODO: FIXME: We have to do that because the arcsde does not
            //       support a distinct Query on Layers.
            if (previousKey == null || !tmpKey.equals(previousKey)){
                previousKey = tmpKey;
                if (!filterWithPresettings || preSettingValues.contains(tmpKey)){
                    keyValueDescibeData.add(
                            new DefaultKeyValueDescribeData(
                                                tmpKey,
                                                resultValue.getString(valuePos),
                                                getID())
                                );
                }
            }
        }
        return keyValueDescibeData;
    }


    public static boolean inBlackList(String key) {
        int length = BLACKLIST.length;
        for (int i = 0; i < length; i++) {
            if (BLACKLIST[i].equals(key)) {
                return true;
            }
        }

        return false;
    }


    public void describe(
        Document    document,
        Node        rootNode,
        CallContext context,
        String      uuid)
    {
        XMLUtils.ElementCreator xCreator = new XMLUtils.ElementCreator(
            document,
            XMLUtils.XFORM_URL,
            XMLUtils.XFORM_PREFIX
        );

        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
            document,
            ArtifactNamespaceContext.NAMESPACE_URI,
            ArtifactNamespaceContext.NAMESPACE_PREFIX
        );

        // append dynamic node
        Node dynamic = (Node) XMLUtils.xpath(
            rootNode,
            XPATH_DYNAMIC_UI,
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE
        );

        describeDynamic(
            creator, xCreator, document, dynamic, context, uuid);

        // append static nodes
        Node staticNode = (Node) XMLUtils.xpath(
            rootNode,
            XPATH_STATIC_UI,
            XPathConstants.NODE,
            ArtifactNamespaceContext.INSTANCE
        );

        State parent = getParent();
        if (parent != null && parent instanceof StateBase) {
            ((StateBase) parent).describeStatic(
                creator,xCreator, document, staticNode, context,uuid);
        }
    }


    protected void describeDynamic(
        XMLUtils.ElementCreator artCreator,
        XMLUtils.ElementCreator creator,
        Document                document,
        Node                    dynamic,
        CallContext             context,
        String                  uuid)
    {
        CallMeta callMeta = context.getMeta();

        if (dataName == null)
            return;

        List<Object> descibeData = getDescibeData(uuid);
        if (descibeData != null) {
            Iterator<Object> it = descibeData.iterator();

            while (it.hasNext()) {
                Object o = it.next();
                if ((!it.hasNext() && dataName != null)) {
                    appendToDynamicNode(
                        artCreator, creator, document, dynamic, callMeta, o);
                }
            }
        }
    }


    protected void describeStatic(
        XMLUtils.ElementCreator artCreator,
        XMLUtils.ElementCreator creator,
        Document                document,
        Node                    staticNode,
        CallContext             context,
        String                  uuid)
    {
        State parent = getParent();
        if (parent instanceof StateBase) {
            ((StateBase) parent).describeStatic(
                artCreator, creator, document, staticNode, context, uuid);
        }

        CallMeta callMeta = context.getMeta();
        appendToStaticNode(artCreator, creator, document, staticNode, callMeta);
    }


    protected void appendToStaticNode(
        XMLUtils.ElementCreator artCreator,
        XMLUtils.ElementCreator creator,
        Document                document,
        Node                    staticNode,
        CallMeta                callMeta
    ) {
        InputData  data = dataName!= null ? inputData.get(dataName) : null;

        if (data == null) {
            return;
        }

        Element selectNode = creator.create("select1");
        creator.addAttr(selectNode, "ref", dataName);

        Element lableNode = creator.create("label");
        lableNode.setTextContent(RessourceFactory.getInstance()
                .getRessource(callMeta.getLanguages(), dataName, dataName));
        Element choiceNode = creator.create("choices");

        artCreator.addAttr(
            selectNode, "state", getID(), true
        );

        String[] descriptions = data.getDescription();
        int              size = descriptions.length;

        for (int i = 0; i < size; i++) {
            Element itemNode = creator.create("item");
            String value = data.getValue();
            String desc  = descriptions[i];
            desc = desc == null ? value : desc;

            creator.addAttr(itemNode, "selected", "true");

            Element choiceLableNode = creator.create("label");
            choiceLableNode.setTextContent(desc);
            itemNode.appendChild(choiceLableNode);

            Element choiceValueNode = creator.create("value");
            choiceValueNode.setTextContent(value);
            itemNode.appendChild(choiceValueNode);
            choiceNode.appendChild(itemNode);
        }

        selectNode.appendChild(lableNode);
        selectNode.appendChild(choiceNode);

        staticNode.appendChild(selectNode);
    }


    protected void appendToDynamicNode(
        XMLUtils.ElementCreator artCreator,
        XMLUtils.ElementCreator creator,
        Document                document,
        Node                    dynamicNode,
        CallMeta                callMeta,
        Object                  o
    ) {
        if (o instanceof Collection<?>) {
            String name = null;
            boolean multiselect = false;
            if (o instanceof NamedCollection<?>) {
                NamedCollection<?> nc = ((NamedCollection<?>) o);
                name = nc.getName();
                multiselect = nc.isMultiSelect();
            } else {
                Object[] names = this.inputValueNames.toArray();
                name = names[names.length - 1].toString();
            }

            Element selectNode = creator.create(multiselect?"select":"select1");
            creator.addAttr(selectNode, "ref", name);

            Element lableNode = creator.create("label");
            lableNode.setTextContent(RessourceFactory.getInstance()
                    .getRessource(callMeta.getLanguages(), name, name));
            Element choiceNode = creator.create("choices");

            Collection<KeyValueDescibeData> values = (Collection<KeyValueDescibeData>) o;
            Iterator<KeyValueDescibeData> resultIt = values.iterator();
            while (resultIt.hasNext()) {
                KeyValueDescibeData result = resultIt.next();
                Element itemNode = creator.create("item");

                if (result.isSelected()) {
                    itemNode.setAttribute("selected", "true");
                }

                Element choiceLableNode = creator.create("label");
                choiceLableNode.setTextContent(result.getValue());
                itemNode.appendChild(choiceLableNode);

                Element choicValueNode = creator.create("value");
                choicValueNode.setTextContent("" + result.getKey());
                itemNode.appendChild(choicValueNode);
                choiceNode.appendChild(itemNode);
            }
            selectNode.appendChild(lableNode);
            selectNode.appendChild(choiceNode);

            dynamicNode.appendChild(selectNode);
        }
        else if (o instanceof MinMaxDescribeData) {
            appendMinMaxDescribeData(
                artCreator,
                creator,
                document,
                dynamicNode,
                callMeta,
                o);
        }
        else if (o instanceof SingleValueDescribeData) {
            appendSingleValueDescribeData(
                artCreator,
                creator,
                document,
                dynamicNode,
                callMeta,
                o);
        }
    }


    protected void appendMinMaxDescribeData(
        XMLUtils.ElementCreator artCreator,
        XMLUtils.ElementCreator creator,
        Document                document,
        Node                    node,
        CallMeta                callMeta,
        Object                  o
    ) {
        MinMaxDescribeData minMaxDescibeData = (MinMaxDescribeData) o;
        Object min = minMaxDescibeData.getMinValue();
        Object max = minMaxDescibeData.getMaxValue();
        if (min instanceof GregorianCalendar) {
            Date d = ((GregorianCalendar) min).getTime();
            min = DateUtils.getPatternedDateAmer(d);
        }

        if (max instanceof GregorianCalendar) {
            Date d = ((GregorianCalendar) max).getTime();
            max = DateUtils.getPatternedDateAmer(d);
        }

        Element groupNode = creator.create("group");
        artCreator.addAttr(groupNode, "state", minMaxDescibeData.getState(), true);

        creator.addAttr(groupNode, "ref", minMaxDescibeData.getName());
        Element groupNodeLableNode = creator.create("label");
        groupNodeLableNode.setTextContent(RessourceFactory
                .getInstance().getRessource(
                        callMeta.getLanguages(),
                        minMaxDescibeData.getName(),
                        minMaxDescibeData.getName()));
        groupNode.appendChild(groupNodeLableNode);

        Element inputMinNode = creator.create("input");
        creator.addAttr(inputMinNode, "ref", MINVALUEFIELDNAME);
        Element inputMinLableNode = creator.create("label");
        inputMinLableNode.setTextContent(RessourceFactory
                .getInstance().getRessource(
                        callMeta.getLanguages(), MINVALUEFIELDNAME,
                        MINVALUEFIELDNAME));
        inputMinNode.appendChild(inputMinLableNode);

        Element inputMinValueNode = creator.create("value");
        inputMinValueNode.setTextContent(min.toString());
        inputMinNode.appendChild(inputMinValueNode);

        Element inputMaxNode = creator.create("input");
        creator.addAttr(inputMaxNode, "ref", MAXVALUEFIELDNAME);
        Element inputMaxLableNode = creator.create("label");
        inputMaxLableNode.setTextContent(RessourceFactory
                .getInstance().getRessource(
                        callMeta.getLanguages(), MAXVALUEFIELDNAME,
                        MAXVALUEFIELDNAME));
        inputMaxNode.appendChild(inputMaxLableNode);

        Element inputMaxValueNode = creator.create("value");
        inputMaxValueNode.setTextContent(max.toString());
        inputMaxNode.appendChild(inputMaxValueNode);

        groupNode.appendChild(inputMinNode);
        groupNode.appendChild(inputMaxNode);

        node.appendChild(groupNode);
    }


    protected void appendSingleValueDescribeData(
        XMLUtils.ElementCreator artCreator,
        XMLUtils.ElementCreator creator,
        Document                document,
        Node                    node,
        CallMeta                callMeta,
        Object                  o
    ) {
        SingleValueDescribeData svdb = (SingleValueDescribeData) o;

        Element groupNode = creator.create("group");
        artCreator.addAttr(groupNode, "state", svdb.getState(), true);
        creator.addAttr(groupNode, "ref",  svdb.getName());

        Element groupNodeLableNode = creator.create("label");
        groupNodeLableNode.setTextContent(RessourceFactory
                .getInstance().getRessource(
                        callMeta.getLanguages(),
                        svdb.getName(),
                        svdb.getName()));
        groupNode.appendChild(groupNodeLableNode);

        Element inputNode = creator.create("input");
        creator.addAttr(inputNode, "ref", svdb.getName());

        Element inputLableNode = creator.create("label");
        inputLableNode.setTextContent("");
        inputNode.appendChild(inputLableNode);

        Element inputValueNode = creator.create("value");
        inputValueNode.setTextContent(svdb.getValue());
        inputNode.appendChild(inputValueNode);

        groupNode.appendChild(inputNode);

        node.appendChild(groupNode);
    }


    protected void setHash(String uuid) {
        String newHash = uuid + HASH_ID_SEPARATOR + id + HASH_ID_SEPARATOR;
        Set    keys    = inputData.keySet();

        int nhash = 0;
        int shift = 0;

        for (Object o: keys) {
            nhash ^= inputData.get(o).hashCode() << shift;
            shift += 2;
        }

        log.info("### OLD HASH: " + hash);
        log.info("### NEW HASH: " + (newHash + nhash));

        this.hash = newHash + nhash;
    }


    protected String getHash() {
        return this.hash;
    }


    public List<Object> getDescibeData(String uuid) {
        CacheFactory factory = CacheFactory.getInstance();
        if (factory.isInitialized()) {
            // we use a cache
            Cache cache = factory.getCache();
            String key  = getHash();

            log.debug("Using cache - key: " + key);

            net.sf.ehcache.Element value = cache.get(key);
            if (value != null) {
                // element already in cache, so return it.
                log.debug("Found element in cache.");
                return (List<Object>) (value.getObjectValue());
            }
            else {
                // element is not in cache yet, so we need to fetch data from
                // database and put it into cache right now
                log.debug("Element not in cache, we need to ask the database");
                try {
                    String[] filterValues = generateFilterValuesFromInputData();
                    List<Object> data     = queryDatabase(filterValues, uuid);

                    cache.put(new net.sf.ehcache.Element(key, data));

                    return data;
                }
                catch (QueryException qe) {
                    log.error(qe, qe);
                }
            }
        }
        else {
            // we don't use a cache, so we have to query the database every
            // single time
            log.debug("Not using cache.");
            String[] filterValues     = generateFilterValuesFromInputData();
            Collection<Result> result = null;
            try {
                return queryDatabase(filterValues, uuid);
            }
            catch (RuntimeException e) {
                log.error(e, e);
            }
            catch (QueryException e) {
                log.error(e, e);
            }
        }

        return null;
    }


    protected List<Object> queryDatabase(String[] filterValues, String uuid)
    throws QueryException {
        Collection<Result> result = null;

        if (queryID != null) {
            QueryExecutor queryExecutor =
                QueryExecutorFactory.getInstance().getQueryExecutor();

            result = queryExecutor.executeQuery(queryID, filterValues);
        }
        return purifyResult(result, uuid);
    }



    public Map<String, InputData> inputData() {
        return inputData;
    }


    public Collection<InputData> getInputData() throws StateException {
        return this.inputData != null ? this.inputData.values() : null;
    }


    public InputData getInputDataByName(String name) {
        State state = this;

        while (state != null) {
            InputData data = state.inputData().get(name);
            if (data != null) {
                return data;
            }

            state = state.getParent();
        }

        return null;
    }


    public void endOfLife(Object globalContext) {
        log.debug("end of life of the current state.");

        CacheFactory factory = CacheFactory.getInstance();
        if (factory.isInitialized()) {
            Cache cache = factory.getCache();
            String key  = getHash();

            if (key != null && cache.remove(key)) {
                log.info("Removed element from cache - key: " + key);
            }
        }
    }


    public void cleanup(Object context) {
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org