view gnv-artifacts/src/main/java/de/intevation/gnv/state/timeseries/TimeSeriesOutputState.java @ 1033:35c442410738

Added a comment after implementing cache cleaner gnv-artifacts/trunk@1073 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Hans Plum <hans.plum@intevation.de>
date Fri, 07 May 2010 06:50:47 +0000
parents 4bcf7bf4bfb7
children 70653c29fc1d
line wrap: on
line source
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.artifactdatabase.Config;
import de.intevation.artifactdatabase.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"));

    /**
     * A field parsing a system property to adjust the alignment of pdf exports.
     * The system property is 'export.pdf.landscape' and should be true or
     * false. If this property is true, PDFs will be created in landscape
     * format.
     */
    protected static final boolean PDF_FORMAT_LANDSCAPE =
        Boolean.parseBoolean(System.getProperty("export.pdf.landscape","true"));

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

    private 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")) {
                    createPDF(
                        outputStream,
                        parameters,
                        measurements,
                        dates,
                        chartLables,
                        uuid,
                        "A4",
                        true,
                        lVisible,
                        sVisible,
                        locale,
                        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")) {
                Collection results = (Collection) getChartResult(uuid, callContext);
                requestParameter.put("locale", locale);

                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 = createHistogramLabels(
                        uuid, callContext, locale, data[i]);

                    ChartTheme theme   = createStyle(callContext);

                    histograms[i] = new DefaultHistogram(
                        labels, data[i], theme, requestParameter);
                }

                if (mode.equalsIgnoreCase("img")) {
                    ChartExportHelper.exportHistograms(
                        outputStream,
                        histograms,
                        exportFormat,
                        chartWidth,
                        chartHeight
                    );
                }
                else if (mode.equalsIgnoreCase("pdf")) {
                    ChartExportHelper.exportHistogramsAsPDF(
                        outputStream,
                        histograms,
                        "A4",
                        PDF_FORMAT_LANDSCAPE,
                        50F, 50F, 50F, 50F
                    );
                }
                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);
        }
    }


    /**
     * @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,
        boolean      landscape,
        boolean      linesVisible,
        boolean      shapesVisible,
        Locale       locale,
        CallContext  context
    ) {
        Chart chart = getChart(
            chartLables,
            createStyle(context),
            parameters,
            measurements,
            dates,
            getChartResult(uuid, context),
            locale,
            uuid,
            linesVisible,
            shapesVisible,
            context
        );

        if (chart == null) {
            log.error("Could not initialize chart.");
            return;
        }

        ChartExportHelper.exportPDF(
            outputStream,
            chart.generateChart(),
            "A4",
            PDF_FORMAT_LANDSCAPE,
            50F, 50F, 50F, 50F
        );
    }


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

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


    /**
     * Creates and returns labels to decorate histograms.
     *
     * @param uuid The UUID of the current artifact.
     * @param context The CallContext object.
     * @param data An array storing strings.
     * @return A ChartLabels object with the 1st string in <i>data</i> as title.
     */
    protected ChartLabels createHistogramLabels(
        String uuid, CallContext context, Locale locale, Object[] data)
    {
        RessourceFactory fac = RessourceFactory.getInstance();

        return new ChartLabels(
            (String) data[0],
            "",
            "",
            fac.getRessource(locale, "histogram.axis.range.title", ""));
    }


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