diff gnv-artifacts/src/main/java/de/intevation/gnv/state/timeseries/TimeSeriesOutputState.java @ 1119:7c4f81f74c47

merged gnv-artifacts
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:00 +0200
parents dec4257ad570
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/state/timeseries/TimeSeriesOutputState.java	Fri Sep 28 12:14:00 2012 +0200
@@ -0,0 +1,1318 @@
+/*
+ * 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.timeseries;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.log4j.Logger;
+import org.jfree.chart.ChartTheme;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import au.com.bytecode.opencsv.CSVWriter;
+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.context.GNVArtifactContext;
+import de.intevation.gnv.artifacts.ressource.RessourceFactory;
+import de.intevation.gnv.chart.Chart;
+import de.intevation.gnv.chart.ChartLabels;
+import de.intevation.gnv.chart.DefaultHistogram;
+import de.intevation.gnv.chart.TimeSeriesChart;
+import de.intevation.gnv.chart.XMLChartTheme;
+import de.intevation.gnv.chart.exception.TechnicalChartException;
+import de.intevation.gnv.exports.ChartExportHelper;
+import de.intevation.gnv.exports.DefaultDataCollector;
+import de.intevation.gnv.exports.DefaultExport;
+import de.intevation.gnv.exports.DefaultProfile;
+import de.intevation.gnv.exports.Export;
+import de.intevation.gnv.exports.ODVExport;
+import de.intevation.gnv.exports.SimpleOdvDataCollector;
+import de.intevation.gnv.exports.Export.Profile;
+import de.intevation.gnv.geobackend.base.Result;
+import de.intevation.gnv.histogram.HistogramHelper;
+import de.intevation.gnv.state.InputData;
+import de.intevation.gnv.state.OutputStateBase;
+import de.intevation.gnv.state.State;
+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.exception.StateException;
+import de.intevation.gnv.statistics.Statistic;
+import de.intevation.gnv.statistics.StatisticSet;
+import de.intevation.gnv.statistics.Statistics;
+import de.intevation.gnv.statistics.TimeseriesStatistics;
+import de.intevation.gnv.statistics.exception.StatisticsException;
+import de.intevation.gnv.timeseries.gap.DefaultTimeGap;
+import de.intevation.gnv.timeseries.gap.TimeGap;
+import de.intevation.gnv.utils.ArtifactXMLUtilities;
+
+/**
+ * @author <a href="mailto:tim.englich@intevation.de">Tim Englich</a>
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class TimeSeriesOutputState extends OutputStateBase {
+
+    /**
+     * A boolean property to enable caching of charts after they have been
+     * created. This property can be adjusted via system property 'cache.chart'.
+     * 'true' of 'false' are valid options - defaults to 'false'.<br>
+     * <b>NOTE:</b> If chart caching is enabled, charts aren't sensible to
+     * changes in the parameterization anymore, after a first chart has been
+     * created for an artifact.
+     */
+    protected static final boolean CACHE_CHART =
+        Boolean.parseBoolean(System.getProperty("cache.chart", "false"));
+
+    /**
+     * Supported image export formats.
+     */
+    protected static final String[] IMG_EXPORT_FORMAT = {
+        "PNG", "JPEG", "GIF"
+    };
+
+    /**
+     * The UID of this Class
+     */
+    private static final long serialVersionUID = 4178407570503098858L;
+
+    /**
+     * the logger, used to log exceptions and additonaly information
+     */
+    private static Logger log = Logger
+            .getLogger(TimeSeriesOutputState.class);
+
+    protected List<TimeGap> timeGapDefinitions = null;
+
+    /**
+     * Key in resource bundle the x-axis title is stored.
+     */
+    protected String domainLable = "chart.timeseries.title.xaxis";
+
+    protected String featureValuesName = "featureid";
+    protected String parameterValuesName = "parameterid";
+    protected String measuremenValueName = "measurementid";
+    protected String dateValueName = "dateid";
+    protected String timeIntervalValueName = "timeinterval";
+
+
+    /**
+     * Array used to specify the columns used in csv exports.
+     */
+    public static final String [] TIMESERIES_CSV_PROFILE_COLUMNS = {
+        "XORDINATE",
+        "YORDINATE",
+        "GROUP1",
+        "GROUP2",
+        "GROUP3"
+    };
+
+
+    /**
+     * Column labels used in csv exports.
+     */
+    public static final String [] TIMESERIES_TIMESERIES_CSV_COLUMN_LABEL = {
+        "Date/Time",
+        "Value",
+        "ParameterID",
+        "MeasurementID",
+        "TimeseriesID"
+    };
+
+
+    /**
+     * Array used to specify the column in csv exports on meshes.
+     */
+    public static final String [] TIMESERIES_MESH_CSV_COLUMN_LABEL = {
+        "Date/Time",
+        "Value",
+        "ParameterID",
+        "FeatureID",
+        "MeshID"
+    };
+
+
+    /**
+     * Array used to specify the columns in odv exports.
+     */
+    public static final String [] TIMESERIES_ODV_PROFILE_NAMES = {
+          "CRUISE",
+          "STATION",
+          "TYPE",
+          "TIMEVALUE",
+          "SHAPE",
+          "BOTDEPTH",
+          "DEPTH",
+          "QF"
+    };
+
+
+    /**
+     * Column labels used in odv exports.
+     */
+    public static final String [] ODV_COLUMN_HEADER = {
+        "Cruise",
+        "Station",
+        "Type",
+        "yyyy-mm-dd hh:mm",
+        "Lon (°E)",
+        "Lat (°N)",
+        "Bot. Depth [m]",
+        "Depth [m]",
+        "QF"
+    };
+
+
+    /**
+     * Profile for exporting data to odv
+     */
+    public static final Profile TIMESERIES_ODV_PROFILE =
+        new DefaultProfile(
+            ODV_COLUMN_HEADER,
+            '\t',
+            CSVWriter.NO_QUOTE_CHARACTER,
+            CSVWriter.NO_ESCAPE_CHARACTER,
+            "ODV",
+            "ISO-8859-1");
+
+
+    /**
+     * Constructor
+     */
+    public TimeSeriesOutputState() {
+        super();
+    }
+
+
+    /**
+     * Calls <code>getChartResult</code> which puts the data used for chart
+     * creation into cache. This redruces waiting periods after selecting an
+     * output type.
+     *
+     * @param uuid The UUID of the current artifact.
+     * @param context The CallContext object.
+     * @throws StateException if an error occured while fetching the data used
+     * for chart creation.
+     */
+    @Override
+    public void initialize(String uuid, CallContext context)
+    throws StateException
+    {
+        getChartResult(uuid, context);
+    }
+
+
+    /**
+     * This out target has following modes:<br>
+     * <ol>
+     *  <li>chart: Creates a chart displaying the data corresponding to the
+     * current parameterization. A chart has following export modes:<br>
+     *      <ol>
+     *          <li>img: Image representation of a chart.</li>
+     *          <li>pdf: PDF representation of a chart.</li>
+     *          <li>svg: SVG representation of a chart.</li>
+     *      </ol>
+     * All selected parameters are drawn into a single chart.<br>
+     *  </li>
+     *  <li>histogram: Creates a histogram displaying the data corresponding to
+     * the current parameterization. A histogram has following export modes:<br>
+     *      <ol>
+     *          <li>img: Image representation of a histogram.</li>
+     *          <li>pdf: PDF representation of a histogram.</li>
+     *          <li>svg: SVG representation of a histogram.</li>
+     *      </ol>
+     * A single histogram is created for each selected parameter.<br>
+     *  </li>
+     *  <li>statistic: Creates a statistic with important figures.</li>
+     *  <li>csv: Creates a csv file.</li>
+     *  <li>odv: Creates a odv file.</li>
+     * </ol>
+     *
+     * @param format Document which contains some export specific information.
+     * @param inputData Contains some meta information to adjust the export
+     * (e.g. width and height of a chart).
+     * @param outputStream The output stream used to return the export.
+     * @param uuid The UUID of the current artifact.
+     * @param callContext The CallContext object.
+     * @throws StateException if an error occured while creating the export
+     * object.
+     */
+    public void out(
+        Document              format,
+        Collection<InputData> inputData,
+        OutputStream          outputStream,
+        String                uuid,
+        CallContext           callContext
+    ) throws StateException
+    {
+        log.debug("TimeSeriesOutputTransition.out");
+
+        String outputMode = XMLUtils.xpathString(
+            format, XPATH_OUTPUT_MODE, ArtifactNamespaceContext.INSTANCE);
+
+        String mode       = XMLUtils.xpathString(
+            format, XPATH_EXPORT_MODE, ArtifactNamespaceContext.INSTANCE);
+
+        if (mode == null || mode.equals("")) {
+            mode = "img";
+        }
+
+        String mimeType = XMLUtils.xpathString(
+            format, XPATH_MIME_TYPE, ArtifactNamespaceContext.INSTANCE);
+
+        CallMeta callMeta         = callContext.getMeta();
+
+        int chartWidth   = 600;
+        int chartHeight  = 400;
+        boolean sVisible = false;
+        int binCount     = 0;
+        int binWidth     = 0;
+
+        Map requestParameter = new HashMap();
+
+        // lines are always visible. if lines should be configurable we need a
+        // parameter in the user interface
+        boolean lVisible = true;
+
+        try {
+            if (inputData != null) {
+                Iterator<InputData> it = inputData.iterator();
+                while (it.hasNext()) {
+                    InputData ip      = it.next();
+                    String optionName = ip.getName().trim();
+                    requestParameter.put(optionName, ip.getValue());
+
+                    if (optionName.equals("width")) {
+                        chartWidth = Integer.parseInt(ip.getValue());
+                    }
+                    else if (optionName.equals("height")) {
+                        chartHeight = Integer.parseInt(ip.getValue());
+                    }
+                    else if (optionName.equals("points")) {
+                        sVisible = Boolean.parseBoolean(ip.getValue());
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.warn(e, e);
+            XMLUtils.toStream(
+                feedFailure("not.a.number"),
+                outputStream);
+            return;
+        }
+
+        try {
+            Collection<KeyValueDescibeData> parameters   =
+                getParameters(uuid);
+            Collection<KeyValueDescibeData> measurements =
+                getMeasurements(uuid);
+            Collection<KeyValueDescibeData> dates        =
+                getDates(uuid);
+
+            Locale[] serverLocales =
+                RessourceFactory.getInstance().getLocales();
+            Locale locale          =
+                callMeta.getPreferredLocale(serverLocales);
+
+            ChartLabels chartLables = createChartLabels(locale, uuid);
+
+            log.debug(
+                "Best locale - regarding intersection of server and " +
+                "browser locales -  is " + locale.toString()
+            );
+
+            String exportFormat = getExportFormat(mimeType);
+
+            // CHART
+            if (outputMode.equalsIgnoreCase("chart")) {
+                log.debug("Chart will be generated.");
+
+                if (mode.equalsIgnoreCase("img")) {
+                    createChart(
+                        outputStream,
+                        parameters,
+                        measurements,
+                        dates,
+                        chartLables,
+                        callContext,
+                        uuid,
+                        exportFormat,
+                        locale,
+                        chartWidth,
+                        chartHeight,
+                        lVisible,
+                        sVisible,
+                        callContext
+                    );
+                }
+                else if (mode.equalsIgnoreCase("pdf")) {
+                    callContext.putContextValue("chart.width", chartWidth);
+                    callContext.putContextValue("chart.height", chartHeight);
+                    callContext.putContextValue("shapes.visible", sVisible);
+                    callContext.putContextValue("lines.visible", lVisible);
+                    callContext.putContextValue("locale", locale);
+
+                    createPDF(
+                        outputStream,
+                        parameters,
+                        measurements,
+                        dates,
+                        chartLables,
+                        uuid,
+                        "A4",
+                        callContext
+                    );
+                }
+                else if (mode.equalsIgnoreCase("svg")) {
+                    createSVG(
+                        outputStream,
+                        getParameters(uuid),
+                        getMeasurements(uuid),
+                        getDates(uuid),
+                        createChartLabels(locale, uuid),
+                        uuid,
+                        locale,
+                        chartWidth,
+                        chartHeight,
+                        lVisible,
+                        sVisible,
+                        callContext
+                    );
+                }
+            }
+            // HISTOGRAM
+            else if (outputMode.equalsIgnoreCase("histogram")) {
+                requestParameter.put("locale", locale);
+
+                Chart[] histograms = getHistograms(
+                    uuid,
+                    callContext,
+                    parameters,
+                    measurements,
+                    dates,
+                    requestParameter);
+
+                if (mode.equalsIgnoreCase("img")) {
+                    ChartExportHelper.exportHistograms(
+                        outputStream,
+                        histograms,
+                        exportFormat,
+                        chartWidth,
+                        chartHeight
+                    );
+                }
+                else if (mode.equalsIgnoreCase("pdf")) {
+                    callContext.putContextValue("chart.width", chartWidth);
+                    callContext.putContextValue("chart.height", chartHeight);
+
+                    ChartExportHelper.exportHistogramsAsPDF(
+                        outputStream,
+                        histograms,
+                        "A4",
+                        50F, 50F, 50F, 50F,
+                        callContext
+                    );
+                }
+                else if (mode.equalsIgnoreCase("svg")) {
+                    ChartExportHelper.exportHistogramsAsSVG(
+                        outputStream,
+                        histograms,
+                        null,
+                        chartWidth,
+                        chartHeight
+                    );
+                }
+            }
+            else if (outputMode.equalsIgnoreCase("csv")) {
+                log.debug("CSV-File will be generated.");
+                Object result = getChartResult(uuid, callContext);
+                if (result instanceof Collection) {
+                    this.createCSV(
+                        outputStream,
+                        (Collection<Result>)result);
+                }
+            } else if (outputMode.equalsIgnoreCase("statistics")) {
+                log.debug("Statistics will be generated.");
+
+                Collection<StatisticSet> statistics;
+
+                Statistics s      = getStatisticsGenerator();
+                Object     result = getChartResult(uuid, callContext);
+
+                if (result != null && s != null) {
+                    statistics = s.calculateStatistics(
+                        result,
+                        parameters,
+                        measurements,
+                        dates);
+                }
+                else {
+                    statistics = new ArrayList<StatisticSet>();
+                }
+
+                Document doc = writeStatistics2XML(statistics, locale);
+
+                XMLUtils.toStream(doc, outputStream);
+
+            }
+            else if (outputMode.equalsIgnoreCase("odv")) {
+                createODV(outputStream, uuid, callContext);
+            }
+        } catch (IOException e) {
+            log.error(e, e);
+            throw new StateException(e);
+        } catch (TechnicalChartException e) {
+            log.error(e, e);
+            throw new StateException(e);
+        } catch (StatisticsException e) {
+            log.error(e, e);
+            throw new StateException(e);
+        }
+    }
+
+
+    protected Chart[] getHistograms(
+        String      uuid,
+        CallContext callContext,
+        Collection<KeyValueDescibeData>  parameters,
+        Collection<KeyValueDescibeData>  measurements,
+        Collection<KeyValueDescibeData>  dates,
+        Map         requestParameter
+    ) {
+        Locale locale = (Locale) requestParameter.get("locale");
+
+        Collection results = (Collection) getChartResult(uuid, callContext);
+        ChartTheme theme   = createStyle(callContext);
+
+        Object[][] data = HistogramHelper.prepareHistogramData(
+            results, parameters, measurements, dates);
+
+        int size           = data.length;
+        Chart[] histograms = new Chart[size];
+
+        for (int i = 0; i < size; i++) {
+            ChartLabels labels = HistogramHelper.createHistogramLabels(
+                uuid, callContext, locale, data[i]);
+
+            histograms[i] = new DefaultHistogram(
+                labels, data[i], theme, requestParameter);
+        }
+
+        return histograms;
+    }
+
+    /**
+     * @param outputStream
+     * @param uuid
+     * @throws IOException
+     * @throws StateException
+     */
+    protected void createODV(OutputStream outputStream,
+                             String uuid,
+                             CallContext callContext)
+                                                    throws IOException,
+                                                    StateException {
+        Collection<Result> odvResult = this.getODVResult(uuid);
+        this.createODV(outputStream, odvResult,uuid);
+    }
+
+
+    /**
+     * Retrieves the export format (e.g. png, gif, jpeg).
+     *
+     * @param mime Export format specified by the incoming  request.
+     * @return <i>mime</i> if it is supported - otherwise the first format in
+     * {@link #IMG_EXPORT_FORMAT}.
+     */
+    protected String getExportFormat(String mime) {
+        for(int i = 0; i < IMG_EXPORT_FORMAT.length; i++) {
+            if (mime.trim().toUpperCase().indexOf(IMG_EXPORT_FORMAT[i]) > 0)
+                return IMG_EXPORT_FORMAT[i];
+        }
+
+        // no format found relating to mimeType, default export as PNG
+        return IMG_EXPORT_FORMAT[0];
+    }
+
+
+    /**
+     * Returns a collection containing all selected KeyValueDescibeData objects
+     * of <i>parameters</i>.
+     *
+     * @param parameters A collection with KeyValueDescibeData objects.
+     * @return a collection cleaned from unselected objects.
+     * @deprecated
+     */
+    protected Collection getCleanedParameters(Collection parameters) {
+        Iterator iter        = parameters.iterator();
+        Collection parameter = new Vector(parameters);
+        while (iter.hasNext()) {
+            KeyValueDescibeData data = (KeyValueDescibeData)iter.next();
+            if (!data.isSelected())
+                parameter.remove(data);
+        }
+
+        return parameter;
+    }
+
+
+    /**
+     * Calls {@link #getCleanedParameters(java.util.Collection)} with the
+     * collection returned by {@link #getParameters(java.lang.String)}.
+     *
+     * @param uuid The UUID of the current artifact.
+     * @return a cleaned collection.
+     */
+    protected Collection getCleanedParameters(String uuid) {
+        return getCleanedParameters(getParameters(uuid));
+    }
+
+
+    /**
+     * Create a csv file representing the data corresponding to the current
+     * parameterization.
+     *
+     * @param out The output stream used to export the csv file.
+     * @param results The data used for csv file creation.
+     * @throws UnsupportedEncodingException if the encoding is not supported.
+     * @throws IOException if an error occured while writing to output stream.
+     * @throws StateException if an error occured while creating the csv file.
+     */
+    protected void createCSV(OutputStream out, Collection<Result> results)
+    throws UnsupportedEncodingException, IOException, StateException
+    {
+        Iterator iter = results.iterator();
+        Result   res  = iter.hasNext() ? (Result) iter.next() : null;
+
+        if (res == null)
+            return;
+
+        Profile profile = null;
+        int     dataid  = res.getInteger("DATAID").intValue();
+
+        // on meshes
+        if (dataid == 2) {
+            profile =  new DefaultProfile(
+                TIMESERIES_MESH_CSV_COLUMN_LABEL,
+                ',',
+                '"',
+                '"',
+                "CSV",
+                "ISO-8859-1");
+        }
+
+        // on timeseries
+        else {
+            profile =  new DefaultProfile(
+                TIMESERIES_TIMESERIES_CSV_COLUMN_LABEL,
+                ',',
+                '"',
+                '"',
+                "CSV",
+                "ISO-8859-1");
+        }
+
+        DefaultExport export = new DefaultExport(
+            new DefaultDataCollector(TIMESERIES_CSV_PROFILE_COLUMNS));
+        export.create(profile, out, results);
+    }
+
+
+    /**
+     * Create an odv file representing the data corresponding to the current
+     * parameterization.
+     *
+     * @param outputStream The output stream used to export the odv file.
+     * @param result The data used for odv file creation.
+     * @param uuid The UUID of the current artifact.
+     * @throws IOException if an error occured while writing to output stream.
+     * @throws StateException if an error occured while creating the odv file.
+     */
+    protected void createODV(OutputStream outputStream,
+                             Collection result,
+                             String uuid)
+    throws IOException, StateException {
+
+        Export export = new ODVExport(new SimpleOdvDataCollector(
+            TIMESERIES_ODV_PROFILE_NAMES),
+            this.getParameters(uuid),
+            this.getStartTime());
+
+        if (result == null)
+            log.error("#################### RESULT == NULL #################");
+        export.create(TIMESERIES_ODV_PROFILE, outputStream, result);
+    }
+
+    /**
+     * Method that returns the Starttime of an TimeSeries or
+     * null if it is not a TimeSeries.
+     * @return the Starttime of an TimeSeries or null if it is not a TimeSeries.
+     */
+    protected String getStartTime(){
+        InputData data = inputData.get(this.timeIntervalValueName);
+        if (data != null){
+            Object describeData = data.getObject();
+            if (describeData instanceof MinMaxDescribeData){
+                String value = ((MinMaxDescribeData)describeData)
+                                .getMinValue().toString();
+                return value.substring(0,value.lastIndexOf(':'));
+            }
+            return null;
+        }else{
+            return null;
+        }
+
+    }
+
+    /**
+     * Returns the statistic generator.
+     *
+     * @return the statistic generator.
+     */
+    protected Statistics getStatisticsGenerator() {
+        Statistics s = new TimeseriesStatistics();
+        return s;
+    }
+
+
+    /**
+     * Writes the statistic into an xml document.
+     *
+     * @param statistic Statistic to be written to xml document.
+     * @return the xml document containing the statistic.
+     */
+    protected Document writeStatistics2XML(
+        Collection<StatisticSet> statistic, Locale locale)
+    {
+        ArtifactXMLUtilities xmlUtilities = new ArtifactXMLUtilities();
+        Document doc = XMLUtils.newDocument();
+
+        NumberFormat format = NumberFormat.getInstance(locale);
+
+        if (statistic != null) {
+            Node statisticResults = ArtifactXMLUtilities.createArtifactElement(doc,
+                    "statistics");
+            doc.appendChild(statisticResults);
+            Iterator<StatisticSet> it = statistic.iterator();
+            while (it.hasNext()) {
+                StatisticSet set = it.next();
+                Element setElement = ArtifactXMLUtilities.createArtifactElement(doc,
+                                                                       "statistic");
+                setElement.setAttribute("name", set.getName());
+
+                Iterator<Statistic> sit = set.getStatistics().iterator();
+                while (sit.hasNext()){
+                    Statistic s = sit.next();
+                    Element result = ArtifactXMLUtilities.createArtifactElement(doc,
+                    "statistic-value");
+                    result.setAttribute("name", s.getKey());
+                    result.setAttribute("value", format.format(s.getValue()));
+                    setElement.appendChild(result);
+                }
+                statisticResults.appendChild(setElement);
+            }
+
+        }
+        return doc;
+    }
+
+
+    /**
+     * Returns the name of the selected feature.
+     *
+     * @param uuid The UUID of the current artifact.
+     * @return the name of the selectef feature.
+     */
+    protected String getSelectedFeatureName(String uuid) {
+        Collection values = getCollection(featureValuesName, uuid);
+
+        if (values != null) {
+            Iterator it = values.iterator();
+
+            while (it.hasNext()) {
+                KeyValueDescibeData data = (KeyValueDescibeData) it.next();
+                return data.getValue();
+            }
+
+            return "";
+        }
+        return "";
+    }
+
+
+    /**
+     * Creates a chart and writes it to <i>outputStream</i>.
+     *
+     * @param outputStream The stream used to write the chart to.
+     * @param parameters A collection with parameters.
+     * @param measurements A collection with measurements.
+     * @param dates A collection with dates.
+     * @param chartLables The labels used to decorate the chart.
+     * @param context The CallContext.
+     * @param uuid The UUID of the current artifact.
+     * @param exportFormat The format the chart used to be exported as.
+     * @param locale The Locale to specify the language.
+     * @param width The width of the chart.
+     * @param height The height of the chart.
+     * @param linesVisible A boolean property to determine the visibility of
+     * lines connecting two datapoins.
+     * @param shapesVisible A boolean property to determine the visibility of
+     * datapoints.
+     * @param callContext The CallContext object.
+     * @throws IOException if an error occured while writing to output stream.
+     * @throws TechnicalChartException if an error occured while chart creation.
+     */
+    protected void createChart(
+        OutputStream outputStream,
+        Collection   parameters,
+        Collection   measurements,
+        Collection   dates,
+        ChartLabels  chartLables,
+        CallContext  context,
+        String       uuid,
+        String       exportFormat,
+        Locale       locale,
+        int          width,
+        int          height,
+        boolean      linesVisible,
+        boolean      shapesVisible,
+        CallContext  callContext
+    )
+    throws IOException, TechnicalChartException
+    {
+        log.debug("Create chart.");
+        Chart chart = getChart(
+            chartLables,
+            createStyle(context),
+            parameters,
+            measurements,
+            dates,
+            getChartResult(uuid, callContext),
+            locale, // Locale
+            uuid,
+            linesVisible,
+            shapesVisible,
+            callContext
+        );
+
+        if (chart == null) {
+            log.error("Could not initialize chart.");
+            return;
+        }
+
+        log.debug(
+            "export chart as " + exportFormat +
+            " in " + width + "x" + height
+        );
+
+        ChartExportHelper.exportImage(
+            outputStream,
+            chart.generateChart(),
+            exportFormat,
+            width,
+            height
+        );
+    }
+
+
+    /**
+     * Creates a chart and writes it as pdf to <i>outputStream</i>.
+     *
+     * @param outputStream The stream used to write the chart to.
+     * @param parameters A collection with parameters.
+     * @param measurements A collection with measurements.
+     * @param dates A collection with dates.
+     * @param chartLables The labels used to decorate the chart.
+     * @param uuid The UUID of the current artifact.
+     * @param exportFormat The format the chart used to be exported as.
+     * @param landscape A boolean property to determine the alignment of the
+     * chart.
+     * @param linesVisible A boolean property to determine the visibility of
+     * lines connecting two datapoins.
+     * @param shapesVisible A boolean property to determine the visibility of
+     * datapoints.
+     * @param locale The Locale to specify the language.
+     * @param context The CallContext object.
+     */
+    protected void createPDF(
+        OutputStream outputStream,
+        Collection   parameters,
+        Collection   measurements,
+        Collection   dates,
+        ChartLabels  chartLables,
+        String       uuid,
+        String       exportFormat,
+        CallContext  context
+    ) {
+        Chart chart = getChart(
+            chartLables,
+            createStyle(context),
+            parameters,
+            measurements,
+            dates,
+            getChartResult(uuid, context),
+            (Locale) context.getContextValue("locale"),
+            uuid,
+            Boolean.TRUE.equals(context.getContextValue("lines.visible")),
+            Boolean.TRUE.equals(context.getContextValue("shapes.visible")),
+            context
+        );
+
+        if (chart == null) {
+            log.error("Could not initialize chart.");
+            return;
+        }
+
+        ChartExportHelper.exportPDF(
+            outputStream,
+            chart.generateChart(),
+            "A4",
+            50F, 50F, 50F, 50F,
+            context
+        );
+    }
+
+
+    /**
+     * Creates a chart and writes it as svg to <i>outputStream</i>.
+     *
+     * @param outputStream The stream used to write the chart to.
+     * @param parameters A collection with parameters.
+     * @param measurements A collection with measurements.
+     * @param dates A collection with dates.
+     * @param chartLables The labels used to decorate the chart.
+     * @param uuid The UUID of the current artifact.
+     * @param locale The Locale to specify the language.
+     * @param width The width of the chart.
+     * @param height The height of the chart.
+     * @param linesVisible A boolean property to determine the visibility of
+     * lines connecting two datapoins.
+     * @param shapesVisible A boolean property to determine the visibility of
+     * datapoints.
+     * @param callContext The CallContext object.
+     */
+    protected void createSVG(
+        OutputStream outputStream,
+        Collection   parameters,
+        Collection   measurements,
+        Collection   dates,
+        ChartLabels  chartLables,
+        String       uuid,
+        Locale       locale,
+        int          width,
+        int          height,
+        boolean      linesVisible,
+        boolean      shapesVisible,
+        CallContext  callContext
+    ) {
+        Chart chart = getChart(
+            chartLables,
+            createStyle(callContext),
+            parameters,
+            measurements,
+            dates,
+            getChartResult(uuid, callContext),
+            locale,
+            uuid,
+            linesVisible,
+            shapesVisible,
+            callContext
+        );
+
+        if (chart == null) {
+            log.error("Could not initialize chart.");
+            return;
+        }
+
+        ChartExportHelper.exportSVG(
+            outputStream,
+            chart.generateChart(),
+            null,
+            width, height
+        );
+
+        log.debug("svg export finished.");
+    }
+
+
+    /**
+     * This method creates a chart and returns it. In normal case, this is the
+     * only method to be overriden by subclasses to create other types of
+     * charts.
+     *
+     * @param chartLables Labels used to decorate the chart.
+     * @param theme The theme used to adjust the look of the chart.
+     * @param parameters A collection with parameters this chart contains.
+     * @param measurements A collection with measurement this chart contains.
+     * @param dates A collection with dates this chart contains.
+     * @param result The data collection used to be displayed in this chart.
+     * @param locale The Locale used to determine the language.
+     * @param uuid The uuid of the current artifact.
+     * @param linesVisible A boolean property to determine the visibility of
+     * lines connecting two points in a chart (not used in this chart type).
+     * @param shapesVisible A boolean property to determine the visiblity of
+     * datapoints in this chart (not used in this chart type).
+     * @param callContext The CallContext object.
+     * @return a timeseries chart.
+     */
+    protected Chart getChart(
+        ChartLabels  chartLables,
+        ChartTheme   theme,
+        Collection   parameters,
+        Collection   measurements,
+        Collection   dates,
+        Object       result,
+        Locale       locale,
+        String       uuid,
+        boolean      linesVisible,
+        boolean      shapesVisible,
+        CallContext  callContext
+    ) {
+        Chart chart = null;
+
+        if (CACHE_CHART) {
+            log.info("Try to get timeseries chart from cache.");
+            chart = (Chart) getChartFromCache(uuid, callContext);
+        }
+
+        if (chart != null)
+            return chart;
+
+        log.info("Chart not in cache yet.");
+        chart = new TimeSeriesChart(
+            chartLables,
+            theme,
+            parameters,
+            measurements,
+            dates,
+            (Collection)result,
+            timeGapDefinitions,
+            locale,
+            linesVisible,
+            shapesVisible
+        );
+        chart.generateChart();
+
+        if (CACHE_CHART) {
+            log.info("Put chart into cache.");
+            purifyChart(chart, uuid);
+        }
+
+        return chart;
+    }
+
+
+    /**
+     * Fetches the ChartTheme from <i>callContext</i> and returns it.
+     *
+     * @param callContext CallContext objects storing a chart theme.
+     * @return a chart theme.
+     */
+    protected ChartTheme createStyle(CallContext callContext) {
+        log.debug("Fetch chart theme from global context");
+
+        GNVArtifactContext context =
+            (GNVArtifactContext) callContext.globalContext();
+
+        XMLChartTheme theme = (XMLChartTheme) context.get(
+            GNVArtifactContext.CHART_TEMPLATE_KEY);
+
+        return theme;
+    }
+
+    /**
+     * Creates a ChartLabels object storing different labels used to decorate a
+     * chart.
+     *
+     * @param locale The Locale object to adjust the language of labels.
+     * @param uuid The UUID of the current artifact.
+     * @return the chart labels.
+     */
+    protected ChartLabels createChartLabels(Locale locale, String uuid) {
+        return new ChartLabels(
+            createChartTitle(locale, uuid),
+            createChartSubtitle(locale, uuid),
+            RessourceFactory.getInstance().getRessource(
+                locale,
+                domainLable,
+                domainLable
+            )
+        );
+    }
+
+
+    /**
+     * Creates and returns the chart title.
+     *
+     * @param locale The Locale used to adjust the language of the title.
+     * @param uuid The UUID of the current artifact.
+     * @return the name of the selected fis.
+     */
+    protected String createChartTitle(Locale locale, String uuid) {
+        return getFisName(locale);
+
+    }
+
+
+    /**
+     * Creates and returns the subtitle of a chart. The subtitle in this class
+     * contains the station and the time interval.
+     *
+     * @param locale The Locale used to adjust the language of the subtitle.
+     * @param uuid The UUID of the current artifact.
+     * @return the selected feature.
+     */
+    protected String createChartSubtitle(Locale locale, String uuid) {
+        String subtitle = "";
+        subtitle       += getSelectedFeatureName(uuid);
+
+        InputData data = inputData.get(timeIntervalValueName);
+        if (data != null){
+            Object describeData = data.getObject();
+            if (describeData instanceof MinMaxDescribeData) {
+                MinMaxDescribeData tmp = (MinMaxDescribeData) describeData;
+
+                DateFormat format = DateFormat.getDateTimeInstance(
+                    DateFormat.MEDIUM, DateFormat.SHORT, locale);
+
+                String lDateStr = (String) tmp.getMinValue();
+                String rDateStr = (String) tmp.getMaxValue();
+
+                try {
+                    Date lDate = srcFormat.parse(lDateStr);
+                    Date rDate = srcFormat.parse(rDateStr);
+
+                    String interval = format.format(lDate);
+                    interval       += " - ";
+                    interval       += format.format(rDate);
+
+                    if (subtitle != null && subtitle.length() > 0)
+                        subtitle += ", ";
+
+                    subtitle += interval;
+                }
+                catch (ParseException pe) {
+                    log.error(pe, pe);
+                }
+            }
+        }
+
+        return subtitle;
+    }
+
+
+    /**
+     * Returns the selected fis name.
+     *
+     * @param locale The Locale object used to adjust the language.
+     * @return the name of the fis.
+     */
+    protected String getFisName(Locale locale) {
+        String    returnValue = "";
+        InputData input       = inputData.get("fisname");
+
+        if (input != null) {
+            String value = input.getValue();
+
+            returnValue = RessourceFactory.getInstance().getRessource(
+                locale,
+                value,
+                value
+            );
+        }
+        return returnValue;
+    }
+
+
+    /**
+     * Returns the name of the selected object in the collection specified by
+     * <i>key</i>.
+     *
+     * @param uuid The UUID of the current artifat.
+     * @param key A key to specify a collection.
+     * @return the name of the selected item.
+     */
+    protected String getSelectedInputDataName(String uuid, String key) {
+        Collection values = getCollection(key, uuid);
+
+        if (values != null) {
+            Iterator it = values.iterator();
+
+            while (it.hasNext()) {
+                KeyValueDescibeData data = (KeyValueDescibeData) it.next();
+
+                if (data.isSelected()) {
+                    return data.getValue();
+                }
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Returns a collection of selected parameters.
+     *
+     * @param uuid The UUID of the current artifact.
+     * @return selected parameters.
+     */
+    protected Collection<KeyValueDescibeData> getParameters(String uuid) {
+        return this.getCollection(parameterValuesName, uuid);
+    }
+
+    /**
+     * Returns a collection of selected measurements.
+     *
+     * @param uuid The UUID of the current artifact.
+     * @return selected measurements.
+     */
+    protected Collection<KeyValueDescibeData> getMeasurements(String uuid) {
+        return this.getCollection(measuremenValueName, uuid);
+    }
+
+    /**
+     * Returns a collection of selected dates.
+     *
+     * @param uuid The UUID of the current artifact.
+     * @return selected dates.
+     */
+    protected Collection<KeyValueDescibeData> getDates(String uuid) {
+        return this.getCollection(dateValueName,uuid);
+    }
+
+    @Override
+    public void setup(Node configuration) {
+        super.setup(configuration);
+        String featureNameValue = Config.getStringXPath(configuration,
+                "value-names/value-name[@name='feature']/@value");
+        if (featureNameValue != null) {
+            this.featureValuesName = featureNameValue;
+        }
+        String parameterNameValue = Config.getStringXPath(configuration,
+                "value-names/value-name[@name='parameter']/@value");
+        if (parameterNameValue != null) {
+            this.parameterValuesName = parameterNameValue;
+        }
+        String measurementNameValue = Config.getStringXPath(configuration,
+                "value-names/value-name[@name='measurement']/@value");
+        if (measurementNameValue != null) {
+            this.measuremenValueName = measurementNameValue;
+        }
+        String timeIntervalValue = Config.getStringXPath(configuration,
+                "value-names/value-name[@name='timeinterval']/@value");
+        if (timeIntervalValue != null){
+            this.timeIntervalValueName = timeIntervalValue;
+        }
+
+        String dateNameValue = Config.getStringXPath(configuration,
+        "value-names/value-name[@name='date']/@value");
+        if (dateNameValue != null) {
+            this.dateValueName = dateNameValue;
+        }
+        if (timeGapDefinitions == null){
+            Element gapDefinition =  (Element)Config.getNodeXPath(configuration,
+                                                                 "time-gap-definition");
+            synchronized (this.getClass()) {
+                if (gapDefinition != null){
+                    String link = gapDefinition.getAttribute("xlink:href");
+                    if (link != null ){
+                        String absolutFileName = Config.replaceConfigDir(link);
+                        gapDefinition = (Element)new ArtifactXMLUtilities().
+                                                     readConfiguration(absolutFileName);
+                    }
+
+                    NodeList gapDefinitions = Config.getNodeSetXPath(gapDefinition,
+                                                                    "/time-gaps/time-gap");
+                    if (gapDefinition != null){
+                        timeGapDefinitions = new ArrayList<TimeGap>(gapDefinitions.
+                                                                    getLength());
+                        for (int i = 0; i < gapDefinitions.getLength(); i++){
+                            Element gapNode = (Element)gapDefinitions.item(i);
+                            String unit = gapNode.getAttribute("unit");
+                            int key = Integer.parseInt(gapNode.getAttribute("key"));
+                            int value = Integer.parseInt(gapNode.getAttribute("gap"));
+                            log.info("Add new Timegap: "+key+" "+value+" "+ unit);
+                            timeGapDefinitions.add(new DefaultTimeGap(unit,
+                                                                      key,
+                                                                      value));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Creates a <code>NamedArrayList</code> storing some <code>
+     * KeyValueDescibeData</code> objects found in the input data with the key
+     * <i>collectionName</i>.
+     *
+     * @param collectionName String to specify the input data.
+     * @param uuid The UUID of the current artifact.
+     * @return a collection with values from input data.
+     */
+    protected Collection<KeyValueDescibeData> getCollection(
+        String collectionName,
+        String uuid)
+    {
+        log.debug("Search for input data: " + collectionName);
+        NamedCollection<KeyValueDescibeData> c = new NamedArrayList<KeyValueDescibeData>(collectionName);
+
+        InputData data = null;
+        State parent   = this;
+        do {
+            data = parent.inputData().get(collectionName);
+
+            if (data != null) {
+                break;
+            }
+        }
+        while ((parent = parent.getParent()) != null);
+
+        if (data == null) {
+            log.warn("No collection found with name: " + collectionName);
+            return c;
+        }
+
+        String[] descs  = data.getDescription();
+        String[] values = data.splitValue();
+        int        size = values.length;
+
+        for (int i = 0; i < size; i++){
+            c.add(new DefaultKeyValueDescribeData(
+                values[i], descs[i], getID()));
+        }
+
+        return c;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :

http://dive4elements.wald.intevation.org