changeset 430:7ab81ff32111 2.3

merged flys-artifacts/2.3
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:10 +0200
parents a6f56ed9238b (current diff) b8377caf4515 (diff)
children 29a15392e229
files
diffstat 86 files changed, 14399 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/ChangeLog	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,1734 @@
+2011-05-17  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 0.1
+
+2011-05-16  Hans Plum <hans@intevation.de>
+
+	* NEWS:
+	New. Giving some user specific perspective to new functionality and
+	changes. This file references releases dates only; details can be find
+	in the client module at flys-client.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	First step to calculate "W fuer ungleichwertige Abfluesse" correctly.
+	flys/issue55
+
+	* src/main/java/de/intevation/flys/artifacts/math/LinearRemap.java:
+	  New. Remaps "gleichwertige" Q values to the corresponding
+	  "ungleichwertige" Q values depending on km.
+	  
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Remap the Q values "ungleichwertig" depending on the 
+	  "gleichwertige" ones.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	First step to fix flys/issue69
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  New code path to implement the calculation of "Wasserstand/Wasspiegellage"
+	  correctly. TODO 1: Use new path in UI. TODO 2: Remove unused old code.
+
+2011-05-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* Changes: Prepared Changes for the upcoming release 2.3 - see Changes
+	  file to get to know about the changes of the version numbers.
+
+2011-05-13  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-37
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java: This
+	  state would be happy if there is a UI provider called "river_panel".
+
+2011-05-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Added new
+	  methods that return the requested chart size as integer array [width,
+	  height]. The requested size is read from the incomding request document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: The size
+	  of a chart is no longer static. The requested size is fetched using
+	  ChartGenerator.getSize().
+
+2011-05-11  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-52
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  The X-Axis of such a chart is inverted, if the head of the river is not
+	  at kilometer 0. This type of charts always have the head of the river at
+	  the left side.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Make incoming XML symmetric to DistanceInfoService.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-47
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Adjusted temporarily the color of the W, Q and corrected W curves to
+	  distinguish each other.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Adjusted the plot of xy charts - the gridlines are displayed now.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-53
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  Added a method that returns the preferred locale based on the available
+	  locales of the server and the desired locales of the request (CallMeta).
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java: Added a
+	  method that creates a number formatter with minimum and maximum digits.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Formatted the number values of the CSV exports.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RangeWithValues.java:
+	  New. A data structure that enables us to save a data triple: a range
+	  that consist of lower and upper double value and a set of values that
+	  belong to this range.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Implemented the validation of W/Q values.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java: New
+	  method to retrieve i18n messages based on keys.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Added headers for CSV exports.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added new i18n strings for
+	  CSV headers.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Fetches river name from incoming XML document. If no river is given all 
+	  infos about all rivers are listed.
+
+	* src/main/resources/metadata/template.xml: Templates honors the 'river'
+	  parameter.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Extended to pass parameters to the templating. Added support for
+	  type conversion.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/StackFrames.java:
+	  Take parameters as an initial stack frame.
+	  
+	* src/main/java/de/intevation/flys/artifacts/services/meta/TypeConverter.java:
+	  New. Converts types off stacked variables.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Added a
+	  method to add subtitles to charts. The implementation in this class does
+	  not add any subtitle. Concrete subclasses may override this method to
+	  add some.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Add subtitles to charts.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n templates for
+	  compound messages (chart subtitles).
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  Added new methods to retrieve translated compound messages.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: Added a
+	  method to set the master artifact that should be used for some special
+	  operations.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Implement the setMasterArtifact() method of the interface.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Call OutGenerator.setMasterArtifact().
+
+	  NOTE: The determination of the master artifact needs to be implemented!
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Removed
+	  hard coded dev code that defined a WQ mode.
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Stripped ugly extra whitespace from output introduced by
+	  templating.
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/resources/metadata/template.xml: Added forgotten
+	  columns of fixation WSTs.
+
+2011-05-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Added a
+	  method to retrieve i18n strings.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Enabled i18n support for chart title and axes labels.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  chart types above.
+
+2011-05-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java:
+	  New. This OutGenerator exports the data of a discharge longitudinal
+	  section computation.
+
+	* doc/conf/conf.xml: Added the DischargeLongitudinalSectionExporter.
+
+	* doc/conf/artifacts/winfo.xml: Added the exporter with CSV facet to the
+	  discharge_longitudinal_section state.
+
+2011-05-07	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Added code to make back jump correction work with both
+	  potential flow directions.
+
+2011-05-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/resources/metadata/template.xml:
+	  Added data cage configuration for 'Längsschnitt'.
+
+2011-05-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added the missing Q facet for discharge
+	  longitudinal sections.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQCKms.java: New. A
+	  derived dataset to store W/Q values with corrected Ws for a kilometer
+	  range.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: Some new
+	  methods and a new constructor to initialize this data object with a
+	  predefined set of values.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: New
+	  methods to retrieve the W/Q values for the 'discharge longitudinal
+	  section' computation.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: New
+	  methods to retrieve and compute data used for the 'discharge
+	  longitudinal section' computation.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Bugfix: just write the ranges of gauges into the DESCRIBE if the
+	  'wq_values' data item is required.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n messages used in
+	  the DESCRIBE of the WQAdapted state.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Changed the title of the y-axis (now 'W [NN+m]').
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Improved the transition model to reach the
+	  output state for creating 'discharge longitudinal section' charts.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method that returns all gauges of the selected river based on a the
+	  given kilometer range.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java: New.
+	  This state creates a set of elements for the DESCRIBE that consist of a
+	  tuple of kilometer values. The number of elements depend on the number
+	  of gauges intersected by the given kilometer range.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java:
+	  New. This state is the output state that is reached after the 'discharge
+	  longitudinal section' computation has been chosen.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Default
+	  step width between two kilometers added - if no step width is given,
+	  this default width is used.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Fixed a potential NullPointerException if there is just a single
+	  kilometer given to create a longitudinal section.
+
+2011-05-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added http://repository.jboss.org/maven2 repo
+	  to fix flys/issue30
+
+2011-05-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  To make diagram generation possible ws are now generated from qs
+	  because they are many ws having different qs.
+
+2011-05-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Fetch the WQ data from WINFO artifact and write those values into the
+	  CSV export.
+
+2011-05-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  methods to compute and retrieve the data for discharge curves (computed).
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Fetch the computed discharge curve data from WINFOArtifact and add the
+	  values into the JFreeChart dataset.
+
+2011-05-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added new transitions and states to enable
+	  the WINFO artifact for computing discharge curves.
+
+	* doc/conf/conf.xml: Added OutGenerators that generate computed discharge
+	  curves and exports for its data.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java:
+	  New. This state is reached if the user chose the computed discharge
+	  curve.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  New. This is only a stub implementation at the moment. This
+	  OutGenerator should create computed discharge curves later. It extends
+	  the DischargeCurveGenerator which should do the same stuff for discharge
+	  curves for gauges.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  New. This is only a stub implementation at the moment. This OutGenerator
+	  should create the exports of the discharge curve computation.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added a new output mode for the duration
+	  curve state (CSV export).
+
+	* doc/conf/conf.xml: Added a new OutGenerator to export duration curve
+	  computations.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java: New.
+	  This abstract OutGenerator represents the base class for exporting
+	  computed data. Currently, the CSV export is supported.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Moved
+	  the most code to export to CSV into the AbstractExporter.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveExporter.java:
+	  New. This exporter exports the computed data of a duration computation.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Modified a wrong debug statement which would confuse the user.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added a new output mode for the waterlevel
+	  state (CSV export).
+
+	* doc/conf/conf.xml: Added a new OutGenerator to export waterlevels.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: New.
+	  This OutGenerator exports the data of a waterlevel computation. Note:
+	  It is necessary to specify the desired facet (e.g.
+	  'waterlevel_export.csv').
+
+	* pom.xml: Added a dependency to OpenCSV.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Introduced an "export" output target. If the target is "export", a
+	  facet (read from the incoming xml document) is a necessary parameter
+	  that determines which facets are written to the output.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_en.properties: Bugfix: replaced german
+	  string (copy & paste mistake).
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added new i18n strings for
+	  for the location selection.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  New. An OutGenerator for creating duration curves.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQDay.java: New. A
+	  model class to store necessary data for creating W and Q facets of a
+	  duration curve. This model stores W, Q and Days.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesFactory.java:
+	  Added a function to retrieve tuples of (day, q) based on a given gauge -
+	  these tuples are necessary for creating duration curves.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  methods to compute and retrieve the data necessary for creating duration
+	  curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  Bugfix: improved the access to the location array (avoid
+	  NullPointerException).
+
+	* doc/conf/conf.xml: Registered the new OutGenerator for duration curves.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/visualize-transitions.xsl: State quoting was done wrong.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Enhanced the transition model to reach the
+	  final state for creating duration curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  New. This state should be reached to just insert an array of locations.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java:
+	  New. This state is reached if the duration curve calculation is
+	  selected.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Adjusted
+	  getDistance() so that it takes care on inserted locations - not just
+	  inserted ranges.
+
+2011-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/cache/CacheFactory.java:
+	  Flush/persist caches at program exists.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Commented out too eloquent debug output.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Uses the methods of FLYSArtifact to retrieve the necessary information
+	  rivername and selected distance.
+
+2011-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Added interpolateW method to take reference to result ws array
+	  as an argument to avoid expensive array allocations in km iterating
+	  loops.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java:
+	  Added a constructor to create backing trove datastructure
+	  with the right capacity.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Move allocation of result ws out of km loop.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added two further data items in the WQ
+	  selection state. Those items are necessary to store the information
+	  about the selected mode (range/single selection) and  the values of the
+	  single selection.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java: If
+	  there is no value for a data item, this item is not written into the
+	  static DESCRIBE part.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RangeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Renamed the method to validate upper and lower values.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: This
+	  state can now handle values inserted in the single selection. Therefore,
+	  new validate methods has been added.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: The
+	  methods getWs() and getWs() take care on the values inserted in the
+	  single insert mode of the client which enables the user to insert single
+	  W and Q.
+
+2011-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Repaired getQForW() by calculating indices on right dimension.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: New
+	  methods for retrieving selected W values (getWs()).
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The
+	  method for retrieving waterlevel data takes care on selected Ws, now.
+	  The selected Ws are transformed using the DischargeTables.getQForW()
+	  into Q values.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Removed an unused parameter 'result' of getQForW().
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Removed
+	  the getDataset() method and replaced it with a addDataset() method.
+	  On this way, concrete subclasses of this OutGenerator can have multiple
+	  datasets (e.g. different datasets for W and Q). This abstract method is
+	  called after the chart generation is finished.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Both classes implement the addDataset() method. The
+	  LongitudinalSectionGenerator has already multiple datasets for W and Q.
+	  Both are added to the chart - both have an own range axis.
+
+2011-05-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Use the cache for the wst value table if configured.
+
+	* doc/conf/cache.xml: Choose a more precise name for the 
+	  wst value table cache.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency to Ehcache. Apache 2.0 license.
+
+	* doc/conf/conf.xml: Added configuration of ehcache.
+
+	* doc/conf/cache.xml: New. Cache configurations.
+
+	* src/main/java/de/intevation/flys/artifacts/cache/CacheFactory.java:
+	  New. Factory to access caches.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Forgot to add.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  New. Given a database connection and a XML template it generates
+	  an output with meta data about the database.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/CompiledStatement.java:
+	  New. Holds prepared statements optimized to be run in the stack of
+	  contextes.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/StackFrames.java:
+	  New. Model to hold a hierarchical scope of variables.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/ResultData.java:
+	  New. Stores data set fetched from a sql select to be iterated in
+	  a context.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed superfluous imports.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Added meta data service.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Stub for the meta data service.
+
+	* src/main/resources/metadata/template.xml: Initial template for
+	  the meta data service.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Bugfix: Now, curves for Q values are drawn into a longitudinal section
+	  chart as well. Therefore, it was necessary to change the datastructure
+	  of the inner class ThemeList that stores all themes included in a chart
+	  in an ordered list (stored in a java.util.Vector now).
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: New. An
+	  abstract base class for ChartGenerators that create XY charts.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Sourced the generate() method out to the XYChartGenerator.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Implemented the methods to add W and Q facets to the chart.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Improved
+	  the calculation of the step with for ranges.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: Added a
+	  method that returns the number of elements stored in the data pool.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java:
+	  Added a get() method which takes destination array as an
+	  argument.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: Changed the
+	  data structure to store w, q and kms values from List<Double> to
+	  TDoubleArrayList which stores native double values instead of big
+	  Double values.
+
+	* pom.xml: Added the GNU Trove dependency.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: Added the
+	  name a the requested facet to doOut(). Concrete generators should just
+	  create output for this facet now.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Adapted the method signature of doOut().
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapted the method call of OutGenerator.doOut().
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Added mappings for the facets
+	  longitudinal.section.w and longitudinal.section.q.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added some
+	  methods to retrieve necessary information for computing the data of a
+	  waterlevel.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  methods to compute and retrieve the data of a waterlevel computation.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: New. This
+	  model class represents a pool of data triples that consist of W, Q and
+	  Kms information. This class might be used to compute data for creating
+	  longitudinal section curves (which are based on those W, Q and Kms
+	  values).
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Bugfix: Removed endless loop and a bug while iterating over Hibernate
+	  results.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  New. An OutGenerator that creates longitudinal section curves.
+
+	  NOTE: This is just the stub - the out creation needs to be implemented!
+
+	* doc/conf/conf.xml: Added the LongitudinalSectionGenerator.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Bugfix: Added missing <output-generators> section.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WaterlevelState.java: New.
+	  This state should be reached if the 'calc.surface.curve' calculation
+	  method has been chosen.
+
+	* doc/conf/artifacts/winfo.xml: Modified a transition and added the
+	  WaterlevelState. This state is reached if the 'calc.surface.curve'
+	  calculation method has been chosen. It currently has 1 output - a
+	  longitudinal section that is not implemented yet!
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/ValueCompareTransition.java:
+	  New. This transition is valid if the a data object of the current
+	  artifact equals/notequals a configured value in the transition model.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Bugfixes:
+	  - Adapted the parameters of the isStateReachable() call - added the
+	    artifact and the current state.
+	  - Append the outputs of a current state if the state is filled with
+	    valid data.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java:
+	  Added the missing init() method that has been introduced in the
+	  interface some commits ealier.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java:
+	  Call init() after a Transition has been created.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  Bugfix: Introduced a <art:outputs> node in the attribute document of a
+	  Collection that contains further <art:output> nodes - instead of having
+	  multiple <art:output> nodes at toplevel of the document.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapted the XPath to retrieve the outputs in the attribute document of a
+	  Collection.
+
+2011-04-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/visualize-transitions.xsl: Added to create a 
+	  Graphviz digraph out of the config.xml. Usage:
+
+	  $ xsltproc --stringparam base-dir ../doc/conf/ \
+	    contrib/visualize-transitions.xsl \
+	    doc/conf/conf.xml > transitions.dot
+
+	  $ dot -Tsvg -o transitions.svg transitions.dot
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  If no data has been inserted so far, an IllegalArgumentException is
+	  thrown.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java::
+	  A lot of new methods to retrieve the theme of a facet - used while
+	  creating the output of a facet/artifact. If a facet has no theme yet, it
+	  is initialized.
+
+2011-04-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java,
+	  src/main/java/de/intevation/flys/collections/OutputParser.java: Removed
+	  useless imports.
+
+2011-04-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: New. An
+	  abstract OutGenerator that might be used to create chart output. Some
+	  basic things that are equal in all charts should be done here!
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  This OutGenerator no longer implements the OutGenerator directly, but it
+	  extends the ChartGenerator now.
+
+2011-04-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Restructured the out() operation of a collection: Now, we collect a list
+	  of artifacts/facets and use this for the OutGenerator. Its doOut()
+	  method gets the attribute of an artifact - the position and the active
+	  state is managed by the Collection itself.
+
+2011-04-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java: New.
+	  a specialized facet that stores information about its position and its
+	  state (active/inactive) in an output of a collection.
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java: New.
+	  This parser takes the attributes (XML) of a collection and extracts the
+	  contained outputs with its facets. The result is a Map<String, Output>.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java: New.
+	  This parser is used to query the artifact's DESCRIBE and to extract the
+	  supported outputs. The result is a Map<String, Output>.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: New.
+	  This writer merges the outputs contained in an attribute of a collection
+	  with the outputs of a collection's artifacts.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  The attributes of a collection are written into its DESCRIBE document
+	  now. The OutputParser and AttributeParser are used to read the supported
+	  attributes by the collection and its artifacts - the AttributeWriter is
+	  used to merge both attributes and create a final attribute document.
+
+2011-04-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java,
+	  src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  The facet-2-theme mappings are initialized at startup and stored in the
+	  FLYSContext.
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java: Added a
+	  function that retrieves a theme from FLYSContext based on its name.
+
+2011-04-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Corrected a silly c&p mistake.
+
+2011-04-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+
+	  Implementation of the "Ruecksprungkorrektur" to be done in
+	  "W fuer angepassten Abflusslaengschnitt".
+
+	  All tests show the expected results. In some corner cases the
+	  algorithm described in the "Anwenderhandbuch" chapter 3.3.4.3 "Korrektur"
+	  has some definition shortcomings:
+
+	  a - What should happend when you cannot find point 2 because
+	      you cannot step back one quarter from point 3 because there
+	      is no data there any more (river too short in this direction)?
+	      The implemented algorithm raises point 3' only to an
+	      according factor. E.g. If you can step back the whole quarter
+	      distance the elevation is the full quarter. If you can
+	      step back only the half of the quarter the elevation is
+	      only an eighth.
+
+	  b - If the water heights between point 2 and 3 are constant then
+	      the algorithm will produce a spline interpolation that
+	      lowers those values. Is this intended?
+
+	  For real data the back jumps are expected to be more in the middle
+	  of the distance ranges so the corner cases are maybe not so
+	  important.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Removed superfluous import.
+
+2011-04-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java: Removed debug
+	  code that has been commited by accident.
+
+2011-04-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: New. A first small theme configuration.
+
+	* doc/conf/conf.xml: Added a link to the theme configuration.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java:
+	  Defined a key that is used to store a themes map in the FLYSContext.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  The theme configuration is read at startup and the themes are stores in
+	  the FLYSContext.
+
+2011-04-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/themes/Theme.java,
+	  src/main/java/de/intevation/flys/themes/DefaultTheme.java:
+	  New. The interface and its default implementation that represents themes
+	  used to style charts and maps.
+
+	* src/main/java/de/intevation/flys/themes/ThemeField.java,
+	  src/main/java/de/intevation/flys/themes/DefaultThemeField.java:
+	  New. The interface and its default implementation that represents fields
+	  in themes. A theme might be "Lines" and one of its field might be
+	  "Color" or "Size".
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java: A factory
+	  that creates new themes based on a theme configuration.
+
+2011-04-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Implementation of "Abflusskurve/Abflusstafel" calculation.
+
+	  Added method interpolateWQ() which takes an km and results in a
+	  tuple of two double arrays containing the w/q values interpolated
+	  between the surrounding w/q values of the table.
+	  w values are interpolated linear, q values with a cubic spline.
+
+	  Drawing w over q gives you the discharge table at the given km.
+
+	  !!! This code needs testing !!!
+
+2011-04-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency to Apache Commons Math 2.2 (Apache License 2.0)
+
+2011-04-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Fix problem when sorting by q (copied w instead of q).
+
+2011-04-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Fixed broken HQL statement.
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  add a method interpolateW() which takes an array of
+	  q values and returns an equal sized array of w values.
+	  This is essentially the "Wasserstand/Wasserspiegellagen" calculation
+	  of desktop FLYS.
+
+	  If you want to do a calculation with given w values you have
+	  to convert the w values with DischargeTables.getQForW() first.
+
+	  !!! This code needs heavy testing !!!
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  We need a getQForW() method and not getWForQ() because when
+	  doing a "Wasserstand/Wasserspiegellagen" calculation with given
+	  w values these values need to be translated to q values with
+	  the master discharge table.
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Sorting of q values was done wrong.
+
+2011-04-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  Ordered the list of annotations returned by this factory based on its
+	  range.
+
+2011-04-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java:
+	  New. This service returns an XML document that includes the main values
+	  of a gauge based on a river name, a start and an end point.
+
+	* doc/conf/conf.xml: Registered the MainValuesService.
+
+2011-04-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesFactory.java:
+	  A factory that provides methods to return MainValues.
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Added static method getWForQ() to interpolate a w value for
+	  a given q value based on a given discharge table.
+	
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Added convenience constructors/methods to ease the access to the master
+	  discharge table of a gauge.
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Moved the query complexity into view 'wst_value_table' and
+	  used this instead.
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Fetches w/q value tables from the backend. TODO: Move this
+	  to the backend and use a view.
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Changed
+	  the error key that is thrown if no input data was found so that the key
+	  is usable for GWT's i18n mechanism.
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  The inserted river is validated now (overrides validate() of
+	  DefaultState).
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  The inserted calculation method is validated now (overrides validate()
+	  of DefaultState).
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  The input data of feed() are validated using the DefaultStates
+	  validate() method.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  New method validate() that needs to be overidden by concrete subclasses.
+	  It should return true, if the data of the State is fine, otherwise it
+	  should raise an exception.
+
+	  NOTE: The exceptions are not translated in the server but in the client!
+
+	* src/main/java/de/intevation/flys/artifacts/states/RangeState.java: New.
+	  This abstract class exists to provide some methods for handling ranges.
+	  Currently, there is a method that validates a given range based on
+	  min/max values.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Implemented input data validation for ranges.
+
+	  NOTE: The input validation of concrete values has not been implemented
+	  yet!
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Removed debug code that has been commited by accident :-/
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: The
+	  getGauge() method returns the first gauge based on the given start and
+	  end point of the river.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Fixed potential bugs: if no gauge could be determined, the default
+	  values for W and Q are the minimum and maximum double values.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: Fills
+	  the DESCRIBE with default values for W and Q.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstFactory.java:
+	  New. Returns Wst object - based on a river.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Now
+	  provides some methods that return some basic objects inserted while
+	  parameterization: River, Gauge and so on.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Bugfix: Repaired broken imports of the SessionHolder.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/SessionHolder.java:
+	  Moved this class to flys-backend.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java,
+	  src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Adapted imports of the SessionHolder.
+
+2011-04-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* trunk/src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java,
+	  trunk/src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Acquire/release sessions in services to avoid db connection leaks.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Changed some method signatures - added a reference to the owner
+	  Artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Modified method signatures based on the changes in DefaultState.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Added default values to the dynamic part of DESCRIBE.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Added a function that returns a River object based on a given river
+	  name.
+
+2011-04-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java,
+	  src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  Static methods are using the SessionHolder, too.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Added the setup() method that has been added to the interface
+	  description in the last commits.
+
+	* doc/conf/conf.xml: Registered the SessionCallContextListener as
+	  CallContext.Listener.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/SessionHolder.java:
+	  Bugfix: Call correct method to retrieve an instance of
+	  SessionFactoryProvider.
+
+2011-04-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/SessionHolder.java:
+	  New. Contains thread local session holder for hibernate sessions.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Uses session from SessionHolder.
+
+	* src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Interacts with SessionHolder now.
+	
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  Removed superfluous import.
+	
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Implementation of a CallContext.Listener to open/close Hibernate
+	  Sessions for each request.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Some modifications related to the last commit - modification of the
+	  describe() signature of a State.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/GaugeSelect.java:
+	  Removed. We do not need a state to select a gauge - the selection takes
+	  place by choosing a start and an end point.
+
+2011-04-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Bugfix: added missing label node to root node and a namespace to the
+	  data node.
+
+2011-04-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  The name of the artifact 'winfo' is written into the DESCRIBE document
+	  now. We need this to have a proper way to distinguish between different
+	  artifacts in the UI.
+
+2011-04-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Bugfix:
+	  States are filled with data before they describe themself.
+
+2011-04-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  New. A factory that returns the annotations of a specific river.
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java:
+	  New. This service provides a document that contains information about
+	  distances of a river.
+
+	* doc/conf/conf.xml: Registered the DistanceInfoService.
+
+2011-04-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  This collection overrides the out() operation now. The incoming request
+	  document is read and the related OutGenerator is used to create the
+	  output.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Removed
+	  the code to create discharge curves. It has moved to the
+	  DischargeCurveGenerator which now does this work.
+
+2011-04-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: The
+	  generate() method throws an IOException now.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  New. This OutGenerator creates discharge curves.
+
+2011-04-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Changed
+	  the visibility of the getData() method. It's now public, because the
+	  OutGenerator needs an artifact's data.
+
+2011-04-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/**/*.java: Removed trailing whitespace.
+
+2011-04-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Make project compilable again by
+	  commenting out a not existing XMLDebug reference.
+
+2011-03-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Added code to parse the configured OutGenerators and to save them (in a
+	  map) in the FLYSContext.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java:
+	  Added a key that is used to save the OutGenerators Map in the context.
+
+2011-03-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java:
+	  New. This interface is used to generator different types of output.
+	  ArtifactCollections will make use of this interface to create a
+	  collected output of all its artifacts.
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed the Backend from FLYSArtifactCollection - used ArtifactDatabase
+	  operations instead.
+
+	* TODO: Removed 'remove Backend reference' TODO.
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Enhanced the configuration of the
+	  discharge curve output mode. This output now provides three facets - W,
+	  Q and the curve itself.
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 0.1
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Renamed an output mode in the WINFO
+	  artifact configuration.
+
+2011-03-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties: Changed a german string.
+
+2011-03-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Modified the available calculation modes and its order in the DESCRIBE
+	  document.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Adapted the names of
+	  calculation modes.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added the hash code of an artifact to the artifact part of the
+	  collection's DESCRIBE document.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Changed
+	  the background color of discharge curves to white.
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Cache the scale, too. Otherwise two calls to getValues() with
+	  different arguments will result in the same output.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Introduced a 'scale' parameter in the getValues() method.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Filled
+	  the out() operation with code that draws a discharge table of one or
+	  more gauges specified by the given range in entered in a previous state.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  New. A helper class to exports charts.
+
+	* pom.xml: Added dependencies to iText, Batik and JFreeChart.
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/PegelGltParser.java:
+	  Fixed swap of operands.
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java:
+	  New. Load gauges for a river and filter them for given
+	  ranges.
+
+2011-03-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Store data in a double [][] instead of interleaved double []
+	  to be compatible with org.jfree.data.xy.DefaultXYDataset.
+
+2011-03-25  Ingo Weinzierl <ingo@intevation.de>
+
+	* TODO: Removed 'i18n' and 'step-back' TODOs and added an issue to remove
+	  the Backend reference from FLYSArtifactCollection.
+
+2011-03-25  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added missing artifact namespace of an attribute in the DESCRIBE
+	  document.
+
+2011-03-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  New. This ArtifactCollection overrides the DefaultArtifactCollection to
+	  implement FLYS specific describe() and out() operations.
+
+	* doc/conf/conf.xml: Use the FLYSArtifactCollection instead of the
+	  DefaultArtifactCollection for this application.
+
+2011-03-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  New. Fetches values of discharge tables in form of packed
+	  w/q double arrays for given gauges.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Removed needless import.
+
+2011-03-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_de_DE.properties: Added a german resource
+	  bundle to avoid exceptions in the flys artifacts. Sometimes, the
+	  Resources class is not able to find a 'de_DE' bundle and throws an
+	  exception. This is really strange, because it should use the 'de' bundle
+	  in that case, but it doesn't.
+
+2011-03-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  some code to append the output modes of previous states to the DESCRIBE
+	  document.
+
+	  TODO: Determine if the current state is already filled with data and
+	  append its output modes as well!
+
+2011-03-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Enhanced the location_distance state with
+	  an output mode 'discharge_table'.
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Write human readable strings as label attribute into the DESCRIBE
+	  output. Those labels are used to be displayed in the GUI.
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Added some further calculation types.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  calculation types
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Implemented the step-back part of the advance() operation.
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_de.properties: Fixed a german umlaut.
+
+2011-03-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added new states for entering a
+	  location/distance and w/q.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: New string for the
+	  location/distance and w/q input states.
+
+2011-03-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  New. A state for the W/Q input of the WINFO parameterization.
+
+2011-03-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Append an attribute 'uiprovider' to the dynamic UI node.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  New. A state for the location/distance selection of the WINFO
+	  parameterization.
+
+2011-03-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added a new method getUIProvider() that might be overriden by concreted
+	  subclasses that should be rendered with a specific UIProvider.
+
+2011-03-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  The static UI part is created by the previous states now. This makes it
+	  possible to group the data objects (which is necessary to group the
+	  objects in the ui).
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added a describeStatic() method that creates a node that contains the
+	  data of that state.
+
+2011-03-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Removed TODO: the document contains the rivers provided by the backend
+	  now.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency to flys backend.
+
+	* src/main/java/de/intevation/flys/artifacts/model/River.java:
+	  Removed. We are using the backend model now.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Fetches the rivers from the backend.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Import fixes.
+
+2011-03-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Added section for database backend configuration.
+
+2011-03-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Fixed build error coming from different import of XMLUtils.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Appended the missing label node that contains the human readable name of
+	  the data item.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  I18N of strings for the DESCRIBE document.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: I18N strings for the
+	  calculcation mode state.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Modified the winfo states.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Adapted
+	  the artifact regarding the changes of the last commit. The states
+	  describe() method creates the dynamic UI node - the artifact needs to
+	  apply this node.
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  New. The state for choosing the calculation mode.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  calculation mode state.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  New. This is the base state for the FLYS application. It provides a
+	  method that creates the dynamic ui node for the DESCRIBE.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/GaugeSelect.java: Both
+	  classes extend the abstract base class DefaultState.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  New. This class retrieves the i18n strings from a ResourceBundle.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Resource files for german and
+	  english translation.
+
+2011-03-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Appended
+	  the data that have been inserted in former states into the static node
+	  of the DESCRIBE.
+
+2011-03-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: The
+	  operations feed() and advance() return the description of the artifact
+	  using the describe() operation. This avoids additional server round trips
+	  in the client - the clients gets to know about the new state of the
+	  artifact immediately.
+
+2011-03-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Implemented a part (step forward) of the advance operation.
+
+	* TODO: Implement Step-Back in advance operation.
+
+2011-03-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Implemented the abstract method getName(). It returns the constant
+	  'winfo' string.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Implemented the feed action. The data of an incoming feed() operation is
+	  stored in StateData objects that are saved in a map in the artifact.
+
+	  NOTE: There is no input validation and no i18n of error messages (see
+	  TODO).
+
+2011-03-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* TODO: This file contains some open points that need to be done.
+
+2011-03-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: New. This
+	  artifact serves as the default artifact for the FLYS application.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: This
+	  artifact now inherits from FLYSArtifact. Furthermore, there is one big
+	  change: we don't store the State objects itself in the artifact, but
+	  just the identifier of those. This makes the artifact smaller and more
+	  compatible agains previous versions of the software.
+
+2011-03-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  New. This service will retrieve a list of provided rivers.
+
+	* doc/conf/conf.xml: Added a configuration for the RiverService.
+
+2011-03-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Added a section user-factory and collection-factory in
+	  the factories part of the configuration.
+
+2011-03-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Replaced the 'special' attribute from DESCRIBE with a 'uiprovider'
+	  attribute.
+
+2011-02-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The
+	  RiverSelect state is called to create the UI part of the describe
+	  document.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Implemented the dynamic UI part of describe(). The static part is not
+	  inserted into the describe document at the moment. We need a reference to
+	  the previous states for this.
+
+2011-02-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/River.java: New. A model
+	  class that represents a river and its factory to create concrete river
+	  instances.
+	  NOTE: Currently, this is just a mockup. The factory just returns two
+	  static rivers "Mosel" and "Saar" without a connection to a backend.
+
+2011-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/StateFactory.java: The
+	  input data of a state is initialized with empty StateData objects after
+	  the State has been created.
+
+	* doc/conf/artifacts/winfo.xml: Renamed the input data nodes of the states
+	  which now fits better to the class name of the implementation.
+
+2011-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The
+	  describe document returned by this artifact now contains the current state
+	  and the reachable states.
+
+2011-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  The transitions are put into the TransitionEngine with the ID of the state
+	  - not longer with the artifact name. On this way, we are able to fetch
+	  just the transitions for a specific state, instead of all the transitions
+	  of an artifact.
+
+2011-02-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Describe()
+	  returns the artifact's uuid and hash value. The whole implementation of
+	  describe() is still outstanding.
+
+2011-02-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Removed useless config stuff.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Removed
+	  useless methods, and improved the init process - the first state is set as
+	  the current state for this artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/GaugeSelect.java: New.
+	  The states are used in the first two steps of the WINFOArtifact.
+	  Currently, they just implement stubs of the necessary methods setup() and
+	  describe().
+
+2011-02-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/State.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/TransitionEngine.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/Transition.java:
+	  Removed. These classes are placed in the artifact-database now.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java:
+	  Adapted imports of Transition.
+
+	* src/main/java/de/intevation/flys/artifacts/states/StateFactory.java: New.
+	  This factory should be used to create concrete State objects.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java: Added
+	  a constant key to store the StateEngine in the context.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  New method that initializes the states at application start.
+
+2011-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/State.java: New. The
+	  interface description of a state.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/Transition.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java:
+	  New. The interface description and a default implementation of a
+	  transition.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/TransitionEngine.java:
+	  New. The TransitionEngine stores all transitions for each artifact and
+	  should be used to determine, if an artifact can advance from one state to
+	  another.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java:
+	  New. Transitions should be created by using this class.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java: New.
+	  The Flys context. It currently defines keys to store important components
+	  in the context.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  New. The context factory initializes the basic components of the
+	  application. Currently, the TransitionEngine is created and all artifacts
+	  with its transitions are read from the global configuration and stored in
+	  the FLYSContext.
+
+	* pom.xml: Added a dependency to the 'artifacts-common' package.
+
+	* doc/conf/artifacts/winfo.xml: Corrected the classname of the
+	  DefaultTransition.
+
+	* doc/conf/conf.xml: Added FLYSContextFactory as context-factory.
+
+2011-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: An initial configuration file for the FLYS artifact
+	  server.
+
+	* doc/conf/artifacts/winfo.xml: An initial transition configuration of an
+	  WINFO artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: A stub of
+	  an artifact for a WINFO parameterization.
+
+	* pom.xml: Set the source code version to 1.5.
+
+2011-02-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/**, pom.xml: Added initial maven project.
+	* ChangeLog: new.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/Changes	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,59 @@
+2011-05-13  RELEASE 2.3
+
+    NEW:
+
+        * Initial release of the artifacts for FLYS. Currently there is a single
+          WINFO artifact for the following computations:
+          - waterlevels
+          - discharge curves
+          - duration curves
+          - discharge longitudinal section curves
+
+        * Configuration is placed in doc/conf/conf.xml
+
+        * WINFO Artifact specific configuration is placed in
+          doc/conf/artifacts/winfo.xml
+
+        * So called 'OutGenerators' produce different types of output.
+          Currently, the flys-artifacts are able to produce charts and exports.
+          Each output type has to be configured in conf.xml.
+
+        * New chart types:
+          - discharge curves (dt. 'Abflusskurven am Pegel')
+          - computed discharge curves (dt. 'Abflusskurve')
+          - longitudinal section curves (dt. 'Längsschnitte')
+          - duration curves (dt. 'Dauerlinie')
+          - discharge longitudinal section curve (dt. 'W bei
+            ungleichmäßigem Abflusslängsschnitt')
+
+        * New exports:
+          - csv of waterlevels
+          - csv of duration curves
+          - csv of computed discharge curves
+          - csv of discharge longitudinal section
+
+        * New services that provides:
+          - supported rivers
+          - main values of a gauge
+          - range information of a river
+          - meta information of a river
+
+        * Caching of computation relevant values
+
+        * Initial model to support chart specific themes (theme.xml)
+
+
+    LIMITATIONS:
+
+        * Charts are not rendered using the themes in theme.xml
+
+
+    !!!
+
+    The version number of this release depends on an existing desktop variant of
+    this software that is in version 2.1.
+
+    !!!
+
+
+2011-03-30  RELEASE 0.1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/NEWS	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,9 @@
+NEWS 
+
+for enduser-specific information, c.f. the NEWS -file in the module flys-client
+
+2011-05-16  Release V 2.3
+
+2011-03-27  Release V 2.2
+
+This file starts with V. 2.2. Earlier versions are based on a Desktop-version until V 2.1.3.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/TODO	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,1 @@
+- Validation of the input values of an incoming feed() call
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/contrib/visualize-transitions.xsl	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+
+ Author: Sascha L. Teichmann (sascha.teichmann@intevation.de)
+-->
+
+<xsl:stylesheet 
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    version="1.0">
+
+    <xsl:output method="text" encoding="UTF-8"/>
+
+    <xsl:param name="base-dir">.</xsl:param>
+
+    <xsl:template match="/">
+        <xsl:text>digraph transition_model {&#xa;</xsl:text>
+        <xsl:apply-templates />
+        <xsl:text>}&#xa;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="artifact">
+        <xsl:choose>
+            <xsl:when test="@xlink:href != ''">
+                <!-- handle external artifacts -->
+                <xsl:variable name="path">
+                    <xsl:call-template name="string-replace-all">
+                    <xsl:with-param name="text" select="@xlink:href" />
+                    <xsl:with-param name="replace">${artifacts.config.dir}</xsl:with-param>
+                    <xsl:with-param name="by" select="$base-dir" />
+                    </xsl:call-template>
+                </xsl:variable>
+                <xsl:for-each select="document($path)">
+                    <xsl:apply-templates select="/artifact"/>
+                </xsl:for-each>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- handle internal artifacts -->
+                <xsl:text>subgraph </xsl:text><xsl:value-of select="@name"/>
+                <xsl:text> {&#xa;</xsl:text>
+                <xsl:text>    label = "Artefakt: </xsl:text>
+                <xsl:value-of select="@name"/>
+                <xsl:text>";&#xa;</xsl:text>
+                <xsl:apply-templates mode="inside-artifact" select="./states/state"/>
+                <xsl:apply-templates mode="inside-artifact" select="./states/transition"/>
+                <xsl:text>}&#xa;</xsl:text>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template match="state" mode="inside-artifact">
+        <xsl:text>    "</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text disable-output-escaping="yes"
+        >" [ shape = "record" label=&lt;&lt;table border="0" cellborder="0" cellpadding="3"&gt;
+        &lt;tr&gt;&lt;td align="center" colspan="2" bgcolor="black"&gt;&lt;font color="white"&gt;</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;</xsl:text>
+            <xsl:apply-templates mode="inside-artifact" />
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/table&gt;&gt;]</xsl:text>
+        <xsl:text>;&#xa;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="data" mode="inside-artifact">
+        <xsl:text disable-output-escaping="yes"
+        >&lt;tr&gt;&lt;td align="right"&gt;</xsl:text>
+        <xsl:value-of select="@name"/>
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/td&gt;&lt;td align="left"&gt;</xsl:text>
+        <xsl:value-of select="@type"/>
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/td&gt;&lt;/tr&gt;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="transition" mode="inside-artifact">
+        <xsl:text>    "</xsl:text>
+        <xsl:value-of select="from/@state"/>
+        <xsl:text disable-output-escaping="yes">" -&gt; "</xsl:text>
+        <xsl:value-of select="to/@state"/>
+        <xsl:text>"</xsl:text>
+        <xsl:apply-templates mode="inside-artifact"/>
+        <xsl:text>;&#xa;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="condition" mode="inside-artifact">
+        <xsl:text> [ label="</xsl:text>
+        <xsl:value-of select="@inputvalue"/>
+        <xsl:text> </xsl:text>
+        <xsl:value-of select="@operator"/>
+        <xsl:text> </xsl:text>
+        <xsl:value-of select="@value"/>
+        <xsl:text>" ]</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="text()" mode="inside-artifact"/>
+    <xsl:template match="text()"/>
+
+     <xsl:template name="string-replace-all">
+        <xsl:param name="text" />
+        <xsl:param name="replace" />
+        <xsl:param name="by" />
+        <xsl:choose>
+          <xsl:when test="contains($text, $replace)">
+            <xsl:value-of select="substring-before($text,$replace)" />
+            <xsl:value-of select="$by" />
+            <xsl:call-template name="string-replace-all">
+              <xsl:with-param name="text"
+                  select="substring-after($text,$replace)" />
+              <xsl:with-param name="replace" select="$replace" />
+              <xsl:with-param name="by" select="$by" />
+            </xsl:call-template>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:value-of select="$text" />
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:template>
+    
+</xsl:stylesheet>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/winfo.xml	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="winfo">
+    <states>
+
+        <state id="state.winfo.river" description="state.winfo.river" state="de.intevation.flys.artifacts.states.RiverSelect">
+            <data name="river" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.river"/>
+            <to state="state.winfo.calculation_mode"/>
+        </transition>
+
+        <state id="state.winfo.calculation_mode" description="state.winfo.calculation_mode" state="de.intevation.flys.artifacts.states.CalculationSelect">
+            <data name="calculation_mode"  type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.location_distance"/>
+            <condition data="calculation_mode" value="calc.surface.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.location"/>
+            <condition data="calculation_mode" value="calc.discharge.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.location"/>
+            <condition data="calculation_mode" value="calc.duration.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.location_distance"/>
+            <condition data="calculation_mode" value="calc.discharge.longitudinal.section" operator="equal"/>
+        </transition>
+
+        <state id="state.winfo.location" description="state.winfo.location" state="de.intevation.flys.artifacts.states.LocationSelect">
+            <data name="ld_locations" type="Double[]" />
+
+            <outputmodes>
+                <outputmode name="discharge_curve" description="output.discharge_curve" mime-type="image/png">
+                    <facets>
+                        <facet name="discharge_curve.curve" description="facet.discharge_curve.curve"/>
+                        <facet name="discharge_curve.w" description="facet.discharge_curve.w"/>
+                        <facet name="discharge_curve.q" description="facet.discharge_curve.q"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.winfo.location_distance" description="state.winfo.location_distance" state="de.intevation.flys.artifacts.states.LocationDistanceSelect">
+            <data name="ld_from" type="Double" />
+            <data name="ld_to" type="Double" />
+            <data name="ld_step" type="Double" />
+
+            <outputmodes>
+                <outputmode name="discharge_curve" description="output.discharge_curve" mime-type="image/png">
+                    <facets>
+                        <facet name="discharge_curve.curve" description="facet.discharge_curve.curve"/>
+                        <facet name="discharge_curve.w" description="facet.discharge_curve.w"/>
+                        <facet name="discharge_curve.q" description="facet.discharge_curve.q"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.location_distance"/>
+            <to state="state.winfo.wq"/>
+            <condition data="calculation_mode" value="calc.surface.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.location_distance"/>
+            <to state="state.winfo.wq_adapted"/>
+            <condition data="calculation_mode" value="calc.discharge.longitudinal.section" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.location"/>
+            <to state="state.winfo.durationcurve"/>
+            <condition data="calculation_mode" value="calc.duration.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.location"/>
+            <to state="state.winfo.computeddischargecurve"/>
+            <condition data="calculation_mode" value="calc.discharge.curve" operator="equal"/>
+        </transition>
+
+        <state id="state.winfo.wq" description="state.winfo.wq" state="de.intevation.flys.artifacts.states.WQSelect">
+            <data name="wq_mode" type="String" />
+            <data name="wq_selection" type="String" />
+            <data name="wq_from" type="Double" />
+            <data name="wq_to" type="Double" />
+            <data name="wq_step" type="Double" />
+            <data name="wq_single" type="Double[]" />
+        </state>
+
+        <state id="state.winfo.wq_adapted" description="state.winfo.wq_adapted" state="de.intevation.flys.artifacts.states.WQAdapted">
+            <!-- TODO Add data objects -->
+            <data name="wq_mode" type="String" />
+            <data name="wq_values" type="WQTriple" />
+        </state>
+
+        <state id="state.winfo.durationcurve" description="state.winfo.durationcurve" state="de.intevation.flys.artifacts.states.DurationCurveState">
+            <outputmodes>
+                <outputmode name="duration_curve" description="output.duration_curve" mime-type="image/png">
+                    <facets>
+                        <facet name="duration_curve.w" description="facet.duration_curve.w"/>
+                        <facet name="duration_curve.q" description="facet.duration_curve.q"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="durationcurve_export" description="output.waterlevel_export" mime-type="text/plain">
+                    <facets>
+                        <facet name="csv" description="facet.durationcurve_export.csv" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.winfo.computeddischargecurve" description="state.winfo.computeddischargecurve" state="de.intevation.flys.artifacts.states.ComputedDischargeCurveState">
+            <outputmodes>
+                <outputmode name="computed_discharge_curve" description="output.computed_discharge_curve" mime-type="image/png">
+                    <facets>
+                        <facet name="computed_discharge_curve.w" description="facet.computed_discharge_curve.w"/>
+                        <facet name="computed_discharge_curve.q" description="facet.computed_discharge_curve.q"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="computed_dischargecurve_export" description="output.computed_dischargecurve_export" mime-type="text/plain">
+                    <facets>
+                        <facet name="csv" description="facet.computed_dischargecurve_export.csv" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.wq"/>
+            <to state="state.winfo.waterlevel"/>
+            <condition data="calculation_mode" value="calc.surface.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.wq_adapted"/>
+            <to state="state.winfo.discharge_longitudinal_section"/>
+        </transition>
+
+        <state id="state.winfo.waterlevel" description="state.winfo.waterlevel" state="de.intevation.flys.artifacts.states.WaterlevelState">
+            <outputmodes>
+                <outputmode name="longitudinal_section" description="output.longitudinal_section" mime-type="image/png">
+                    <facets>
+                        <facet name="longitudinal_section.w" description="facet.longitudinal_section.w"/>
+                        <facet name="longitudinal_section.q" description="facet.longitudinal_section.q"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="waterlevel_export" description="output.waterlevel_export" mime-type="text/plain">
+                    <facets>
+                        <facet name="csv" description="facet.waterlevel_export.csv" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.winfo.discharge_longitudinal_section" description="state.winfo.discharge_longitudinal_section" state="de.intevation.flys.artifacts.states.DischargeLongitudinalSection">
+            <outputmodes>
+                <outputmode name="discharge_longitudinal_section" description="output.discharge_longitudinal_section" mime-type="image/png">
+                    <facets>
+                        <facet name="discharge_longitudinal_section.w"/>
+                        <facet name="discharge_longitudinal_section.q"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="discharge_longitudinal_section_export" description="output.discharge_longitudinal_section_export" mime-type="text/plain">
+                    <facets>
+                        <facet name="csv" description="facet.discharge_longitudinal_section_export.csv" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+    </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/cache.xml	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ehcache>
+
+    <diskStore path="java.io.tmpdir"/>
+
+    <defaultCache
+            maxElementsInMemory="1000"
+            eternal="false"
+            timeToIdleSeconds="120"
+            timeToLiveSeconds="3600"
+            overflowToDisk="true"
+            maxElementsOnDisk="100000"
+            diskPersistent="false"
+            diskExpiryThreadIntervalSeconds="120"
+            memoryStoreEvictionPolicy="LRU"
+            />
+
+    <!-- This one is used for the WST value tables -->
+
+    <cache name="wst-value-table"
+           maxElementsInMemory="20"
+           maxElementsOnDisk="100"
+           eternal="false"
+           timeToIdleSeconds="360"
+           overflowToDisk="true"
+           timeToLiveSeconds="14400"
+           diskPersistent="true"
+           memoryStoreEvictionPolicy="LRU"
+           />
+</ehcache>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/conf.xml	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,100 @@
+<artifact-database>
+    <export-secret>YOUR_SECRET</export-secret>
+    <factories>
+        <context-factory>de.intevation.flys.artifacts.context.FLYSContextFactory</context-factory>
+
+        <collection-factory
+            name="DefaultArtifactCollectionFactory"
+            description="The default artifact collection factory"
+            ttl="60000"
+            artifact-collection="de.intevation.flys.collections.FLYSArtifactCollection">de.intevation.artifactdatabase.DefaultArtifactCollectionFactory</collection-factory>
+
+        <artifact-factories>
+            <!-- All Artifactfactories which are available in this Database.
+                 Each Artifactfactory represents one river. -->
+            <artifact-factory name="winfo" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WINFOArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+        </artifact-factories>
+
+        <user-factory name="default" description="Factory to create new users">de.intevation.artifactdatabase.DefaultUserFactory</user-factory>
+
+        <service-factories>
+            <service-factory
+                name="rivers"
+                service="de.intevation.flys.artifacts.services.RiverService"
+                description="This service returns a list of provided rivers by the artifact server.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="distanceinfo"
+                service="de.intevation.flys.artifacts.services.DistanceInfoService"
+                description="Returns a list of distances supported by a specific river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="mainvalues"
+                service="de.intevation.flys.artifacts.services.MainValuesService"
+                description="Returns the main values of a river's gauge based on a start and end point of the river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="metadata"
+                service="de.intevation.flys.artifacts.services.MetaDataService"
+                description="The service provides some introspection into the database content.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+        </service-factories>
+
+    </factories>
+
+    <callcontext-listener
+        name="SessionCallContextListener"
+        description="A CallContext.Listener to open and close Hibernatesessions">de.intevation.flys.artifacts.context.SessionCallContextListener</callcontext-listener>
+
+    <artifacts>
+        <artifact name="winfo" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/winfo.xml" />
+    </artifacts>
+
+    <output-generators>
+        <output-generator name="discharge_curve">de.intevation.flys.exports.DischargeCurveGenerator</output-generator>
+        <output-generator name="computed_discharge_curve">de.intevation.flys.exports.ComputedDischargeCurveGenerator</output-generator>
+        <output-generator name="longitudinal_section">de.intevation.flys.exports.LongitudinalSectionGenerator</output-generator>
+        <output-generator name="duration_curve">de.intevation.flys.exports.DurationCurveGenerator</output-generator>
+        <output-generator name="discharge_longitudinal_section">de.intevation.flys.exports.DischargeLongitudinalSectionGenerator</output-generator>
+        <output-generator name="waterlevel_export">de.intevation.flys.exports.WaterlevelExporter</output-generator>
+        <output-generator name="durationcurve_export">de.intevation.flys.exports.DurationCurveExporter</output-generator>
+        <output-generator name="computed_dischargecurve_export">de.intevation.flys.exports.ComputedDischargeCurveExporter</output-generator>
+        <output-generator name="discharge_longitudinal_section_export">de.intevation.flys.exports.DischargeLongitudinalSectionExporter</output-generator>
+    </output-generators>
+
+    <rest-server>
+        <!--  at this Point it is possible to Change the Port the ArtifactDatabase will bind to.-->
+        <port>8181</port>
+        <listen>localhost</listen>
+    </rest-server>
+    <!-- garbage collection of outdated artifacts -->
+    <cleaner>
+        <sleep-time>60000</sleep-time>
+    </cleaner>
+
+    <cache>
+        <config-file>${artifacts.config.dir}/cache.xml</config-file>
+    </cache>
+
+    <database>
+        <!-- This Section configures the Settings for connecting to the 
+             Artifact-Database instance. e.g. SQLite -->
+        <user>SA</user>
+        <password></password>
+        <url>jdbc:h2:${artifacts.config.dir}/../artifactdb/artifacts.db</url>
+    </database>
+    <!-- This is the default backend db configuration -->
+    <!--
+    <backend-database>
+        <user>flys</user>
+        <password>flys</password>
+        <dialect>org.hibernate.dialect.PostgreSQLDialect</dialect>
+        <driver>org.postgresql.Driver</driver>
+        <url>jdbc:postgresql://localhost:5432/flys</url>
+    </backend-database>
+    -->
+
+    <flys>
+        <themes>
+            <configuration>${artifacts.config.dir}/themes.xml</configuration>
+        </themes>
+    </flys>
+</artifact-database>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/themes.xml	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,62 @@
+<themes>
+
+    <!-- Concrete themes are following now! -->
+    <theme name="DischargeCurve">
+        <inherits>
+            <inherit from="HiddenColorLines"/>
+        </inherits>
+    </theme>
+
+    <theme name="LongitudinalSectionW">
+        <inherits>
+            <inherit from="HiddenColorLines"/>
+        </inherits>
+    </theme>
+
+    <theme name="LongitudinalSectionQ">
+        <inherits>
+            <inherit from="HiddenColorLines"/>
+        </inherits>
+    </theme>
+
+
+
+    <!-- Virtual themes are following now! -->
+    <theme name="Lines" type="virtual">
+        <fields>
+            <field name="showlines" type="boolean" display="Linie anzeigen" default="true"/>
+            <field name="linesize"  type="int"     display="Liniendicke"    default="1"/>
+            <field name="linetype"  type="Dash"    display="Linienart"      default="new Dash()"/>
+        </fields>
+    </theme>
+
+    <theme name="ColorLines" type="virtual">
+        <inherits>
+            <inherit from="Lines"/>
+        </inherits>
+        <fields>
+            <field name="linecolor" type="Color" display="Linienfarbe" default="Color.BLACK"/>
+        </fields>
+    </theme>
+
+    <theme name="HiddenColorLines" type="virtual">
+        <inherits>
+            <inherit from="ColorLines"/>
+        </inherits>
+        <fields>
+            <field name="showlines" type="boolean" display="Linie anzeigen" default="true"        hints="h"/>
+            <field name="linesize"  type="int"     display="Liniendicke"    default="1"           hints="h"/>
+            <field name="linetype"  type="Dash"    display="Linienart"      default="new Dash()"  hints="h"/>
+        </fields>
+    </theme>
+
+
+
+    <!-- Mappings are following now. A mapping maps between a name of a facet
+         and a theme. -->
+    <mappings>
+        <mapping from="discharge_curve.curve" to="DischargeCurve"/>
+        <mapping from="longitudinal_section.w" to="LongitudinalSectionW"/>
+        <mapping from="longitudinal_section.q" to="LongitudinalSectionQ"/>
+    </mappings>
+</themes>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/pom.xml	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,106 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>de.intevation.flys.artifacts</groupId>
+  <artifactId>flys-artifacts</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>flys-artifacts</name>
+  <url>http://maven.apache.org</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>2.0.2</version>
+          <configuration>
+              <source>1.5</source>
+              <target>1.5</target>
+          </configuration>
+      </plugin>
+     </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>net.sf.ehcache</groupId>
+      <artifactId>ehcache-core</artifactId>
+      <version>2.4.2</version>
+    </dependency>
+    <dependency>
+      <groupId>jfree</groupId>
+      <artifactId>jfreechart</artifactId>
+      <version>1.0.13</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>trove</groupId>
+      <artifactId>trove</artifactId>
+      <version>2.1.1</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.opencsv</groupId>
+      <artifactId>opencsv</artifactId>
+      <version>2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>de.intevation.bsh.artifact-database</groupId>
+      <artifactId>artifact-database</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>de.intevation.artifacts.common</groupId>
+      <artifactId>artifacts-common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>de.intevation.flys</groupId>
+      <artifactId>flys-backend</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>jfree</groupId>
+      <artifactId>jfreechart</artifactId>
+      <version>1.0.13</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.xmlgraphics</groupId>
+      <artifactId>batik-dom</artifactId>
+      <version>1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.xmlgraphics</groupId>
+      <artifactId>batik-svggen</artifactId>
+      <version>1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>com.lowagie</groupId>
+      <artifactId>itext</artifactId>
+      <version>2.1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-math</artifactId>
+      <version>2.2</version>
+    </dependency>
+  </dependencies>
+  <repositories>
+    <repository>
+        <id>jboss-repo2</id>
+        <name>JBoss repo2</name>
+        <url>http://repository.jboss.org/maven2</url>
+    </repository>
+  </repositories>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/App.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,13 @@
+package de.intevation.flys.artifacts;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+    public static void main( String[] args )
+    {
+        System.out.println( "Hello World!" );
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,926 @@
+package de.intevation.flys.artifacts;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.xml.xpath.XPathConstants;
+
+import gnu.trove.TDoubleArrayList;
+
+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.ArtifactFactory;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.DefaultArtifact;
+import de.intevation.artifactdatabase.data.DefaultStateData;
+import de.intevation.artifactdatabase.data.StateData;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.model.DischargeTables;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.artifacts.states.DefaultState;
+
+
+/**
+ * The defaul FLYS artifact.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class FLYSArtifact extends DefaultArtifact {
+
+    /** The logger that is used in this artifact.*/
+    private static Logger logger = Logger.getLogger(FLYSArtifact.class);
+
+
+    /** The XPath that points to the input data elements of the FEED document.*/
+    public static final String XPATH_FEED_INPUT =
+        "/art:action/art:data/art:input";
+
+    /** The XPath that points to the name of the target state of ADVANCE.*/
+    public static final String XPATH_ADVANCE_TARGET =
+        "/art:action/art:target/@art:name";
+
+    /** The constant string that shows that an operation was successful.*/
+    public static final String OPERATION_SUCCESSFUL = "SUCCESS";
+
+    /** The constant string that shows that an operation failed.*/
+    public static final String OPERATION_FAILED = "FAILURE";
+
+    /** The default number of steps between the start end end of a selected Q
+     * range.*/
+    public static final int DEFAULT_Q_STEPS = 30;
+
+    /** The default step width between the start end end kilometer.*/
+    public static final double DEFAULT_KM_STEPS = 0.1;
+
+
+    /** The identifier of the current state. */
+    protected String currentStateId;
+
+    /** The identifiers of previous states on a stack.*/
+    protected Vector<String> previousStateIds;
+
+    /** The name of the artifact.*/
+    protected String name;
+
+    /** The data that have been inserted into this artifact.*/
+    protected Map<String, StateData> data;
+
+
+    /**
+     * The default constructor that creates an empty FLYSArtifact.
+     */
+    public FLYSArtifact() {
+        data             = new HashMap<String, StateData>();
+        previousStateIds = new Vector<String>();
+    }
+
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public abstract String getName();
+
+
+    /**
+     * Returns the FLYSContext from context object.
+     *
+     * @param context The CallContext or the FLYSContext.
+     *
+     * @return the FLYSContext.
+     */
+    protected FLYSContext getFlysContext(Object context) {
+        return context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) ((CallContext) context).globalContext();
+    }
+
+
+    /**
+     * Initialize the artifact and insert new data if <code>data</code> contains
+     * information necessary for this artifact.
+     *
+     * @param identifier The UUID.
+     * @param factory The factory that is used to create this artifact.
+     * @param context The CallContext.
+     * @param data Some optional data.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        Document        data)
+    {
+        logger.debug("Setup this artifact with the uuid: " + identifier);
+
+        super.setup(identifier, factory, context, data);
+
+        FLYSContext flysContext = (FLYSContext) context;
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        String name = getName();
+
+        logger.debug("Set initial state for artifact '" + name + "'");
+        List<State> states = engine.getStates(name);
+
+        setCurrentState(states.get(0));
+    }
+
+
+    /**
+     * Insert new data included in <code>input</code> into the current state.
+     *
+     * @param target XML document that contains new data.
+     * @param context The CallContext.
+     *
+     * @return a document that contains a SUCCESS or FAILURE message.
+     */
+    @Override
+    public Document feed(Document target, CallContext context) {
+        logger.info("FLYSArtifact.feed()");
+
+        Document doc = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element result = creator.create("result");
+        doc.appendChild(result);
+
+        try {
+            saveData(target, XPATH_FEED_INPUT, context);
+            return describe(target, context);
+        }
+        catch (IllegalArgumentException iae) {
+            creator.addAttr(result, "type", OPERATION_FAILED, true);
+
+            result.setTextContent(iae.getMessage());
+        }
+
+        return doc;
+    }
+
+
+    /**
+     * This method handles request for changing the current state of an
+     * artifact. It is possible to step forward or backward.
+     *
+     * @param target The incoming ADVANCE document.
+     * @param context The CallContext.
+     *
+     * @return a document that contains a SUCCESS or FAILURE message.
+     */
+    public Document advance(Document target, CallContext context) {
+        Document doc = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element result = ec.create("result");
+
+        String targetState = XMLUtils.xpathString(
+            target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE);
+
+        logger.info("FLYSArtifact.advance() to '" + targetState + "'");
+
+        if (isStateReachable(targetState, context)) {
+            logger.info("Advance: Step forward");
+
+            Vector<String> prev = getPreviousStateIds();
+            prev.add(getCurrentStateId());
+
+            setCurrentStateId(targetState);
+
+            return describe(target, context);
+        }
+        else if (isPreviousState(targetState, context)) {
+            logger.info("Advance: Step back to");
+
+            Vector<String> prevs = getPreviousStateIds();
+            int targetIdx        = prevs.indexOf(targetState);
+            int start            = prevs.size() - 1;
+
+            for (int i = start; i >= targetIdx; i--) {
+                String prev = prevs.get(i);
+                logger.debug("Remove state id '" + prev + "'");
+                prevs.remove(prev);
+            }
+
+            setCurrentStateId(targetState);
+
+            return describe(target, context);
+        }
+
+        logger.warn("Advance: Cannot advance to '" + targetState + "'");
+        ec.addAttr(result, "type", OPERATION_FAILED, true);
+
+        doc.appendChild(result);
+
+        return doc;
+    }
+
+
+    /**
+     * Returns the identifier of the current state.
+     *
+     * @return the identifier of the current state.
+     */
+    protected String getCurrentStateId() {
+        return currentStateId;
+    }
+
+
+    /**
+     * Sets the identifier of the current state.
+     *
+     * @param id the identifier of a state.
+     */
+    protected void setCurrentStateId(String id) {
+        currentStateId = id;
+    }
+
+
+    /**
+     * Set the current state of this artifact. <b>NOTE</b>We don't store the
+     * State object itself - which is not necessary - but its identifier. So
+     * this method will just call the setCurrentStateId() method with the
+     * identifier of <i>state</i>.
+     *
+     * @param state The new current state.
+     */
+    protected void setCurrentState(State state) {
+        setCurrentStateId(state.getID());
+    }
+
+
+    /**
+     * Returns the current state of the artifact.
+     *
+     * @return the current State of the artifact.
+     */
+    protected State getCurrentState(Object context) {
+        FLYSContext flysContext = getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        return engine.getState(getCurrentStateId());
+    }
+
+
+    /**
+     * Returns the vector of previous state identifiers.
+     *
+     * @return the vector of previous state identifiers.
+     */
+    protected Vector<String> getPreviousStateIds() {
+        return previousStateIds;
+    }
+
+
+    /**
+     * Adds a new StateData item to the data pool of this artifact.
+     *
+     * @param name the name of the data object.
+     * @param data the data object itself.
+     */
+    protected void addData(String name, StateData data) {
+        this.data.put(name, data);
+    }
+
+
+    /**
+     * This method returns a specific StateData object that is stored in the
+     * data pool of this artifact.
+     *
+     * @param name The name of the data object.
+     *
+     * @return the StateData object if existing, otherwise null.
+     */
+    public StateData getData(String name) {
+        return data.get(name);
+    }
+
+
+    /**
+     * This method stores the data that is contained in the FEED document.
+     *
+     * @param feed The FEED document.
+     * @param xpath The XPath that points to the data nodes.
+     */
+    public void saveData(Document feed, String xpath, CallContext context)
+    throws IllegalArgumentException
+    {
+        if (feed == null || xpath == null || xpath.length() == 0) {
+            throw new IllegalArgumentException("error_feed_no_data");
+        }
+
+        NodeList nodes = (NodeList) XMLUtils.xpath(
+            feed,
+            xpath,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (nodes == null || nodes.getLength() == 0) {
+            throw new IllegalArgumentException("error_feed_no_data");
+        }
+
+        int count = nodes.getLength();
+        logger.debug("Try to save " + count + " data items.");
+
+        for (int i = 0; i < count; i++) {
+            Node node = nodes.item(i);
+
+            String name = XMLUtils.xpathString(
+                node, "@art:name", ArtifactNamespaceContext.INSTANCE);
+            String value = XMLUtils.xpathString(
+                node, "@art:value", ArtifactNamespaceContext.INSTANCE);
+
+            if (name != null && value != null) {
+                logger.debug("Save data item for '" + name + "' : " + value);
+
+                addData(name, new DefaultStateData(name, null, null, value));
+            }
+        }
+
+        State current           = getCurrentState(context);
+        DefaultState toValidate = (DefaultState) fillState(current);
+
+        toValidate.validate(this, context);
+    }
+
+
+    /**
+     * This method fills a state object with the data that have been inserted to
+     * this artifact. This is necessary to use the isStateReachable() method,
+     * because the Transitions need to know about the inserted data.
+     *
+     * @param state The state that needs to be filled with data.
+     *
+     * @return the filled state.
+     */
+    protected State fillState(State state) {
+        Map<String, StateData> stateData = state.getData();
+
+        if (stateData == null) {
+            return state;
+        }
+
+        Set<String>                 keys = stateData.keySet();
+
+        for (String key: keys) {
+            StateData tmp = getData(key);
+
+            if (tmp != null) {
+                StateData data = stateData.get(key);
+                data.setValue(tmp.getValue());
+            }
+        }
+
+        return state;
+    }
+
+
+    /**
+     * Determines if the state with the identifier <i>stateId</i> is reachable
+     * from the current state. The determination itself takes place in the
+     * TransitionEngine.
+     *
+     * @param stateId The identifier of a state.
+     * @param context The context object.
+     *
+     * @return true, if the state specified by <i>stateId</i> is reacahble,
+     * otherwise false.
+     */
+    protected boolean isStateReachable(String stateId, Object context) {
+        logger.debug("Determine if the state '" + stateId + "' is reachable.");
+
+        FLYSContext flysContext = getFlysContext(context);
+
+        State currentState  = fillState(getCurrentState(context));
+        StateEngine sEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine tEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        return tEngine.isStateReachable(this, stateId, currentState, sEngine);
+    }
+
+
+    /**
+     * Determines if the state with the identifier <i>stateId</i> is a previous
+     * state of the current state.
+     *
+     * @param stateId The target state identifier.
+     * @param context The context object.
+     */
+    protected boolean isPreviousState(String stateId, Object context) {
+        logger.debug("Determine if the state '" + stateId + "' is old.");
+
+        Vector<String> prevs = getPreviousStateIds();
+        if (prevs.contains(stateId)) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns the selected River object based on the 'river' data that might
+     * have been inserted by the user.
+     *
+     * @return the selected River or null if no river has been chosen yet.
+     */
+    public River getRiver() {
+        StateData dRiver = getData("river");
+
+        return dRiver != null
+            ? RiverFactory.getRiver((String) dRiver.getValue())
+            : null;
+    }
+
+
+    /**
+     * Returns the selected distance of points.
+     *
+     * @return the selected distance or points.
+     */
+    public double[] getDistance() {
+        StateData dFrom      = getData("ld_from");
+        StateData dTo        = getData("ld_to");
+        StateData dLocations = getData("ld_locations");
+
+        if (dFrom != null && dTo != null) {
+            return getDistanceByRange(dFrom, dTo);
+        }
+        else if (dLocations != null) {
+            double[] locations = getLocations();
+            return new double[] { locations[0], locations[locations.length-1] };
+        }
+
+        logger.warn("No data found for distance determination!");
+
+        return null;
+    }
+
+
+    /**
+     * This method returns the given distance 
+     *
+     * @return an array with lower and upper kilometer range for each
+     * intersected gauge.
+     */
+    public double[][] getSplittedDistance() {
+        double[]    dist   = getDistance();
+        List<Gauge> gauges = getGauges();
+
+        int num = gauges != null ? gauges.size() : 0;
+
+        double[][] res = new double[num][2];
+
+        for (int i = 0; i < num; i++) {
+            Range range = gauges.get(i).getRange();
+
+            double lower = range.getA().doubleValue();
+            double upper = range.getB().doubleValue();
+
+            res[i][0] = dist[0] < lower ? lower : dist[0];
+            res[i][1] = dist[1] > upper ? upper : dist[1];
+        }
+
+        return res;
+    }
+
+
+    /**
+     * Returns the selected locations based on a given array of locations.
+     *
+     * @param locations The StateData that contains the locations.
+     *
+     * @return the selected locations.
+     */
+    public double[] getLocations() {
+        StateData dLocations  = getData("ld_locations");
+        String    locationStr = dLocations != null
+            ? (String) dLocations.getValue()
+            : "";
+
+        if (locationStr == null || locationStr.length() == 0) {
+            logger.warn("No valid location string found!");
+            return null;
+        }
+
+        String[] tmp               = locationStr.split(" ");
+        TDoubleArrayList locations = new TDoubleArrayList();
+
+        for (String l: tmp) {
+            try {
+                locations.add(Double.parseDouble(l));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        locations.sort();
+
+        return locations.toNativeArray();
+    }
+
+
+    /**
+     * Returns the selected distance based on a given range (from, to).
+     *
+     * @param dFrom The StateData that contains the lower value.
+     * @param dTo The StateData that contains the upper value.
+     *
+     * @return the selected distance.
+     */
+    protected double[] getDistanceByRange(StateData dFrom, StateData dTo) {
+        double from = Double.parseDouble((String) dFrom.getValue());
+        double to   = Double.parseDouble((String) dTo.getValue());
+
+        return new double[] { from, to };
+    }
+
+
+    /**
+     * Returns the selected Kms.
+     *
+     * @param distance An 2dim array with [lower, upper] values.
+     *
+     * @return the selected Kms.
+     */
+    public double[] getKms(double[] distance) {
+        StateData dStep = getData("ld_step");
+
+        if (dStep == null) {
+            logger.warn("No step width given. Cannot compute Kms.");
+            return null;
+        }
+
+        double step = Double.parseDouble((String) dStep.getValue());
+
+        // transform step from 'm' into 'km'
+        step = step / 1000;
+
+        if (step == 0d) {
+            step = DEFAULT_KM_STEPS;
+        }
+
+        return getExplodedValues(distance[0], distance[1], step);
+    }
+
+
+    /**
+     * Returns the selected Kms.
+     *
+     * @return the selected kms.
+     */
+    public double[] getKms() {
+        double[] distance = getDistance();
+        return getKms(distance);
+    }
+
+
+    /**
+     * Returns the gauge based on the current distance and river.
+     *
+     * @return the gauge.
+     */
+    public Gauge getGauge() {
+        River    river = getRiver();
+        double[] dist  = getDistance();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Determine gauge for:");
+            logger.debug("... river: " + river.getName());
+            logger.debug("... distance: " + dist[0] + " - " + dist[1]);
+        }
+
+        Gauge gauge = river.determineGauge(dist[0], dist[1]);
+
+        String name = gauge != null ? gauge.getName() : "'n/a";
+        logger.debug("Found gauge: " + name);
+
+        return gauge;
+    }
+
+
+    /**
+     * Returns the gauges that match the selected kilometer range.
+     *
+     * @return the gauges based on the selected kilometer range.
+     */
+    public List<Gauge> getGauges() {
+        River    river = getRiver();
+        double[] dist  = getDistance();
+
+        return river.determineGauges(dist[0], dist[1]);
+    }
+
+
+    /**
+     * This method returns the Q values.
+     *
+     * @return the selected Q values or null, if no Q values are selected.
+     */
+    public double[] getQs() {
+        StateData dMode   = getData("wq_mode");
+        StateData dSingle = getData("wq_single");
+
+        String mode = dMode != null ? (String) dMode.getValue() : "";
+
+        if (mode.equals("Q")) {
+            if (dSingle != null) {
+                return getSingleWQValues();
+            }
+            else {
+                return getWQTriple();
+            }
+        }
+        else {
+            logger.warn("You try to get Qs, but W has been inserted.");
+            return null;
+        }
+    }
+
+
+    /**
+     * Returns the Q values based on a specified kilometer range.
+     *
+     * @param range A 2dim array with lower and upper kilometer range.
+     *
+     * @return an array of Q values.
+     */
+    public double[] getQs(double[] range) {
+        StateData dMode   = getData("wq_mode");
+        StateData dValues = getData("wq_values");
+
+        String mode = dMode != null ? (String) dMode.getValue() : "";
+
+        if (mode.equals("Q")) {
+            return getWQForDist(range);
+        }
+
+        logger.warn("You try to get Qs, but Ws has been inserted.");
+        return null;
+    }
+
+
+    /**
+     * Returns the W values based on a specified kilometer range.
+     *
+     * @param range A 2dim array with lower and upper kilometer range.
+     *
+     * @return an array of W values.
+     */
+    public double[] getWs(double[] range) {
+        StateData dMode   = getData("wq_mode");
+        StateData dValues = getData("wq_values");
+
+        String mode = dMode != null ? (String) dMode.getValue() : "";
+
+        if (mode.equals("W")) {
+            return getWQForDist(range);
+        }
+
+        logger.warn("You try to get Ws, but Qs has been inserted.");
+        return null;
+    }
+
+
+    /**
+     * This method returns the W values.
+     *
+     * @return the selected W values or null, if no W values are selected.
+     */
+    public double[] getWs() {
+        StateData dMode   = getData("wq_mode");
+        StateData dSingle = getData("wq_single");
+
+        String mode = dMode != null ? (String) dMode.getValue() : "";
+
+        if (mode.equals("W")) {
+            if (dSingle != null) {
+                return getSingleWQValues();
+            }
+            else {
+                return getWQTriple();
+            }
+        }
+        else {
+            logger.warn("You try to get Qs, but W has been inserted.");
+            return null;
+        }
+    }
+
+
+    /**
+     * Returns the Qs for a number of Ws. This method makes use of
+     * DischargeTables.getQForW().
+     *
+     * @param ws An array of W values.
+     *
+     * @return an array of Q values.
+     */
+    public double[] getQsForWs(double[] ws) {
+        logger.debug("FLYSArtifact.getQsForWs");
+
+        River r = getRiver();
+        Gauge g = getGauge();
+
+        DischargeTables dt = new DischargeTables(r.getName(), g.getName());
+        Map<String, double [][]>  tmp = dt.getValues();
+
+        double[][] values = tmp.get(g.getName());
+        double[]   qs     = new double[ws.length];
+
+        for (int i = 0; i < ws.length; i++) {
+            qs[i] = dt.getQForW(values, ws[i]);
+        }
+
+        return qs;
+    }
+
+
+    /**
+     * This method returns the given W or Q values for a specific range
+     * (inserted in the WQ input panel for discharge longitudinal sections).
+     *
+     * @param dist A 2dim array with lower und upper kilometer values.
+     *
+     * @return an array of W or Q values.
+     */
+    protected double[] getWQForDist(double[] dist) {
+        logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]);
+        StateData data = getData("wq_values");
+
+        if (data == null) {
+            logger.warn("Missing wq values!");
+            return null;
+        }
+
+        String dataString = (String) data.getValue();
+        String[]   ranges = dataString.split(":");
+
+        for (String range: ranges) {
+            String[] parts = range.split(";");
+
+            double lower = Double.parseDouble(parts[0]);
+            double upper = Double.parseDouble(parts[1]);
+
+            if (lower <= dist[0] && upper >= dist[1]) {
+                String[] values = parts[2].split(",");
+
+                int      num = values.length;
+                double[] res = new double[num];
+
+                for (int i = 0; i < num; i++) {
+                    try {
+                        res[i] = Double.parseDouble(values[i]);
+                    }
+                    catch (NumberFormatException nfe) {
+                        logger.warn(nfe, nfe);
+                    }
+                }
+
+                return res;
+            }
+        }
+
+        logger.warn("Specified range for WQ not found!");
+
+        return null;
+    }
+
+
+    /**
+     * This method returns an array of inserted WQ triples that consist of from,
+     * to and the step width.
+     *
+     * @return an array of from, to and step width.
+     */
+    protected double[] getWQTriple() {
+        StateData dFrom = getData("wq_from");
+        StateData dTo   = getData("wq_to");
+
+        if (dFrom == null || dTo == null) {
+            logger.warn("Missing start or end value for range.");
+            return null;
+        }
+
+        double from = Double.parseDouble((String) dFrom.getValue());
+        double to   = Double.parseDouble((String) dTo.getValue());
+
+        StateData dStep = getData("wq_step");
+
+        if (dStep == null) {
+            logger.warn("No step width given. Cannot compute Qs.");
+            return null;
+        }
+
+        double step  = Double.parseDouble((String) dStep.getValue());
+
+        // if no width is given, the DEFAULT_Q_STEPS is used to compute the step
+        // width. Maybe, we should round the value to a number of digits.
+        if (step == 0d) {
+            double diff = to - from;
+            step = diff / DEFAULT_Q_STEPS;
+        }
+
+        return getExplodedValues(from, to, step);
+    }
+
+
+    /**
+     * Returns an array of inserted WQ double values stored as whitespace
+     * separated list.
+     *
+     * @return an array of W or Q values.
+     */
+    protected double[] getSingleWQValues() {
+        StateData dSingle = getData("wq_single");
+
+        if (dSingle == null) {
+            logger.warn("Cannot determine single WQ values. No data given.");
+            return null;
+        }
+
+        String   tmp       = (String) dSingle.getValue();
+        String[] strValues = tmp.split(" ");
+
+        TDoubleArrayList values = new TDoubleArrayList();
+
+        for (String strValue: strValues) {
+            try {
+                values.add(Double.parseDouble(strValue));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        values.sort();
+
+        return values.toNativeArray();
+    }
+
+
+    /**
+     * Returns an array of double values. The values contained in this array
+     * begin with the value <i>from</i> and end with the value <i>to</i>. The
+     * number of values in the result array depends on the <i>step</i> width.
+     *
+     * @param from The lower value.
+     * @param to The upper value.
+     * @param step The step width between two values in the result array.
+     *
+     * @return an array of double values.
+     */
+    public double[] getExplodedValues(double from, double to, double step) {
+        double lower = from;
+
+        double diff = to - from;
+        double tmp  = diff / step;
+        int    num = (int) Math.ceil(tmp) ;
+
+        double[] values = new double[num];
+
+        for (int idx = 0; idx < num; idx++) {
+            values[idx] = lower;
+            lower      += step;
+        }
+
+        return values;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,537 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+import java.util.Vector;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.math.BackJumpCorrector;
+import de.intevation.flys.artifacts.model.MainValuesFactory;
+import de.intevation.flys.artifacts.model.WQCKms;
+import de.intevation.flys.artifacts.model.WQDay;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WstValueTable;
+
+
+/**
+ * The default WINFO artifact.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WINFOArtifact extends FLYSArtifact {
+
+    /** The logger for this class */
+    private static Logger logger = Logger.getLogger(WINFOArtifact.class);
+
+
+    /** The name of the artifact.*/
+    public static final String ARTIFACT_NAME = "winfo";
+
+    /** XPath */
+    public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static";
+
+
+    /**
+     * The default constructor.
+     */
+    public WINFOArtifact() {
+    }
+
+
+    /**
+     * This method returns a description of this artifact.
+     *
+     * @param data Some data.
+     * @param context The CallContext.
+     *
+     * @return the description of this artifact.
+     */
+    public Document describe(Document data, CallContext context) {
+        logger.debug("Describe: the current state is: " + getCurrentStateId());
+
+        FLYSContext flysContext = null;
+        if (context instanceof FLYSContext) {
+            flysContext = (FLYSContext) context;
+        }
+        else {
+            flysContext = (FLYSContext) context.globalContext();
+        }
+
+        StateEngine stateEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        List<State> reachable = transitionEngine.getReachableStates(
+            this, getCurrentState(context), stateEngine);
+
+        Document description            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            description,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        description.appendChild(root);
+
+        State current = getCurrentState(context);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        ProtocolUtils.appendState(creator, root, current);
+        ProtocolUtils.appendReachableStates(creator, root, reachable);
+
+        Element name = ProtocolUtils.createArtNode(
+            creator, "name",
+            new String[] { "value" },
+            new String[] { getName() });
+
+        Element ui = ProtocolUtils.createArtNode(
+            creator, "ui", null, null);
+
+        Element staticUI  = ProtocolUtils.createArtNode(
+            creator, "static", null, null);
+
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+        appendOutputModes(description, outs, context, identifier());
+
+        appendStaticUI(description, staticUI, context, identifier());
+
+        Element dynamic = current.describe(
+            this,
+            description,
+            root,
+            context,
+            identifier());
+
+        if (dynamic != null) {
+            ui.appendChild(dynamic);
+        }
+
+        ui.appendChild(staticUI);
+
+        root.appendChild(name);
+        root.appendChild(ui);
+        root.appendChild(outs);
+
+        return description;
+    }
+
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+
+    protected void appendOutputModes(
+        Document    doc,
+        Element     outs,
+        CallContext context,
+        String      uuid)
+    {
+        Vector<String> stateIds = getPreviousStateIds();
+
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        FLYSContext flysContext = getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append output modes for state: " + stateId);
+            State state = engine.getState(stateId);
+
+            List<Output> list = state.getOutputs();
+            if (list == null || list.size() == 0) {
+                continue;
+            }
+
+            ProtocolUtils.appendOutputModes(creator, outs, list);
+        }
+
+        try {
+            DefaultState cur = (DefaultState) getCurrentState(context);
+            if (cur.validate(this, context)) {
+                List<Output> list = cur.getOutputs();
+                if (list != null && list.size() > 0) {
+                    logger.debug(
+                        "Append output modes for state: " + cur.getID());
+
+                    ProtocolUtils.appendOutputModes(creator, outs, list);
+                }
+            }
+        }
+        catch (IllegalArgumentException iae) {
+            // state is not valid, so we do not append its outputs.
+        }
+    }
+
+
+    /**
+     * This method appends the static data - that has already been inserted by
+     * the user - to the static node of the DESCRIBE document.
+     *
+     * @param doc The document.
+     * @param ui The root node.
+     * @param context The CallContext.
+     * @param uuid The identifier of the artifact.
+     */
+    protected void appendStaticUI(
+        Document    doc,
+        Node        ui,
+        CallContext context,
+        String uuid)
+    {
+        Vector<String> stateIds = getPreviousStateIds();
+
+        FLYSContext flysContext = getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append static data for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+            state = (DefaultState) fillState(state);
+
+            ui.appendChild(state.describeStatic(doc, ui, context, uuid));
+        }
+    }
+
+
+    //
+    // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
+    //
+
+    /**
+     * Returns the data that is computed by a waterlevel computation.
+     *
+     * @return an array of data triples that consist of W, Q and Kms.
+     */
+    public WQKms[] getWaterlevelData()
+    throws NullPointerException
+    {
+        logger.debug("WINFOArtifact.getWaterlevelData");
+
+        River river = getRiver();
+        if (river == null) {
+            throw new NullPointerException("No river selected.");
+        }
+
+        double[] kms = getKms();
+        if (kms == null) {
+            throw new NullPointerException("No Kms selected.");
+        }
+
+        double[] qs = getQs();
+        if (qs == null) {
+            logger.debug("Determine Q values based on a set of W values.");
+
+            double[] ws = getWs();
+            qs = getQsForWs(ws);
+        }
+
+        WstValueTable wst = WstValueTable.getTable(river);
+        if (wst == null) {
+            throw new NullPointerException("No Wst found for selected river.");
+        }
+
+        // TODO Introduce a caching mechanism here!
+
+        return computeWaterlevelData(kms, qs, wst);
+    }
+
+
+    /**
+     * Computes the data of a waterlevel computation based on the interpolation
+     * in WstValueTable.
+     *
+     * @param kms The kilometer values.
+     * @param qa The discharge values.
+     * @param wst The WstValueTable used for the interpolation.
+     *
+     * @return an array of data triples that consist of W, Q and Kms.
+     */
+    public static WQKms[] computeWaterlevelData(
+        double[]      kms,
+        double[]      qs,
+        WstValueTable wst)
+    {
+        logger.info("WINFOArtifact.computeWaterlevelData");
+
+        WQKms[] wqkms = new WQKms[qs.length];
+        for (int i = 0; i < wqkms.length; i++) {
+            wqkms[i] = new WQKms(kms.length);
+        }
+
+        double [] interpolatedW = new double[qs.length];
+
+        for (double km: kms) {
+            wst.interpolateW(km, qs, interpolatedW);
+
+            // TODO Modify the interpolation to return a better formed data
+            // structure.
+            for (int i = 0; i < interpolatedW.length; i++) {
+                wqkms[i].add(interpolatedW[i], qs[i], km);
+            }
+        }
+
+        return wqkms;
+    }
+
+
+    /**
+     * Returns the data that is computed by a duration curve computation.
+     *
+     * @return the data computed by a duration curve computation.
+     */
+    public WQDay getDurationCurveData()
+    throws NullPointerException
+    {
+        logger.debug("WINFOArtifact.getDurationCurveData");
+
+        River r = getRiver();
+
+        if (r == null) {
+            throw new NullPointerException("Cannot determine river.");
+        }
+
+        Gauge g = getGauge();
+
+        if (g == null) {
+            throw new NullPointerException("Cannot determine gauge.");
+        }
+
+        double[] locations = getLocations();
+
+        if (locations == null) {
+            throw new NullPointerException("Cannot determine location.");
+        }
+
+        WstValueTable wst = WstValueTable.getTable(r);
+        if (wst == null) {
+            throw new NullPointerException("No Wst found for selected river.");
+        }
+
+        // TODO Introduce a caching mechanism here!
+
+        return computeDurationCurveData(g, wst, locations[0]);
+    }
+
+
+    /**
+     * Computes the data used to create duration curves.
+     *
+     * @param gauge The selected gauge.
+     * @param location The selected location.
+     *
+     * @return the computed data.
+     */
+    public static WQDay computeDurationCurveData(
+        Gauge           gauge,
+        WstValueTable   wst,
+        double          location)
+    {
+        logger.info("WINFOArtifact.computeDurationCurveData");
+
+        Object[] obj = MainValuesFactory.getDurationCurveData(gauge);
+
+        int[]    days = (int[]) obj[0];
+        double[] qs   = (double[]) obj[1];
+
+        double[] interpolatedW = new double[qs.length];
+        interpolatedW          = wst.interpolateW(location, qs, interpolatedW);
+
+        WQDay wqday = new WQDay(qs.length);
+
+        for (int i = 0; i < days.length; i++) {
+            wqday.add(days[i], interpolatedW[i], qs[i]);
+        }
+
+        return wqday;
+    }
+
+
+    /**
+     * Returns the data that is computed by a discharge curve computation.
+     *
+     * @return the data computed by a discharge curve computation.
+     */
+    public WQKms getComputedDischargeCurveData()
+    throws NullPointerException
+    {
+        logger.debug("WINFOArtifact.getComputedDischargeCurveData");
+
+        River r = getRiver();
+
+        if (r == null) {
+            throw new NullPointerException("Cannot determine river.");
+        }
+
+        double[] locations = getLocations();
+
+        if (locations == null) {
+            throw new NullPointerException("Cannot determine location.");
+        }
+
+        WstValueTable wst = WstValueTable.getTable(r);
+        if (wst == null) {
+            throw new NullPointerException("No Wst found for selected river.");
+        }
+
+        // TODO Introduce a caching mechanism here!
+
+        return computeDischargeCurveData(wst, locations[0]);
+    }
+
+
+    /**
+     * Computes the data used to create computed discharge curves.
+     *
+     * @param wst The WstValueTable that is used for the interpolation.
+     * @param location The location where the computation should be based on.
+     *
+     * @return an object that contains tuples of W/Q values at the specified
+     * location.
+     */
+    public static WQKms computeDischargeCurveData(
+        WstValueTable wst,
+        double location)
+    {
+        logger.info("WINFOArtifact.computeDischargeCurveData");
+
+        double[][] wqs = wst.interpolateWQ(location);
+
+        if (wqs == null) {
+            logger.error("Cannot compute discharge curve data.");
+            return null;
+        }
+
+        double[] ws = wqs[0];
+        double[] qs = wqs[1];
+
+        WQKms wqkms = new WQKms(ws.length);
+
+        for (int i = 0; i < ws.length; i++) {
+            wqkms.add(ws[i], qs[i], location);
+        }
+
+        return wqkms;
+    }
+
+
+    /**
+     * Returns the data computed by the discharge longitudinal section
+     * computation.
+     *
+     * @return an array of WQKms object - one object for each given Q value.
+     */
+    public WQKms[] getDischargeLongitudinalSectionData() {
+        logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");
+
+        River river = getRiver();
+        if (river == null) {
+            logger.error("No river selected.");
+        }
+
+        WstValueTable wst = WstValueTable.getTable(river);
+        if (wst == null) {
+            logger.error("No Wst found for selected river.");
+        }
+
+        double[][] dist = getSplittedDistance();
+        int        num  = dist != null ? dist.length : 0;
+
+        WQKms[][] wqkms = new WQKms[num][];
+
+        for (int i = 0; i < num; i++) {
+            double[] kms = getKms(dist[i]);
+            if (kms == null) {
+                // XXX maybe we should cancel this operation here.
+                continue;
+            }
+
+            double[] qs = getQs(dist[i]);
+            if (qs == null) {
+                logger.debug("Determine Q values based on a set of W values.");
+
+                double[] ws = getWs(dist[i]);
+                qs = getQsForWs(ws);
+            }
+
+            wqkms[i] = computeWaterlevelData(kms, qs, wst);
+        }
+
+        WQKms[] merged    = WQKms.merge(wqkms);
+        int     numMerged = merged.length;
+        WQKms[] computed  = new WQKms[numMerged];
+
+        for (int i = 0; i < numMerged; i++) {
+            computed[i] = computeDischargeLongitudinalSectionData(merged[i]);
+        }
+
+        // TODO Introduce a caching mechanism here!
+
+        return computed;
+    }
+
+
+    /**
+     * Computes the data used for a discharge longitudinal section based on a
+     * given WQKms object. If there are backjumps while computing the data, a
+     * WQCKms object is returned, otherwise the incoming wqkms object.
+     *
+     * @param wqkms The WQKms object that contains W, Q and Kms.
+     *
+     * @return an instance of WQKms or WQCKms.
+     */
+    public static WQKms computeDischargeLongitudinalSectionData(WQKms wqkms) {
+        logger.info("WINFOArtifact.computeDischargeLongitudinalSectionData");
+
+        BackJumpCorrector bjc = new BackJumpCorrector();
+
+        bjc.doCorrection(wqkms.getKms(), wqkms.getWs());
+
+        if (bjc.hasBackJumps()) {
+            logger.info("Discharge longitudinal section has backjumps.");
+            return new WQCKms(
+                wqkms.getKms(),
+                wqkms.getQs(),
+                wqkms.getWs(),
+                bjc.getCorrected());
+        }
+        else {
+            logger.info("Discharge longitudinal section has no backjumps.");
+            return wqkms;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WaterlevelState.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,29 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+public class WaterlevelState extends DefaultState {
+
+    /** The logger that is used in this state.*/
+    private static Logger logger = Logger.getLogger(WaterlevelState.class);
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("WaterlevelState.createItems");
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/cache/CacheFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,61 @@
+package de.intevation.flys.artifacts.cache;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+
+import org.apache.log4j.Logger;
+
+public final class CacheFactory
+{
+    private static Logger log = Logger.getLogger(CacheFactory.class);
+
+    public static final String XPATH_CACHE_CONFIG_FILE =
+        "/artifact-database/cache/config-file/text()";
+
+    private CacheFactory() {
+    }
+
+    private static boolean      initialized;
+
+    private static CacheManager cacheManager;
+
+    public static final Cache getCache() {
+        return getCache(Cache.DEFAULT_CACHE_NAME);
+    }
+
+    public static final synchronized Cache getCache(String cacheName) {
+        if (!initialized) {
+            initialized = true; // try only once
+            String configFile = Config.getStringXPath(XPATH_CACHE_CONFIG_FILE);
+            if (configFile != null) {
+                configFile = Config.replaceConfigDir(configFile);
+                try {
+                    cacheManager = CacheManager.create(configFile);
+                    //System.setProperty(
+                    //  "net.sf.ehcache.enableShutdownHook", "true");
+                    Runtime.getRuntime().addShutdownHook(new Thread() {
+                        public void run() {
+                            log.info("shutting down caches");
+                            for (String name: cacheManager.getCacheNames()) {
+                                log.info("\tflushing '" + name + "'");
+                                cacheManager.getCache(name).flush();
+                            }
+                            cacheManager.shutdown();
+                        }
+                    });
+                }
+                catch (CacheException ce) {
+                    log.error("cannot configure cache", ce);
+                }
+            }
+        }
+
+        return cacheManager != null
+            ? cacheManager.getCache(cacheName)
+            : null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,57 @@
+package de.intevation.flys.artifacts.context;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.DefaultArtifactContext;
+
+
+/**
+ * This class is used to store application wide information in a global context.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FLYSContext extends DefaultArtifactContext {
+
+    /** The logger used in this class */
+    private static Logger logger = Logger.getLogger(FLYSContext.class);
+
+    /** The key that is used to store the TransitionEngine in the context */
+    public static final String TRANSITION_ENGINE_KEY =
+        "artifact.transition.engine";
+
+    /** The key that is used to store the StateEngine in the context */
+    public static final String STATE_ENGINE_KEY =
+        "artifact.state.engine";
+
+    /** The key that is used to store the Map of OutGenerator classes in the
+     * context.*/
+    public static final String OUTGENERATORS_KEY =
+        "flys.export.outgenerators";
+
+    /** The key that is used to store the map of themes in the context.*/
+    public static final String THEMES =
+        "flys.themes.map";
+
+    /** The key that is used to store a map of theme mappings in the context.*/
+    public static final String THEME_MAPPING =
+        "flys.themes.mapping.map";
+
+
+    /**
+     * The default constructor.
+     */
+    public FLYSContext() {
+        super();
+    }
+
+
+    /**
+     * A constructor with a config document.
+     */
+    public FLYSContext(Document config) {
+        super(config);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,363 @@
+package de.intevation.flys.artifacts.context;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+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.ArtifactContextFactory;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+import de.intevation.artifactdatabase.transition.Transition;
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.flys.artifacts.states.StateFactory;
+import de.intevation.flys.artifacts.transitions.TransitionFactory;
+import de.intevation.flys.themes.Theme;
+import de.intevation.flys.themes.ThemeFactory;
+
+
+/**
+ * The ArtifactContextFactory is used to initialize basic components and put
+ * them into the global context of the application.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FLYSContextFactory implements ArtifactContextFactory {
+
+    /** The logger that is used in this class */
+    private static Logger logger = Logger.getLogger(FLYSContextFactory.class);
+
+    /** The XPath to the artifacts configured in the configuration. */
+    public static final String XPATH_ARTIFACTS =
+        "/artifact-database/artifacts/artifact";
+
+    /** The XPath to the name of the artifact. */
+    public static final String XPATH_ARTIFACT_NAME = "/artifact/@name";
+
+    /** The XPath to the xlink ref in an artifact configuration. */
+    public static final String XPATH_XLINK = "xlink:href";
+
+    /** The XPath to the transitions configured in the artifact config. */
+    public static final String XPATH_TRANSITIONS =
+        "/artifact/states/transition";
+
+    /** The XPath to the states configured in the artifact config. */
+    public static final String XPATH_STATES =
+        "/artifact/states/state";
+
+    public static final String XPATH_OUTPUT_GENERATORS =
+        "/artifact-database/output-generators/output-generator";
+
+    public static final String XPATH_THEME_CONFIG =
+        "/artifact-database/flys/themes/configuration/text()";
+
+    public static final String XPATH_THEMES =
+        "/themes/theme";
+
+    public static final String XPATH_THEME_MAPPINGS =
+        "/themes/mappings/mapping";
+
+    /**
+     * Creates a new FLYSArtifactContext object and initialize all
+     * components required by the application.
+     *
+     * @param config The artifact server configuration.
+     * @return a FLYSArtifactContext.
+     */
+    public Object createArtifactContext(Document config) {
+        FLYSContext context = new FLYSContext(config);
+
+        configureTransitions(config, context);
+        configureStates(config, context);
+        configureOutGenerators(config, context);
+        configureThemes(config, context);
+        configureThemesMappings(config, context);
+
+        return context;
+    }
+
+
+    /**
+     * This method initializes the transition configuration.
+     *
+     * @param config the config document.
+     * @param context the FLYSContext.
+     */
+    protected void configureTransitions(Document config, FLYSContext context) {
+        TransitionEngine engine = new TransitionEngine();
+
+        Document[] artifacts = getArtifactConfigurations(config);
+        logger.info("Found " + artifacts.length + " artifacts in the config.");
+
+        for (Document doc: artifacts) {
+            List<Transition> transitions = new ArrayList<Transition>();
+
+            String artName = (String) XMLUtils.xpath(
+                doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
+
+            NodeList list = (NodeList) XMLUtils.xpath(
+                doc, XPATH_TRANSITIONS, XPathConstants.NODESET);
+
+            if (list == null) {
+                logger.warn("The artifact has no transitions configured.");
+                continue;
+            }
+
+            int trans = list.getLength();
+
+            logger.info(
+                "Artifact '" + artName + "' has " + trans + " transitions.");
+
+            for (int i = 0; i < trans; i++) {
+                Transition t = TransitionFactory.createTransition(list.item(i));
+                String     s = t.getFrom();
+                engine.addTransition(s, t);
+            }
+        }
+
+        context.put(FLYSContext.TRANSITION_ENGINE_KEY, engine);
+    }
+
+
+    /**
+     * This method returns all artifact documents defined in
+     * <code>config</code>. <br>NOTE: The artifact configurations need to be
+     * stored in own files referenced by an xlink.
+     *
+     * @param config The global configuration.
+     *
+     * @return an array of Artifact configurations.
+     */
+    protected Document[] getArtifactConfigurations(Document config) {
+        NodeList artifacts = (NodeList) XMLUtils.xpath(
+            config, XPATH_ARTIFACTS, XPathConstants.NODESET);
+
+        int count = artifacts.getLength();
+
+        Document[] artifactDocs = new Document[count];
+
+        for (int i = 0; i < count; i++) {
+            Element tmp = (Element) artifacts.item(i);
+
+            String xlink = tmp.getAttribute(XPATH_XLINK);
+            xlink        = Config.replaceConfigDir(xlink);
+
+            File artifactFile = new File(xlink);
+            artifactDocs[i]   = XMLUtils.parseDocument(artifactFile);
+        }
+
+        return artifactDocs;
+    }
+
+
+    /**
+     * This method initializes the transition configuration.
+     *
+     * @param config the config document.
+     * @param context the FLYSContext.
+     */
+    protected void configureStates(Document config, FLYSContext context) {
+        StateEngine engine = new StateEngine();
+
+        Document[] artifacts = getArtifactConfigurations(config);
+        logger.info("Found " + artifacts.length + " artifacts in the config.");
+
+        for (Document doc: artifacts) {
+            List<State> states = new ArrayList<State>();
+
+            String artName = (String) XMLUtils.xpath(
+                doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
+
+            NodeList stateList = (NodeList) XMLUtils.xpath(
+                doc, XPATH_STATES, XPathConstants.NODESET);
+
+            if (stateList == null) {
+                logger.warn("The artifact has no states configured.");
+                continue;
+            }
+
+            int count = stateList.getLength();
+
+            logger.info(
+                "Artifact '" + artName + "' has " + count + " states.");
+
+            for (int i = 0; i < count; i++) {
+                states.add(StateFactory.createState(
+                    stateList.item(i)));
+            }
+
+            engine.addStates(artName, states);
+        }
+
+        context.put(FLYSContext.STATE_ENGINE_KEY, engine);
+    }
+
+
+    /**
+     * This method intializes the provided output generators.
+     *
+     * @param config the config document.
+     * @param context the FLYSContext.
+     */
+    protected void configureOutGenerators(Document config, FLYSContext context){
+        Map<String, Class> generators = new HashMap<String, Class>();
+
+        NodeList outGenerators = (NodeList) XMLUtils.xpath(
+            config,
+            XPATH_OUTPUT_GENERATORS,
+            XPathConstants.NODESET);
+
+        int num = outGenerators == null ? 0 : outGenerators.getLength();
+
+        if (num == 0) {
+            logger.warn("No output generators configured in this application.");
+            return;
+        }
+
+        logger.info("Found " + num + " configured output generators.");
+
+        int idx = 0;
+
+        for (int i = 0; i < num; i++) {
+            Node item = outGenerators.item(i);
+
+            String name = (String) XMLUtils.xpath(
+                item, "@name", XPathConstants.STRING);
+
+            String clazz = (String) XMLUtils.xpath(
+                item, "text()", XPathConstants.STRING);
+
+            if (name == null || clazz == null) {
+                continue;
+            }
+
+            try {
+                generators.put(name, Class.forName(clazz));
+
+                idx++;
+            }
+            catch (ClassNotFoundException cnfe) {
+                logger.warn(cnfe, cnfe);
+            }
+        }
+
+        logger.info("Successfully loaded " + idx + " output generators.");
+        context.put(FLYSContext.OUTGENERATORS_KEY, generators);
+    }
+
+
+    /**
+     * This methods reads the configured themes and puts them into the
+     * FLYSContext.
+     *
+     * @param config The global configuration.
+     * @param context The FLYSContext.
+     */
+    protected void configureThemes(Document config, FLYSContext context) {
+        logger.debug("FLYSContextFactory.configureThemes");
+
+        Document cfg = getThemeConfig(config);
+
+        NodeList themes = (NodeList) XMLUtils.xpath(
+            cfg, XPATH_THEMES, XPathConstants.NODESET);
+
+        int num = themes != null ? themes.getLength() : 0;
+
+        if (num == 0) {
+            logger.warn("There are no themes configured!");
+            return;
+        }
+
+        logger.debug("Found " + num + " configured themes.");
+
+        Map<String, Theme> theThemes = new HashMap<String, Theme>();
+
+        for (int i = 0; i < num; i++) {
+            Node theme = themes.item(i);
+
+            Theme theTheme = ThemeFactory.createTheme(cfg, theme);
+
+            if (theme != null) {
+                theThemes.put(theTheme.getName(), theTheme);
+            }
+        }
+
+        logger.debug("Initialized " + theThemes.size() + "/" + num + " themes");
+
+        context.put(FLYSContext.THEMES, theThemes);
+    }
+
+
+    /**
+     * This method is used to retrieve the theme configuration document.
+     *
+     * @param config The global configuration.
+     *
+     * @return the theme configuration.
+     */
+    protected Document getThemeConfig(Document config) {
+        String themeConfig = (String) XMLUtils.xpath(
+            config,
+            XPATH_THEME_CONFIG,
+            XPathConstants.STRING);
+
+        themeConfig = Config.replaceConfigDir(themeConfig);
+
+        logger.debug("Parse theme cfg: " + themeConfig);
+
+        return XMLUtils.parseDocument(new File(themeConfig));
+    }
+
+
+    protected void configureThemesMappings(Document cfg, FLYSContext context) {
+        logger.debug("FLYSContextFactory.configureThemesMappings");
+
+        Document config = getThemeConfig(cfg);
+
+        NodeList mappings = (NodeList) XMLUtils.xpath(
+            config, XPATH_THEME_MAPPINGS, XPathConstants.NODESET);
+
+        int num = mappings != null ? mappings.getLength() : 0;
+
+        if (num == 0) {
+            logger.warn("No theme <--> facet mappins found!");
+            return;
+        }
+
+        Map<String, String> mapping = new HashMap<String, String>();
+
+        for (int i = 0; i < num; i++) {
+            Node node = mappings.item(i);
+
+            String from = (String) XMLUtils.xpath(
+                node, "@from", XPathConstants.STRING);
+
+            String to = (String) XMLUtils.xpath(
+                node, "@to", XPathConstants.STRING);
+
+            if (from != null && to != null) {
+                mapping.put(from, to);
+            }
+        }
+
+        logger.debug("Found " + mapping.size() + " theme/facet mappings.");
+
+        context.put(FLYSContext.THEME_MAPPING, mapping);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,68 @@
+package de.intevation.flys.artifacts.context;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import org.hibernate.Session;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallContext.Listener;
+
+
+/**
+ * This CallContextListener is used to initialize a ThreadLocal variable in
+ * each CallContext (for each request) that holds Sessions.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class SessionCallContextListener implements Listener {
+
+    public static final String SESSION_KEY = "context.session";
+
+    /** The logger that is used in this class.*/
+    private static Logger logger =
+        Logger.getLogger(SessionCallContextListener.class);
+
+
+    public SessionCallContextListener() {
+    }
+
+
+    public void setup(Document config, Node listenerConfig) {
+        // nothing to do here
+    }
+
+
+    /**
+     * Initializes a ThreadLocal variable that is used to hold sessions.
+     *
+     * @param context The CallContext.
+     */
+    public void init(CallContext context) {
+        logger.debug("SessionCallContextListener.init");
+
+        Session session = SessionHolder.acquire();
+
+        context.putContextValue(SESSION_KEY, session);
+    }
+
+
+    /**
+     * Closes open sessions of the ThreadLocal variable opened in init().
+     *
+     * @param context The CallContext.
+     */
+    public void close(CallContext context) {
+        logger.debug("SessionCallContextListener.close");
+
+        Session session = (Session)context.getContextValue(SESSION_KEY);
+        session.close();
+
+        SessionHolder.release();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,228 @@
+package de.intevation.flys.artifacts.math;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
+
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+
+import org.apache.commons.math.ArgumentOutsideDomainException;
+
+import org.apache.log4j.Logger;
+
+public class BackJumpCorrector
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(BackJumpCorrector.class);
+
+    protected ArrayList<Double> backjumps;
+
+    protected double [] corrected;
+
+    public BackJumpCorrector() {
+        backjumps = new ArrayList<Double>();
+    }
+
+    public boolean hasBackJumps() {
+        return !backjumps.isEmpty();
+    }
+
+    public List<Double> getBackJumps() {
+        return backjumps;
+    }
+
+    public double [] getCorrected() {
+        return corrected;
+    }
+
+    private static final boolean isIncreasing(double [] ws) {
+        int inc = 0;
+        int dec = 0;
+        int sweet = (ws.length-1)/2;
+        for (int i = 1; i < ws.length; ++i) {
+            if (ws[i] > ws[i-1]) {
+                if (++inc > sweet) {
+                    return true;
+                }
+            }
+            else if (++dec > sweet) {
+                return false;
+            }
+        }
+        return inc > sweet;
+    }
+
+    public boolean doCorrection(double [] km, double [] ws) {
+        boolean isIncreasing = isIncreasing(ws);
+
+        if (isIncreasing) {
+            // mirror along x axis to simulate decreasing values
+            ws = (double [])ws.clone();
+            for (int i = 0; i < ws.length; ++i) {
+                ws[i] = -ws[i];
+            }
+        }
+
+        boolean hasBackJumps = doCorrectionClean(km, ws);
+
+        if (hasBackJumps && isIncreasing) {
+            // mirror back
+            for (int i = 0; i < corrected.length; ++i) {
+                corrected[i] = -corrected[i];
+            }
+        }
+
+        return hasBackJumps;
+    }
+
+    protected boolean doCorrectionClean(double [] km, double [] ws) {
+        int N = km.length;
+
+        if (N != ws.length) {
+            throw new IllegalArgumentException("km.length != ws.length");
+        }
+
+        if (N < 2) {
+            return false;
+        }
+
+        SplineInterpolator interpolator = null;
+
+        for (int i = 1; i < N; ++i) {
+            if (ws[i] <= ws[i-1]) {
+                // no back jump
+                continue;
+            }
+            backjumps.add(km[i]);
+
+            if (corrected == null) {
+                // lazy cloning
+                ws = corrected = (double [])ws.clone();
+            }
+
+            int    back = i-2;
+            double distance = Math.abs(km[i] - km[i-1]);
+            double rest = 0.0;
+            double ikm  = 0.0;
+
+            while (back > -1) {
+                if (ws[back] < ws[i]) { // under water
+                    // continue scanning backwards
+                    distance += Math.abs(km[back+1] - km[back]);
+                    --back;
+                    continue;
+                }
+                if (ws[back] > ws[i]) { // over water
+                    // need to calculate intersection
+                    log.debug("intersection case");
+                    double m = (km[back+1]-km[back])/(ws[back+1]-ws[back]);
+                    double b = km[back]-ws[back]*m;
+                    ikm = m*ws[i] + b;
+                    distance += Math.abs(ikm - km[back+1]);
+                    rest = Math.abs(km[back] - ikm);
+                }
+                else {
+                    // equals height at ws[back]
+                    log.debug("exact height match");
+                    distance += Math.abs(km[back+1] - km[back]);
+                    ikm = km[back];
+                }
+                break;
+            }
+
+            if (back < 0) {
+                log.debug("run over left border");
+                // fill with same as ws[i]
+                for (int j = 0; j < i; ++j) {
+                    ws[j] = ws[i];
+                }
+                continue;
+            }
+
+            double quarterDistance = 0.25*distance;
+
+            // Now further seek back for the max height
+
+            double restDistance = Math.max(0.0, quarterDistance - rest);
+            --back;
+
+            double mkmw = ws[i];
+            double mkm  = km[0];
+
+            while (back > -1) {
+                double d = Math.abs(km[back+1] - km[back]);
+                restDistance -= d;
+                if (restDistance > 0.0) {
+                    --back;
+                    continue;
+                }
+                if (restDistance < 0.0) {
+                    // need to calculate intersection
+                    if (km[back] == km[back+1]) { // should not happen
+                        mkm = km[back];
+                        mkmw = 0.5*(ws[back] + ws[back+1]);
+                    }
+                    else {
+                        double m = (ws[back+1]-ws[back])/(km[back+1]-km[back]);
+                        double b = ws[back] - km[back]*m;
+                        mkm = km[back] + restDistance;
+                        mkmw = m*mkm + b;
+                    }
+                }
+                else {
+                    // exact match
+                    mkm  = km[back];
+                    mkmw = ws[back];
+                }
+                break;
+            }
+
+            double factor = back >= 0 && Math.abs(restDistance) < 1e-4
+                ? 1.0
+                : 1.0 - Math.min(1, Math.max(0, restDistance/quarterDistance));
+
+            double ikmw = factor*0.25*(mkmw-ws[i]) + ws[i];
+
+            double end = ikm + quarterDistance*factor;
+
+            double [] x = { mkm,  ikm,  end   };
+            double [] y = { mkmw, ikmw, ws[i] };
+
+            if (interpolator == null) {
+                interpolator = new SplineInterpolator();
+            }
+
+            PolynomialSplineFunction spline = interpolator.interpolate(x, y);
+
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("spline points:");
+                    for (int j = 0; j < x.length; ++j) {
+                        log.debug(x[j] + " " + y[j] + " " + spline.value(x[j]));
+                    }
+                }
+
+                for (back = Math.max(back, 0);
+                     back < i && km[back] < end; 
+                     ++back
+                ) {
+                    // to 3/4 point fill with spline values
+                    ws[back] = spline.value(km[back]);
+                }
+                while (back < i) {
+                    // fill the rest with ws[i]
+                    ws[back++] = ws[i];
+                }
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                log.error("spline interpolation failed", aode);
+            }
+        } // for all km
+
+        return !backjumps.isEmpty();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/LinearRemap.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,78 @@
+package de.intevation.flys.artifacts.math;
+
+public class LinearRemap
+{
+    public static class Segment {
+
+        protected Segment next;
+
+        protected double from;
+        protected double to;
+
+        protected double m;
+        protected double b;
+
+        public Segment() {
+        }
+
+        public Segment(
+            double from, double to, 
+            double m,    double b,
+            Segment next
+        ) {
+            this.from = from;
+            this.to   = to;
+            this.m    = m;
+            this.b    = b;
+        }
+
+        public double eval(double x) {
+            return m*x + b;
+        }
+    } // class Segment
+
+    protected Segment head;
+
+    public LinearRemap() {
+    }
+
+    public void add(
+        double from, double to,
+        double x1,   double y1,
+        double x2,   double y2
+    ) {
+        // y1 = m*x1 + b <=> b = y1 - m*x1
+        // y2 = m*x2 + b
+        // y2 - y1 = m*(x2 - x1)
+        // m = (y2 - y1)/(x2 - x1)
+
+        double m, b;
+
+        if (x2 == x1) {
+            m = 0.0;
+            b = 0.5*(y2 + y1);
+        }
+        else {
+            m = (y2 - y1)/(x2 - x1);
+            b = y1 - m*x1;
+        }
+
+        head = new Segment(from, to, m, b, head);
+    }
+
+    public double eval(double pos, double x) {
+        Segment current = head;
+
+        while (current != null) {
+
+            if (pos >= current.from && pos <= current.to) {
+                return current.eval(x);
+            }
+
+            current = current.next;
+        }
+
+        return Double.NaN;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class AnnotationsFactory {
+
+    public static List<Annotation> getAnnotations(River river) {
+        return getAnnotations(river.getName());
+    }
+
+
+    public static List<Annotation> getAnnotations(String river) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query rangesQuery = session.createQuery(
+            "from Range where river.name=:name");
+        rangesQuery.setParameter("name", river);
+        List<Range> ranges = rangesQuery.list();
+
+        Query query = session.createQuery(
+            "from Annotation where range in (:ranges) order by range.a");
+        query.setParameterList("ranges", ranges);
+        return query.list();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,234 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import java.io.Serializable;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.DischargeTableValue;
+
+public class DischargeTables
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(DischargeTables.class);
+
+    public static final double DEFAULT_SCALE = 100.0;
+
+    public static final int MASTER = 0;
+
+    protected List<String> gaugeNames;
+
+    protected String riverName;
+
+    protected double scale;
+
+    protected int    kind;
+
+    protected Map<String, double [][]> values;
+
+    public DischargeTables() {
+    }
+
+    public DischargeTables(String riverName, String gaugeName) {
+        this(riverName, gaugeName, MASTER);
+    }
+
+    public DischargeTables(String riverName, String gaugeName, int kind) {
+        this(riverName, new String [] { gaugeName }, kind);
+    }
+
+    public DischargeTables(String riverName, String [] gaugeNames) {
+        this(riverName, gaugeNames, MASTER);
+    }
+
+    public DischargeTables(String riverName, String [] gaugeNames, int kind) {
+        this(riverName, Arrays.asList(gaugeNames), kind);
+    }
+
+    public DischargeTables(
+        String       riverName,
+        List<String> gaugeNames,
+        int          kind
+    ) {
+        scale           = Double.NaN;
+        this.kind       = kind;
+        this.riverName  = riverName;
+        this.gaugeNames = gaugeNames;
+    }
+
+    public double [][] getFirstTable() {
+        return getFirstTable(DEFAULT_SCALE);
+    }
+
+    public double [][] getFirstTable(double scale) {
+        Map<String, double [][]> values = getValues(scale);
+        for (double [][] table: values.values()) {
+            return table;
+        }
+        return null;
+    }
+
+    public Map<String, double [][]> getValues() {
+        return getValues(DEFAULT_SCALE);
+    }
+
+    public Map<String, double [][]> getValues(double scale) {
+        if (values == null || scale != this.scale) {
+            values = loadValues(scale);
+            this.scale = scale;
+        }
+        return values;
+    }
+
+    protected Map<String, double [][]> loadValues(double scale) {
+        Map<String, double [][]> values = new HashMap<String, double [][]>();
+
+        Session session = SessionHolder.HOLDER.get();
+
+        Query gaugeQuery = session.createQuery(
+            "from Gauge where name=:gauge and river.name=:river");
+        gaugeQuery.setParameter("river", riverName);
+
+        for (String gaugeName: gaugeNames) {
+            gaugeQuery.setParameter("gauge", gaugeName);
+            List<Gauge> gauges = gaugeQuery.list();
+            if (gauges.isEmpty()) {
+                log.warn(
+                    "no gauge '"+gaugeName+"' at river '"+riverName+"'");
+                continue;
+            }
+            Gauge gauge = gauges.get(0);
+
+            List<DischargeTable> tables = gauge.getDischargeTables();
+
+            if (tables.isEmpty()) {
+                log.warn(
+                    "no discharge table for gauge '" + gaugeName + "'");
+                continue;
+            }
+
+            // TODO: Filter by time interval
+            DischargeTable table = tables.get(0);
+
+            List<DischargeTableValue> dtvs =
+                table.getDischargeTableValues();
+
+            final double [][] vs = new double[2][dtvs.size()];
+
+            boolean qSorted = true;
+
+            double lastQ = -Double.MAX_VALUE;
+
+            int idx = 0;
+            for (DischargeTableValue dtv: dtvs) {
+                double q = dtv.getQ().doubleValue();
+                vs[0][idx] = q * scale;
+                vs[1][idx] = dtv.getW().doubleValue() * scale;
+                ++idx;
+
+                if (qSorted && lastQ > q) {
+                    qSorted = false;
+                }
+                lastQ = q;
+            }
+
+            if (!qSorted) {
+                log.debug("need to sort by q values");
+                // TODO: Do this db level.
+                // XXX: This is so ugly :-(
+                Integer [] indices = new Integer[vs[0].length];
+                for (int i = 0; i < indices.length; ++i) {
+                    indices[i] = i;
+                }
+
+                Arrays.sort(indices, new Comparator<Integer>() {
+                    public int compare(Integer a, Integer b) {
+                        double va = vs[1][a];
+                        double vb = vs[1][b];
+                        double d = va - vb;
+                        if (d < 0.0) return -1;
+                        if (d > 0.0) return +1;
+                        return 0;
+                    }
+                });
+
+                double [][] vs2 = new double[2][vs[0].length];
+                for (int i = 0; i < indices.length; ++i) {
+                    vs2[0][i] = vs[0][indices[i]];
+                    vs2[1][i] = vs[1][indices[i]];
+                }
+                values.put(gaugeName, vs2);
+            }
+            else {
+                values.put(gaugeName, vs);
+            }
+
+        }
+
+        return values;
+    }
+
+    public static double getQForW(double [][] values, double w) {
+
+        boolean debug = log.isDebugEnabled();
+
+        if (debug) {
+            log.debug("calculating getQForW(" + w + ")");
+        }
+
+        int index = Arrays.binarySearch(values[0], w);
+        if (index >= 0) {
+            return values[0][index];
+        }
+
+        index = -index - 1; // insert position
+
+        if (index < 1 || index >= values[0].length) {
+            // do not extraploate
+            if (debug) {
+                log.debug("we do not extrapolate: NaN");
+            }
+            return Double.NaN;
+        }
+
+        double w1 = values[0][index-1];
+        double w2 = values[0][index  ];
+        double q1 = values[1][index-1];
+        double q2 = values[1][index  ];
+
+        // q1 = m*w1 + b
+        // q2 = m*w2 + b
+        // q2 - q1 = m*(w2 - w1)
+        // m = (q2 - q1)/(w2 - w1) # w2 != w1
+        // b = q1 - m*w1
+
+        double q;
+        if (w1 == w2) {
+            q = 0.5*(q1 + q2);
+            if (debug) {
+                log.debug("same w1 and w1: " + w1);
+            }
+        }
+        else {
+            double m = (q2 - q1)/(w2 - w1);
+            double b = q1 - m*w1;
+            q = w*m + b;
+        }
+        if (debug) {
+            log.debug("Q(" + w + ") = " + q);
+        }
+        return q;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,57 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.Range;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+public class GaugesFactory
+{
+    public static List<Gauge> getGauges(River river) {
+        return getGauges(river.getName());
+    }
+
+    public static List<Gauge> getGauges(String river) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from Gauge where river.name=:name");
+        query.setParameter("name", river);
+        return query.list();
+    }
+
+    public static List<Gauge> filterRanges(
+        List<Gauge>     gauges,
+        List<double []> ranges
+    ) {
+        // XXX: Inefficent!
+        ArrayList<Range> rs = new ArrayList<Range>();
+        for (double [] range: ranges) {
+            double a = range[0];
+            double b = range[1];
+            rs.add(new Range(Math.min(a, b), Math.max(a, b), null));
+        }
+        return filter(gauges, rs);
+    }
+
+    public static List<Gauge> filter(List<Gauge> gauges, List<Range> ranges) {
+        // TODO: Make it an HQL filter!
+        ArrayList<Gauge> out = new ArrayList<Gauge>();
+        for (Gauge gauge: gauges) {
+            Range range = gauge.getRange();
+            for (Range cmp: ranges) {
+                if (range.intersects(cmp)) {
+                    out.add(gauge);
+                    break;
+                }
+            }
+        }
+        return out;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MainValuesFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,80 @@
+package de.intevation.flys.artifacts.model;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValue;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MainValuesFactory {
+
+    private static Logger logger = Logger.getLogger(MainValuesFactory.class);
+
+    public static List<MainValue> getMainValues(Gauge gauge) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from MainValue where gauge=:gauge");
+        query.setParameter("gauge", gauge);
+
+        return query.list();
+    }
+
+
+    /**
+     * Returns an array of [days, qs] necessary to create duration curves.
+     *
+     * @param gauge The selected gauge.
+     *
+     * @return a 2dim array of [days, qs] where days is an int[] and qs is
+     * an double[].
+     */
+    public static Object[] getDurationCurveData(Gauge gauge) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "select cast(nmv.name as integer) as days, mv.value as q " +
+            "from MainValue as mv " +
+            "join mv.mainValue as nmv " +
+            "join nmv.type mvt " +
+            "where mvt.name = 'D' and mv.gauge.id = :gauge_id " +
+            "order by days");
+
+        query.setParameter("gauge_id", gauge.getId());
+
+        List<Object> results = query.list();
+        int[]        days    = new int[results.size()];
+        double[]     qs      = new double[results.size()];
+
+        int idx = 0;
+
+        for (Object obj: results) {
+            Object[] arr = (Object[]) obj;
+
+            try {
+                int  day = ((Integer)    arr[0]).intValue();
+                double q = ((BigDecimal) arr[1]).doubleValue();
+
+                days[idx] = day;
+                qs[idx++] = q;
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        return new Object[] { days, qs };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,57 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+
+
+public class ManagedFacet extends DefaultFacet {
+
+    /** The uuid of the owner artifact.*/
+    protected String uuid;
+
+    /** A property that determines the position of this facet.*/
+    protected int position;
+
+    /** A property that determines if this facet is active or not.*/
+    protected int active;
+
+
+    public ManagedFacet(
+        String  name,
+        String  desc,
+        String  uuid,
+        int     pos,
+        int     active)
+    {
+        super(name, desc);
+
+        this.uuid     = uuid;
+        this.position = pos;
+        this.active   = active;
+    }
+
+
+    public void setPosition(int pos) {
+        this.position = pos;
+    }
+
+
+    public int getPosition() {
+        return position;
+    }
+
+
+    public void setActive(int active) {
+        this.active = active;
+    }
+
+
+    public int getActive() {
+        return active;
+    }
+
+
+    public String getArtifact() {
+        return uuid;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/RangeWithValues.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,41 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RangeWithValues implements Serializable {
+
+    protected double   lower;
+    protected double   upper;
+    protected double[] values;
+
+
+    public RangeWithValues() {
+    }
+
+
+    public RangeWithValues(double lower, double upper, double[] values) {
+        this.lower  = lower;
+        this.upper  = upper;
+        this.values = values;
+    }
+
+
+    public double getLower() {
+        return lower;
+    }
+
+
+    public double getUpper() {
+        return upper;
+    }
+
+
+    public double[] getValues() {
+        return values;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,52 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.River;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RiverFactory {
+
+    /** We don't need to instantiate concrete objects of this class. */
+    private RiverFactory() {
+    }
+
+
+    /**
+     * Returns all rivers that were found in the backend.
+     *
+     * @return all rivers.
+     */
+    public static List<River> getRivers() {
+        Session session = SessionHolder.HOLDER.get();
+        return (List<River>)session
+            .createQuery("from River order by name").list();
+    }
+
+
+    /**
+     * Returns a River object fetched from database based on its name.
+     *
+     * @param river The name of a river.
+     *
+     * @return the River object.
+     */
+    public static River getRiver(String river) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from River where name =:name");
+        query.setParameter("name", river);
+
+        List<River> rivers = query.list();
+
+        return (rivers != null && rivers.size() > 0) ? rivers.get(0) : null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQCKms.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,70 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+
+/**
+ * This class represents a pool of data triples that consists of 'W', 'Q' and
+ * 'KM' data with corrected 'W' values computed by a BackJumpCorrector.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQCKms extends WQKms {
+
+    protected TDoubleArrayList cw;
+
+
+    public WQCKms(double[] kms, double[] qs, double[] ws, double[] cws) {
+        super(kms, qs, ws);
+
+        this.cw = new TDoubleArrayList(cws);
+    }
+
+
+    /**
+     * Adds a new row to this data pool with corrected W.
+     *
+     * @param w a W.
+     * @param q a Q.
+     * @param kms a Kms.
+     * @param cw The corrected W.
+     */
+    public void add(double w, double q, double kms, double cw) {
+        add(w, q, kms);
+        this.cw.add(cw);
+    }
+
+
+    /**
+     * This method returns a 4dim array of W, Q,Kms and corrected W.
+     *
+     * @param idx The position of the triple.
+     * @param dst destination array
+     *
+     * @return a 4dim array of [W, Q, Kms, CW] in dst.
+     */
+    public double[] get(int idx, double[] dst) {
+        dst = super.get(idx, dst);
+
+        if (dst.length < 4) {
+            return dst;
+        }
+
+        if (cw != null && cw.size() > idx) {
+            dst[3] = cw.get(idx);
+        }
+
+        return dst;
+    }
+
+
+    /**
+     * Returns the double array of corrected W values.
+     *
+     * @return the double array of corrected W values.
+     */
+    public double[] getCWs() {
+        return cw.toNativeArray();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQDay.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,64 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import gnu.trove.TDoubleArrayList;
+import gnu.trove.TIntArrayList;
+
+
+/**
+ * This class represents a pool of data triples that consists of 'W', 'Q' and
+ * 'Day' data.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQDay implements Serializable {
+
+    protected TIntArrayList days;
+
+    protected TDoubleArrayList ws;
+
+    protected TDoubleArrayList qs;
+
+
+    public WQDay() {
+        days = new TIntArrayList();
+        ws   = new TDoubleArrayList();
+        qs   = new TDoubleArrayList();
+    }
+
+
+    public WQDay(int capacity) {
+        days = new TIntArrayList(capacity);
+        ws   = new TDoubleArrayList(capacity);
+        qs   = new TDoubleArrayList(capacity);
+    }
+
+
+    public void add(int day, double w, double q) {
+        days.add(day);
+        ws.add(w);
+        qs.add(q);
+    }
+
+
+    public int size() {
+        return days.size();
+    }
+
+
+    public int getDay(int idx) {
+        return days.get(idx);
+    }
+
+
+    public double getW(int idx) {
+        return ws.get(idx);
+    }
+
+
+    public double getQ(int idx) {
+        return qs.get(idx);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQKms.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,158 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * This class represents a pool of data triples that consists of 'W', 'Q' and
+ * 'KM' data.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQKms implements Serializable {
+
+    private static Logger logger = Logger.getLogger(WQKms.class);
+
+
+    /** The array that contains the 'W' values.*/
+    protected TDoubleArrayList w;
+
+    /** The array that contains the 'Q' values.*/
+    protected TDoubleArrayList q;
+
+    /** The array that contains the 'KMs' values.*/
+    protected TDoubleArrayList kms;
+
+
+    public WQKms() {
+        this.w   = new TDoubleArrayList();
+        this.q   = new TDoubleArrayList();
+        this.kms = new TDoubleArrayList();
+    }
+
+    public WQKms(int capacity) {
+        this.w   = new TDoubleArrayList(capacity);
+        this.q   = new TDoubleArrayList(capacity);
+        this.kms = new TDoubleArrayList(capacity);
+    }
+
+
+    public WQKms(double[] kms, double[] qs, double[] ws) {
+        this.w   = new TDoubleArrayList(ws);
+        this.q   = new TDoubleArrayList(qs);
+        this.kms = new TDoubleArrayList(kms);
+    }
+
+
+    /**
+     * Adds a new row to this data pool.
+     *
+     * @param w a W.
+     * @param q a Q.
+     * @param kms a Kms.
+     */
+    public void add(double w, double q, double kms) {
+        this.w.add(w);
+        this.q.add(q);
+        this.kms.add(kms);
+    }
+
+
+    public void add(double[] w, double[] q, double[] kms) {
+        this.w.add(w);
+        this.q.add(q);
+        this.kms.add(kms);
+    }
+
+
+    /**
+     * Returns the number of triples stored in this data pool.
+     *
+     * @return the number of triples stored in this data pool.
+     */
+    public int size() {
+        return kms.size();
+    }
+
+    /**
+     * This method returns a triple of W, Q and Kms in a single 3dim array.
+     *
+     * @param idx The position of the triple.
+     * @param dst destination array
+     *
+     * @return a triple of [W, Q, Kms] in dst.
+     */
+    public double[] get(int idx, double [] dst) {
+        dst[0] = w  .get(idx);
+        dst[1] = q  .get(idx);
+        dst[2] = kms.get(idx);
+        return dst;
+    }
+
+
+    public double getKms(int idx) {
+        return kms.get(idx);
+    }
+
+
+    public double[] getKms() {
+        return kms.toNativeArray();
+    }
+
+
+    public double[] getWs() {
+        return w.toNativeArray();
+    }
+
+
+    public double[] getQs() {
+        return q.toNativeArray();
+    }
+
+
+    /**
+     * Returns a string that consist of the first and last kilometer.
+     *
+     * @return a string that consist of the first and last kilometer.
+     */
+    public String toString() {
+        double from = getKms(0);
+        double to   = getKms(size()-1);
+        return Double.toString(from) + " - " + Double.toString(to);
+    }
+
+
+    /**
+     * Merges the WQKms objects of an incoming 2dim array (table) where the
+     * objects of a column belong together.
+     *
+     * @param toMerge The objects that need to be merged.
+     *
+     * @return an array of merged WQKms objects.
+     */
+    public static WQKms[] merge(WQKms[][] toMerge) {
+        int num = toMerge[0].length;
+
+        // TODO IS THE LENGTH CORRECT?
+        WQKms[] merged = new WQKms[num];
+        for (int i = 0; i < num; i++) {
+            merged[i] = new WQKms();
+        }
+
+        for (int i = 0; i < toMerge.length; i++) {
+            WQKms[] tmp = toMerge[i];
+
+            for (int j = 0; j < num; j++) {
+                WQKms toAdd = tmp[j];
+                merged[j].add(toAdd.getWs(), toAdd.getQs(), toAdd.getKms());
+            }
+        }
+
+        return merged;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,41 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WstFactory {
+
+    /** We don't need to instantiate concrete objects of this class. */
+    private WstFactory() {
+    }
+
+
+    /**
+     * Returns the Wst object for a given river.
+     *
+     * @param river The river.
+     *
+     * @return the Wst of <i>river</i>.
+     */
+    public static Wst getWst(River river) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from Wst where river=:river");
+        query.setParameter("river", river);
+
+        List<Wst> wst = query.list();
+
+        return (wst != null && wst.size() > 0) ? wst.get(0) : null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,817 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+import de.intevation.flys.model.WstColumn;
+
+import de.intevation.flys.artifacts.math.LinearRemap;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Collections;
+
+import org.apache.log4j.Logger;
+
+import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
+
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+
+import org.apache.commons.math.ArgumentOutsideDomainException;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+import org.hibernate.SQLQuery;
+
+import org.hibernate.type.StandardBasicTypes;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+public class WstValueTable
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(WstValueTable.class);
+
+    public static final String CACHE_NAME = "wst-value-table";
+
+    // TODO: put this into a property file
+    public static final String SQL_POS_WQ = 
+        "SELECT position, w, q, column_pos" +
+        "    FROM wst_value_table"          +
+        "    WHERE wst_id = :wst_id";
+
+    public static final int DEFAULT_Q_STEPS = 500;
+
+    public static class Column
+    implements          Serializable
+    {
+        protected String name;
+
+        public Column() {
+        }
+
+        public Column(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+    // class Column
+
+    public static class QPosition {
+
+        protected double q;
+        protected int    index;
+        protected double weight;
+
+        public QPosition() {
+        }
+
+        public QPosition(double q, int index, double weight) {
+            this.index  = index;
+            this.weight = weight;
+        }
+
+    } // class Position
+
+    public static class Row
+    implements          Serializable, Comparable<Row>
+    {
+        double    km;
+        double [] ws;
+        double [] qs;
+        boolean   qSorted;
+
+        public Row() {
+        }
+
+        public Row(double km) {
+            this.km = km;
+        }
+
+        public Row(double km, double [] ws, double [] qs) {
+            this(km);
+            this.ws = ws;
+            this.qs = qs;
+        }
+
+        public static final double linear(
+            double x,
+            double x1, double x2,
+            double y1, double y2
+        ) {
+            // y1 = m*x1 + b
+            // y2 = m*x2 + b
+            // y2 - y1 = m*(x2 - x1)
+            // m = (y2 - y1)/(x2 - x1) # x2 != x1
+            // b = y1 - m*x1
+
+            if (x1 == x2) {
+                return 0.5*(y1 + y2);
+            }
+            double m = (y2 - y1)/(x2 - x1);
+            double b = y1 - m*x1;
+            return x*m + b;
+        }
+
+        public static final double factor(double x, double p1, double p2) {
+            // 0 = m*p1 + b <=> b = -m*p1
+            // 1 = m*p2 + b
+            // 1 = m*(p2 - p1)
+            // m = 1/(p2 - p1) # p1 != p2
+            // f(x) = x/(p2-p1) - p1/(p2-p1) <=> (x-p1)/(p2-p1)
+
+            return p1 == p2 ? 0.0 : (x-p1)/(p2-p1);
+        }
+
+        public static final double weight(double factor, double a, double b) {
+            return (1.0-factor)*a + factor*b;
+        }
+
+        public double getWForKM(Row other, int index, double km) {
+            double w1  = ws[index];
+            double w2  = other.ws[index];
+            double km1 = this.km;
+            double km2 = other.km;
+            return linear(km, km1, km2, w1, w2);
+        }
+
+        public void interpolateW(
+            Row       other,
+            double    km,
+            double [] input,
+            double [] output
+        ) {
+            double factor = factor(km, this.km, other.km);
+
+            for (int i = 0; i < input.length; ++i) {
+                double q = input[i];
+                int idx1 =       getQIndex(q);
+                int idx2 = other.getQIndex(q);
+
+                double w1 = idx1 >= 0
+                    ? ws[idx1]
+                    : interpolateW(-idx1-1, q);
+
+                double w2 = idx2 >= 0
+                    ? other.ws[idx2]
+                    : other.interpolateW(-idx2-1, q);
+
+                output[i] = weight(factor, w1, w2);
+            }
+        }
+
+        public double interpolateW(int idx, double q) {
+            return idx < 1 || idx >= qs.length
+                ? Double.NaN // do not extrapolate
+                : linear(q, qs[idx-1], qs[idx], ws[idx-1], ws[idx]);
+        }
+
+        public double interpolateW(double q) {
+            if (Double.isNaN(q)) return Double.NaN;
+            int index = getQIndex(q);
+            return index >= 0 ? ws[index] : interpolateW(-index -1, q);
+        }
+
+        public int getQIndex(double q) {
+            return qSorted ? binaryQIndex(q) : linearQIndex(q);
+        }
+
+        public int binaryQIndex(double q) {
+            return Arrays.binarySearch(qs, q);
+        }
+
+        public int linearQIndex(double q) {
+            switch (qs.length) {
+                case 0: break;
+                case 1:
+                    if (qs[0] == q) return 0;
+                    if (qs[0] <  q) return -(1+1);
+                    return -(0+1);
+                default:
+                    for (int i = 1; i < qs.length; ++i) {
+                        double qa = qs[i-1];
+                        double qb = qs[i];
+                        if (qa == q) return i-1;
+                        if (qb == q) return i;
+                        if (qa > qb) { double t = qa; qa = qb; qb = t; }
+                        if (q > qa && q < qb) return -(i+1);
+                    }
+                    return -qs.length;
+            }
+
+            return -1;
+        }
+
+        public int compareTo(Row other) {
+            double d = km - other.km;
+            if (d < 0.0) return -1;
+            if (d > 0.0) return +1;
+            return 0;
+        }
+
+        public double [][] cloneWQs() {
+            return new double [][] {
+                (double [])ws.clone(),
+                (double [])qs.clone() };
+        }
+
+        public double [][] interpolateWQ(Row other, double km, int steps) {
+
+            int W = Math.min(ws.length, other.ws.length);
+
+            if (W < 1) {
+                return new double[2][0];
+            }
+
+            double factor = factor(km, this.km, other.km);
+
+            double [] splineQ = new double[W];
+            double [] splineW = new double[W];
+
+            double minQ =  Double.MAX_VALUE;
+            double maxQ = -Double.MAX_VALUE;
+
+            for (int i = 0; i < W; ++i) {
+                double wws = weight(factor, ws[i], other.ws[i]);
+                double wqs = weight(factor, qs[i], other.qs[i]);
+                splineW[i] = wws;
+                splineQ[i] = wqs;
+                if (wqs < minQ) minQ = wqs;
+                if (wqs > maxQ) maxQ = wqs;
+            }
+
+            double stepWidth = (maxQ - minQ)/steps;
+
+            SplineInterpolator interpolator = new SplineInterpolator();
+            PolynomialSplineFunction spline =
+                interpolator.interpolate(splineQ, splineW);
+
+            double [] outWs = new double[steps];
+            double [] outQs = new double[steps];
+
+            try {
+                double q = minQ;
+                for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
+                    outWs[i] = spline.value(outQs[i] = q);
+                }
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                log.error("Spline interpolation failed.", aode);
+            }
+
+            return new double [][] { outWs, outQs };
+        }
+
+        public double [][] interpolateWQ(int steps) {
+
+            int W = ws.length;
+
+            if (W < 1) {
+                return new double[2][0];
+            }
+
+            double [] splineW = new double[W];
+            double [] splineQ = new double[W];
+
+            double minQ =  Double.MAX_VALUE; 
+            double maxQ = -Double.MAX_VALUE; 
+
+            for (int i = 0; i < W; ++i) {
+                splineW[i] = ws[i];
+                splineQ[i] = qs[i];
+                if (qs[i] < minQ) minQ = qs[i];
+                if (qs[i] > maxQ) maxQ = qs[i];
+            }
+
+            double stepWidth = (maxQ - minQ)/steps;
+
+            SplineInterpolator interpolator = new SplineInterpolator();
+
+            PolynomialSplineFunction spline =
+                interpolator.interpolate(splineQ, splineW);
+
+            double [] outWs = new double[steps];
+            double [] outQs = new double[steps];
+
+            try {
+                double q = minQ;
+                for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
+                    outWs[i] = spline.value(outQs[i] = q);
+                }
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                log.error("Spline interpolation failed.", aode);
+            }
+
+            return new double [][] { outWs, outQs };
+        }
+
+        public int ascendingWs() {
+            if (ws.length < 2) {
+                return ws.length;
+            }
+
+            int idx = 1;
+
+            for (; idx < ws.length; ++idx) {
+                if (Double.isNaN(ws[idx]) || ws[idx] < ws[idx-1]) {
+                    return idx;
+                }
+            }
+
+            return idx;
+        }
+
+        public double [][] weightWQs(Row other, double km) {
+            int W = Math.min(ws.length, other.ws.length);
+            double factor = factor(km, this.km, other.km);
+
+            double [] outWs = new double[W];
+            double [] outQs = new double[W];
+
+            for (int i = 0; i < W; ++i) {
+                outWs[i] = weight(factor, ws[i], other.ws[i]);
+                outQs[i] = weight(factor, qs[i], other.qs[i]);
+            }
+
+            return new double [][] { outWs, outQs };
+        }
+
+        public QPosition getQPosition(double q) {
+            int qIndex = getQIndex(q);
+
+            if (qIndex >= 0) {
+                // direct column match
+                return new QPosition(q, qIndex, 1.0);
+            }
+
+            qIndex = -qIndex -1;
+
+            if (qIndex < 0 || qIndex >= qs.length-1) {
+                // do not extrapolate
+                return null;
+            }
+
+            return new QPosition(
+                q, qIndex, factor(q, qs[qIndex-1], qs[qIndex]));
+        }
+
+        public QPosition getQPosition(Row other, double km, double q) {
+
+            QPosition qpt = getQPosition(q);
+            QPosition qpo = other.getQPosition(q);
+
+            if (qpt == null || qpo == null) {
+                return null;
+            }
+
+            double kmWeight = factor(km, this.km, other.km);
+
+            // XXX: Index interpolation is a bit sticky here!
+
+            int index = (int)Math.round(
+                weight(kmWeight, qpt.index, qpo.index));
+
+            double weight = weight(kmWeight, qpt.weight, qpo.weight);
+
+            return new QPosition(q, index, weight);
+        }
+
+        public void storeWQ(
+            QPosition qPosition,
+            double [] ows,
+            double [] oqs,
+            int       index
+        ) {
+            int    qIdx    = qPosition.index;
+            double qWeight = qPosition.weight;
+
+            if (qWeight == 1.0) {
+                oqs[index] = qs[qIdx];
+                ows[index] = ws[qIdx];
+            }
+            else {
+                oqs[index] = weight(qWeight, qs[qIdx-1], qs[qIdx]); 
+                ows[index] = weight(qWeight, ws[qIdx-1], ws[qIdx]); 
+            }
+        }
+
+        public void storeWQ(
+            Row       other,
+            double    km,
+            QPosition qPosition,
+            double [] ows,
+            double [] oqs,
+            int       index
+        ) {
+            double kmWeight = factor(km, this.km, other.km);
+
+            double tq, tw;
+            double oq, ow;
+
+            int    qIdx    = qPosition.index;
+            double qWeight = qPosition.weight;
+
+            if (qWeight == 1.0) {
+                tq = qs[qIdx];
+                tw = ws[qIdx];
+                oq = other.qs[qIdx];
+                ow = other.ws[qIdx];
+            }
+            else {
+                tq = weight(qWeight, qs[qIdx-1], qs[qIdx]); 
+                tw = weight(qWeight, ws[qIdx-1], ws[qIdx]); 
+                oq = weight(qWeight, other.qs[qIdx-1], other.qs[qIdx]); 
+                ow = weight(qWeight, other.ws[qIdx-1], other.ws[qIdx]); 
+            }
+
+            oqs[index] = weight(kmWeight, oq, tq);
+            ows[index] = weight(kmWeight, ow, tw);
+        }
+
+        public void storeWQ(
+            QPosition   qPosition,
+            LinearRemap remap,
+            double []   ows,
+            double []   oqs,
+            int         index
+        ) {
+            int    qIdx    = qPosition.index;
+            double qWeight = qPosition.weight;
+
+            oqs[index] = remap.eval(km, qWeight == 1.0
+                ? qs[qIdx]
+                : weight(qWeight, qs[qIdx-1], qs[qIdx])); 
+
+            ows[index] = interpolateW(oqs[index]);
+        }
+
+        public void storeWQ(
+            Row         other,
+            double      km,
+            QPosition   qPosition,
+            LinearRemap remap,
+            double []   ows,
+            double []   oqs,
+            int         index
+        ) {
+            double kmWeight = factor(km, this.km, other.km);
+
+            double tq, tw;
+            double oq, ow;
+
+            int    qIdx    = qPosition.index;
+            double qWeight = qPosition.weight;
+
+            if (qWeight == 1.0) {
+                tq = remap.eval(this.km,  qs[qIdx]);
+                oq = remap.eval(other.km, other.qs[qIdx]);
+            }
+            else {
+                tq = remap.eval(
+                    this.km,
+                    weight(qWeight, qs[qIdx-1], qs[qIdx]));
+                oq = remap.eval(
+                    other.km,
+                    weight(qWeight, other.qs[qIdx-1], other.qs[qIdx])); 
+            }
+
+            tw = interpolateW(tq);
+            ow = other.interpolateW(oq);
+
+            oqs[index] = weight(kmWeight, oq, tq);
+            ows[index] = weight(kmWeight, ow, tw);
+        }
+    }
+    // class Row
+
+    protected List<Row> rows;
+
+    protected Column [] columns;
+
+    public WstValueTable() {
+        rows = new ArrayList<Row>();
+    }
+
+    public WstValueTable(Column [] columns) {
+        this();
+        this.columns = columns;
+    }
+
+    public double [] interpolateW(double km, double [] qs) {
+        return interpolateW(km, qs, new double[qs.length]);
+    }
+
+    public double [] interpolateW(double km, double [] qs, double [] ws) {
+
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex >= 0) { // direct row match
+            Row row = rows.get(rowIndex);
+            for (int i = 0; i < qs.length; ++i) {
+                ws[i] = row.interpolateW(qs[i]);
+            }
+        }
+        else { // needs bilinear interpolation
+            rowIndex = -rowIndex -1;
+
+            if (rowIndex < 1 || rowIndex >= rows.size()) {
+                // do not extrapolate
+                Arrays.fill(ws, Double.NaN);
+            }
+            else {
+                rows.get(rowIndex-1).interpolateW(
+                    rows.get(rowIndex),
+                    km, qs, ws);
+            }
+        }
+
+        return ws;
+    }
+
+
+    public double [][] interpolateWQ(double km) {
+        return interpolateWQ(km, DEFAULT_Q_STEPS);
+    }
+
+    public double [][] interpolateWQ(double km, int steps) {
+
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex >= 0) { // direct row match
+            Row row = rows.get(rowIndex);
+            return row.interpolateWQ(steps);
+        }
+
+        rowIndex = -rowIndex -1;
+
+        if (rowIndex < 1 || rowIndex >= rows.size()) {
+            // do not extrapolate
+            return new double[2][0];
+        }
+
+        Row r1 = rows.get(rowIndex-1);
+        Row r2 = rows.get(rowIndex);
+
+        return r1.interpolateWQ(r2, km, steps);
+    }
+
+    public void interpolate(
+        double []   kms,
+        double []   ws,
+        double []   qs,
+        QPosition   qPosition,
+        LinearRemap remap
+    ) {
+        int R1 = rows.size()-1;
+
+        Row kmKey = new Row();
+        for (int i = 0; i < kms.length; ++i) {
+            kmKey.km = kms[i];
+            int rowIndex = Collections.binarySearch(rows, kmKey);
+
+            if (rowIndex >= 0) {
+                // direct row match
+                rows.get(rowIndex).storeWQ(qPosition, remap, ws, qs, i);
+                continue;
+            }
+
+            rowIndex = -rowIndex -1;
+
+            if (rowIndex < 1 || rowIndex >= R1) {
+                // do not extrapolate
+                ws[i] = qs[i] = Double.NaN;
+                continue;
+            }
+            Row r1 = rows.get(rowIndex-1);
+            Row r2 = rows.get(rowIndex);
+            r1.storeWQ(r2, kms[i], qPosition, remap, ws, qs, i);
+        }
+    }
+
+    public QPosition interpolate(
+        double    q,
+        int       referenceIndex,
+        double [] kms,
+        double [] ws,
+        double [] qs
+    ) {
+        Row kmKey = new Row(kms[referenceIndex]);
+
+        int rowIndex = Collections.binarySearch(rows, kmKey);
+
+        int R1 = rows.size()-1;
+
+        QPosition qPosition = null;
+
+        if (rowIndex >= 0) { // direct row match
+            qPosition = rows.get(rowIndex).getQPosition(q);
+        }
+        else {
+            rowIndex = -rowIndex -1;
+
+            if (rowIndex < 1 || rowIndex >= R1) {
+                // do not extrapolate
+                return null;
+            }
+
+            // interpolation case
+            Row r1 = rows.get(rowIndex-1);
+            Row r2 = rows.get(rowIndex);
+
+            qPosition = r1.getQPosition(r2, kms[referenceIndex], q);
+        }
+
+        if (qPosition == null) {
+            // we cannot locate q in reference row
+            return null;
+        }
+
+        for (int i = 0; i < kms.length; ++i) {
+            kmKey.km = kms[i];
+            rowIndex = Collections.binarySearch(rows, kmKey);
+
+            if (rowIndex >= 0) {
+                // direct row match
+                rows.get(rowIndex).storeWQ(qPosition, ws, qs, i);
+                continue;
+            }
+
+            rowIndex = -rowIndex -1;
+
+            if (rowIndex < 1 || rowIndex >= R1) {
+                // do not extrapolate
+                ws[i] = qs[i] = Double.NaN;
+                continue;
+            }
+            Row r1 = rows.get(rowIndex-1);
+            Row r2 = rows.get(rowIndex);
+            r1.storeWQ(r2, kms[i], qPosition, ws, qs, i);
+        }
+
+        return qPosition;
+    }
+
+    public static WstValueTable getTable(River river) {
+        return getTable(river, 0);
+    }
+
+    public static final class CacheKey
+    implements                Serializable
+    {
+        private int riverId;
+        private int kind;
+
+        public CacheKey(int riverId, int kind) {
+            this.riverId = riverId;
+            this.kind    = kind;
+        }
+
+        public int hashCode() {
+            return (riverId << 8) | kind;
+        }
+
+        public boolean equals(Object other) {
+            if (!(other instanceof CacheKey)) {
+                return false;
+            }
+            CacheKey o = (CacheKey)other;
+            return riverId == o.riverId && kind == o.kind;
+        }
+    } // class CacheKey
+
+    public static WstValueTable getTable(River river, int kind) {
+
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+
+        CacheKey cacheKey;
+
+        if (cache != null) {
+            cacheKey = new CacheKey(river.getId(), kind);
+            Element element = cache.get(cacheKey);
+            if (element != null) {
+                log.debug("got wst value table from cache");
+                return (WstValueTable)element.getValue();
+            }
+        }
+        else {
+            cacheKey = null;
+        }
+
+        WstValueTable valueTable = getTableUncached(river, kind);
+
+        if (cacheKey != null) {
+            log.debug("store wst value in cache");
+            Element element = new Element(cacheKey, valueTable);
+            cache.put(element);
+        }
+
+        return valueTable;
+    }
+
+    public static WstValueTable getTableUncached(River river, int kind) {
+
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from Wst where river=:river and kind=:kind");
+        query.setParameter("river", river);
+        query.setInteger("kind",    kind);
+
+        List<Wst> wsts = query.list();
+
+        if (wsts.isEmpty()) {
+            return null;
+        }
+
+        Wst wst = wsts.get(0);
+
+        // TODO: Do this sorting at database level
+        List<WstColumn> wstColumns = new ArrayList(wst.getColumns());
+        Collections.sort(wstColumns, new Comparator<WstColumn>() {
+            public int compare(WstColumn a, WstColumn b) {
+                int pa = a.getPosition();
+                int pb = b.getPosition();
+                if (pa < pb) return -1;
+                if (pa > pb) return +1;
+                return 0;
+            }
+        });
+
+        Column [] columns = new Column[wstColumns.size()];
+        for (int i = 0; i < columns.length; ++i) {
+            columns[i] = new Column(wstColumns.get(i).getName());
+        }
+
+        // using native SQL here to avoid myriad of small objects.
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_POS_WQ)
+            .addScalar("position",   StandardBasicTypes.DOUBLE)
+            .addScalar("w",          StandardBasicTypes.DOUBLE)
+            .addScalar("q",          StandardBasicTypes.DOUBLE)
+            .addScalar("column_pos", StandardBasicTypes.INTEGER);
+
+        sqlQuery.setInteger("wst_id", wst.getId());
+
+        WstValueTable valueTable = new WstValueTable(columns);
+
+        int lastColumnNo = -1;
+        Row row          = null;
+
+        Double lastQ     = -Double.MAX_VALUE;
+        boolean qSorted  = true;
+
+        for (Object  r: sqlQuery.list()) {
+            Object[] result = (Object[]) r;
+
+            double km    = (Double) result[0];
+            Double w     = (Double) result[1];
+            Double q     = (Double) result[2];
+            int columnNo = (Integer)result[3];
+
+            if (columnNo > lastColumnNo) { // new row
+                if (row != null) {
+                    row.qSorted = qSorted;
+                    valueTable.rows.add(row);
+                }
+                row = new Row(
+                    km,
+                    new double[columnNo+1],
+                    new double[columnNo+1]);
+                lastQ = -Double.MAX_VALUE;
+                qSorted = true;
+            }
+
+            row.ws[columnNo] = w != null ? w : Double.NaN;
+            row.qs[columnNo] = q != null ? q : Double.NaN;
+
+            if (qSorted && (q == null || lastQ > q)) {
+                qSorted = false;
+            }
+            lastQ = q;
+
+            lastColumnNo = columnNo;
+        }
+
+        if (row != null) {
+            valueTable.rows.add(row);
+        }
+
+        return valueTable;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/resources/Resources.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,144 @@
+package de.intevation.flys.artifacts.resources;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallMeta;
+
+/**
+ * This class provides methods for i18n.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class Resources {
+
+    /** The logger that is used in this class.*/
+    private static Logger logger = Logger.getLogger(Resources.class);
+
+    /** The singleton instance.*/
+    private static Resources INSTANCE;
+
+    /** The locales supported by this server.*/
+    protected Locale[] locales;
+
+    /**
+     * No instance of this class is necessary.
+     */
+    private Resources() {
+    }
+
+
+    /**
+     * Returns the locales supported by this server.
+     *
+     * @return the supported locales.
+     */
+    public Locale[] getLocales() {
+        if (locales == null) {
+            readLocales();
+        }
+
+        return locales;
+    }
+
+
+    /**
+     * Read the locales configured for this server.
+     */
+    protected void readLocales() {
+        // TODO IMPLEMENT ME
+
+        locales = new Locale[2];
+        locales[0] = Locale.GERMANY;
+        locales[1] = Locale.ENGLISH;
+    }
+
+
+    public static Locale getLocale(CallMeta meta) {
+        if (INSTANCE == null) {
+            INSTANCE = new Resources();
+        }
+
+        Locale[] locales = INSTANCE.getLocales();
+        return meta.getPreferredLocale(locales);
+    }
+
+
+    /**
+     * This method returns the translated value for <i>key</i> or <i>def</i> if
+     * <i>key</i> is not existing in the resource bundle.
+     *
+     * @param meta The CallMeta object of the request that contains the
+     * preferred locale.
+     * @param key The key that should be translated.
+     * @param def A default value that is returned, if <i>key</i> was not found.
+     *
+     * @return the translated message.
+     */
+    public static String getMsg(CallMeta meta, String key, String def) {
+        if (INSTANCE == null) {
+            INSTANCE = new Resources();
+        }
+
+        Locale[] locales = INSTANCE.getLocales();
+        Locale   locale  = meta.getPreferredLocale(locales);
+
+        return getMsg(locale, key, def);
+    }
+
+
+    /**
+     * Returns a translated message based on a template specified by <i>key</i>
+     * that has necessary values to be filled in.
+     *
+     * @param meta The CallMeta object.
+     * @param key The key of the template in the resource bundle.
+     * @param def the default value if no template was found with <i>key</i>.
+     * @param args The arguments that are necessary for the template.
+     *
+     * @return a translated string.
+     */
+    public static String getMsg(
+        CallMeta meta,
+        String   key,
+        String   def,
+        Object[] args)
+    {
+        String template = getMsg(meta, key, null);
+
+        if (template == null) {
+            return def;
+        }
+
+        return MessageFormat.format(template, args);
+    }
+
+
+    /**
+     * This method returns the translated value for <i>key</i> or <i>def</i> if
+     * <i>key</i> is not existing in the resource bundle.
+     *
+     * @param locale The locale.
+     * @param key The key that should be translated.
+     * @param def A default value that is returned, if <i>key</i> was not found.
+     *
+     * @return the translated message.
+     */
+    public static String getMsg(Locale locale, String key, String def) {
+        ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
+
+        try {
+            return bundle.getString(key);
+        }
+        catch (MissingResourceException mre) {
+            logger.warn("No message found for key: " + key);
+
+            return def;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,126 @@
+package de.intevation.flys.artifacts.services;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.DefaultService;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.Attribute;
+import de.intevation.flys.model.Position;
+import de.intevation.flys.model.Range;
+
+import de.intevation.flys.artifacts.model.AnnotationsFactory;
+
+import org.hibernate.Session;
+
+/**
+ * This service provides information about distances of a specified river.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DistanceInfoService extends DefaultService {
+
+    /** The logger used in this service.*/
+    private static Logger logger = Logger.getLogger(DistanceInfoService.class);
+
+
+    /**
+     * The default constructor.
+     */
+    public DistanceInfoService() {
+    }
+
+
+    public Document process(
+        Document data,
+        Object   globalContext,
+        CallMeta callMeta)
+    {
+        logger.debug("DistanceInfoService.process");
+
+        Document result = XMLUtils.newDocument();
+
+        String river = XMLUtils.xpathString(
+            data, "/art:river/text()", ArtifactNamespaceContext.INSTANCE);
+
+        if (river == null || river.trim().length() == 0) {
+            logger.warn("No river specified. Cannot return distance info!");
+            return result;
+        }
+
+        logger.debug("Search distances for river: " + river);
+
+        ElementCreator ec = new ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Session session = SessionHolder.acquire();
+        try {
+            List<Annotation> annotations = AnnotationsFactory.getAnnotations(river);
+
+            if (annotations == null || annotations.size() == 0) {
+                logger.warn("No information found for the specified river!");
+                return result;
+            }
+
+            Element all = ec.create("distances");
+
+            for (Annotation a: annotations) {
+                Element distance = buildDistanceNode(ec, a);
+
+                if (distance != null) {
+                    all.appendChild(distance);
+                }
+            }
+
+            result.appendChild(all);
+        }
+        finally {
+            session.close();
+            SessionHolder.release();
+        }
+
+        return result;
+    }
+
+
+    /**
+     * This method build an Element for a distance info.
+     *
+     * @param ec The ElementCreator.
+     * @param anno The Annotation that provides information about the distance.
+     *
+     * @return an Element that contains information about a distance.
+     */
+    protected Element buildDistanceNode(ElementCreator ec, Annotation anno) {
+        Position  pos   = anno.getPosition();
+        Range     range = anno.getRange();
+        Attribute attr  = anno.getAttribute();
+
+        BigDecimal a = range.getA();
+        BigDecimal b = range.getB();
+
+        Element distance = ec.create("distance");
+        ec.addAttr(distance, "description", pos.getValue(), true);
+        ec.addAttr(distance, "from", a != null ? a.toString() : "", true);
+        ec.addAttr(distance, "to", b != null ? b.toString() : "", true);
+        ec.addAttr(distance, "riverside", attr.getValue(), true);
+
+        return distance;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,316 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.hibernate.Session;
+
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.DefaultService;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValue;
+import de.intevation.flys.model.MainValueType;
+import de.intevation.flys.model.NamedMainValue;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.MainValuesFactory;
+import de.intevation.flys.artifacts.model.RiverFactory;
+
+
+/**
+ * This service returns the main values of a river's gauge based on the start
+ * and end point of the river.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MainValuesService extends DefaultService {
+
+    /** The logger that is used by this service.*/
+    private static Logger logger = Logger.getLogger(MainValuesService.class);
+
+
+    /** The XPath that points to the river definition of the incoming request.*/
+    public static final String XPATH_RIVER = "/art:mainvalues/art:river/text()";
+
+    /** The XPath that points to the start definition of the incoming request.*/
+    public static final String XPATH_START = "/art:mainvalues/art:end/text()";
+
+    /** The XPath that points to the end definition of the incoming request.*/
+    public static final String XPATH_END = "/art:mainvalues/art:start/text()";
+
+    /**
+     * The default constructor.
+     */
+    public MainValuesService() {
+    }
+
+
+    public Document process(
+        Document data,
+        Object   context,
+        CallMeta callMeta)
+    {
+        logger.debug("MainValuesService.process");
+
+        try {
+            River river     = getRequestedRiver(data);
+            double[] minmax = getRequestedStartEnd(data, river);
+            Gauge gauge     = river.determineGauge(minmax[0], minmax[1]);
+
+            List<MainValue> mainValues = getMainValues(river, gauge);
+
+            return buildDocument(river, gauge, mainValues, context);
+        }
+        catch (NullPointerException npe) {
+            logger.error("Could not process the request.");
+            logger.error(npe, npe);
+
+            return XMLUtils.newDocument();
+        }
+    }
+
+
+    /**
+     * This method extracts the river from the incoming request. If no river
+     * string was found or no river is found in the database based on this
+     * string a NullPointerException is thrown.
+     *
+     * @param data The incoming request data.
+     *
+     * @return the River object.
+     */
+    protected River getRequestedRiver(Document data)
+    throws    NullPointerException
+    {
+        logger.debug("MainValuesService.getRiver");
+
+        String riverStr = XMLUtils.xpathString(
+            data, XPATH_RIVER, ArtifactNamespaceContext.INSTANCE);
+
+        if (riverStr == null || riverStr.trim().length() == 0) {
+            throw new NullPointerException("No river found in the request.");
+        }
+
+        River river = RiverFactory.getRiver(riverStr);
+
+        if (river == null) {
+            throw new NullPointerException("No such river found: " + riverStr);
+        }
+
+        return river;
+    }
+
+
+    /**
+     * This method extracts the start and end point from incoming request
+     * document and returns both values in an array. If no start and end strings
+     * are found in the document, the min/max values of the <i>river</i> are
+     * returned.
+     *
+     * @param data The incoming request data.
+     * @param river The river of the request.
+     *
+     * @return the start and end point.
+     */
+    protected double[] getRequestedStartEnd(Document data, River river) {
+        logger.debug("MainValuesService.getStartEnd");
+
+        String startStr = XMLUtils.xpathString(
+            data, XPATH_START, ArtifactNamespaceContext.INSTANCE);
+
+        String endStr = XMLUtils.xpathString(
+            data, XPATH_END, ArtifactNamespaceContext.INSTANCE);
+
+        try {
+            double start = Double.parseDouble(startStr);
+            double end   = Double.parseDouble(endStr);
+
+            logger.debug("Found start: " + start);
+            logger.debug("Found end: " + end);
+
+            return new double[] { start, end };
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn(nfe, nfe);
+
+            return river.determineMinMaxDistance();
+        }
+    }
+
+
+    /**
+     * This method creates the result document that includes the main values of
+     * the specified <i>gauge</i>.
+     *
+     * @param river The river.
+     * @param gauge The gauge.
+     *
+     * @return a document that includes the main values of the specified river
+     * at the specified gauge.
+     */
+    protected List<MainValue> getMainValues(River river, Gauge gauge)
+    throws    NullPointerException
+    {
+        if (logger.isDebugEnabled()) {
+            logger.debug("MainValuesService.buildMainValues");
+            logger.debug("River: " + river.getName());
+            logger.debug("Gauge: " + gauge.getName());
+        }
+
+        Session session = SessionHolder.acquire();
+        try {
+            List<MainValue> mainValues = MainValuesFactory.getMainValues(gauge);
+
+            if (mainValues == null || mainValues.isEmpty()) {
+                throw new NullPointerException("No main values found.");
+            }
+
+            logger.debug(mainValues.size() + " main values found.");
+
+            return mainValues;
+        }
+        finally {
+            session.close();
+            SessionHolder.release();
+        }
+    }
+
+
+    protected Document buildDocument(
+        River           river,
+        Gauge           gauge,
+        List<MainValue> mainValues,
+        Object          context)
+    {
+        logger.debug("MainValuesService.buildDocument");
+
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element rootEl = cr.create("service");
+        cr.addAttr(rootEl, "name", "mainvalues");
+
+        doc.appendChild(rootEl);
+
+        appendMetaInformation(doc, rootEl, river, gauge, context);
+        appendMainValues(doc, rootEl, mainValues, context);
+
+        return doc;
+    }
+
+
+    /**
+     * This method appends some meta information to the result document.
+     * Currently, the river's and gauge's names and the gauge's range are
+     * appended.
+     *
+     * @param root The root element of the result document.
+     * @param river The river.
+     * @param gauge The gauge.
+     * @param context The context object.
+     */
+    protected void appendMetaInformation(
+        Document doc,
+        Element  root,
+        River    river,
+        Gauge    gauge,
+        Object   context)
+    {
+        logger.debug("MainValuesService.appendMetaInformation");
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Range range = gauge.getRange();
+
+        Element riverEl = cr.create("river");
+        cr.addAttr(riverEl, "name", river.getName());
+
+        Element gaugeEl = cr.create("gauge");
+        cr.addAttr(gaugeEl, "name", gauge.getName());
+        cr.addAttr(gaugeEl, "from", range.getA().toString());
+        cr.addAttr(gaugeEl, "to", range.getB().toString());
+
+        root.appendChild(riverEl);
+        root.appendChild(gaugeEl);
+    }
+
+
+    protected void appendMainValues(
+        Document        doc,
+        Element         root,
+        List<MainValue> mainValues,
+        Object          context)
+    {
+        logger.debug("MainValuesService.appendMainValues");
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element list = cr.create("mainvalues");
+
+        for (MainValue mainValue: mainValues) {
+            Element newEl = buildMainValueElement(doc, mainValue, context);
+
+            if (newEl != null) {
+                list.appendChild(newEl);
+            }
+        }
+
+        root.appendChild(list);
+    }
+
+
+    /**
+     * This method builds a concrete mainvalue element. This element consists of
+     * three attributes: the value, its name and its type.
+     *
+     * @param doc The owner document.
+     * @param mainValue The mainvalue.
+     * @param context The context object.
+     *
+     * @return a mainvalue element.
+     */
+    protected Element buildMainValueElement(
+        Document  doc,
+        MainValue mainValue,
+        Object    context)
+    {
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        NamedMainValue namedMainValue = mainValue.getMainValue();
+        MainValueType  mainValueType  = namedMainValue.getType();
+
+        Element el = cr.create("mainvalue");
+
+        cr.addAttr(el, "value", mainValue.getValue().toString());
+        cr.addAttr(el, "name", namedMainValue.getName());
+        cr.addAttr(el, "type", mainValueType.getName());
+
+        return el;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,129 @@
+package de.intevation.flys.artifacts.services;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.ServiceFactory;
+
+import de.intevation.artifactdatabase.DefaultService;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.flys.artifacts.services.meta.Builder;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import org.hibernate.Session;
+
+import org.hibernate.jdbc.Work;
+
+public class MetaDataService
+extends      DefaultService
+{
+    private static Logger log = Logger.getLogger(MetaDataService.class);
+
+    public static final String XPATH_RIVER = "/art:river/text()";
+
+    public static final String META_DATA_TEMPLATE = "/metadata/template.xml";
+
+    protected Builder builder;
+
+    public MetaDataService() {
+    }
+
+    protected static Map<String, Object> extractParameters(Document data) {
+        HashMap<String, Object> parameters = new HashMap<String, Object>();
+
+        String river = XMLUtils.xpathString(
+            data, XPATH_RIVER, ArtifactNamespaceContext.INSTANCE);
+
+        if (river == null || (river = river.trim()).length() == 0) {
+            river = "%"; // matches all rivers
+        }
+
+        parameters.put("river", river);
+
+        return parameters;
+    }
+
+    @Override
+    public Document process(
+        Document data,
+        Object   globalContext,
+        CallMeta callMeta
+    ) {
+        log.debug("MetaDataService.process");
+
+        final Document result = XMLUtils.newDocument();
+
+        if (builder == null) {
+            log.error("MetaDataService is not setup properly.");
+            return result;
+        }
+
+        final Map<String, Object> parameters = extractParameters(data);
+
+        Session session = SessionHolder.acquire();
+        try {
+            session.doWork(new Work() {
+                @Override
+                public void execute(Connection connection)
+                throws SQLException
+                {
+                    log.debug("MetaDataService.execute");
+                    builder.build(connection, result, parameters);
+                }
+            });
+        }
+        finally {
+            session.close();
+            SessionHolder.release();
+        }
+
+        return result;
+    }
+
+    @Override
+    public void setup(ServiceFactory factory, Object globalContext) {
+        log.debug("MetaDataService.setup");
+
+        InputStream in = getClass().getResourceAsStream(META_DATA_TEMPLATE);
+
+        if (in == null) {
+            log.error("cannot get template resource");
+            return;
+        }
+
+        try {
+            Document template = XMLUtils.parseDocument(in);
+            if (template == null) {
+                log.error("cannot parse meta data template");
+            }
+            else {
+                builder = new Builder(template);
+            }
+        }
+        finally {
+            try {
+                in.close();
+            }
+            catch (IOException ioe) {
+                log.error(ioe);
+            }
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/RiverService.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,80 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.DefaultService;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.RiverFactory;
+
+import org.hibernate.Session;
+
+/**
+ * This service provides information about the supported rivers by this
+ * application.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RiverService extends DefaultService {
+
+    /** The logger used in this service.*/
+    private static Logger logger = Logger.getLogger(RiverService.class);
+
+
+    /**
+     * The default constructor.
+     */
+    public RiverService() {
+    }
+
+
+    public Document process(
+        Document data,
+        Object   globalContext,
+        CallMeta callMeta)
+    {
+        logger.debug("RiverService.process");
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Session session = SessionHolder.acquire();
+        try {
+            List<River> allRivers = RiverFactory.getRivers();
+
+            Element rivers = ec.create("rivers");
+
+            for (River river: allRivers) {
+                Element r = ec.create("river");
+                ec.addAttr(r, "name", river.getName(), true);
+
+                rivers.appendChild(r);
+            }
+
+            result.appendChild(rivers);
+        }
+        finally {
+            session.close();
+            SessionHolder.release();
+        }
+
+        return result;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,289 @@
+package de.intevation.flys.artifacts.services.meta;
+
+import java.util.regex.Matcher;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+
+import java.sql.SQLException;
+import java.sql.Connection;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import org.apache.log4j.Logger;
+
+public class Builder
+{
+    private static Logger log = Logger.getLogger(Builder.class);
+
+    public static final String DC_NAMESPACE_URI =
+        "http://www.intevation.org/2011/Datacage";
+
+    protected Document template;
+
+    public class BuildHelper
+    {
+        protected Document                       output;
+        protected StackFrames                    frames;
+        protected Connection                     connection;
+        protected Map<String, CompiledStatement> statements;
+
+        public BuildHelper(
+            Document            output,
+            Connection          connection,
+            Map<String, Object> parameters
+        ) {
+            this.output     = output;
+            this.connection = connection;
+            frames          = new StackFrames(parameters);
+            statements      = new HashMap<String, CompiledStatement>();
+        }
+
+        public void build(List<Node> elements) throws SQLException {
+            try {
+                for (Node current: elements) {
+                    build(output, current);
+                }
+            }
+            finally {
+                for (CompiledStatement cs: statements.values()) {
+                    cs.close();
+                }
+                statements.clear();
+            }
+        }
+
+        protected void context(Node parent, Element current)
+        throws SQLException
+        {
+            NodeList elements = current.getElementsByTagNameNS(
+                DC_NAMESPACE_URI, "elements");
+
+            if (elements.getLength() < 1) {
+                log.warn("no elements found -> ignore");
+                return;
+            }
+
+            NodeList subs = elements.item(0).getChildNodes();
+            int S = subs.getLength();
+
+            if (S < 1) {
+                log.warn("elements is empty -> ignore");
+                return;
+            }
+
+            NodeList stmntNode = current.getElementsByTagNameNS(
+                DC_NAMESPACE_URI, "statement");
+
+            if (stmntNode.getLength() < 1) {
+                log.warn("dc:context: too less statements");
+                return;
+            }
+
+            String stmntText = stmntNode.item(0).getTextContent();
+
+            if (stmntText == null
+            || (stmntText = stmntText.trim()).length() == 0) {
+                log.warn("dc:context: no sql statement found -> ignored");
+                return;
+            }
+
+            CompiledStatement cs = statements.get(stmntText);
+
+            if (cs == null) {
+                cs = new CompiledStatement(stmntText);
+                statements.put(stmntText, cs);
+            }
+
+            ResultData rd = cs.execute(connection, frames);
+
+            String [] columns = rd.getColumnLabels();
+
+
+            for (Object [] row: rd.getRows()) {
+                frames.enter();
+                try {
+                    frames.put(columns, row);
+                    for (int i = 0; i < S; ++i) {
+                        build(parent, subs.item(i));
+                    }
+                }
+                finally {
+                    frames.leave();
+                }
+            }
+        }
+
+        protected void element(Node parent, Element current)
+        throws SQLException
+        {
+            String attr = expand(current.getAttribute("name"));
+
+            if (log.isDebugEnabled()) {
+                log.debug("dc:element -> '" + attr + "'");
+            }
+
+            if (attr.length() == 0) {
+                log.warn("no name attribute found");
+                return;
+            }
+
+            Element element = output.createElement(attr);
+
+            NodeList children = current.getChildNodes();
+            for (int i = 0, N = children.getLength(); i < N; ++i) {
+                build(element, children.item(i));
+            }
+
+            parent.appendChild(element);
+        }
+
+        protected void text(Node parent, Element current)
+        throws SQLException
+        {
+            log.debug("dc:text");
+            String value = expand(current.getTextContent());
+            parent.appendChild(output.createTextNode(value));
+        }
+
+
+        protected void attribute(Node parent, Element current) {
+
+            if (parent.getNodeType() != Node.ELEMENT_NODE) {
+                log.warn("need element here");
+                return;
+            }
+
+            String name  = expand(current.getAttribute("name"));
+            String value = expand(current.getAttribute("value"));
+
+            Element element = (Element)parent;
+
+            element.setAttribute(name, value);
+        }
+
+        protected void convert(Node parent, Element current) {
+
+            String variable = expand(current.getAttribute("var"));
+            String type     = expand(current.getAttribute("type"));
+
+            if (frames.containsKey(variable)) {
+                Object object = TypeConverter.convert(
+                    frames.get(variable),
+                    type);
+                frames.put(variable, object);
+            }
+        }
+
+        protected String expand(String s) {
+            Matcher m = CompiledStatement.VAR.matcher(s);
+
+            StringBuffer sb = new StringBuffer();
+            while (m.find()) {
+                String key = m.group(1);
+                Object value = frames.get(key);
+                m.appendReplacement(sb, value != null ? value.toString() : "");
+            }
+            m.appendTail(sb);
+            return sb.toString();
+        }
+
+        protected void build(Node parent, Node current) 
+        throws SQLException
+        {
+            String ns = current.getNamespaceURI();
+            if (ns != null && ns.equals(DC_NAMESPACE_URI)) {
+                if (current.getNodeType() != Node.ELEMENT_NODE) {
+                    log.warn("need elements here");
+                }
+                else {
+                    String localName = current.getLocalName();
+                    if ("context".equals(localName)) {
+                        context(parent, (Element)current);
+                    }
+                    else if ("attribute".equals(localName)) {
+                        attribute(parent, (Element)current);
+                    }
+                    else if ("element".equals(localName)) {
+                        element(parent, (Element)current);
+                    }
+                    else if ("text".equals(localName)) {
+                        text(parent, (Element)current);
+                    }
+                    else if ("convert".equals(localName)) {
+                        convert(parent, (Element)current);
+                    }
+                    else {
+                        log.warn("unknown '" + localName + "' -> ignore");
+                    }
+                }
+                return;
+            }
+
+            if (current.getNodeType() == Node.TEXT_NODE) {
+                String txt = current.getNodeValue();
+                if (txt != null && txt.trim().length() == 0) {
+                    return;
+                }
+            }
+
+            Node copy = output.importNode(current, false);
+
+            NodeList children = current.getChildNodes();
+            for (int i = 0, N = children.getLength(); i < N; ++i) {
+                build(copy, children.item(i));
+            }
+            parent.appendChild(copy);
+        }
+    } // class BuildHelper
+
+
+    public Builder() {
+    }
+
+    public Builder(Document template) {
+        this.template = template;
+    }
+
+    public Document build(Connection connection)
+    throws SQLException
+    {
+        return build(connection, XMLUtils.newDocument(), null);
+    }
+
+    public Document build(
+        Connection          connection,
+        Document            output,
+        Map<String, Object> parameters
+    )
+    throws SQLException
+    {
+        NodeList roots = template.getElementsByTagNameNS(
+            DC_NAMESPACE_URI, "template");
+
+        BuildHelper helper = new BuildHelper(output, connection, parameters);
+
+        List<Node> elements = new ArrayList<Node>();
+
+        for (int i = 0, N = roots.getLength(); i < N; ++i) {
+            NodeList rootChildren = roots.item(i).getChildNodes();
+            for (int j = 0, M = rootChildren.getLength(); j < M; ++j) {
+                Node child = rootChildren.item(j);
+                if (child.getNodeType() == Node.ELEMENT_NODE) {
+                    elements.add(child);
+                }
+            }
+        }
+        helper.build(elements);
+
+        return output;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/meta/CompiledStatement.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,100 @@
+package de.intevation.flys.artifacts.services.meta;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+
+public class CompiledStatement
+{
+    public static final Pattern VAR = Pattern.compile("\\$\\{([a-zA-Z0-9_]+)\\}");
+
+    protected String original;
+    protected String statement;
+
+    protected Map<String, List<Integer>> positions;
+
+    protected PreparedStatement preparedStatement;
+
+    public CompiledStatement() {
+    }
+
+    public CompiledStatement(String original) {
+        this.original = original;
+        positions = new HashMap<String, List<Integer>>();
+        compile();
+    }
+
+    protected void compile() {
+
+        StringBuffer sb = new StringBuffer();
+
+        Matcher m = VAR.matcher(original);
+
+        int index = 1;
+
+        while (m.find()) {
+            String key = m.group(1);
+            List<Integer> indices = positions.get(key);
+            if (indices == null) {
+                indices = new ArrayList<Integer>();
+                positions.put(key, indices);
+            }
+            indices.add(index);
+            m.appendReplacement(sb, "?");
+            ++index;
+        }
+
+        m.appendTail(sb);
+
+        statement = sb.toString();
+    }
+
+    public String getStatement() {
+        return statement;
+    }
+
+    public ResultData execute(Connection connection, StackFrames frames) 
+    throws SQLException
+    {
+        if (preparedStatement == null) {
+            preparedStatement = connection.prepareStatement(statement);
+        }
+
+        for (Map.Entry<String, List<Integer>> entry: positions.entrySet()) {
+            Object value = frames.get(entry.getKey());
+            for (Integer index: entry.getValue()) {
+                preparedStatement.setObject(index, value);
+            }
+        }
+
+        ResultSet result = preparedStatement.executeQuery();
+        try {
+            return new ResultData(preparedStatement.getMetaData())
+                .addAll(result);
+        }
+        finally {
+            result.close();
+        }
+    }
+
+    public void close() {
+        if (preparedStatement != null) {
+            try {
+                preparedStatement.close();
+            }
+            catch (SQLException sqle) {
+            }
+            preparedStatement = null;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/meta/ResultData.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,57 @@
+package de.intevation.flys.artifacts.services.meta;
+
+import java.sql.ResultSetMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class ResultData
+{
+    protected String [] columns;
+
+    protected List<Object []> rows;
+
+    public ResultData() {
+        rows = new ArrayList<Object []>();
+    }
+
+    public ResultData(ResultSetMetaData meta) 
+    throws SQLException
+    {
+        this();
+
+        int N = meta.getColumnCount();
+
+        columns = new String[N];
+
+        for (int i = 1; i <= N; ++i) {
+            columns[i-1] = meta.getColumnLabel(i);
+        }
+    }
+
+    public String [] getColumnLabels() {
+        return columns;
+    }
+
+    public ResultData addAll(ResultSet result) throws SQLException {
+        while (result.next()) {
+            add(result);
+        }
+        return this;
+    }
+
+    public void add(ResultSet result) throws SQLException {
+        Object [] row = new Object[columns.length];
+        for (int i = 0; i < columns.length; ++i) {
+            row[i] = result.getObject(i+1);
+        }
+        rows.add(row);
+    }
+
+    public List<Object []> getRows() {
+        return rows;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/meta/StackFrames.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,70 @@
+package de.intevation.flys.artifacts.services.meta;
+
+import java.util.Map;
+import java.util.List;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+public class StackFrames
+{
+    protected List<Map<String, Object>> frames;
+
+    public StackFrames() {
+        frames = new ArrayList<Map<String, Object>>();
+    }
+
+    public StackFrames(Map<String, Object> initialFrame) {
+        this();
+        if (initialFrame != null) {
+            frames.add(new HashMap<String, Object>(initialFrame));
+        }
+    }
+
+    public void enter() {
+        frames.add(new HashMap<String, Object>());
+    }
+
+    public void leave() {
+        frames.remove(frames.size()-1);
+    }
+
+    public void put(String key, Object value) {
+        int N = frames.size();
+        if (N > 0) {
+            frames.get(N-1).put(key, value);
+        }
+    }
+
+    public void put(String [] keys, Object [] values) {
+        Map<String, Object> top = frames.get(frames.size()-1);
+        for (int i = 0; i < keys.length; ++i) {
+            top.put(keys[i], values[i]);
+        }
+    }
+
+    public boolean containsKey(String key) {
+        for (int i = frames.size()-1; i >= 0; --i) {
+            if (frames.get(i).containsKey(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public Object get(String key) {
+        return get(key, null);
+    }
+
+    public Object get(String key, Object def) {
+
+        for (int i = frames.size()-1; i >= 0; --i) {
+            Map<String, Object> frame = frames.get(i);
+            if (frame.containsKey(key)) {
+                return frame.get(key);
+            }
+        }
+
+        return def;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/meta/TypeConverter.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,31 @@
+package de.intevation.flys.artifacts.services.meta;
+
+public class TypeConverter
+{
+    private TypeConverter() {
+    }
+
+    public static Object convert(Object object, String type) {
+
+        if (type == null) {
+            return object;
+        }
+
+        if ("Integer".equals(type)) {
+            return Integer.valueOf(object.toString());
+        }
+
+        if ("Double".equals(type)) {
+            return Double.valueOf(object.toString());
+        }
+
+        if ("String".equals(type)) {
+            return object.toString();
+        }
+
+        // TODO: Add more types
+
+        return object;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,133 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class CalculationSelect extends DefaultState {
+
+    /** The logger that is used in this class.*/
+    private static Logger logger = Logger.getLogger(CalculationSelect.class);
+
+
+    /** Constant value for the reference line calculation.*/
+    public static final String CALCULATION_SURFACE_CURVE =
+        "calc.surface.curve";
+
+    /** Constant value for the differences calculation.*/
+    public static final String CALCULATION_DURATION_CURVE =
+        "calc.duration.curve";
+
+    /** Constant value for the flood map calculation.*/
+    public static final String CALCULATION_FLOOD_MAP =
+        "calc.flood.map";
+
+    /** Constant value for the profile calculation.*/
+    public static final String CALCULATION_DISCHARGE_LONGITUDINAL_CURVE =
+        "calc.discharge.longitudinal.section";
+
+    /** Constant value for the state discharge curve calculation.*/
+    public static final String CALCULATION_DISCHARGE_CURVE =
+        "calc.discharge.curve";
+
+    /** An array that holds all available calculation modes.*/
+    public static final String[] CALCULATIONS = {
+        CALCULATION_SURFACE_CURVE,
+        CALCULATION_FLOOD_MAP,
+        CALCULATION_DISCHARGE_CURVE,
+        CALCULATION_DURATION_CURVE,
+        CALCULATION_DISCHARGE_LONGITUDINAL_CURVE };
+
+
+    /** Error message that is thrown if no mode has been chosen.*/
+    public static final String ERROR_NO_CALCULATION_MODE =
+        "error_feed_no_calculation_mode";
+
+    /** Error message that is thrown if an invalid calculation mode has been
+     * chosen.*/
+    public static  final String ERROR_INVALID_CALCULATION_MODE =
+        "error_feed_invalid_calculation_mode";
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        CallMeta meta   = context.getMeta();
+        Element[] calcs = new Element[CALCULATIONS.length];
+
+        int i = 0;
+
+        for (String calc: CALCULATIONS) {
+            calcs[i++] = createItem(
+                cr, new String[] {
+                    Resources.getMsg(meta, calc, calc),
+                    calc
+                });
+        }
+
+        return calcs;
+    }
+
+
+    protected Element createItem(XMLUtils.ElementCreator cr, Object obj) {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        String[] arr = (String[]) obj;
+
+        label.setTextContent(arr[0]);
+        value.setTextContent(arr[1]);
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        return item;
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("CalculationSelect.validate");
+
+        Map<String, StateData> data = getData();
+
+        String calc = (String) data.get("calculation_mode").getValue();
+
+        if (calc == null) {
+            throw new IllegalArgumentException(ERROR_NO_CALCULATION_MODE);
+        }
+
+        calc = calc.trim().toLowerCase();
+
+        for (String mode: CALCULATIONS) {
+            if (mode.equals(calc)) {
+                return true;
+            }
+        }
+
+        throw new IllegalArgumentException(ERROR_INVALID_CALCULATION_MODE);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,36 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * The final state that will be reached after the discharge curve calculation
+ * mode has been chosen.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputedDischargeCurveState extends DefaultState {
+
+    /** The logger that is used in this state.*/
+    private static Logger logger =
+        Logger.getLogger(ComputedDischargeCurveState.class);
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("ComputedDischargeCurveState.createItems");
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DefaultState.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,211 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+import de.intevation.artifactdatabase.state.AbstractState;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class DefaultState extends AbstractState {
+
+    /** The logger that is used in this class.*/
+    private static Logger logger = Logger.getLogger(DefaultState.class);
+
+
+    public Element describeStatic(
+        Document    document,
+        Node        root,
+        CallContext context,
+        String      uuid)
+    {
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            document,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        CallMeta meta = context.getMeta();
+
+        String label = Resources.getMsg(meta, getID(), getID());
+        Element ui   = ProtocolUtils.createArtNode(
+            creator, "state",
+            new String[] { "name", "uiprovider", "label" },
+            new String[] { getID(), getUIProvider(), label });
+
+        Map<String, StateData> theData = getData();
+        if (theData == null) {
+            return ui;
+        }
+
+        Collection<StateData> dataItems = theData.values();
+
+        for (StateData data: dataItems) {
+            String name  = data.getName();
+            String value = (String) data.getValue();
+
+            if (value == null) {
+                continue;
+            }
+
+            logger.debug("Append element '" + name + "' (" + value + ")");
+
+            Element dataElement = creator.create("data");
+            creator.addAttr(dataElement, "name", name, true);
+            creator.addAttr(dataElement, "type", data.getType(), true);
+
+            Element itemElement = creator.create("item");
+            creator.addAttr(itemElement, "value", value, true);
+            creator.addAttr(
+                itemElement,
+                "label",
+                Resources.getMsg(meta, value, value),
+                true);
+
+            dataElement.appendChild(itemElement);
+            ui.appendChild(dataElement);
+        }
+
+        return ui;
+    }
+
+
+    public Element describe(
+        Artifact    artifact,
+        Document    document,
+        Node        root,
+        CallContext context,
+        String      uuid)
+    {
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            document,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element ui        = null;
+        String uiprovider = getUIProvider();
+        if (uiprovider != null) {
+            ui  = ProtocolUtils.createArtNode(
+                creator, "dynamic",
+                new String[] { "uiprovider" },
+                new String[] { uiprovider });
+        }
+        else {
+            ui  = ProtocolUtils.createArtNode(creator, "dynamic", null, null);
+        }
+
+        Map<String, StateData> theData = getData();
+        if (theData == null) {
+            return ui;
+        }
+
+        Collection<StateData> dataItems = theData.values();
+
+        for (StateData data: dataItems) {
+            String name    = data.getName();
+            Element select = createData(creator, artifact, data, context);
+
+            Element choices = ProtocolUtils.createArtNode(
+            creator, "choices", null, null);
+
+            select.appendChild(choices);
+            ui.appendChild(select);
+
+            Element[] items = createItems(creator, artifact, name, context);
+            for (Element item: items) {
+                choices.appendChild(item);
+            }
+        }
+
+        return ui;
+    }
+
+
+    /**
+     * This method creates the root node that contains the list of selectable
+     * items.
+     *
+     * @param cr The ElementCreator.
+     * @param name The name of the amount of data.
+     *
+     * @return the root node of the item list.
+     */
+    protected Element createData(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select", null, null);
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        select.appendChild(label);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            getID(),
+            getID()));
+
+        return select;
+    }
+
+
+    /**
+     * This method creates a list of items. These items represent the amount of
+     * input data that is possible for this state.
+     *
+     * @param cr The ElementCreator.
+     * @param name The name of the amount of data.
+     *
+     * @return a list of items.
+     */
+    protected abstract Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context);
+
+
+    /**
+     * This method validates the inserted data and returns true, if everything
+     * was correct, otherwise an exception is thrown.
+     *
+     * @param artifact A reference to the owner artifact.
+     * @param context The CallContext object.
+     *
+     * @return true, if everything was fine.
+     */
+    public boolean validate(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        return true;
+    }
+
+
+    protected String getUIProvider() {
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,30 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+public class DischargeLongitudinalSection extends DefaultState {
+
+    /** The logger that is used in this state.*/
+    private static Logger logger =
+        Logger.getLogger(DischargeLongitudinalSection.class);
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("DischargeLongitudinalSection.createItems");
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,35 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * The final state that will be reached after the duration curve calculation
+ * mode has been chosen.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DurationCurveState extends DefaultState {
+
+    /** The logger that is used in this state.*/
+    private static Logger logger = Logger.getLogger(DurationCurveState.class);
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("DurationCurveState.createItems");
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,185 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.artifacts.resources.Resources;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LocationDistanceSelect extends RangeState {
+
+    /** The logger used in this class.*/
+    private static Logger logger = Logger.getLogger(LocationDistanceSelect.class);
+
+
+    /** The default step width.*/
+    public static final String DEFAULT_STEP = "100";
+
+    /** The name of the 'from' field. */
+    public static final String FROM = "ld_from";
+
+    /** The name of the 'to' field. */
+    public static final String TO = "ld_to";
+
+    /** The name of the 'step' field. */
+    public static final String STEP = "ld_step";
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public LocationDistanceSelect() {
+    }
+
+    protected Element createData(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select", null, null);
+
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        Element choices = ProtocolUtils.createArtNode(
+            cr, "choices", null, null);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            data.getName(),
+            data.getName()));
+
+        select.appendChild(label);
+
+        return select;
+    }
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        double[] minmax = getMinMaxDistance(artifact);
+
+        double minVal = Double.MIN_VALUE;
+        double maxVal = Double.MAX_VALUE;
+
+        if (minmax != null) {
+            minVal = minmax[0];
+            maxVal = minmax[1];
+        }
+        else {
+            logger.warn("Could not read min/max distance values!");
+        }
+
+        if (name.equals("ld_from")) {
+            Element min = createItem(
+                cr,
+                new String[] {"min", new Double(minVal).toString()});
+
+            return new Element[] { min };
+        }
+        else if (name.equals("ld_to")) {
+            Element max = createItem(
+                cr,
+                new String[] {"max", new Double(maxVal).toString()});
+
+            return new Element[] { max };
+        }
+        else {
+            Element step = createItem(cr, new String[] {"step", DEFAULT_STEP});
+            return new Element[] { step };
+        }
+
+    }
+
+
+    protected Element createItem(XMLUtils.ElementCreator cr, Object obj) {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        String[] arr = (String[]) obj;
+
+        label.setTextContent(arr[0]);
+        value.setTextContent(arr[1]);
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        return item;
+    }
+
+
+    protected String getUIProvider() {
+        return "location_distance_panel";
+    }
+
+
+    protected double[] getMinMaxDistance(Artifact artifact) {
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+        StateData    data         = flysArtifact.getData("river");
+
+        String name = (String) data.getValue();
+
+        logger.debug("Search for the min/max distances of '" + name + "'");
+
+        River river = RiverFactory.getRiver(name);
+
+        return river != null ? river.determineMinMaxDistance() : null;
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("LocationDistanceSelect.validate");
+
+        Map<String, StateData> data = getData();
+
+        String fromStr = (String) data.get(FROM).getValue();
+        String toStr   = (String) data.get(TO).getValue();
+        String stepStr = (String) data.get(STEP).getValue();
+
+        if (fromStr == null || toStr == null || stepStr == null) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        try {
+            double from = Double.parseDouble(fromStr);
+            double to   = Double.parseDouble(toStr);
+            double step = Double.parseDouble(stepStr);
+
+            double[] minmaxDist = getMinMaxDistance(artifact);
+
+            return validateBounds(minmaxDist[0], minmaxDist[1], from, to, step);
+        }
+        catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException("error_feed_number_format");
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,93 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.Map;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+
+/**
+ * This state is used to realize the input of multiple locations as string. The
+ * string should be a whitespace separated list of double values where each
+ * double value represents a location.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LocationSelect extends LocationDistanceSelect {
+
+    /** The logger used in this class.*/
+    private static Logger logger = Logger.getLogger(LocationSelect.class);
+
+
+    /** The name of the StateData object that stores the location string.*/
+    public static final String FIELD_LOCATIONS = "ld_locations";
+
+
+    public LocationSelect() {
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "location_panel";
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("LocationSelect.validate");
+
+        Map<String, StateData> data = getData();
+
+        String locationStr = (String) data.get(FIELD_LOCATIONS).getValue();
+
+        if (locationStr == null || locationStr.length() == 0) {
+            logger.error("No locations given.");
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        double[] minmax = getMinMaxDistance(artifact);
+        double[] mm     = extractLocations(locationStr);
+
+        logger.debug("Inserted min location: " + mm[0]);
+        logger.debug("Inserted max location: " + mm[mm.length-1]);
+
+        return validateBounds(minmax[0], minmax[1], mm[0], mm[mm.length-1], 0d);
+    }
+
+
+    /**
+     * This method takes a string that consist of whitespace separated double
+     * values and returns the double values as array.
+     *
+     * @param locationStr The locations inserted in this state.
+     *
+     * @return the locations as array.
+     */
+    protected double[] extractLocations(String locationStr) {
+        String[] tmp               = locationStr.split(" ");
+        TDoubleArrayList locations = new TDoubleArrayList();
+
+        for (String l: tmp) {
+            try {
+                locations.add(Double.parseDouble(l));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        locations.sort();
+
+        return locations.toNativeArray();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/RangeState.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,54 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class RangeState extends DefaultState {
+
+    /** The logger that is used in this class.*/
+    private static Logger logger = Logger.getLogger(RangeState.class);
+
+
+    public RangeState() {
+    }
+
+
+    /**
+     * Validates a given range with a given valid range.
+     *
+     * @param fromValid Valid lower value of the range.
+     * @param toValid Valid upper value of the range.
+     * @param from The lower value.
+     * @param to The upper value.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateBounds(
+        double fromValid, double toValid,
+        double from,      double to,      double step)
+    throws IllegalArgumentException
+    {
+        logger.debug("RangeState.validateRange");
+
+        if (from < fromValid) {
+            logger.error(
+                "Invalid 'from'. " + from + " is smaller than " + fromValid);
+            throw new IllegalArgumentException("error_feed_from_out_of_range");
+        }
+        else if (to > toValid) {
+            logger.error(
+                "Invalid 'to'. " + to + " is bigger than " + toValid);
+            throw new IllegalArgumentException("error_feed_to_out_of_range");
+        }
+        else if (from > to) {
+            throw new IllegalArgumentException("error_feed_from_bigger_to");
+        }
+
+        return true;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,162 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RiverSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(RiverSelect.class);
+
+
+    /** Error message that is thrown if no river was found based on a given
+     * name.*/
+    public static final String ERROR_NO_SUCH_RIVER =
+        "error_feed_no_such_river";
+
+    /** Error message that is thrown if no river was found based on a given
+     * name.*/
+    public static final String ERROR_NO_RIVER_SELECTED =
+        "error_feed_no_river_selected";
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public RiverSelect() {
+    }
+
+
+    /**
+     * Initialize the state based on the state node in the configuration.
+     *
+     * @param config The state configuration node.
+     */
+    public void setup(Node config) {
+        super.setup(config);
+    }
+
+
+    protected Element createData(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select",
+            new String[] { "uiprovider" },
+            new String[] { "select_with_map" });
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        Element choices = ProtocolUtils.createArtNode(
+            cr, "choices", null, null);
+
+        select.appendChild(label);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            getID(),
+            getID()));
+
+        return select;
+    }
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        List<River> rivers = RiverFactory.getRivers();
+        Element[] items    = new Element[rivers.size()];
+
+        int idx = 0;
+        for (River river: rivers) {
+            items[idx++] = createRiverItem(cr, river);
+        }
+
+        return items;
+    }
+
+
+    /**
+     * This method creates a node that represents a river item. This node
+     * contains the label and the value that describe the river.
+     *
+     * @param cr The ElementCreator.
+     * @param river The river.
+     *
+     * @return the element that contains the information about the river.
+     */
+    protected Element createRiverItem(XMLUtils.ElementCreator cr, River river) {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        label.setTextContent(river.getName());
+        value.setTextContent(river.getName());
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        return item;
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("RiverSelect.validate");
+
+        Map<String, StateData> data = getData();
+
+        StateData dRiver = data.get("river");
+
+        if (dRiver == null || dRiver.getValue() == null) {
+            throw new IllegalArgumentException(ERROR_NO_RIVER_SELECTED);
+        }
+
+        River river = RiverFactory.getRiver((String) dRiver.getValue());
+
+        if (river == null) {
+            throw new IllegalArgumentException(ERROR_NO_SUCH_RIVER);
+        }
+
+        return true;
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "river_panel";
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/StateFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,121 @@
+package de.intevation.flys.artifacts.states;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifactdatabase.data.DefaultStateData;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class StateFactory {
+
+    /** The logger used in this class */
+    private static Logger logger = Logger.getLogger(StateFactory.class);
+
+    /** The XPath to the classname of the state */
+    public static final String XPATH_STATE = "@state";
+
+    /** The XPath to the data items of the state relative to the state node. */
+    public static final String XPATH_DATA = "data";
+
+    /** The XPath to the data name relative to the data node.*/
+    public static final String XPATH_DATA_NAME = "@name";
+
+    /** The XPath to the data type relative to the data node.*/
+    public static final String XPATH_DATA_TYPE = "@type";
+
+    /** The XPath to the data description relative to the data node.*/
+    public static final String XPATH_DATA_DESCRIPTION = "@description";
+
+
+    /**
+     * Creates a new State based on the configured class provided by
+     * <code>stateConf</code>.
+     *
+     * @param stateConf The configuration of the state.
+     *
+     * @return a State.
+     */
+    public static State createState(Node stateConf) {
+        String clazz = (String) XMLUtils.xpath(
+            stateConf, XPATH_STATE, XPathConstants.STRING);
+
+        State state = null;
+
+        try {
+            logger.debug("Create a new State for class: " + clazz);
+            state = (State) Class.forName(clazz).newInstance();
+            state.setup(stateConf);
+
+            initializeStateData(state, stateConf);
+        }
+        catch (InstantiationException ie) {
+            logger.error(ie, ie);
+        }
+        catch (IllegalAccessException iae) {
+            logger.error(iae, iae);
+        }
+        catch (ClassNotFoundException cnfe) {
+            logger.error(cnfe, cnfe);
+        }
+
+        return state;
+    }
+
+
+    /**
+     * This method extracts the configured input data of a state and adds new
+     * StateData objects to the State.
+     *
+     * @param state The state.
+     * @param stateConf The state configuration node.
+     */
+    protected static void initializeStateData(State state, Node stateConf) {
+        NodeList dataList = (NodeList) XMLUtils.xpath(
+            stateConf, XPATH_DATA, XPathConstants.NODESET);
+
+        if (dataList == null || dataList.getLength() == 0) {
+            logger.debug("The state has no input data configured.");
+
+            return;
+        }
+
+        int items = dataList.getLength();
+
+        logger.debug("The state has " + items + " data items configured.");
+
+        for (int i = 0; i < items; i++) {
+            Node data = dataList.item(i);
+
+            String name = (String) XMLUtils.xpath(
+                data, XPATH_DATA_NAME, XPathConstants.STRING);
+            String type = (String) XMLUtils.xpath(
+                data, XPATH_DATA_TYPE, XPathConstants.STRING);
+            String desc = (String) XMLUtils.xpath(
+                data, XPATH_DATA_DESCRIPTION, XPathConstants.STRING);
+
+            if (name == null || name.equals("")) {
+                logger.warn("No name for data item at pos " + i + " found.");
+                continue;
+            }
+
+            if (type == null || type.equals("")) {
+                logger.warn("No type for data item at pos " + i + " found.");
+                logger.warn("Default type 'string' used.");
+                type = "string";
+            }
+
+            state.addData(name, new DefaultStateData(name, type, desc));
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,321 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.RangeWithValues;
+import de.intevation.flys.artifacts.model.WstFactory;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQAdapted extends DefaultState {
+
+    /** The logger used in this state.*/
+    private static Logger logger = Logger.getLogger(WQAdapted.class);
+
+
+    public static final String FIELD_WQ_MODE = "wq_mode";
+
+    public static final String FIELD_WQ_VALUES = "wq_values";
+
+
+    /**
+     * This method creates one element for each gauge of the selected river that
+     * is intersected by the given kilometer range. Each element is a tuple of
+     * (from;to) where <i>from</i> is the lower bounds of the gauge or the lower
+     * kilometer range. <i>to</i> is the upper bounds of the gauge or the upper
+     * kilometer range.
+     *
+     * @param cr The ElementCreator.
+     * @param artifact The FLYS artifact.
+     * @param name The name of the data item.
+     * @param context The CallContext.
+     *
+     * @return a list of elements that consist of tuples of the intersected
+     * gauges of the selected river.
+     */
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("WQAdapted.createItems");
+
+        if (name != null && name.equals(FIELD_WQ_MODE)) {
+            return createModeItems(cr, artifact, name, context);
+        }
+        else if (name != null && name.equals(FIELD_WQ_VALUES)) {
+            return createValueItems(cr, artifact, name, context);
+        }
+        else {
+            logger.warn("Unknown data object: " + name);
+            return null;
+        }
+    }
+
+
+    protected Element[] createModeItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("WQAdapted.createModeItems");
+
+        Element w = createItem(cr, new String[] { "w", "W" });
+        Element q = createItem(cr, new String[] { "q", "Q" });
+
+        return new Element[] { w, q };
+    }
+
+
+    protected Element[] createValueItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("WQAdapted.createValueItems");
+
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+
+        double[]    dist   = flysArtifact.getDistance();
+        List<Gauge> gauges = flysArtifact.getGauges();
+
+        int num = gauges != null ? gauges.size() : 0;
+
+        if (num == 0) {
+            logger.warn("Selected distance matches no gauges.");
+            return null;
+        }
+
+        Element[] elements = new Element[num];
+
+        int idx = 0;
+
+        for (Gauge gauge: gauges) {
+            Range range  = gauge.getRange();
+            double lower = range.getA().doubleValue();
+            double upper = range.getB().doubleValue();
+
+            double from = dist[0] < lower ? lower : dist[0];
+            double to   = dist[1] > upper ? upper : dist[1];
+
+            String key = Double.toString(from) + ";" + Double.toString(to);
+
+            elements[idx++] = createItem(cr, new String[] {key, ""});
+        }
+
+        return elements;
+    }
+
+
+    protected Element createItem(XMLUtils.ElementCreator cr, Object obj) {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        String[] arr = (String[]) obj;
+
+        label.setTextContent(arr[0]);
+        value.setTextContent(arr[1]);
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        return item;
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "wq_panel_adapted";
+    }
+
+
+    public boolean validate(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQAdapted.validate");
+
+        Map<String, StateData> data = getData();
+
+        String mode = (String) data.get(FIELD_WQ_MODE).getValue();
+
+        if (mode != null && mode.equals("W")) {
+            return validateW(artifact, context);
+        }
+        else if (mode != null && mode.equals("Q")) {
+            return validateQ(artifact, context);
+        }
+        else {
+            throw new IllegalArgumentException("error_feed_no_wq_mode_selected");
+        }
+    }
+
+
+    protected boolean validateW(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQAdapted.validateW");
+
+        RangeWithValues[] rwvs = extractInput(getData("wq_values"));
+        List<Gauge>     gauges = ((FLYSArtifact) artifact).getGauges();
+
+        for (Gauge gauge: gauges) {
+            Range range  = gauge.getRange();
+            double lower = range.getA().doubleValue();
+            double upper = range.getB().doubleValue();
+
+            for (RangeWithValues rwv: rwvs) {
+                if (lower <= rwv.getLower() && upper >= rwv.getUpper()) {
+                    compareWsWithGauge(gauge, rwv.getValues());
+                }
+            }
+        }
+
+        return true;
+    }
+
+
+    protected boolean validateQ(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQAdapted.validateQ");
+
+        RangeWithValues[] rwvs = extractInput(getData("wq_values"));
+        List<Gauge>     gauges = ((FLYSArtifact) artifact).getGauges();
+        River            river = ((FLYSArtifact) artifact).getRiver();
+        Wst                wst = WstFactory.getWst(river);
+
+        for (Gauge gauge: gauges) {
+            Range range  = gauge.getRange();
+            double lower = range.getA().doubleValue();
+            double upper = range.getB().doubleValue();
+
+            for (RangeWithValues rwv: rwvs) {
+                if (lower <= rwv.getLower() && upper >= rwv.getUpper()) {
+                    compareQsWithGauge(wst, gauge, rwv.getValues());
+                }
+            }
+        }
+
+        return true;
+    }
+
+
+    protected boolean compareQsWithGauge(Wst wst, Gauge gauge, double[] qs)
+    throws IllegalArgumentException
+    {
+        double[] minmax = gauge != null
+            ? wst.determineMinMaxQ(gauge.getRange())
+            : null;
+
+        if (minmax == null) {
+            logger.warn("Could not determine min/max Q of gauge.");
+            return true;
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Validate Qs with:");
+            logger.debug("-- Gauge: " + gauge.getName());
+            logger.debug("-- Gauge min: " + minmax[0]);
+            logger.debug("-- Gauge max: " + minmax[1]);
+        }
+
+        for (double q: qs) {
+            if (q < minmax[0] || q > minmax[1]) {
+                throw new IllegalArgumentException(
+                    "error_feed_q_values_invalid");
+            }
+        }
+
+        return true;
+    }
+
+
+    protected boolean compareWsWithGauge(Gauge gauge, double[] ws)
+    throws IllegalArgumentException
+    {
+        double[] minmax = gauge != null
+            ? gauge.determineMinMaxW()
+            : null;
+
+        if (minmax == null) {
+            logger.warn("Could not determine min/max W of gauge.");
+            return true;
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Validate Ws with:");
+            logger.debug("-- Gauge: " + gauge.getName());
+            logger.debug("-- Gauge min: " + minmax[0]);
+            logger.debug("-- Gauge max: " + minmax[1]);
+        }
+
+        for (double w: ws) {
+            if (w < minmax[0] || w > minmax[1]) {
+                throw new IllegalArgumentException(
+                    "error_feed_w_values_invalid");
+            }
+        }
+
+        return true;
+    }
+
+
+    protected RangeWithValues[] extractInput(StateData data) {
+        String dataString = (String) data.getValue();
+        String[]   ranges = dataString.split(":");
+
+        List<RangeWithValues> rwv = new ArrayList<RangeWithValues>();
+
+        for (String range: ranges) {
+            String[] parts = range.split(";");
+
+            double lower = Double.parseDouble(parts[0]);
+            double upper = Double.parseDouble(parts[1]);
+
+            String[] values = parts[2].split(",");
+
+            int      num = values.length;
+            double[] res = new double[num];
+
+            for (int i = 0; i < num; i++) {
+                try {
+                    res[i] = Double.parseDouble(values[i]);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.warn(nfe, nfe);
+                }
+            }
+
+            rwv.add(new RangeWithValues(lower, upper, res));
+        }
+
+        return (RangeWithValues[]) rwv.toArray(new RangeWithValues[rwv.size()]);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WQSelect.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,356 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.Map;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.WstFactory;
+import de.intevation.flys.artifacts.resources.Resources;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQSelect extends RangeState {
+
+    /** The logger used in this class.*/
+    private static Logger logger = Logger.getLogger(WQSelect.class);
+
+
+    /** The default step width for Qs.*/
+    public static final String DEFAULT_STEP_Q = "50";
+
+    /** The default step width for Qs.*/
+    public static final String DEFAULT_STEP_W = "30";
+
+    /** The name of the 'mode' field. */
+    public static final String WQ_MODE = "wq_mode";
+
+    /** The name of the 'selection' field.*/
+    public static final String WQ_SELECTION = "wq_selection";
+
+    /** The name of the 'from' field. */
+    public static final String WQ_FROM = "wq_from";
+
+    /** The name of the 'to' field. */
+    public static final String WQ_TO = "wq_to";
+
+    /** The name of the 'step' field. */
+    public static final String WQ_STEP = "wq_step";
+
+    /** The name of the 'single' field. */
+    public static final String WQ_SINGLE = "wq_single";
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public WQSelect() {
+    }
+
+    protected Element createData(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select", null, null);
+
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        Element choices = ProtocolUtils.createArtNode(
+            cr, "choices", null, null);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            data.getName(),
+            data.getName()));
+
+        select.appendChild(label);
+
+        return select;
+    }
+
+
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        // TODO Insert correct min/max values!
+        double[] minmaxW = determineMinMaxW(artifact);
+        double[] minmaxQ = determineMinMaxQ(artifact);
+
+        if (name.equals("wq_from")) {
+            Element minW = createItem(
+                cr, new String[] {"minW", new Double(minmaxW[0]).toString()});
+            Element minQ = createItem(
+                cr, new String[] {"minQ", new Double(minmaxQ[0]).toString()});
+            return new Element[] { minW, minQ };
+        }
+        else if (name.equals("wq_to")) {
+            Element maxW = createItem(
+                cr, new String[] {"maxW", new Double(minmaxW[1]).toString()});
+            Element maxQ = createItem(
+                cr, new String[] {"maxQ", new Double(minmaxQ[1]).toString()});
+            return new Element[] { maxW, maxQ };
+        }
+        else {
+            Element stepW = createItem(cr, new String[] {"stepW", DEFAULT_STEP_W});
+            Element stepQ = createItem(cr, new String[] {"stepQ", DEFAULT_STEP_Q});
+            return new Element[] { stepW, stepQ };
+        }
+    }
+
+
+    protected Element createItem(XMLUtils.ElementCreator cr, Object obj) {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        String[] arr = (String[]) obj;
+
+        label.setTextContent(arr[0]);
+        value.setTextContent(arr[1]);
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        return item;
+    }
+
+
+    protected String getUIProvider() {
+        return "wq_panel";
+    }
+
+
+    /**
+     * Determines the min and max W value for the current gauge. If no min and
+     * max values could be determined, this method will return
+     * [Double.MIN_VALUE, Double.MAX_VALUE].
+     *
+     * @param artifact The FLYSArtifact.
+     *
+     * @return the min and max W values for the current gauge.
+     */
+    protected double[] determineMinMaxW(Artifact artifact) {
+        logger.debug("WQSelect.determineCurrentGauge");
+
+        Gauge    gauge   = ((FLYSArtifact) artifact).getGauge();
+        double[] minmaxW = gauge != null ? gauge.determineMinMaxW() : null;
+
+        double minW = minmaxW != null ? minmaxW[0] : Double.MIN_VALUE;
+        double maxW = minmaxW != null ? minmaxW[1] : Double.MAX_VALUE;
+
+        return new double[] { minW, maxW };
+    }
+
+
+    /**
+     * Determines the min and max Q value for the current gauge. If no min and
+     * max values could be determined, this method will return
+     * [Double.MIN_VALUE, Double.MAX_VALUE].
+     *
+     * @param artifact The FLYSArtifact.
+     *
+     * @return the min and max Q values for the current gauge.
+     */
+    protected double[] determineMinMaxQ(Artifact artifact) {
+        logger.debug("WQSelect.determineMinMaxQ");
+
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+
+        River    river = flysArtifact.getRiver();
+        Gauge    gauge = flysArtifact.getGauge();
+        Wst      wst   = WstFactory.getWst(river);
+
+        double[] minmaxQ = gauge != null
+            ? wst.determineMinMaxQ(gauge.getRange())
+            : null;
+
+        double minQ = minmaxQ != null ? minmaxQ[0] : Double.MIN_VALUE;
+        double maxQ = minmaxQ != null ? minmaxQ[1] : Double.MAX_VALUE;
+
+        return new double[] { minQ, maxQ };
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact, CallContext context)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQSelect.validate");
+
+        Map<String, StateData> data = getData();
+
+        String selectionMode = (String) data.get(WQ_SELECTION).getValue();
+
+        if (selectionMode == null || selectionMode.equals("single")) {
+            return validateSingle(artifact, context);
+        }
+        else {
+            return validateRange(artifact, context);
+        }
+    }
+
+
+    protected boolean validateSingle(Artifact artifact, CallContext context)
+    throws    IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateSingle");
+
+        String tmp = (String) data.get(WQ_SINGLE).getValue();
+
+        if (tmp == null || tmp.length() == 0) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        String[] strValues = tmp.split(" ");
+        TDoubleArrayList all = new TDoubleArrayList();
+
+        for (String strValue: strValues) {
+            try {
+                all.add(Double.parseDouble(strValue));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        all.sort();
+
+        String mode = (String) data.get(WQ_MODE).getValue();
+        logger.debug("WQ Mode: " + mode);
+
+        double[] minmax = null;
+
+        if (mode != null && mode.trim().toLowerCase().equals("w")) {
+            minmax = determineMinMaxW(artifact);
+        }
+        else {
+            minmax = determineMinMaxQ(artifact);
+        }
+
+        double min = all.get(0);
+        double max = all.get(all.size()-1);
+
+        logger.debug("Inserted min value = " + min);
+        logger.debug("Inserted max value = " + max);
+
+        return validateBounds(minmax[0], minmax[1], min, max, 0d);
+    }
+
+
+    protected boolean validateRange(Artifact artifact, CallContext context)
+    throws    IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateRange");
+
+        String mode = (String) data.get(WQ_MODE).getValue();
+        logger.debug("WQ Mode: " + mode);
+
+        String fromStr = (String) data.get(WQ_FROM).getValue();
+        String toStr   = (String) data.get(WQ_TO).getValue();
+        String stepStr = (String) data.get(WQ_STEP).getValue();
+
+        if (fromStr == null || toStr == null || stepStr == null) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        double from = Double.parseDouble(fromStr);
+        double to   = Double.parseDouble(toStr);
+        double step = Double.parseDouble(stepStr);
+
+        try {
+            if (mode != null && mode.trim().toLowerCase().equals("w")) {
+                return validateW(artifact, context, from, to, step);
+            }
+            else if (mode != null && mode.trim().toLowerCase().equals("q")) {
+                return validateQ(artifact, context, from, to, step);
+            }
+            else {
+                throw new IllegalArgumentException("error_feed_invalid_wq_mode");
+            }
+        }
+        catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException("error_feed_number_format");
+        }
+    }
+
+
+    /**
+     * Validates the inserted W values.
+     *
+     * @param artifact The owner artifact.
+     * @param context The CallContext
+     * @param from The lower value of the W range.
+     * @param to The upper value of the W range.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateW(
+        Artifact    artifact,
+        CallContext context,
+        double from,
+        double to,
+        double step)
+    throws    IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateW");
+
+        double[] minmaxW = determineMinMaxW(artifact);
+
+        return validateBounds(minmaxW[0], minmaxW[1], from, to, step);
+    }
+
+
+    /**
+     * Validates the inserted Q values.
+     *
+     * @param artifact The owner artifact.
+     * @param context The CallContext
+     * @param from The lower value of the Q range.
+     * @param to The upper value of the Q range.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateQ(
+        Artifact    artifact,
+        CallContext context,
+        double from,
+        double to,
+        double step)
+    throws    IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateQ");
+
+        double[] minmaxQ = determineMinMaxQ(artifact);
+
+        return validateBounds(minmaxQ[0], minmaxQ[1], from, to, step);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,104 @@
+package de.intevation.flys.artifacts.transitions;
+
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.transition.Transition;
+
+
+/**
+ * The default implementation of a <code>Transition</code>.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultTransition implements Transition {
+
+    /** The ID of the current state */
+    protected String from;
+
+    /** The ID of the target state */
+    protected String to;
+
+
+    /**
+     * The default constructor.
+     */
+    public DefaultTransition() {
+    }
+
+
+    /**
+     * The default constructor.
+     *
+     * @param from The current state.
+     * @param to The target state.
+     */
+    public DefaultTransition(String from, String to) {
+        this.from = from;
+        this.to   = to;
+    }
+
+
+    public void init(Node config) {
+        // nothing to do in the default transition
+    }
+
+
+    /**
+     * Returns the current state ID.
+     *
+     * @return the current state ID.
+     */
+    public String getFrom() {
+        return from;
+    }
+
+
+    /**
+     * Returns the target state ID.
+     *
+     * @return the target state ID.
+     */
+    public String getTo() {
+        return to;
+    }
+
+
+    /**
+     * Set the current state ID.
+     *
+     * @param to the current state ID.
+     */
+    public void setFrom(String from) {
+        this.from = from;
+    }
+
+
+    /**
+     * Set the target state ID.
+     *
+     * @param to the target state ID.
+     */
+    public void setTo(String to) {
+        this.to = to;
+    }
+
+
+    /**
+     * Determines if its valid to step from state <i>a</i> of an artifact
+     * <i>artifact</i> to state <i>b</i>. This method always returns true - no
+     * validation takes place.
+     *
+     * @param artifact The owner artifact of state a and b.
+     * @param a The current state.
+     * @param b The target state.
+     *
+     * @return true, if it is valid to step from a to b, otherwise false.
+     */
+    public boolean isValid(Artifact artifact, State a, State b) {
+        return true;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,71 @@
+package de.intevation.flys.artifacts.transitions;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Node;
+
+import de.intevation.artifactdatabase.transition.Transition;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class TransitionFactory {
+
+    /** The logger used in this class */
+    private static Logger logger = Logger.getLogger(TransitionFactory.class);
+
+    /** The XPath to the classname of the transition */
+    public static final String XPATH_TRANSITION = "@transition";
+
+    /** The XPath to the current state ID */
+    public static final String XPATH_CURRENT_STATE = "from/@state";
+
+    /** The XPath to the target state ID */
+    public static final String XPATH_TARGET_STATE = "to/@state";
+
+
+    /**
+     * Creates a new Transition based on the configured class provided by
+     * <code>transitionConf</code>.
+     *
+     * @param transitionConf The configuration of the transition.
+     *
+     * @return a Transition.
+     */
+    public static Transition createTransition(Node transitionConf) {
+        String clazz = (String) XMLUtils.xpath(
+            transitionConf, XPATH_TRANSITION, XPathConstants.STRING);
+
+        Transition transition = null;
+
+        try {
+            transition = (Transition) Class.forName(clazz).newInstance();
+
+            String from = (String) XMLUtils.xpath(
+                transitionConf, XPATH_CURRENT_STATE, XPathConstants.STRING);
+            String to = (String) XMLUtils.xpath(
+                transitionConf, XPATH_TARGET_STATE, XPathConstants.STRING);
+
+            transition.init(transitionConf);
+            transition.setFrom(from);
+            transition.setTo(to);
+        }
+        catch (InstantiationException ie) {
+            logger.error(ie, ie);
+        }
+        catch (IllegalAccessException iae) {
+            logger.error(iae, iae);
+        }
+        catch (ClassNotFoundException cnfe) {
+            logger.error(cnfe, cnfe);
+        }
+
+        return transition;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/transitions/ValueCompareTransition.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,118 @@
+package de.intevation.flys.artifacts.transitions;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifactdatabase.data.StateData;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * This transition compares data objects with a <i>equal</i> or <i>notequal</i>
+ * operator.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ValueCompareTransition extends DefaultTransition {
+
+    /** The logger that is used in this transition.*/
+    private static Logger log = Logger.getLogger(ValueCompareTransition.class);
+
+
+    /** XPath that points to the condition's operator.*/
+    public static final String XPATH_OPERATOR = "condition/@operator";
+
+    /** XPath that points to the condition's value.*/
+    public static final String XPATH_VALUE    = "condition/@value";
+
+    /** XPath that points  to the condition's dataname.*/
+    public static final String XPATH_DATANAME = "condition/@data";
+
+    /** The value of the 'equal' operator.*/
+    public static final String OPERATOR_EQUAL = "equal";
+
+    /** The value of the 'not equal' operator.*/
+    public static final String OPERATOR_NOTEQUAL = "notequal";
+
+
+    /** The operator.*/
+    protected String operator;
+
+    /** The value used for comparison.*/
+    protected String value;
+
+    /** The name of the data used for the comparison.*/
+    protected String dataname;
+
+
+
+    public ValueCompareTransition() {
+    }
+
+
+    public ValueCompareTransition(String from, String to) {
+        super(from, to);
+    }
+
+
+    @Override
+    public void init(Node config) {
+        log.debug("ValueCompareTransition.init");
+
+        String tmp = (String) XMLUtils.xpath(
+            config,
+            XPATH_OPERATOR,
+            XPathConstants.STRING);
+        operator = tmp.trim().toLowerCase();
+
+        value = (String) XMLUtils.xpath(
+            config,
+            XPATH_VALUE,
+            XPathConstants.STRING);
+
+        dataname = (String) XMLUtils.xpath(
+            config,
+            XPATH_DATANAME,
+            XPathConstants.STRING);
+    }
+
+
+    @Override
+    public boolean isValid(Artifact artifact, State a, State b) {
+        log.debug("ValueCompareTransition.isValid");
+
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+
+        StateData dataObj = flysArtifact.getData(dataname);
+        String    dataVal = dataObj != null ? (String) dataObj.getValue() : "";
+
+        if (log.isDebugEnabled()) {
+            log.debug("Compare settings:");
+            log.debug("-- dataname: " + dataname);
+            log.debug("-- operator: " + operator);
+            log.debug("-- compare value: " + value);
+            log.debug("-- state value: " + dataVal);
+        }
+
+        if (operator.equals(OPERATOR_EQUAL)) {
+            return value.equals(dataVal);
+        }
+        else if (operator.equals(OPERATOR_NOTEQUAL)) {
+            return !(value.equals(dataVal));
+        }
+
+        log.error("Wrong operator set! No comparison takes place.");
+
+        return false;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/AttributeParser.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,126 @@
+package de.intevation.flys.collections;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Output;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.ManagedFacet;
+
+
+public class AttributeParser {
+
+    /** Constant XPath that points to the outputmodes of an artifact.*/
+    public static final String XPATH_ARTIFACT_OUTPUTMODES =
+        "/art:output";
+
+
+    private static Logger logger = Logger.getLogger(AttributeParser.class);
+
+
+    protected Map<String, Output> outs;
+
+
+    public AttributeParser() {
+        this.outs = new HashMap<String, Output>();
+    }
+
+
+    public void parse(Document doc) {
+        logger.debug("AttributeParser.parse");
+
+        NodeList outs = (NodeList) XMLUtils.xpath(
+            doc,
+            XPATH_ARTIFACT_OUTPUTMODES,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        int num = outs != null ? outs.getLength() : 0;
+
+        logger.debug("Attribute has " + num + " outputs.");
+
+        for (int i = 0; i < num; i++) {
+            Node out = outs.item(i);
+
+            parseOutput(out);
+        }
+    }
+
+
+    public Map<String, Output> getOuts() {
+        return outs;
+    }
+
+
+    protected void addItem(String out, ManagedFacet item) {
+        Output o = outs.get(out);
+
+        if (o != null) {
+            o.addFacet(item);
+        }
+    }
+
+
+    protected void parseOutput(Node out) {
+        String name = XMLUtils.xpathString(
+            out, "@name", ArtifactNamespaceContext.INSTANCE);
+
+        if (outs.get(name) == null) {
+            logger.debug("Create new output: " + name);
+
+            Output o = new DefaultOutput(name, null, null);
+            outs.put(name, o);
+        }
+
+        parseItems(out, name);
+    }
+
+
+    protected void parseItems(Node out, String outname) {
+        NodeList themes = (NodeList) XMLUtils.xpath(
+            out, "art:theme",
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        int num = themes != null ? themes.getLength() : 0;
+
+        logger.debug("Output has " + num + " themes.");
+
+        for (int i = 0; i < num; i++) {
+            Node theme = themes.item(i);
+
+            String name = XMLUtils.xpathString(
+                theme, "@facet", ArtifactNamespaceContext.INSTANCE);
+
+            String uuid = XMLUtils.xpathString(
+                theme, "@artifact", ArtifactNamespaceContext.INSTANCE);
+
+            String pos = XMLUtils.xpathString(
+                theme, "@pos", ArtifactNamespaceContext.INSTANCE);
+
+            String active = XMLUtils.xpathString(
+                theme, "@active", ArtifactNamespaceContext.INSTANCE);
+
+            ManagedFacet item = new ManagedFacet(
+                name, "", uuid,
+                Integer.parseInt(pos),
+                Integer.parseInt(active));
+
+            addItem(outname, item);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/AttributeWriter.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,148 @@
+package de.intevation.flys.collections;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.artifacts.model.ManagedFacet;
+
+
+public class AttributeWriter {
+
+    protected Map<String, Output> oldAttr;
+    protected Map<String, Output> newAttr;
+
+    private static Logger logger = Logger.getLogger(AttributeWriter.class);
+
+
+    public AttributeWriter(
+        Map<String, Output> oldAttr,
+        Map<String, Output> newAttr)
+    {
+        this.oldAttr = oldAttr;
+        this.newAttr = newAttr;
+    }
+
+
+    protected Document write() {
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element outs = cr.create("outputs");
+        doc.appendChild(outs);
+
+        Iterator<String> iter = newAttr.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String outName = iter.next();
+
+            Output a = newAttr.get(outName);
+            Output b = oldAttr.get(outName);
+
+            writeOutput(doc, outs, cr, a, b);
+        }
+
+        return doc;
+    }
+
+
+    protected void writeOutput(
+        Document       doc,
+        Node           outs,
+        ElementCreator cr,
+        Output         a,
+        Output         b)
+    {
+        Element output = cr.create("output");
+        cr.addAttr(output, "name", a.getName());
+
+        outs.appendChild(output);
+
+        List<Facet> facetsA = a.getFacets();
+        List<Facet> facetsB = null;
+
+        if (b != null) {
+            facetsB = b.getFacets();
+        }
+
+        writeFacets(doc, cr, output, facetsA, facetsB);
+    }
+
+
+    protected void writeFacets(
+        Document       doc,
+        ElementCreator cr,
+        Element        output,
+        List<Facet>    a,
+        List<Facet>    b)
+    {
+        int num = a.size();
+
+        for (int i = 0; i < num; i++) {
+            ManagedFacet fA = (ManagedFacet) a.get(i);
+
+            if (!mergeFacets(doc, cr, output, fA, b)) {
+                writeFacet(doc, cr, output, fA);
+            }
+        }
+    }
+
+
+    protected boolean mergeFacets(
+        Document       doc,
+        ElementCreator cr,
+        Element        output,
+        ManagedFacet   a,
+        List<Facet>    list)
+    {
+        String name = a.getName();
+
+        if (list == null) {
+            return false;
+        }
+
+        for (Facet facet: list) {
+            if (name.equals(facet.getName())) {
+                writeFacet(doc, cr, output, (ManagedFacet) facet);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    protected void writeFacet(
+        Document       doc,
+        ElementCreator cr,
+        Node           output,
+        ManagedFacet   f)
+    {
+        Element theme = cr.create("theme");
+        cr.addAttr(theme, "artifact", f.getArtifact(), true);
+        cr.addAttr(theme, "facet", f.getName(), true);
+        cr.addAttr(theme, "pos", Integer.toString(f.getPosition()), true);
+        cr.addAttr(theme, "active", Integer.toString(f.getActive()), true);
+
+        output.appendChild(theme);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,754 @@
+package de.intevation.flys.collections;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.xml.xpath.XPathConstants;
+
+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.Artifact;
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.ClientProtocolUtils;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.Backend;
+import de.intevation.artifactdatabase.Backend.PersistentArtifact;
+import de.intevation.artifactdatabase.DefaultArtifactCollection;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.model.ManagedFacet;
+import de.intevation.flys.exports.OutGenerator;
+import de.intevation.flys.themes.Theme;
+import de.intevation.flys.themes.ThemeFactory;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FLYSArtifactCollection extends DefaultArtifactCollection {
+
+    /** The logger used in this class.*/
+    private static Logger log = Logger.getLogger(FLYSArtifactCollection.class);
+
+
+    /** Constant XPath that points to the outputmodes of an artifact.*/
+    public static final String XPATH_ARTIFACT_OUTPUTMODES =
+        "/art:result/art:outputmodes";
+
+    public static final String XPATH_COLLECTION_ITEMS =
+        "/art:result/art:artifact-collection/art:collection-item";
+
+    public static final String XPATH_OUT_NAME = "/art:action/@art:name";
+
+    public static final String XPATH_OUT_TYPE = "/art:action/@art:type";
+
+
+
+    @Override
+    public Document describe(CallContext context) {
+        log.debug("FLYSArtifactCollection.describe: " + identifier);
+
+        Document doc = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Date creationTime = getCreationTime();
+        String creation   = creationTime != null
+            ? Long.toString(creationTime.getTime())
+            : "";
+
+        Element collection = ec.create("artifact-collection");
+        Element artifacts  = ec.create("artifacts");
+        Element attributes = ec.create("attribute");
+
+        ec.addAttr(collection, "name", getName(), true);
+        ec.addAttr(collection, "uuid", identifier(), true);
+        ec.addAttr(collection, "creation", creation,  true);
+
+        collection.appendChild(artifacts);
+        collection.appendChild(attributes);
+        doc.appendChild(collection);
+
+        ArtifactDatabase db = context.getDatabase();
+
+        try {
+            String[] artifactUUIDs = getArtifactUUIDs(context);
+
+            Document oldAttrs = getAttribute();
+            Document attrs    = buildAttributes(
+                db, context,
+                oldAttrs,
+                artifactUUIDs);
+
+            db.setCollectionAttribute(identifier(), context.getMeta(), attrs);
+
+            Node child = attrs.getFirstChild();
+            attributes.appendChild(doc.importNode(child, true));
+
+            for (String uuid: artifactUUIDs) {
+                try {
+                    artifacts.appendChild(
+                        buildArtifactNode(db, uuid, context, ec));
+                }
+                catch (ArtifactDatabaseException dbe) {
+                    log.warn(dbe, dbe);
+                }
+            }
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error(ade, ade);
+        }
+
+        return doc;
+    }
+
+
+    @Override
+    public void out(Document format, OutputStream out, CallContext context)
+    throws IOException
+    {
+        log.info("FLYSArtifactCollection.out");
+
+        String name = XMLUtils.xpathString(
+            format, XPATH_OUT_NAME, ArtifactNamespaceContext.INSTANCE);
+
+        String type = XMLUtils.xpathString(
+            format, XPATH_OUT_TYPE, ArtifactNamespaceContext.INSTANCE);
+
+        log.debug("Output name = " + name);
+        log.debug("Output type = " + type);
+
+        OutGenerator generator = getOutGenerator(context, name, type);
+        if (generator == null) {
+            log.error("There is no generator specified for output: " + name);
+            // TODO throw an exception.
+
+            return;
+        }
+
+        generator.init(format, out, context);
+
+        // TODO Determine the correct master artifact here!
+
+        try {
+            doOut(generator, name, type, getAttribute(context, name), context);
+        }
+        catch (ArtifactDatabaseException adbe) {
+            log.error(adbe, adbe);
+        }
+    }
+
+
+    /**
+     * This method creates the concrete output.
+     *
+     * @param generator The OutGenerator that creates the output.
+     * @param outputName The name of the requested output.
+     * @param attributes The collection's attributes for this concrete output
+     * type.
+     * @param context The context object.
+     */
+    protected void doOut(
+        OutGenerator generator,
+        String       outName,
+        String       facet,
+        Document     attributes,
+        CallContext  context)
+    throws IOException
+    {
+        log.debug("FLYSArtifactCollection.doOut: " + outName);
+
+        ThemeList themeList = new ThemeList(attributes);
+
+        int size = themeList.size();
+        log.debug("Output will contain " + size + " elements.");
+
+        try {
+            for (int i = 0; i < size; i++) {
+                ManagedFacet theme = themeList.get(i);
+
+                if (theme == null) {
+                    log.debug("Theme is empty - no output is generated.");
+                    continue;
+                }
+
+                String art = theme.getArtifact();
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Do output for...");
+                    log.debug("... artifact: " + art);
+                    log.debug("... facet: " + theme.getName());
+                }
+
+                String facetName = theme.getName();
+
+                if (outName.equals("export") && !facetName.equals(facet)) {
+                    continue;
+                }
+
+                // TODO Remove the following two lines of code! The master
+                // artifact has to be determined correctly after
+                // OutGenerator.init is called!
+                Artifact artifact = getArtifact(art, context);
+                generator.setMasterArtifact(artifact);
+
+                generator.doOut(
+                    artifact,
+                    facetName,
+                    getFacetThemeFromAttribute(
+                        art,
+                        outName,
+                        facetName,
+                        context));
+            }
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error(ade, ade);
+        }
+
+        generator.generate();
+    }
+
+
+    protected Document buildAttributes(
+        ArtifactDatabase db,
+        CallContext      context,
+        Document         oldAttr,
+        String[]         items)
+    {
+        Document doc = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        AttributeParser aParser = new AttributeParser();
+        OutputParser    oParser = new OutputParser(db, context.getMeta());
+
+        for (String uuid: items) {
+            try {
+                oParser.parse(uuid);
+            }
+            catch (ArtifactDatabaseException ade) {
+                log.warn(ade, ade);
+            }
+        }
+
+        aParser.parse(oldAttr);
+
+        return new AttributeWriter(aParser.getOuts(), oParser.getOuts()).write();
+    }
+
+
+    /**
+     * Returns the attribute for a specific output type.
+     *
+     * @param output The name of the desired output type.
+     *
+     * @return the attribute for the desired output type.
+     */
+    protected Document getAttribute(CallContext context, String output)
+    throws    ArtifactDatabaseException
+    {
+        Document attr = buildAttributes(
+            context.getDatabase(),
+            context,
+            getAttribute(),
+            getArtifactUUIDs(context));
+
+        Node out = (Node) XMLUtils.xpath(
+            attr,
+            "art:outputs/art:output[@name='" + output + "']",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+
+        if (out != null) {
+            Document o = XMLUtils.newDocument();
+
+            o.appendChild(o.importNode(out, true));
+
+            return o;
+        }
+
+        return null;
+    }
+
+
+    /**
+     * This method returns the list of artifact UUIDs that this collections
+     * contains.
+     *
+     * @param context The CallContext that is necessary to get information about
+     * the ArtifactDatabase.
+     *
+     * @return a list of uuids.
+     */
+    protected String[] getArtifactUUIDs(CallContext context)
+    throws    ArtifactDatabaseException
+    {
+        log.debug("FLYSArtifactCollection.getArtifactUUIDs");
+
+        ArtifactDatabase db = context.getDatabase();
+        CallMeta meta       = context.getMeta();
+
+        Document itemList = db.listCollectionArtifacts(identifier(), meta);
+        NodeList items    = (NodeList) XMLUtils.xpath(
+            itemList,
+            XPATH_COLLECTION_ITEMS,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (items == null || items.getLength() == 0) {
+            log.debug("No artifacts found in this collection.");
+            return null;
+        }
+
+        int num = items.getLength();
+
+        List<String> uuids = new ArrayList<String>(num);
+
+        for (int i = 0; i < num; i++) {
+            String uuid = XMLUtils.xpathString(
+                items.item(i),
+                "@art:uuid",
+                ArtifactNamespaceContext.INSTANCE);
+
+            if (uuid != null && uuid.trim().length() != 0) {
+                uuids.add(uuid);
+            }
+        }
+
+        return (String[]) uuids.toArray(new String[uuids.size()]);
+    }
+
+
+    /**
+     * Returns a concrete Artifact of this collection specified by its uuid.
+     *
+     * @param uuid The Artifact's uuid.
+     * @param context The CallContext.
+     *
+     * @return an Artifact.
+     */
+    protected Artifact getArtifact(String uuid, CallContext context)
+    throws    ArtifactDatabaseException
+    {
+        log.debug("FLYSArtifactCollection.getArtifact");
+
+        Backend backend               = Backend.getInstance();
+        PersistentArtifact persistent = backend.getArtifact(uuid);
+
+        return persistent != null ? persistent.getArtifact() : null;
+    }
+
+
+    /**
+     * Returns the attribute that belongs to an artifact stored in this
+     * collection.
+     *
+     * @param uuid The Artifact's uuid.
+     * @param outname The name of the requested output.
+     * @param facet The name of the requested facet.
+     * @param context The CallContext.
+     *
+     * @return an attribute in form of a document.
+     */
+    protected Document getFacetThemeFromAttribute(
+        String      uuid,
+        String      outName,
+        String      facet,
+        CallContext context)
+    throws    ArtifactDatabaseException
+    {
+        log.debug("FLYSArtifactCollection.getFacetThemeFromAttribute");
+
+        ArtifactDatabase db = context.getDatabase();
+        CallMeta       meta = context.getMeta();
+
+        FLYSContext flysContext = context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) context.globalContext();
+
+        Map<String, String> mappings = (Map<String, String>)
+            flysContext.get(FLYSContext.THEME_MAPPING);
+
+        String themeName = mappings.get(facet);
+
+        Document attr = db.getCollectionItemAttribute(identifier(), uuid, meta);
+
+        if (attr == null) {
+            attr = initItemAttribute(uuid, facet, context);
+
+            if (attr == null) {
+                return null;
+            }
+        }
+
+        log.debug("Search attribute of collection item: " + uuid);
+
+        Node tmp = (Node) XMLUtils.xpath(
+            attr,
+            "/art:attribute",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (tmp == null) {
+            log.warn("No attribute found. Operation failed.");
+            return null;
+        }
+
+        log.debug("Search theme '" + themeName + "' in attribute.");
+
+        Node theme = (Node) XMLUtils.xpath(
+            tmp,
+            "art:themes/theme[@name='" + themeName + "']",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (theme == null) {
+            log.warn("Could not find the theme in attribute of: " + uuid);
+
+            Theme t = getThemeForFacet(uuid, facet, context);
+
+            if (t == null) {
+                log.warn("No theme found for facet: " + facet);
+                return null;
+            }
+
+            addThemeToAttribute(uuid, attr, t, context);
+            theme   = t.toXML().getFirstChild();
+        }
+
+        Document doc = XMLUtils.newDocument();
+        doc.appendChild(doc.importNode(theme, true));
+
+        return doc;
+    }
+
+
+    /**
+     * Adds the theme of a facet to a CollectionItem's attribute.
+     *
+     * @param uuid The uuid of the artifact.
+     * @param attr The current attribute of an artifact.
+     * @param t The theme to add.
+     * @param context The CallContext.
+     */
+    protected void addThemeToAttribute(
+        String      uuid,
+        Document    attr,
+        Theme       t,
+        CallContext context)
+    {
+        log.debug("FLYSArtifactCollection.addThemeToAttribute: " + uuid);
+
+        if (t == null) {
+            log.warn("Theme is empty - cancel adding it to attribute!");
+            return;
+        }
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            attr,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Node tmp = (Node) XMLUtils.xpath(
+            attr,
+            "/art:attribute",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (tmp == null) {
+            tmp = ec.create("attribute");
+            attr.appendChild(tmp);
+        }
+
+        Node themes = (Node) XMLUtils.xpath(
+            tmp,
+            "art:themes",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (themes == null) {
+            themes = ec.create("themes");
+            tmp.appendChild(themes);
+        }
+
+        themes.appendChild(attr.importNode(t.toXML().getFirstChild(), true));
+
+        try {
+            setCollectionItemAttribute(uuid, attr, context);
+
+            log.debug("Successfully added theme to item attribute.");
+        }
+        catch (ArtifactDatabaseException e) {
+            // do nothing
+            log.warn("Cannot set attribute of item: " + uuid);
+        }
+    }
+
+
+    /**
+     * Initializes the attribute of an collection item with the theme of a
+     * specific facet.
+     *
+     * @param uuid The uuid of an artifact.
+     * @param facet The name of a facet.
+     * @param context The CallContext.
+     *
+     * @param the new attribute.
+     */
+    protected Document initItemAttribute(
+        String      uuid,
+        String      facet,
+        CallContext context)
+    {
+        log.info("FLYSArtifactCollection.initItemAttribute");
+
+        Theme t = getThemeForFacet(uuid, facet, context);
+
+        if (t == null) {
+            log.info("Could not find theme for facet. Cancel initialization.");
+            return null;
+        }
+
+        Document attr = XMLUtils.newDocument();
+
+        addThemeToAttribute(uuid, attr, t, context);
+
+        return attr;
+    }
+
+
+    /**
+     * Sets the attribute of a CollectionItem specified by <i>uuid</i> to a new
+     * value <i>attr</i>.
+     *
+     * @param uuid The uuid of the CollectionItem.
+     * @param attr The new attribute for the CollectionItem.
+     * @param context The CallContext.
+     */
+    public void setCollectionItemAttribute(
+        String      uuid,
+        Document    attr,
+        CallContext context)
+    throws ArtifactDatabaseException
+    {
+        Document doc = ClientProtocolUtils.newSetItemAttributeDocument(
+            uuid,
+            attr);
+
+        if (doc == null) {
+            log.warn("Cannot set item attribute: No attribute found.");
+            return;
+        }
+
+        ArtifactDatabase db = context.getDatabase();
+        CallMeta       meta = context.getMeta();
+
+        db.setCollectionItemAttribute(identifier(), uuid, doc, meta);
+    }
+
+
+    /**
+     * Returns the theme of a specific facet.
+     *
+     * @param uuid The uuid of an artifact.
+     * @param facet The name of the facet.
+     * @param context The CallContext object.
+     *
+     * @return the desired theme.
+     */
+    protected Theme getThemeForFacet(
+        String uuid,
+        String facet,
+        CallContext context)
+    {
+        log.info("FLYSArtifactCollection.getThemeForFacet: " + facet);
+
+        FLYSContext flysContext = context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) context.globalContext();
+
+        return ThemeFactory.getTheme(flysContext, facet);
+    }
+
+
+    /**
+     * Returns the OutGenerator for a specified <i>type</i>.
+     *
+     * @param name The name of the output type.
+     * @param type Defines the type of the desired OutGenerator.
+     *
+     * @return The OutGenerator specified by <i>type</i>.
+     */
+    protected OutGenerator getOutGenerator(
+        CallContext context,
+        String      name,
+        String      type)
+    {
+        FLYSContext flysContext = context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) context.globalContext();
+
+        Map<String, Class> generators = (Map<String, Class>)
+            flysContext.get(FLYSContext.OUTGENERATORS_KEY);
+
+        if (generators == null) {
+            log.error("No output generators found in the running application!");
+            return null;
+        }
+
+        Class clazz = generators.get(name);
+
+        try {
+            return clazz != null ? (OutGenerator) clazz.newInstance() : null;
+        }
+        catch (InstantiationException ie) {
+            log.error(ie, ie);
+        }
+        catch (IllegalAccessException iae) {
+            log.error(iae, iae);
+        }
+
+        return null;
+    }
+
+
+    protected Element buildArtifactNode(
+        ArtifactDatabase        database,
+        String                  uuid,
+        CallContext             context,
+        XMLUtils.ElementCreator ec)
+    throws ArtifactDatabaseException
+    {
+        log.debug("Append artifact '" + uuid + "' to collection description");
+
+        // TODO
+        String hash = "MYHASH";
+
+        Element ci   = ec.create("artifact");
+        ec.addAttr(ci, "uuid", uuid, true);
+        ec.addAttr(ci, "hash", hash, true);
+
+        // XXX I am not sure if it works well every time with an empty document
+        // in the describe operation of an artifact.
+        Document description = database.describe(uuid, null, context.getMeta());
+
+        Node outputModes = (Node) XMLUtils.xpath(
+            description,
+            XPATH_ARTIFACT_OUTPUTMODES,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (outputModes != null) {
+            Document doc = ci.getOwnerDocument();
+            ci.appendChild(doc.importNode(outputModes, true));
+        }
+
+        return ci;
+    }
+
+
+    /**
+     * Inner class to structure/order the themes of a chart.
+     */
+    private class ThemeList {
+        private Logger logger = Logger.getLogger(ThemeList.class);
+        protected Vector<ManagedFacet> themes;
+
+        public ThemeList(Document output) {
+            themes = new Vector<ManagedFacet>();
+            parse(output);
+        }
+
+        protected void parse(Document output) {
+            NodeList themes = (NodeList) XMLUtils.xpath(
+                output,
+                "art:output/art:theme",
+                XPathConstants.NODESET,
+                ArtifactNamespaceContext.INSTANCE);
+
+            int num = themes != null ? themes.getLength() : 0;
+
+            logger.debug("Output has " +  num + " elements.");
+
+            if (num == 0) {
+                return;
+            }
+
+            for (int i = 0; i < num; i++) {
+                Node theme = themes.item(i);
+
+                String name = XMLUtils.xpathString(
+                    theme, "@art:facet", ArtifactNamespaceContext.INSTANCE);
+
+                String uuid = XMLUtils.xpathString(
+                    theme, "@art:artifact", ArtifactNamespaceContext.INSTANCE);
+
+                String pos = XMLUtils.xpathString(
+                    theme, "@art:pos", ArtifactNamespaceContext.INSTANCE);
+
+                String active = XMLUtils.xpathString(
+                    theme, "@art:active", ArtifactNamespaceContext.INSTANCE);
+
+                addTheme(uuid, name, pos, active);
+            }
+        }
+
+        protected void addTheme(
+            String uuid,
+            String name,
+            String position,
+            String active)
+        {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Add theme: ");
+                logger.debug(".. name: " + name);
+                logger.debug(".. uuid: " + uuid);
+                logger.debug(".. position: " + position);
+                logger.debug(".. active: " + active);
+            }
+
+            try {
+                int pos = Integer.parseInt(position);
+                int act = Integer.parseInt(active);
+
+                themes.add(pos-1, new ManagedFacet(name, null, uuid, pos, act));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        public ManagedFacet get(int idx) {
+            return themes.get(idx);
+        }
+
+        public int size() {
+            return themes.size();
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/OutputParser.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,141 @@
+package de.intevation.flys.collections;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Output;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.ManagedFacet;
+
+
+public class OutputParser {
+
+    /** Constant XPath that points to the outputmodes of an artifact.*/
+    public static final String XPATH_ARTIFACT_OUTPUTMODES =
+        "/art:result/art:outputmodes/art:output";
+
+
+    private static Logger logger = Logger.getLogger(OutputParser.class);
+
+    protected ArtifactDatabase db;
+    protected CallMeta         meta;
+
+    protected Map<String, Output> outs;
+
+
+    public OutputParser(ArtifactDatabase db, CallMeta meta) {
+        this.db   = db;
+        this.meta = meta;
+        this.outs = new HashMap<String, Output>();
+    }
+
+
+    public void parse(String uuid)
+    throws ArtifactDatabaseException
+    {
+        logger.debug("OutputParser.parse: " + uuid);
+
+        // XXX I am not sure if it works well every time with an empty
+        // document in the describe operation of an artifact.
+        Document description = db.describe(uuid, null, meta);
+
+        NodeList outs = (NodeList) XMLUtils.xpath(
+            description,
+            XPATH_ARTIFACT_OUTPUTMODES,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        int num = outs != null ? outs.getLength() : 0;
+
+        logger.debug("Artifact has " + num + " outputs.");
+
+        for (int i = 0; i < num; i++) {
+            Node out = outs.item(i);
+
+            parseOutput(uuid, out);
+        }
+    }
+
+
+    public Map<String, Output> getOuts() {
+        return outs;
+    }
+
+
+    protected void addItem(String out, ManagedFacet item) {
+        Output o = outs.get(out);
+
+        if (o != null) {
+            int num = o.getFacets().size();
+            item.setPosition(num+1);
+
+            o.addFacet(item);
+        }
+    }
+
+
+    protected void parseOutput(String uuid, Node out) {
+        String name = XMLUtils.xpathString(
+            out, "@art:name", ArtifactNamespaceContext.INSTANCE);
+
+        if (outs.get(name) == null) {
+            logger.debug("Create new output: " + name);
+            newOutput(out, name);
+        }
+
+        parseItems(uuid, out, name);
+    }
+
+
+    protected void newOutput(Node out, String name) {
+        String desc = XMLUtils.xpathString(
+            out, "@art:description", ArtifactNamespaceContext.INSTANCE);
+
+        String mimetype = XMLUtils.xpathString(
+            out, "@art:mime-type", ArtifactNamespaceContext.INSTANCE);
+
+        Output o = new DefaultOutput(name, desc, mimetype);
+
+        outs.put(name, o);
+    }
+
+
+    protected void parseItems(String uuid, Node out, String outname) {
+        NodeList facets = (NodeList) XMLUtils.xpath(
+            out, "art:facets/art:facet",
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        int num = facets != null ? facets.getLength() : 0;
+
+        logger.debug("Output has " + num + " facets.");
+
+        for (int i = 0; i < num; i++) {
+            Node facet = facets.item(i);
+
+            String name = XMLUtils.xpathString(
+                facet, "@art:name", ArtifactNamespaceContext.INSTANCE);
+
+            ManagedFacet item = new ManagedFacet(name, null, uuid, 1, 1);
+
+            addItem(outname, item);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/AbstractExporter.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,196 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * An abstract exporter that implements some basic methods for exporting data of
+ * artifacts.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class AbstractExporter implements OutGenerator {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(AbstractExporter.class);
+
+
+    /** The name of the CSV facet which triggers the CSV creation.*/
+    public static final String FACET_CSV = "csv";
+
+    /** The default charset for the CSV export.*/
+    public static final String DEFAULT_CSV_CHARSET = "UTF-8";
+
+    /** The default separator for the CSV export.*/
+    public static final char DEFAULT_CSV_SEPARATOR = ',';
+
+
+    /** The document of the incoming out() request.*/
+    protected Document request;
+
+    /** The output stream where the data should be written to.*/
+    protected OutputStream out;
+
+    /** The CallContext object.*/
+    protected CallContext context;
+
+    /** The selected facet.*/
+    protected String facet;
+
+    /** The master artifact.*/
+    protected Artifact master;
+
+
+    /**
+     * Concrete subclasses need to use this method to write their special data
+     * objects into the CSV document.
+     *
+     * @param writer The CSVWriter.
+     */
+    protected abstract void writeCSVData(CSVWriter writer);
+
+
+    /**
+     * This method enables concrete subclasses to collected its own special
+     * data.
+     *
+     * @param artifacts The artifact that stores the data that has to be
+     * exported.
+     */
+    protected abstract void addData(Artifact artifact);
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("AbstractExporter.init");
+
+        this.request = request;
+        this.out     = out;
+        this.context = context;
+    }
+
+
+    public void setMasterArtifact(Artifact master) {
+        this.master = master;
+    }
+
+
+    /**
+     * This doOut() just collects the data of multiple artifacts. Therefore, it
+     * makes use of the addData() method which enables concrete subclasses to
+     * store its data on its own. The real output creation takes place in the
+     * concrete generate() methods.
+     *
+     * @param artifact The artifact.
+     * @param facet The facet to add - NOTE: the facet needs to fit to the first
+     * facet inserted into this exporter. Otherwise this artifact/facet is
+     * skipped.
+     * @param attr The attr document.
+     */
+    public void doOut(Artifact artifact, String facet, Document attr) {
+        logger.debug("AbstractExporter.doOut: " + facet);
+
+        if (!isFacetValid(facet)) {
+            logger.warn("Facet '" + facet + "' not valid. No output created!");
+            return;
+        }
+
+        addData(artifact);
+    }
+
+
+    /**
+     * Generates an export based on a specified facet.
+     */
+    public void generate()
+    throws IOException
+    {
+        logger.debug("AbstractExporter.generate");
+
+        if (facet != null && facet.equals(FACET_CSV)) {
+            generateCSV();
+        }
+        else {
+            throw new IOException("invalid facet for exporter.");
+        }
+    }
+
+
+    /**
+     * Determines if the desired facet is valid for this exporter. If no facet
+     * is currently set, <i>facet</i> is set.
+     *
+     * @param facet The desired facet.
+     *
+     * @return true, if <i>facet</i> is valid, otherwise false.
+     */
+    protected boolean isFacetValid(String facet) {
+        logger.debug("AbstractExporter.isFacetValid");
+
+        if (facet == null || facet.length() == 0) {
+            return false;
+        }
+        else if (this.facet == null || this.facet.length() == 0) {
+            logger.debug("Set the facet of this export: " + facet);
+            this.facet = facet;
+
+            return true;
+        }
+        else {
+            return this.facet.equals(facet);
+        }
+    }
+
+
+
+    protected String msg(String key, String def) {
+        return Resources.getMsg(context.getMeta(), key, def);
+    }
+
+
+    /**
+     * This method starts CSV creation. It makes use of writeCSVData() which has
+     * to be implemented by concrete subclasses.
+     */
+    protected void generateCSV()
+    throws    IOException
+    {
+        logger.info("AbstractExporter.generateCSV");
+
+        CSVWriter writer = new CSVWriter(
+            new OutputStreamWriter(
+                out,
+                DEFAULT_CSV_CHARSET),
+            DEFAULT_CSV_SEPARATOR);
+
+        writeCSVData(writer);
+
+        writer.close();
+    }
+
+
+    protected NumberFormat getFormatter(int min, int max) {
+        Locale       locale = Resources.getLocale(context.getMeta());
+        NumberFormat nf     = NumberFormat.getInstance(locale);
+
+        nf.setMaximumFractionDigits(max);
+        nf.setMinimumFractionDigits(min);
+
+        return nf;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartExportHelper.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2011 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.flys.exports;
+
+import com.lowagie.text.Document;
+import com.lowagie.text.DocumentException;
+import com.lowagie.text.PageSize;
+import com.lowagie.text.Rectangle;
+
+import com.lowagie.text.pdf.PdfContentByte;
+import com.lowagie.text.pdf.PdfTemplate;
+import com.lowagie.text.pdf.PdfWriter;
+
+import java.awt.Graphics2D;
+import java.awt.Transparency;
+
+import java.awt.geom.Rectangle2D.Double;
+
+import java.awt.geom.Rectangle2D;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+import javax.imageio.ImageIO;
+
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.svggen.SVGGraphics2DIOException;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * This class is a helper class which supports some methods to export charts
+ * into specific formats.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ChartExportHelper {
+
+    /**
+     * Constant field to define A4 as default page size.
+     */
+    private static final String  DEFAULT_PAGE_SIZE = "A4";
+
+    /**
+     * Constant field to define UTF-8 as default encoding.
+     */
+    private static final String  DEFAULT_ENCODING  = "UTF-8";
+
+    /**
+     * Logger used for logging with log4j.
+     */
+    private static Logger log = Logger.getLogger(ChartExportHelper.class);
+
+
+    /**
+     * A method to export a <code>JFreeChart</code> as image to an
+     * <code>OutputStream</code> with a given format, width and height.
+     *
+     * @param out OutputStream
+     * @param chart JFreeChart object to be exported.
+     * @param format Format (e.g. png, gif, jpg)
+     * @param width Width, the image used to be
+     * @param height Height, the image used to be
+     *
+     * @throws IOException if writing image to OutputStream failed.
+     */
+    public static void exportImage(
+        OutputStream out,
+        JFreeChart chart,
+        String format,
+        int width,
+        int height
+    )
+    throws IOException
+    {
+        log.info("export chart as png");
+
+        ImageIO.write(
+            chart.createBufferedImage(
+                width, height, Transparency.BITMASK, null
+            ),
+            format,
+            out
+        );
+    }
+
+
+    /**
+     * A method to export a <code>JFreeChart</code> as SVG to an
+     * <code>OutputStream</code>.
+     *
+     * @param out OutputStream
+     * @param chart JFreeChart to be exported
+     * @param encoding Encoding, defaults to {@link #DEFAULT_ENCODING} if null
+     * @param width Width the svg used to be
+     * @param height Height the svg used to be
+     */
+    public static void exportSVG(
+        OutputStream out,
+        JFreeChart   chart,
+        String       encoding,
+        int          width,
+        int          height
+    ) {
+        log.info("export chart as svg");
+
+        if (encoding == null)
+            encoding = DEFAULT_ENCODING;
+
+        org.w3c.dom.Document document = XMLUtils.newDocument();
+        SVGGraphics2D        graphics = new SVGGraphics2D(document);
+
+        chart.draw(graphics, new Rectangle2D.Double(0.0D, 0.0D,width,height));
+
+        try {
+            graphics.stream(new OutputStreamWriter(out, encoding));
+        }
+        catch (SVGGraphics2DIOException svge) {
+            log.error("Error while writing svg export to output stream.", svge);
+        }
+        catch (UnsupportedEncodingException uee) {
+            log.error("Unsupported encoding: " + encoding, uee);
+        }
+    }
+
+
+    /**
+     * A method to export a <code>JFreeChart</code> as PDF to an
+     * <code>OutputStream</code>.
+     *
+     * @param out OutputStream
+     * @param chart JFreeChart
+     * @param pageFormat String to specify a page format, {@link
+     * #DEFAULT_PAGE_SIZE} is used if no pageFormat is given
+     * @param landscape If this is true, the pdf is delivered in landscape
+     * format
+     * @param marginLeft Space to left border
+     * @param marginRight Space to right border
+     * @param marginTop Space to upper border
+     * @param marginBottom Space to lower border
+     */
+    public static void exportPDF(
+        OutputStream out,
+        JFreeChart   chart,
+        String       pageFormat,
+        float        marginLeft,
+        float        marginRight,
+        float        marginTop,
+        float        marginBottom,
+        CallContext  context
+    ) {
+        log.info("export chart as pdf.");
+
+        if (pageFormat == null)
+            pageFormat = DEFAULT_PAGE_SIZE;
+
+        // max size of the chart
+        Rectangle page = PageSize.getRectangle(pageFormat);
+        float pageWidth  = page.getWidth();
+        float pageHeight = page.getHeight();
+
+        // the chart width
+        int chartWidth  = (Integer) context.getContextValue("chart.width");
+        int chartHeight = (Integer) context.getContextValue("chart.height");
+
+        boolean landscape = chartWidth > chartHeight ? true : false;
+
+        float width  = 0;
+        float height = 0;
+        if (landscape) {
+            width  = pageHeight;
+            height = pageWidth;
+        }
+        else {
+            width  = pageWidth;
+            height = pageHeight;
+        }
+
+        float spaceX = width  - marginLeft - marginRight;
+        if (chartWidth > spaceX) {
+            log.warn("Width of the chart is too big for pdf -> resize it now.");
+            double ratio = ((double)spaceX) / chartWidth;
+            chartWidth  *= ratio;
+            chartHeight *= ratio;
+            log.debug("Resized chart to " + chartWidth + "x" + chartHeight);
+        }
+
+        float spaceY = height - marginTop  - marginBottom;
+        if (chartHeight > spaceY) {
+            log.warn("Height of the chart is too big for pdf -> resize it now.");
+            double ratio = ((double)spaceY) / chartHeight;
+            chartWidth  *= ratio;
+            chartHeight *= ratio;
+            log.debug("Resized chart to " + chartWidth + "x" + chartHeight);
+        }
+
+        Document document = null;
+        if (landscape) {
+            document = new Document(page.rotate());
+            log.debug("Create landscape pdf.");
+        }
+        else
+            document = new Document(page);
+
+        try {
+            PdfWriter writer = PdfWriter.getInstance(document, out);
+
+            document.addSubject(chart.getTitle().getText());
+            document.addCreationDate();
+            document.open();
+
+            PdfContentByte content  = writer.getDirectContent();
+
+            PdfTemplate template = content.createTemplate(width, height);
+            Graphics2D  graphics = template.createGraphics(width, height);
+
+            double[] origin = getCenteredAnchor(
+                marginLeft, marginRight, marginBottom, marginTop,
+                width, height,
+                chartWidth, chartHeight);
+
+            Rectangle2D area = new Rectangle2D.Double(
+                origin[0], origin[1], chartWidth, chartHeight);
+
+            chart.draw(graphics, area);
+            graphics.dispose();
+            content.addTemplate(template, 0f, 0f);
+        }
+        catch (DocumentException de) {
+            log.error("Error while exporting chart to pdf.", de);
+        }
+        finally {
+            document.close();
+        }
+    }
+
+
+    /**
+     * This method returns the anchor of the chart so that the chart is centered
+     * according to the given parameters.
+     *
+     * @param mLeft Left margin
+     * @param mRight Right margin
+     * @param mBottom Bottom margin
+     * @param mTop Top margin
+     * @param width The complete width of the drawing area.
+     * @param height The complete height of the drawing area.
+     * @param chartWidth The width of the chart.
+     * @param chartHeight The height of the chart.
+     *
+     * @return an array that contains the anchor for a chart with the given
+     * parameters. The first value is the x point, the second value is the y
+     * point.
+     */
+    public static double[] getCenteredAnchor(
+        double mLeft,      double mRight,      double mBottom, double mTop,
+        double width,      double height,
+        double chartWidth, double chartHeight
+    ) {
+        if (log.isDebugEnabled()) {
+            log.debug("Calculate centered origin...");
+            log.debug("-> PDF width    : " + width);
+            log.debug("-> PDF height   : " + height);
+            log.debug("-> Chart width  : " + chartWidth);
+            log.debug("-> Chart height : " + chartHeight);
+            log.debug("-> margin left  : " + mLeft);
+            log.debug("-> margin right : " + mRight);
+            log.debug("-> margin bottom: " + mBottom);
+            log.debug("-> margin top   : " + mTop);
+        }
+
+        double[] origin = new double[2];
+
+        double centerX = width  / 2;
+        double centerY = height / 2;
+
+        origin[0] = centerX - chartWidth / 2;
+        origin[1] = centerY - chartHeight / 2;
+
+        origin[0] = origin[0] >= mLeft ? origin[0] : mLeft;
+        origin[1] = origin[1] >= mTop ? origin[1] : mTop;
+
+        if (log.isDebugEnabled()) {
+            log.debug("==> centered left origin: " + origin[0]);
+            log.debug("==> centered top  origin: " + origin[1]);
+        }
+
+        return origin;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,152 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * The base class for chart creation. It should provide some basic things that
+ * equal in all chart types.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class ChartGenerator implements OutGenerator {
+
+    private static Logger logger = Logger.getLogger(ChartGenerator.class);
+
+
+    /** The default chart width, if no other width is set.*/
+    public static final int DEFAULT_CHART_WIDTH  = 600;
+
+    /** The default chart height, if no other height is set.*/
+    public static final int DEFAULT_CHART_HEIGHT = 400;
+
+    /** The XPath that points to the chart size of the incoming request
+     * document.*/
+    public static final String XPATH_CHART_SIZE =
+        "/art:action/art:attributes/art:size";
+
+
+    /** The document of the incoming out() request.*/
+    protected Document request;
+
+    /** The output stream where the data should be written to.*/
+    protected OutputStream out;
+
+    /** The CallContext object.*/
+    protected CallContext context;
+
+    /** The artifact that is used to decorate the chart with meta information.*/
+    protected Artifact master;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("ChartGenerator.init");
+
+        this.request = request;
+        this.out     = out;
+        this.context = context;
+    }
+
+
+    public void setMasterArtifact(Artifact master) {
+        this.master = master;
+    }
+
+
+    protected String msg(String key, String def) {
+        return Resources.getMsg(context.getMeta(), key, def);
+    }
+
+
+    protected String msg(String key, String def, Object[] args) {
+        return Resources.getMsg(context.getMeta(), key, def, args);
+    }
+
+
+    protected String getRiverName() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        River river = flys.getRiver();
+        return river != null ? river.getName() : "";
+    }
+
+
+    protected double[] getRange() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return flys.getDistance();
+    }
+
+
+    /**
+     * Returns the size of a chart export as array which has been specified by
+     * the incoming request document.
+     *
+     * @return the size of a chart as [width, height] or the result of
+     * getDefaultSize() if no width or height are given in the request document.
+     */
+    protected int[] getSize() {
+        int[] size = new int[2];
+
+        Node sizeEl = (Node) XMLUtils.xpath(
+            request,
+            XPATH_CHART_SIZE,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (sizeEl != null) {
+            String w = XMLUtils.xpathString(
+                sizeEl, "@art:width", ArtifactNamespaceContext.INSTANCE);
+
+            String h = XMLUtils.xpathString(
+                sizeEl, "@art:height", ArtifactNamespaceContext.INSTANCE);
+
+            if (w != null && w.length() > 0 && h != null && h.length() > 0) {
+                try {
+                    size[0] = Integer.parseInt(w);
+                    size[1] = Integer.parseInt(h);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.warn("Wrong values for chart width/height.");
+                }
+            }
+        }
+
+        return size[0] > 0 && size[1] > 0 ? size : getDefaultSize();
+    }
+
+
+    /**
+     * Returns the default size of a chart export as array.
+     *
+     * @return the default size of a chart as [width, height].
+     */
+    protected int[] getDefaultSize() {
+        return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT };
+    }
+
+
+    public abstract void doOut(Artifact artifact, String facet, Document attr);
+
+    public abstract void generate() throws IOException;
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,118 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQKms;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputedDischargeCurveExporter extends AbstractExporter {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger =
+        Logger.getLogger(ComputedDischargeCurveExporter.class);
+
+
+    public static final int W_MIN_DIGITS  = 2;
+    public static final int W_MAX_DIGITS  = 2;
+    public static final int Q_MIN_DIGITS  = 0;
+    public static final int Q_MAX_DIGITS  = 0;
+
+    public static final String CSV_W_HEADER =
+        "export.computed.discharge.curve.csv.header.w";
+
+    public static final String CSV_Q_HEADER =
+        "export.computed.discharge.curve.csv.header.q";
+
+    public static final String DEFAULT_CSV_W_HEADER  = "W [NN + m]";
+    public static final String DEFAULT_CSV_Q_HEADER  = "Q [m³/s]";
+
+
+    protected List<WQKms> data;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("ComputedDischargeCurveExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WQKms>();
+    }
+
+
+    protected void addData(Artifact artifact) {
+        WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
+        data.add(winfoArtifact.getComputedDischargeCurveData());
+    }
+
+
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("ComputedDischargeCurveExporter.writeData");
+
+        writeCSVHeader(writer);
+
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        double[] res = new double[3];
+
+        for (WQKms wqkms: data) {
+            int size = wqkms.size();
+
+            for (int i = 0; i < size; i++) {
+                res = wqkms.get(i, res);
+
+                writer.writeNext(new String[] {
+                    wf.format(res[0]),
+                    qf.format(res[1])
+                });
+            }
+        }
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.debug("ComputedDischargeCurveExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+            msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER)
+        });
+    }
+
+
+    /**
+     * Returns the number formatter for W values.
+     *
+     * @return the number formatter for W values.
+     */
+    protected NumberFormat getWFormatter() {
+        return getFormatter(W_MIN_DIGITS, W_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values.
+     *
+     * @return the number formatter for Q values.
+     */
+    protected NumberFormat getQFormatter() {
+        return getFormatter(Q_MIN_DIGITS, Q_MAX_DIGITS);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,98 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.title.TextTitle;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQKms;
+
+
+/**
+ * An OutGenerator that generates discharge curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputedDischargeCurveGenerator extends DischargeCurveGenerator {
+
+    /** The logger used in this generator.*/
+    private static Logger logger =
+        Logger.getLogger(ComputedDischargeCurveGenerator.class);
+
+
+    public static final String I18N_CHART_TITLE =
+        "chart.computed.discharge.curve.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.computed.discharge.curve.subtitle";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.computed.discharge.curve.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT = "Abflusskurve";
+    public static final String I18N_YAXIS_LABEL_DEFAULT = "W [NN + m]";
+
+
+    @Override
+    protected String getChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        double[] dist  = getRange();
+
+        Object[] args = new Object[] {
+            getRiverName(),
+            dist[0]
+        };
+
+        String subtitle = msg(I18N_CHART_SUBTITLE, "", args);
+        chart.addSubtitle(new TextTitle(subtitle));
+    }
+
+
+    @Override
+    protected String getYAxisLabel() {
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    @Override
+    public void doOut(Artifact artifact, String facet, Document attr) {
+        logger.debug("ComputedDischargeCurveGenerator.doOut");
+
+        WQKms wqkms = getData(artifact);
+
+        int size = wqkms.size();
+
+        double[][] data = new double[2][size];
+        double[]   res  = new double[3];
+
+        for (int i = 0; i < size; i++) {
+            res = wqkms.get(i, res);
+
+            data[0][i] = res[1];
+            data[1][i] = res[0];
+        }
+
+        // TODO find the correct name
+        dataset.addSeries("Abflusskurve", data);
+    }
+
+
+    protected WQKms getData(Artifact artifact) {
+        logger.debug("ComputedDischargeCurveGenerator.getData");
+
+        WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
+
+        return winfoArtifact.getComputedDischargeCurveData();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,139 @@
+package de.intevation.flys.exports;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.xy.DefaultXYDataset;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.DischargeTables;
+import de.intevation.flys.artifacts.model.GaugesFactory;
+
+
+/**
+ * An OutGenerator that generates discharge curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeCurveGenerator extends XYChartGenerator {
+
+    /** The logger used in this generator.*/
+    private static Logger logger =
+        Logger.getLogger(DischargeCurveGenerator.class);
+
+
+    public static final String I18N_CHART_TITLE =
+        "chart.discharge.curve.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.discharge.curve.subtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.discharge.curve.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.discharge.curve.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  = "Abflusskurven";
+    public static final String I18N_XAXIS_LABEL_DEFAULT  = "Q [m³/s]";
+    public static final String I18N_YAXIS_LABEL_DEFAULT  = "W [cm]";
+
+
+    /** The Dataset that contains the data for the chart.*/
+    protected DefaultXYDataset dataset;
+
+
+    public DischargeCurveGenerator() {
+        super();
+
+        this.dataset = new DefaultXYDataset();
+    }
+
+
+    protected String getChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+
+    }
+
+
+    protected String getXAxisLabel() {
+        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
+    }
+
+
+    protected String getYAxisLabel() {
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    protected void addDatasets(JFreeChart chart) {
+        XYPlot plot = (XYPlot) chart.getPlot();
+        plot.setDataset(0, dataset);
+    }
+
+
+    public void doOut(Artifact artifact, String facet, Document attr) {
+        logger.debug("DischargeCurveGenerator.doOut: " + facet);
+
+        if (!(artifact instanceof FLYSArtifact)) {
+            logger.error("Artifact is no instance of FLYSArtifact.");
+            return;
+        }
+
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+
+        River    river    = flysArtifact.getRiver();
+        double[] distance = flysArtifact.getDistance();
+
+        List<double[]> ranges = new ArrayList<double[]>();
+        ranges.add(distance);
+
+        List<Gauge> gauges   = GaugesFactory.getGauges(river);
+        List<Gauge> filtered = GaugesFactory.filterRanges(gauges, ranges);
+
+        if (logger.isDebugEnabled()) {
+            int numGauges   = gauges != null ? gauges.size() : 0;
+            int numFiltered = filtered != null ? filtered.size() : 0;
+
+            logger.debug("++++++++++++++++++++");
+            logger.debug("Search gauges for river: " + river.getName());
+            logger.debug("-> ... from range: " + distance[0]);
+            logger.debug("-> ... to range: " + distance[1]);
+            logger.debug("-> Found " + numGauges + " gauges in total.");
+            logger.debug("-> Found " + numFiltered + " gauges after filtering.");
+            logger.debug("++++++++++++++++++++");
+        }
+
+        String[] gaugeNames = new String[filtered.size()];
+        int idx = 0;
+        for (Gauge gauge: filtered) {
+            gaugeNames[idx++] = gauge.getName();
+        }
+
+        DischargeTables dt = new DischargeTables(river.getName(), gaugeNames);
+        Map<String, double[][]> gaugeValues = dt.getValues(100d);
+
+        for (String gauge: gaugeNames) {
+            double[][] values = gaugeValues.get(gauge);
+            dataset.addSeries(gauge, values);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,94 @@
+package de.intevation.flys.exports;
+
+import java.text.NumberFormat;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQCKms;
+import de.intevation.flys.artifacts.model.WQKms;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeLongitudinalSectionExporter extends WaterlevelExporter {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger =
+        Logger.getLogger(DischargeLongitudinalSectionExporter.class);
+
+
+    public static final String CSV_KM_HEADER =
+        "export.discharge.longitudinal.section.csv.header.km";
+
+    public static final String CSV_W_HEADER =
+        "export.discharge.longitudinal.section.csv.header.w";
+
+    public static final String CSV_CW_HEADER =
+        "export.discharge.longitudinal.section.csv.header.cw";
+
+    public static final String CSV_Q_HEADER =
+        "export.discharge.longitudinal.section.csv.header.q";
+
+    public static final String DEFAULT_CSV_KM_HEADER = "Fluss-Km";
+    public static final String DEFAULT_CSV_W_HEADER  = "W [NN + m]";
+    public static final String DEFAULT_CSV_CW_HEADER = "W korr.";
+    public static final String DEFAULT_CSV_Q_HEADER  = "Q [m³/s]";
+
+
+    @Override
+    protected void addData(Artifact artifact) {
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        WQKms[]       wqkms = winfo.getDischargeLongitudinalSectionData();
+
+        logger.debug("Got " + wqkms != null ? wqkms.length : 0 + " WQKms obj.");
+
+        data.add(wqkms);
+    }
+
+
+    @Override
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.info("WaterlevelExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
+            msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+            msg(CSV_CW_HEADER, DEFAULT_CSV_CW_HEADER),
+            msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER)
+        });
+    }
+
+    @Override
+    protected void wQKms2CSV(CSVWriter writer, WQKms wqkms) {
+        logger.debug("WaterlevelExporter.wQKms2CSV");
+
+        int      size   = wqkms.size();
+        double[] result = new double[4];
+
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        for (int i = 0; i < size; i ++) {
+            result = wqkms.get(i, result);
+
+            String wc = "";
+            if (wqkms instanceof WQCKms) {
+                wc = wf.format(result[3]);
+            }
+
+            writer.writeNext(new String[] {
+                kmf.format(result[2]),
+                wf.format(result[0]),
+                wc,
+                qf.format(result[1])
+            });
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,180 @@
+package de.intevation.flys.exports;
+
+import java.awt.Color;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQCKms;
+import de.intevation.flys.artifacts.model.WQKms;
+
+
+/**
+ * An OutGenerator that generates discharge longitudinal section curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeLongitudinalSectionGenerator
+extends      LongitudinalSectionGenerator
+{
+    private static Logger logger =
+        Logger.getLogger(DischargeLongitudinalSectionGenerator.class);
+
+
+    /** The facet for Ws.*/
+    public static final String FACET_W = "discharge_longitudinal_section.w";
+
+    /** The facet for Qs.*/
+    public static final String FACET_Q = "discharge_longitudinal_section.q";
+
+
+    /** The storage for the corrected W series to be drawn in this chart.*/
+    protected XYSeriesCollection cw;
+
+
+    public DischargeLongitudinalSectionGenerator() {
+        super();
+
+        this.cw = new XYSeriesCollection();
+    }
+
+
+    public void addDatasets(JFreeChart chart) {
+        super.addDatasets(chart);
+
+        XYPlot plot = (XYPlot) chart.getPlot();
+
+        plot.setDataset(2, cw);
+    }
+
+
+    protected void adjustPlot(XYPlot plot) {
+        super.adjustPlot(plot);
+
+        // TODO REMOVE THIS CODE, IF WE HAVE INTRODUCED THEMES!
+        XYLineAndShapeRenderer rw = (XYLineAndShapeRenderer)
+            plot.getRendererForDataset(w);
+
+        XYLineAndShapeRenderer rcw = null;
+        try {
+            rcw = (XYLineAndShapeRenderer) rw.clone();
+        }
+        catch (Exception e) {
+            logger.error(e, e);
+        }
+
+        int cwNum = cw.getSeriesCount();
+
+        for (int i = 0; i < cwNum; i++) {
+            rcw.setSeriesPaint(i, Color.RED);
+        }
+
+        plot.setRenderer(2, rcw);
+    }
+
+
+
+    @Override
+    public void doOut(Artifact artifact, String facet, Document attr) {
+        logger.debug("DischargeLongitudinalSectionGenerator.doOut");
+
+        if (facet != null && facet.equals(FACET_W)) {
+            doWOut(getDischargeLongitudinalSectionData(artifact));
+        }
+        else if (facet != null && facet.equals(FACET_Q)) {
+            doQOut(getDischargeLongitudinalSectionData(artifact));
+        }
+        else {
+            logger.warn("Unknown facet name: " + facet);
+            return;
+        }
+    }
+
+
+    protected WQKms[] getDischargeLongitudinalSectionData(Artifact artifact) {
+        WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
+        return winfoArtifact.getDischargeLongitudinalSectionData();
+    }
+
+
+    /**
+     * This method adds new Series for the wqkms objects. If there are instances
+     * of WQCKms in this array, there is a further curve that displays the
+     * corrected W values.
+     *
+     * @param wqkms An array of WQKms objects to be displayed.
+     */
+    protected void doWOut(WQKms[] wqkms) {
+        logger.debug("LongitudinalSectionGenerator.doWOut");
+
+        int idx = 0;
+        for (WQKms tmp: wqkms) {
+            if (tmp instanceof WQCKms) {
+                doCorrectedWOut((WQCKms) tmp);
+            }
+
+            doWOut(tmp);
+        }
+    }
+
+
+    /**
+     * Adds a new series for the W curve.
+     *
+     * @param wqkms The object that contains the W values.
+     */
+    protected void doWOut(WQKms wqkms) {
+        logger.debug("DischargeLongitudinalSectionGenerator.doWOut");
+
+        // TODO CREATE CORRECT SERIES NAME
+        XYSeries series = new XYSeries("W");
+
+        double[] target = new double[4];
+        int      size   = wqkms.size();
+
+        for (int i = 0; i < size; i++) {
+            target = wqkms.get(i, target);
+
+            series.add(target[2], target[0]);
+        }
+
+        w.addSeries(series);
+    }
+
+
+    /**
+     * Adds a new series for the corrected W curve.
+     *
+     * @param wqckms The object that contains the corrected W values.
+     */
+    protected void doCorrectedWOut(WQCKms wqckms) {
+        logger.debug("DischargeLongitudinalSectionGenerator.doCorrectedWOut");
+
+        // TODO CREATE CORRECT SERIES NAME
+        XYSeries series = new XYSeries("Korrigiert");
+
+        double[] target = new double[4];
+        int      size   = wqckms.size();
+
+        for (int i = 0; i < size; i++) {
+            target = wqckms.get(i, target);
+
+            series.add(target[2], target[3]);
+        }
+
+        if (series.getItemCount() > 0) {
+            cw.addSeries(series);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveExporter.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,139 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQDay;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DurationCurveExporter extends AbstractExporter {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(WaterlevelExporter.class);
+
+
+    public static final int W_MIN_DIGITS = 0;
+    public static final int W_MAX_DIGITS = 2;
+    public static final int Q_MIN_DIGITS = 0;
+    public static final int Q_MAX_DIGITS = 1;
+    public static final int D_MIN_DIGITS = 0;
+    public static final int D_MAX_DIGITS = 0;
+
+    public static final String CSV_DURATION_HEADER =
+        "export.duration.curve.csv.header.duration";
+
+    public static final String CSV_W_HEADER =
+        "export.duration.curve.csv.header.w";
+
+    public static final String CSV_Q_HEADER =
+        "export.duration.curve.csv.header.q";
+
+    public static final String DEFAULT_CSV_DURATION_HEADER = "D [Tagen]";
+    public static final String DEFAULT_CSV_W_HEADER  = "W [NN + m]";
+    public static final String DEFAULT_CSV_Q_HEADER  = "Q [m³/s]";
+
+
+    /** The storage that contains all WQKms objects for the different facets.*/
+    protected List<WQDay> data;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("DurationCurveExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WQDay>();
+    }
+
+
+    protected void addData(Artifact artifact) {
+        WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
+        data.add(winfoArtifact.getDurationCurveData());
+    }
+
+
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("DurationCurveExporter.writeData");
+
+        writeCSVHeader(writer);
+
+        for (WQDay wqday: data) {
+            wQDay2CSV(writer, wqday);
+        }
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.info("DurationCurveExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+            msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
+            msg(CSV_DURATION_HEADER, DEFAULT_CSV_DURATION_HEADER)
+        });
+    }
+
+
+    protected void wQDay2CSV(CSVWriter writer, WQDay wqday) {
+        logger.debug("DurationCurveExporter.wQDay2CSV");
+
+        int size = wqday.size();
+
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+        NumberFormat df  = getDFormatter();
+
+        for (int i = 0; i < size; i ++) {
+            writer.writeNext(new String[] {
+                wf.format(wqday.getW(i)),
+                qf.format(wqday.getQ(i)),
+                df.format(wqday.getDay(i))
+            });
+        }
+    }
+
+
+    /**
+     * Returns the number formatter for W values.
+     *
+     * @return the number formatter for W values.
+     */
+    protected NumberFormat getWFormatter() {
+        return getFormatter(W_MIN_DIGITS, W_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values.
+     *
+     * @return the number formatter for Q values.
+     */
+    protected NumberFormat getQFormatter() {
+        return getFormatter(Q_MIN_DIGITS, Q_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for duration values.
+     *
+     * @return the number formatter for duration values.
+     */
+    protected NumberFormat getDFormatter() {
+        return getFormatter(D_MIN_DIGITS, D_MAX_DIGITS);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,234 @@
+package de.intevation.flys.exports;
+
+import java.awt.Color;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQDay;
+
+
+/**
+ * An OutGenerator that generates duration curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DurationCurveGenerator extends XYChartGenerator {
+
+    private static Logger logger =
+        Logger.getLogger(DurationCurveGenerator.class);
+
+    /** The storage for the W series to be drawn in this chart.*/
+    protected XYSeriesCollection w;
+
+    /** The storage for the Q series to be drawn in this chart.*/
+    protected XYSeriesCollection q;
+
+
+    public static final String DURATION_CURVE_W =
+        "duration_curve.w";
+
+    public static final String DURATION_CURVE_Q =
+        "duration_curve.q";
+
+    public static final String I18N_CHART_TITLE =
+        "chart.duration.curve.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.duration.curve.subtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.duration.curve.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.duration.curve.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  =
+        "Dauerlinie";
+
+    public static final String I18N_XAXIS_LABEL_DEFAULT  =
+        "Unterschreitungsdauer [Tage]";
+
+    public static final String I18N_YAXIS_LABEL_DEFAULT  =
+        "W [NN + m]";
+
+
+    public DurationCurveGenerator() {
+        super();
+
+        this.w = new XYSeriesCollection();
+        this.q = new XYSeriesCollection();
+    }
+
+
+    protected String getChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        double[] dist  = getRange();
+
+        Object[] args = new Object[] {
+            getRiverName(),
+            dist[0]
+        };
+
+        String subtitle = msg(I18N_CHART_SUBTITLE, "", args);
+        chart.addSubtitle(new TextTitle(subtitle));
+    }
+
+
+    protected String getXAxisLabel() {
+        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
+    }
+
+
+    protected String getYAxisLabel() {
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    public void addDatasets(JFreeChart chart) {
+        XYPlot plot = (XYPlot) chart.getPlot();
+
+        plot.setDataset(0, w);
+        plot.setDataset(1, q);
+    }
+
+
+    protected void adjustPlot(XYPlot plot) {
+        super.adjustPlot(plot);
+
+        // TODO REMOVE THIS CODE, IF WE HAVE INTRODUCED THEMES!
+        XYLineAndShapeRenderer rw = (XYLineAndShapeRenderer)
+            plot.getRendererForDataset(w);
+
+        XYLineAndShapeRenderer rq = null;
+        try {
+            rq = (XYLineAndShapeRenderer) rw.clone();
+        }
+        catch (Exception e) {
+            logger.error(e, e);
+        }
+
+        int wNum = w.getSeriesCount();
+        int qNum = q.getSeriesCount();
+
+        for (int i = 0; i < wNum; i++) {
+            rw.setSeriesPaint(i, Color.BLUE);
+        }
+
+        for (int i = 0; i < qNum; i++) {
+            rq.setSeriesPaint(i, Color.GREEN);
+        }
+
+        plot.setRenderer(0, rw);
+        plot.setRenderer(1, rq);
+    }
+
+
+    protected void adjustAxes(XYPlot plot) {
+        super.adjustAxes(plot);
+
+        NumberAxis qAxis = new NumberAxis("Q [m³/s]");
+
+        plot.setRangeAxis(2, qAxis);
+        plot.mapDatasetToRangeAxis(1, 2);
+    }
+
+
+    public void doOut(Artifact artifact, String facet, Document attr) {
+        logger.debug("DurationCurveGenerator.doOut: " + facet);
+
+        if (facet == null || facet.length() == 0) {
+            logger.error("No facet given. Cannot create dataset.");
+            return;
+        }
+
+        if (facet.equals(DURATION_CURVE_W)) {
+            doWOut(getDurationCurveData(artifact));
+        }
+        else if (facet.equals(DURATION_CURVE_Q)) {
+            doQOut(getDurationCurveData(artifact));
+        }
+        else {
+            logger.warn("Unknown facet name: " + facet);
+            return;
+        }
+    }
+
+
+    /**
+     * Creates the series for a duration curve's W facet.
+     *
+     * @param wqdays The WQDay store that contains the Ws.
+     */
+    protected void doWOut(WQDay wqdays) {
+        logger.debug("DurationCurveGenerator.doWOut");
+
+        // TODO find the correct series name
+        XYSeries series = new XYSeries("W-1");
+
+        int size = wqdays.size();
+        for (int i = 0; i < size; i++) {
+            int  day = wqdays.getDay(i);
+            double w = wqdays.getW(i);
+
+            series.add((double) day, w);
+        }
+
+        this.w.addSeries(series);
+    }
+
+
+    /**
+     * Creates the series for a duration curve's Q facet.
+     *
+     * @param wqdays The WQDay store that contains the Qs.
+     */
+    protected void doQOut(WQDay wqdays) {
+        logger.debug("DurationCurveGenerator.doQOut");
+
+        // TODO find the correct series name
+        XYSeries series = new XYSeries("Q-1");
+
+        int size = wqdays.size();
+        for (int i = 0; i < size; i++) {
+            int  day = wqdays.getDay(i);
+            double q = wqdays.getQ(i);
+
+            series.add((double) day, q);
+        }
+
+        this.q.addSeries(series);
+    }
+
+
+    /**
+     * Returns the computed data for a duration curve based on the artifact's
+     * computation method.
+     *
+     * @param artifact The WINFO artifact.
+     *
+     * @return the computed data for a duration curve's W and Q facet.
+     */
+    protected WQDay getDurationCurveData(Artifact artifact) {
+        WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
+        return winfoArtifact.getDurationCurveData();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,295 @@
+package de.intevation.flys.exports;
+
+import java.awt.Color;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQKms;
+
+
+/**
+ * An OutGenerator that generates discharge curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LongitudinalSectionGenerator extends XYChartGenerator {
+
+    /** The logger that is used in this generator.*/
+    private static Logger logger =
+        Logger.getLogger(LongitudinalSectionGenerator.class);
+
+
+    public static final String LONGITUDINAL_SECTION_W =
+        "longitudinal_section.w";
+
+    public static final String LONGITUDINAL_SECTION_Q =
+        "longitudinal_section.q";
+
+    public static final String I18N_CHART_TITLE =
+        "chart.longitudinal.section.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.longitudinal.section.subtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.longitudinal.section.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.longitudinal.section.yaxis.label";
+
+    public static final String I18N_2YAXIS_LABEL =
+        "chart.longitudinal.section.yaxis.second.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  = "W-Längsschnitt";
+    public static final String I18N_XAXIS_LABEL_DEFAULT  = "km";
+    public static final String I18N_YAXIS_LABEL_DEFAULT  = "W [NN + m]";
+    public static final String I18N_2YAXIS_LABEL_DEFAULT = "Q [m³/s]";
+
+    /** The storage for the W series to be drawn in this chart.*/
+    protected XYSeriesCollection w;
+
+    /** The storage for the Q series to be drawn in this chart.*/
+    protected XYSeriesCollection q;
+
+
+    public LongitudinalSectionGenerator() {
+        super();
+
+        this.w = new XYSeriesCollection();
+        this.q = new XYSeriesCollection();
+    }
+
+
+    protected String getChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        double[] dist  = getRange();
+
+        Object[] args = new Object[] {
+            getRiverName(),
+            dist[0],
+            dist[1]
+        };
+
+        String subtitle = msg(I18N_CHART_SUBTITLE, "", args);
+        chart.addSubtitle(new TextTitle(subtitle));
+    }
+
+
+    protected String getXAxisLabel() {
+        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
+    }
+
+
+    protected String getYAxisLabel() {
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    protected void addDatasets(JFreeChart chart) {
+        XYPlot plot = (XYPlot) chart.getPlot();
+
+        plot.setDataset(0, w);
+        plot.setDataset(1, q);
+    }
+
+
+    protected void adjustPlot(XYPlot plot) {
+        super.adjustPlot(plot);
+
+        // TODO REMOVE THIS CODE, IF WE HAVE INTRODUCED THEMES!
+        XYLineAndShapeRenderer rw = (XYLineAndShapeRenderer)
+            plot.getRendererForDataset(w);
+
+        XYLineAndShapeRenderer rq = null;
+        try {
+            rq = (XYLineAndShapeRenderer) rw.clone();
+        }
+        catch (Exception e) {
+            logger.error(e, e);
+        }
+
+        int wNum = w.getSeriesCount();
+        int qNum = q.getSeriesCount();
+
+        for (int i = 0; i < wNum; i++) {
+            rw.setSeriesPaint(i, Color.BLUE);
+        }
+
+        for (int i = 0; i < qNum; i++) {
+            rq.setSeriesPaint(i, Color.GREEN);
+        }
+
+        plot.setRenderer(0, rw);
+        plot.setRenderer(1, rq);
+    }
+
+
+    protected void adjustAxes(XYPlot plot) {
+        super.adjustAxes(plot);
+
+        NumberAxis qAxis = new NumberAxis(
+            msg(I18N_2YAXIS_LABEL, I18N_2YAXIS_LABEL_DEFAULT));
+
+        plot.setRangeAxis(2, qAxis);
+        plot.mapDatasetToRangeAxis(1, 2);
+
+        invertXAxis(plot.getDomainAxis());
+    }
+
+
+    /**
+     * This method inverts the x-axis based on the kilometer information of the
+     * selected river. If the head of the river is at kilometer 0, the axis is
+     * not inverted, otherwise it is.
+     *
+     * @param xaxis The domain axis.
+     */
+    protected void invertXAxis(ValueAxis xaxis) {
+        FLYSArtifact artifact = (FLYSArtifact) master;
+        River        river    = artifact.getRiver();
+
+        boolean up = river.getKmUp();
+
+        if (up) {
+            logger.info("Invert X-Axis.");
+            xaxis.setInverted(true);
+        }
+    }
+
+
+    public void doOut(Artifact artifact, String facet, Document attr) {
+        logger.debug("LongitudinalSectionGenerator.doOut: " + facet);
+
+        if (facet == null) {
+            logger.error("No facet name for doOut(). No output generated!");
+            return;
+        }
+
+        if (facet.equals(LONGITUDINAL_SECTION_W)) {
+            doWOut(getWaterlevelData(artifact));
+        }
+        else if (facet.equals(LONGITUDINAL_SECTION_Q)) {
+            doQOut(getWaterlevelData(artifact));
+        }
+        else {
+            logger.warn("Unknown facet name: " + facet);
+            return;
+        }
+    }
+
+
+    /**
+     * Returns the waterlevel data computed by the WINFOArtifact.
+     *
+     * @param artifact The WINFOArtifact.
+     *
+     * @return the computed waterlevel data.
+     */
+    protected WQKms[] getWaterlevelData(Artifact artifact) {
+        WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
+        WQKms[]       wqkms         = winfoArtifact.getWaterlevelData();
+
+        logger.debug("Got " + wqkms.length + " WQKms objects.");
+
+        return wqkms;
+    }
+
+
+    /**
+     * Process the output for W facets in a longitudinal section curve.
+     *
+     * @param wqkms An array of WQKms values.
+     */
+    protected void doWOut(WQKms[] wqkms) {
+        logger.debug("LongitudinalSectionGenerator.doWOut");
+
+        int idx = 0;
+        for (WQKms tmp: wqkms) {
+            XYSeries series = new XYSeries(getSeriesName(tmp, "w", idx++));
+
+            double[] target = new double[3];
+            int      size   = tmp.size();
+
+            if (logger.isDebugEnabled()) {
+                if (tmp.size() > 0) {
+                    logger.debug("Generate series: " + series.getKey());
+                    logger.debug("Start km: " + tmp.getKms(0));
+                    logger.debug("End   km: " + tmp.getKms(size-1));
+                    logger.debug("Values  : " + size);
+                }
+            }
+
+            for (int i = 0; i < size; i++) {
+                target = tmp.get(i, target);
+
+                series.add(target[2], target[0]);
+            }
+
+            w.addSeries(series);
+        }
+    }
+
+
+    /**
+     * Process the output for Q facets in a longitudinal section curve.
+     *
+     * @param wqkms An array of WQKms values.
+     */
+    protected void doQOut(WQKms[] wqkms) {
+        logger.debug("LongitudinalSectionGenerator.doQOut");
+
+        int idx = 0;
+        for (WQKms tmp: wqkms) {
+            XYSeries series = new XYSeries(getSeriesName(tmp, "Q", idx++));
+
+            double[] target = new double[3];
+            int      size   = tmp.size();
+
+            if (logger.isDebugEnabled()) {
+                if (tmp.size() > 0) {
+                    logger.debug("Generate series: " + series.getKey());
+                    logger.debug("Start km: " + tmp.getKms(0));
+                    logger.debug("End   km: " + tmp.getKms(size-1));
+                    logger.debug("Values  : " + size);
+                }
+            }
+
+            for (int i = 0; i < size; i++) {
+                target = tmp.get(i, target);
+
+                //logger.debug("++ Q Tuple: " + target[2] + " -> " + target[1]);
+                series.add(target[2], target[1]);
+            }
+
+            q.addSeries(series);
+        }
+    }
+
+
+    protected String getSeriesName(WQKms wqkms, String prefix, int idx) {
+        return prefix + "-" + idx;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/OutGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,56 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+
+/**
+ * An OutGenerator is used to create a collected outputs of a list of Artifacts.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface OutGenerator {
+
+    /**
+     * Initializes the OutGenerator with meta information which are necessary
+     * for the output generation.
+     *
+     * @param request The incomding request document.
+     * @param out     The output stream.
+     * @param context The CallContext that provides further information and
+     * objects used for the output generation.
+     */
+    void init(Document request, OutputStream out, CallContext context);
+
+    /**
+     * This method is used to tell the OutGenerator which artifact is the master
+     * artifact which is used for special operations.
+     *
+     * @param master The master artifact.
+     */
+    void setMasterArtifact(Artifact master);
+
+    /**
+     * Creates the output of an Artifact and appends that single output to the
+     * total output.
+     *
+     * @param artifact The artifact that provides information and data for the
+     * single output.
+     * @param attr A document that might contain some attributes used while
+     * producing the output.
+     */
+    void doOut(Artifact artifact, String facet, Document attr);
+
+    /**
+     * Writes the collected output of all artifacts specified in the
+     * <i>request</i> (see init()) document to the OutputStream <i>out</i> (see
+     * init()).
+     */
+    void generate() throws IOException;
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/WaterlevelExporter.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,162 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.WQKms;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WaterlevelExporter extends AbstractExporter {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(WaterlevelExporter.class);
+
+
+    public static final int KM_MIN_DIGITS = 3;
+    public static final int KM_MAX_DIGITS = 3;
+    public static final int W_MIN_DIGITS  = 0;
+    public static final int W_MAX_DIGITS  = 2;
+    public static final int Q_MIN_DIGITS  = 0;
+    public static final int Q_MAX_DIGITS  = 2;
+
+
+    public static final String CSV_KM_HEADER =
+        "export.waterlevel.csv.header.km";
+
+    public static final String CSV_W_HEADER =
+        "export.waterlevel.csv.header.w";
+
+    public static final String CSV_Q_HEADER =
+        "export.waterlevel.csv.header.q";
+
+    public static final String DEFAULT_CSV_KM_HEADER = "Fluss-Km";
+    public static final String DEFAULT_CSV_W_HEADER  = "W [NN + m]";
+    public static final String DEFAULT_CSV_Q_HEADER  = "Q [m³/s]";
+
+
+    /** The storage that contains all WQKms objects for the different facets.*/
+    protected List<WQKms[]> data;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("WaterlevelExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WQKms[]>();
+    }
+
+
+    protected void addData(Artifact artifact) {
+        data.add(getWaterlevelData(artifact));
+    }
+
+
+    /**
+     * Returns the waterlevel data computed by the WINFOArtifact.
+     *
+     * @param artifact The WINFOArtifact.
+     *
+     * @return the computed waterlevel data.
+     */
+    protected WQKms[] getWaterlevelData(Artifact artifact) {
+        WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
+        WQKms[]       wqkms         = winfoArtifact.getWaterlevelData();
+
+        logger.debug("Got " + wqkms.length + " WQKms objects.");
+
+        return wqkms;
+    }
+
+
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("WaterlevelExporter.writeData");
+
+        writeCSVHeader(writer);
+
+        for (WQKms[] tmp: data) {
+            for (WQKms wqkms: tmp) {
+                wQKms2CSV(writer, wqkms);
+            }
+        }
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.info("WaterlevelExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
+            msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+            msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER)
+        });
+    }
+
+
+    protected void wQKms2CSV(CSVWriter writer, WQKms wqkms) {
+        logger.debug("WaterlevelExporter.wQKms2CSV");
+
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        int      size   = wqkms.size();
+        double[] result = new double[3];
+
+        for (int i = 0; i < size; i ++) {
+            result = wqkms.get(i, result);
+
+            writer.writeNext(new String[] {
+                kmf.format(result[2]),
+                wf.format(result[0]),
+                qf.format(result[1])
+            });
+        }
+    }
+
+
+    /**
+     * Returns the number formatter for kilometer values.
+     *
+     * @return the number formatter for kilometer values.
+     */
+    protected NumberFormat getKmFormatter() {
+        return getFormatter(KM_MIN_DIGITS, KM_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for W values.
+     *
+     * @return the number formatter for W values.
+     */
+    protected NumberFormat getWFormatter() {
+        return getFormatter(W_MIN_DIGITS, W_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values.
+     *
+     * @return the number formatter for Q values.
+     */
+    protected NumberFormat getQFormatter() {
+        return getFormatter(Q_MIN_DIGITS, Q_MAX_DIGITS);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,135 @@
+package de.intevation.flys.exports;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Stroke;
+
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+
+import de.intevation.flys.exports.ChartExportHelper;
+
+
+/**
+ * An abstract base class for creating XY charts.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class XYChartGenerator extends ChartGenerator {
+
+    /** The logger that is used in this generator.*/
+    private static Logger logger = Logger.getLogger(ChartGenerator.class);
+
+
+    public static final Color DEFAULT_GRID_COLOR      = Color.GRAY;
+    public static final float DEFAULT_GRID_LINE_WIDTH = 0.3f;
+
+
+    /**
+     * Returns the title of a chart.
+     *
+     * @return the title of a chart.
+     */
+    protected abstract String getChartTitle();
+
+    /**
+     * Returns the X-Axis label of a chart.
+     *
+     * @return the X-Axis label of a chart.
+     */
+    protected abstract String getXAxisLabel();
+
+    /**
+     * Returns the Y-Axis label of a chart.
+     *
+     * @return the Y-Axis label of a chart.
+     */
+    protected abstract String getYAxisLabel();
+
+    /**
+     * This method is called to add all datasets of a concrete XYChartGenerator
+     * to the JFreeChart.
+     *
+     * @param chart The JFreeChart object.
+     */
+    protected abstract void addDatasets(JFreeChart chart);
+
+
+    public void generate()
+    throws IOException
+    {
+        logger.debug("XYChartGenerator.generate");
+
+        JFreeChart chart = ChartFactory.createXYLineChart(
+            getChartTitle(),
+            getXAxisLabel(),
+            getYAxisLabel(),
+            null,
+            PlotOrientation.VERTICAL,
+            true,
+            false,
+            false);
+
+        chart.setBackgroundPaint(Color.WHITE);
+        chart.getPlot().setBackgroundPaint(Color.WHITE);
+
+        XYPlot plot = (XYPlot) chart.getPlot();
+
+        addDatasets(chart);
+        addSubtitles(chart);
+        adjustPlot(plot);
+        adjustAxes(plot);
+
+        int[] size = getSize();
+
+        ChartExportHelper.exportImage(
+            out,
+            chart,
+            "png",
+            size[0], size[1]);
+    }
+
+
+    /**
+     * Adjusts the axes of a plot.
+     *
+     * @param plot The XYPlot of the chart.
+     */
+    protected void adjustAxes(XYPlot plot) {
+        NumberAxis yAxis = (NumberAxis) plot.getRangeAxis();
+
+        yAxis.setAutoRangeIncludesZero(false);
+    }
+
+
+    protected void adjustPlot(XYPlot plot) {
+        Stroke gridStroke = new BasicStroke(
+            DEFAULT_GRID_LINE_WIDTH,
+            BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_MITER,
+            3.0f,
+            new float[] { 3.0f },
+            0.0f);
+
+        plot.setDomainGridlineStroke(gridStroke);
+        plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR);
+        plot.setDomainGridlinesVisible(true);
+
+        plot.setRangeGridlineStroke(gridStroke);
+        plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR);
+        plot.setRangeGridlinesVisible(true);
+    }
+
+
+    protected void addSubtitles(JFreeChart chart) {
+        // override this method in subclasses that need subtitles
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/DefaultTheme.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,167 @@
+package de.intevation.flys.themes;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultTheme implements Theme {
+
+    /** The name of the theme.*/
+    protected String name;
+
+    /** The description of the theme.*/
+    protected String description;
+
+
+    /** The map storing the fields of this theme.*/
+    protected Map<String, ThemeField> fields;
+
+    /** The map storing the attributes of this theme.*/
+    protected Map<String, String> attr;
+
+
+    /**
+     * Initializes the components of this Theme.
+     */
+    public DefaultTheme(String name, String description) {
+        this.name        = name;
+        this.description = description;
+        this.fields      = new HashMap<String, ThemeField>();
+        this.attr        = new HashMap<String, String>();
+    }
+
+
+    public void init(Node config) {
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+
+    public String getDescription() {
+        return description;
+    }
+
+
+    public void addAttribute(String name, String value) {
+        if (name != null && value != null) {
+            attr.put(name, value);
+        }
+    }
+
+
+    public String getAttribute(String name) {
+        return attr.get(name);
+    }
+
+
+    public void addField(String name, ThemeField field) {
+        if (name != null && field != null) {
+            fields.put(name, field);
+        }
+    }
+
+
+    public void setFieldValue(String name, Object value) {
+        if (name != null && value != null) {
+            ThemeField field = fields.get(name);
+
+            if (field != null) {
+                field.setValue(value);
+            }
+        }
+    }
+
+
+    public ThemeField getField(String name) {
+        return fields.get(name);
+    }
+
+
+    public String getFieldType(String name) {
+        ThemeField field = fields.get(name);
+
+        return field != null ? field.getType() : null;
+    }
+
+
+    public Object getFieldValue(String name) {
+        ThemeField field = fields.get(name);
+
+        return field != null ? field.getValue() : null;
+    }
+
+
+    public Document toXML() {
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(doc, null, null);
+
+        Element theme = cr.create("theme");
+
+        appendAttributes(cr, theme);
+        appendFields(cr, theme);
+
+        doc.appendChild(theme);
+
+        return doc;
+    }
+
+
+    /**
+     * Appends the attributes configured for this theme.
+     *
+     * @param cr The ElementCreator.
+     * @param theme The document root element.
+     */
+    protected void appendAttributes(ElementCreator cr, Element theme) {
+        Iterator<String> iter = attr.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String key = iter.next();
+            String val = getAttribute(key);
+
+            if (key == null || val == null) {
+                continue;
+            }
+
+            cr.addAttr(theme, key, val);
+        }
+    }
+
+
+    /**
+     * Appends the fields configured for this theme.
+     *
+     * @param cr The ElementCreator.
+     * @param theme The document root element.
+     */
+    protected void appendFields(ElementCreator cr, Element theme) {
+        Iterator<String> iter = fields.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String name = iter.next();
+
+            ThemeField field = getField(name);
+
+            Document doc = field.toXML();
+            Node    root = doc.getFirstChild();
+
+            theme.appendChild(theme.getOwnerDocument().importNode(root, true));
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/DefaultThemeField.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,85 @@
+package de.intevation.flys.themes;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultThemeField implements ThemeField {
+
+    protected String name;
+
+    protected Map<String, Object> attr;
+
+
+    public DefaultThemeField(String name) {
+        this.name = name;
+        this.attr = new HashMap<String, Object>();
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+
+    public String getType() {
+        return (String) getAttribute("type");
+    }
+
+
+    public Object getValue() {
+        return getAttribute("value");
+    }
+
+
+    public void setValue(Object value) {
+        setAttribute("value", value);
+    }
+
+
+    public Object getAttribute(String name) {
+        return attr.get(name);
+    }
+
+
+    public void setAttribute(String name, Object value) {
+        if (name == null || value == null) {
+            return;
+        }
+
+        attr.put(name, value);
+    }
+
+
+    public Document toXML() {
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(doc, null, null);
+
+        Element field = cr.create("field");
+
+        Iterator<String> iter = attr.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String name  = iter.next();
+            String value = (String) getAttribute(name);
+
+            cr.addAttr(field, name, value);
+        }
+
+        doc.appendChild(field);
+
+        return doc;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/Theme.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,110 @@
+package de.intevation.flys.themes;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Theme {
+
+    /**
+     * Method to initialize the theme.
+     *
+     * @param config The configuration node.
+     */
+    void init(Node config);
+
+
+    /**
+     * Returns the name of the theme.
+     *
+     * @return the name of the theme.
+     */
+    String getName();
+
+
+    /**
+     * Returns the description of the theme.
+     *
+     * @return the description of the theme.
+     */
+    String getDescription();
+
+
+    /**
+     * Adds a new attribute.
+     *
+     * @param name The name of the attribute.
+     * @param value The value of the attribute.
+     */
+    void addAttribute(String name, String value);
+
+
+    /**
+     * Returns the value of a specific attribute.
+     *
+     * @param name the name of the attribute.
+     *
+     * @return the value of the attribute <i>name</i>.
+     */
+    String getAttribute(String name);
+
+
+    /**
+     * Adds a new field to the theme.
+     *
+     * @param name The name of the field.
+     * @param field The field.
+     */
+    void addField(String name, ThemeField field);
+
+
+    /**
+     * Sets the value of an field.
+     *
+     * @param name The name of the field.
+     * @param value The new value of the field.
+     */
+    void setFieldValue(String name, Object value);
+
+
+    /**
+     * Returns the field specified by name.
+     *
+     * @param name The name of the desired field.
+     *
+     * @return an field.
+     */
+    ThemeField getField(String name);
+
+
+    /**
+     * Returns the typename of a field.
+     *
+     * @param name the name of the field.
+     *
+     * @return the typename of a field.
+     */
+    String getFieldType(String name);
+
+
+    /**
+     * Returns the value of a field.
+     *
+     * @param name The name of the field.
+     *
+     * @return the value of a field.
+     */
+    Object getFieldValue(String name);
+
+
+    /**
+     * Dumps the theme to XML.
+     *
+     * @return a document.
+     */
+    Document toXML();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/ThemeFactory.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,218 @@
+package de.intevation.flys.themes;
+
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ThemeFactory {
+
+    private static Logger logger = Logger.getLogger(ThemeFactory.class);
+
+    private ThemeFactory() {
+    }
+
+
+    /**
+     * Creates a new theme from <i>config</i>.
+     *
+     * @param themeCfg The theme config document that is used to fetch parent
+     * themes.
+     * @param config The theme config node.
+     *
+     * @return a new theme.
+     */
+    public static Theme createTheme(Document themeCfg, Node config) {
+        String name = getName(config);
+        String desc = getDescription(config);
+
+        logger.debug("Create new theme: " + name);
+
+        Theme theme = new DefaultTheme(name, desc);
+
+        parseInherits(themeCfg, config, theme);
+        parseFields(config, theme);
+        parseAttrs(config, theme);
+
+        return theme;
+    }
+
+
+    /**
+     * Returns the theme for a specified output type and facet.
+     *
+     * @param c The FLYSContext that stores the mappings and themes.
+     * @param name The name of the mapping.
+     *
+     * @return a theme.
+     */
+    public static Theme getTheme(FLYSContext c, String name) {
+        if (c == null || name == null) {
+            logger.warn("Cannot search for theme.");
+            return null;
+        }
+
+        Map<String, String> map = (Map<String, String>)
+            c.get(FLYSContext.THEME_MAPPING);
+
+        Map<String, Theme> t = (Map<String, Theme>)
+            c.get(FLYSContext.THEMES);
+
+        if (map == null || map.size() == 0 || t == null || t.size() == 0) {
+            logger.warn("No mappings or themes found. Cannot retrieve theme!");
+            return null;
+        }
+
+        String themeName = map.get(name);
+
+        if (themeName == null) {
+            logger.warn("No theme found for mapping: " + name);
+        }
+
+        return t.get(themeName);
+    }
+
+
+    protected static String getName(Node config) {
+        return (String) XMLUtils.xpath(config, "@name", XPathConstants.STRING);
+    }
+
+
+    protected static String getDescription(Node config) {
+        return (String) XMLUtils.xpath(config, "@desc", XPathConstants.STRING);
+    }
+
+
+    protected static void parseInherits(Document themeCfg, Node cfg, Theme t) {
+        logger.debug("ThemeFactory.parseInherits");
+
+        NodeList inherits = (NodeList) XMLUtils.xpath(
+            cfg, "inherits/inherit", XPathConstants.NODESET);
+
+        int num = inherits != null ? inherits.getLength() : 0;
+
+        if (num == 0) {
+            logger.debug("Theme does not inherit from other themes.");
+            return;
+        }
+
+        logger.debug("Theme inherits from " + num + " other themes.");
+
+        for (int i = 0; i < num; i++) {
+            Node inherit = inherits.item(i);
+            String from  = (String) XMLUtils.xpath(
+                inherit, "@from", XPathConstants.STRING);
+
+            Node tmp = getThemeNode(themeCfg, from);
+
+            parseInherits(themeCfg, tmp, t);
+            parseFields(tmp, t);
+        }
+    }
+
+
+    protected static Node getThemeNode(Document themeCfg, String name) {
+        if (name == null) {
+            logger.warn("Cannot search theme config without name!");
+            return null;
+        }
+
+        logger.debug("Search for theme: " + name);
+
+        String xpath = "/themes/theme[@name='" + name + "']";
+
+        return (Node) XMLUtils.xpath(themeCfg, xpath, XPathConstants.NODE);
+    }
+
+
+    protected static void parseFields(Node config, Theme theme) {
+        if (config == null || theme == null) {
+            logger.warn("Parsing fields without node or theme is senseless!");
+            return;
+        }
+
+        NodeList fields = (NodeList) XMLUtils.xpath(
+            config, "fields/field", XPathConstants.NODESET);
+
+        int num = fields != null ? fields.getLength() : 0;
+
+        logger.debug("Found " + num + " own fields in this theme.");
+
+        if (num == 0) {
+            logger.debug("Theme has no own fields.");
+            return;
+        }
+
+        for (int i = 0; i < num; i++) {
+            Node field = fields.item(i);
+
+            addField(theme, field);
+        }
+    }
+
+
+    protected static void addField(Theme theme, Node field) {
+        String name = (String) XMLUtils.xpath(
+            field, "@name", XPathConstants.STRING);
+
+        logger.debug("Add field: " + name);
+
+        NamedNodeMap attrs = field.getAttributes();
+
+        int num = attrs != null ? attrs.getLength() : 0;
+
+        if (num == 0) {
+            logger.warn("This field has no attributes.");
+            return;
+        }
+
+        ThemeField theField = new DefaultThemeField(name);
+
+        for (int i = 0; i < num; i++) {
+            Node attr    = attrs.item(i);
+
+            String key   = attr.getNodeName();
+            String value = attr.getNodeValue();
+
+            theField.setAttribute(key, value);
+        }
+
+        theme.addField(name, theField);
+    }
+
+
+    protected static void parseAttrs(Node config, Theme theme) {
+        NamedNodeMap attrs = config.getAttributes();
+
+        int num = attrs != null ? attrs.getLength() : 0;
+
+        if (num == 0) {
+            logger.debug("Theme has no attributes set.");
+            return;
+        }
+
+        for (int i = 0; i < num; i++) {
+            Node attr = attrs.item(i);
+
+            String name  = attr.getNodeName();
+            String value = attr.getNodeValue();
+
+            theme.addAttribute(name, value);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/ThemeField.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,58 @@
+package de.intevation.flys.themes;
+
+import org.w3c.dom.Document;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface ThemeField {
+
+    /**
+     * Returns the name of this field.
+     *
+     * @return the name of this field.
+     */
+    String getName();
+
+    /**
+     * Returns the type of this field.
+     *
+     * @return the type of this field.
+     */
+    String getType();
+
+
+    /**
+     * Returns the value of this field.
+     *
+     * @return the value of this field.
+     */
+    Object getValue();
+
+
+    /**
+     * Changes the value of this field.
+     *
+     * @param value The new value.
+     */
+    void setValue(Object value);
+
+
+    /**
+     * Sets the value of an attribute.
+     *
+     * @param name The name of an attribute.
+     * @param value The value of an attribute.
+     */
+    void setAttribute(String name, Object value);
+
+
+    /**
+     * Dumps the field to XML.
+     *
+     * @return a document.
+     */
+    Document toXML();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages.properties	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,45 @@
+state.winfo.river = River
+state.winfo.calculation_mode = Calculation Mode
+state.winfo.location_distance = Location or distance selection
+state.winfo.wq = Input for W/Q data
+state.winfo.wq_adapted = Input for W/Q data
+state.winfo.location = Choose the location
+
+calc.surface.curve = Water Level/Surface Curve
+calc.flood.map = Flood Plain
+calc.discharge.curve = State Discharge Curve/Stage Discharge Relation
+calc.duration.curve = Duration Curve
+calc.discharge.longitudinal.section = TODO (W bei...)
+
+river = River
+calculation_mode = Calculation Mode
+ld_locations = Location(s)
+
+chart.longitudinal.section.title = W-Longitudinal Section
+chart.longitudinal.section.subtitle = Range: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.longitudinal.section.xaxis.label = km
+chart.longitudinal.section.yaxis.label = W [NN + m]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.discharge.curve.title = Discharge Curve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.computed.discharge.curve.title = Discharge Curve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.duration.curve.title = Duration Curve
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Duration of Non-Exceedence [Days]
+chart.duration.curve.yaxis.label = W [NN + m]
+
+export.waterlevel.csv.header.km = River-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Days]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = River-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W corr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages_de.properties	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,45 @@
+state.winfo.river = Gew\u00e4sser
+state.winfo.calculation_mode = Berechnungsart
+state.winfo.location_distance = Wahl des Berechnungsortes/strecke
+state.winfo.wq = Eingabe f\u00fcr W/Q Daten
+state.winfo.wq_adapted = Eingabe f\u00fcr W/Q Daten
+state.winfo.location = Wahl des Berechnungsortes
+
+calc.surface.curve = Wasserstand/Wasserspiegellage
+calc.flood.map = \u00dcberschwemmungsfl\u00e4che
+calc.discharge.curve = Abflusskurve/Abflusstafel
+calc.duration.curve = Dauerlinie
+calc.discharge.longitudinal.section = W bei ungleichm\u00e4\u00dfigem Abflussl\u00e4ngsschnitt
+
+river = Fluss
+calculation_mode = Berechnungsart
+ld_locations = Ort(e)
+
+chart.longitudinal.section.title = W-L\u00e4ngsschnitt
+chart.longitudinal.section.subtitle = Bereich: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.longitudinal.section.xaxis.label = km
+chart.longitudinal.section.yaxis.label = W [NN + m]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.discharge.curve.title = Abflusskurve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.computed.discharge.curve.title = Abflusskurve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.duration.curve.title = Dauerlinie
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Unterschreitungsdauer [Tage]
+chart.duration.curve.yaxis.label = W [NN + m]
+
+export.waterlevel.csv.header.km = Fluss-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Tagen]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = Fluss-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W korr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages_de_DE.properties	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,45 @@
+state.winfo.river = Gew\u00e4sser
+state.winfo.calculation_mode = Berechnungsart
+state.winfo.location_distance = Wahl des Berechnungsortes/strecke
+state.winfo.wq = Eingabe f\u00fcr W/Q Daten
+state.winfo.wq_adapted = Eingabe f\u00fcr W/Q Daten
+state.winfo.location = Wahl des Berechnungsortes
+
+calc.surface.curve = Wasserstand/Wasserspiegellage
+calc.flood.map = \u00dcberschwemmungsfl\u00e4che
+calc.discharge.curve = Abflusskurve/Abflusstafel
+calc.duration.curve = Dauerlinie
+calc.discharge.longitudinal.section = W bei ungleichm\u00e4\u00dfigem Abflussl\u00e4ngsschnitt
+
+river = Fluss
+calculation_mode = Berechnungsart
+ld_locations = Ort(e)
+
+chart.longitudinal.section.title = W-L\u00e4ngsschnitt
+chart.longitudinal.section.subtitle = Bereich: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.longitudinal.section.xaxis.label = km
+chart.longitudinal.section.yaxis.label = W [NN + m]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.discharge.curve.title = Abflusskurve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.computed.discharge.curve.title = Abflusskurve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.duration.curve.title = Dauerlinie
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Unterschreitungsdauer [Tage]
+chart.duration.curve.yaxis.label = W [NN + m]
+
+export.waterlevel.csv.header.km = Fluss-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Tagen]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = Fluss-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W korr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages_en.properties	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,45 @@
+state.winfo.river = River
+state.winfo.calculation_mode = Calculation Mode
+state.winfo.location_distance = Location or distance selection
+state.winfo.wq = Input for W/Q data
+state.winfo.wq_adapted = Input for W/Q data
+state.winfo.location = Choose the location
+
+calc.surface.curve = Water Level/Surface Curve
+calc.flood.map = Flood Plain
+calc.discharge.curve = State Discharge Curve/Stage Discharge Relation
+calc.duration.curve = Duration Curve
+calc.discharge.longitudinal.section = TODO (W bei...)
+
+river = River
+calculation_mode = Calculation Mode
+ld_locations = Location(s)
+
+chart.longitudinal.section.title = W-Longitudinal Section
+chart.longitudinal.section.subtitle = Range: {0}-km {1,number,double} - {2,number,double}
+chart.longitudinal.section.xaxis.label = km
+chart.longitudinal.section.yaxis.label = W [NN + m]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.discharge.curve.title = Discharge Curve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.computed.discharge.curve.title = Discharge Curve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.duration.curve.title = Duration Curve
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Duration of Non-Exceedence [Days]
+chart.duration.curve.yaxis.label = W [NN + m]
+
+export.waterlevel.csv.header.km = River-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Days]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = River-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W corr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/metadata/template.xml	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<dc:template xmlns:dc="http://www.intevation.org/2011/Datacage">
+<datacage>
+  <dc:context>
+    <dc:statement>
+      SELECT id AS river_id, name as river_name FROM rivers
+      WHERE name ILIKE ${river}
+    </dc:statement>
+    <dc:elements>
+      <river>
+        <dc:attribute name="name" value="${river_name}"/>
+        <dc:attribute name="river-id" value="${river_id}"/>
+        <discharge-table-nn>
+          <discharge-table-gauge>
+            <dc:context>
+              <dc:statement>
+                SELECT id   AS gauge_id,
+                       name AS gauge_name
+                FROM gauges WHERE river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <gauge>
+                  <dc:attribute name="name" value="${gauge_name}"/>
+                  <dc:attribute name="gauge-id" value="${gauge_id}"/>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT description   AS gauge_desc, 
+                             d.id          AS discharge_id,
+                             ti.start_time AS g_start, 
+                             ti.stop_time  AS g_stop
+                      FROM discharge_tables d JOIN time_intervals ti 
+                      ON d.time_interval_id = ti.id
+                      WHERE d.gauge_id = ${gauge_id} AND d.kind = 1
+                    </dc:statement>
+                    <dc:elements>
+                      <historical>
+                        <dc:attribute name="name" value="${gauge_desc}"/>
+                        <dc:attribute name="from" value="${g_start}"/>
+                        <dc:attribute name="to" value="${g_stop}"/>
+                        <dc:attribute name="discharge-id" value="${discharge_id}"/></historical>
+                    </dc:elements>
+                  </dc:context>
+                </gauge>
+              </dc:elements>
+            </dc:context>
+          </discharge-table-gauge>
+          <fixations>
+            <dc:attribute name="id" value="fixations-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS fix_id,
+                       description AS fix_description
+                FROM wsts WHERE kind = 2 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <fixation>
+                  <dc:attribute name="name" value="${fix_description}"/>
+                  <dc:attribute name="fix-id" value="${fix_id}"/>
+                  <columns>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id   AS fix_column_id,
+                               name AS fix_column_name
+                        FROM wst_columns WHERE wst_id = ${fix_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${fix_column_name}"/>
+                          <dc:attribute name="column-id" value="${fix_column_id}"/></column>
+                      </dc:elements>
+                    </dc:context>
+                  </columns>
+                </fixation>
+              </dc:elements>
+            </dc:context>
+          </fixations>
+          <flood-protections>
+            <dc:attribute name="id" value="flood-protections-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 5 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <flood-protection>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:attribute name="prot-id" value="${prot_id}"/>
+                  <columns>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id   AS prot_column_id,
+                               name AS prot_column_name
+                        FROM wst_columns WHERE wst_id = ${prot_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${prot_column_name}"/>
+                          <dc:attribute name="column-id" value="${prot_column_id}"/></column>
+                      </dc:elements>
+                    </dc:context>
+                  </columns>
+                </flood-protection>
+              </dc:elements>
+            </dc:context>
+          </flood-protections>
+          <flood-water-marks>
+            <dc:attribute name="id" value="flood-water-marks-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS fw_id,
+                       description AS fw_description
+                FROM wsts WHERE kind = 4 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <flood-water-mark>
+                  <dc:attribute name="name" value="${fw_description}"/>
+                  <dc:attribute name="fw-id" value="${fw_id}"/>
+                  <columns>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id   AS fw_column_id,
+                               name AS fw_column_name
+                        FROM wst_columns WHERE wst_id = ${fw_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${fw_column_name}"/>
+                          <dc:attribute name="column-id" value="${fw_column_id}"/></column>
+                      </dc:elements>
+                    </dc:context>
+                  </columns>
+                </flood-water-mark>
+              </dc:elements>
+            </dc:context>
+          </flood-water-marks>
+          <water-levels>
+            <dc:attribute name="id" value="water-levels-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS wl_id,
+                       description AS wl_description
+                FROM wsts WHERE kind = 0 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <water-level>
+                  <dc:attribute name="name" value="${wl_description}"/>
+                  <dc:attribute name="wl-id" value="${wl_id}"/>
+                  <columns>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id   AS wl_column_id,
+                               name AS wl_column_name
+                        FROM wst_columns WHERE wst_id = ${wl_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${wl_column_name}"/>
+                          <dc:attribute name="column-id" value="${wl_column_id}"/></column>
+                      </dc:elements>
+                    </dc:context>
+                  </columns>
+                </water-level>
+              </dc:elements>
+            </dc:context>
+          </water-levels>
+          <extra-longitudinal-sections>
+            <dc:attribute name="id" value="extra-longitudinal-sections-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS els_id,
+                       description AS els_description
+                FROM wsts WHERE kind = 1 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <extra-longitudinal-section>
+                  <dc:attribute name="name" value="${els_description}"/>
+                  <dc:attribute name="els-id" value="${els_id}"/>
+                  <columns>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id   AS els_column_id,
+                               name AS els_column_name
+                        FROM wst_columns WHERE wst_id = ${els_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                            <dc:attribute name="name" value="${els_column_name}"/>
+                            <dc:attribute name="column-id" value="${els_column_id}"/></column>
+                      </dc:elements>
+                    </dc:context>
+                  </columns>
+                </extra-longitudinal-section>
+              </dc:elements>
+            </dc:context>
+          </extra-longitudinal-sections>
+        </discharge-table-nn>
+        <longitudinal-section>
+          <fixations><dc:attribute name="ref" value="fixations-${river_id}"/></fixations>
+          <flood-protections><dc:attribute name="ref" value="flood-protections-${river_id}"/></flood-protections>
+          <flood-water-marks><dc:attribute name="ref" value="flood-water-marks-${river_id}"/></flood-water-marks>
+          <water-levels><dc:attribute name="ref" value="water-levels-${river_id}"/></water-levels>
+          <extra-longitudinal-sections><dc:attribute name="ref" value="extra-longitudinal-sections-${river_id}"/></extra-longitudinal-sections>
+        </longitudinal-section>
+      </river>
+    </dc:elements>
+  </dc:context>
+</datacage>
+</dc:template>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/test/java/de/intevation/flys/artifacts/AppTest.java	Fri Sep 28 12:14:10 2012 +0200
@@ -0,0 +1,38 @@
+package de.intevation.flys.artifacts;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+    extends TestCase
+{
+    /**
+     * Create the test case
+     *
+     * @param testName name of the test case
+     */
+    public AppTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * @return the suite of tests being tested
+     */
+    public static Test suite()
+    {
+        return new TestSuite( AppTest.class );
+    }
+
+    /**
+     * Rigourous Test :-)
+     */
+    public void testApp()
+    {
+        assertTrue( true );
+    }
+}

http://dive4elements.wald.intevation.org