changeset 774:461b8bd696a7 2.4

merged flys-backend/2.4
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:15 +0200
parents 8d5bd3a08dd1 (current diff) ade8e3d3cff8 (diff)
children 0a56dbf81cc6
files
diffstat 63 files changed, 9388 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/ChangeLog	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,1174 @@
+2011-06-28  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.4
+
+2011-06-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* Changes: Prepared changes for the upcoming release.
+
+2011-06-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/AnnotationsParser.java,
+	  src/main/java/de/intevation/flys/importer/AnnotationClassifier.java,
+	  src/main/java/de/intevation/flys/importer/AtFileParser.java,
+	  src/main/java/de/intevation/flys/importer/ValueKey.java,
+	  src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Removed trailing whitespace.
+
+2011-06-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/AnnotationClassifier.java:
+	  Removed superfluous imports.
+
+2011-06-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Added a method maxOverlap to determine the gauge which has
+	  the max common length to a given interval. This is for
+	  numerical stability in slightly overlapping gauge ranges.
+
+2011-06-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java: When
+	looking up a gauge by km containment use precision of 1e-6.
+
+2011-06-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Added method to find gauge by its name.
+
+2011-06-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/annotation-types.xml: Fixed some rules.
+
+	* src/main/java/de/intevation/flys/importer/Importer.java,
+	  src/main/java/de/intevation/flys/importer/InfoGewParser.java,
+	  src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Call the right constructors now.
+
+	* src/main/java/de/intevation/flys/importer/ImportAnnotationType.java:
+	  Added getter/setter for name property.
+
+	* src/main/java/de/intevation/flys/importer/AnnotationsParser.java:
+	  Print duplicated at WARN level not as DEBUG.
+
+	* src/main/java/de/intevation/flys/importer/AnnotationClassifier.java:
+	  Fixed XPath expression to build the internal lookup structures
+	  correctly.
+
+2011-06-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Added classification of annotation types. Needs testing!
+
+	* doc/annotation-types.xml: New. Rules to classify the different
+	  types of annotations. The classification works like this:
+
+	  There are unique types like 'Bruecke', 'Pegel' and so on.
+	  They are defined in the /annotation/type section and
+	  identified by their name. One of the types can be set 
+	  as the default type if no rule applies.
+
+	  In the /annotation/pattern section are two types of pattern.
+
+	  1 - file pattern: If a KM file is opened its filename is
+	      matched against the regular expressions of these
+		  patterns. If a match is found the corresponding type
+		  is used as the default type in the open file.
+		  If no match is found the global default type is used
+		  as the default type.
+
+	  2 - line patterns: For each line of an open KM file these
+	      patterns are applied to find a match. If a match is
+		  found the corresponding type is used as the type of
+		  the annotation. If no match is found the default
+		  file default is assumed to be the right type. For
+		  the file default see 1.
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  To activate the annotation type classification set
+	  the system property
+
+	      'flys.backend.importer.annotation.types'
+
+	  to the path of a XML looking like the annotation-types.xml
+	  file. If the system property is not set no classification
+	  is done.
+
+	* src/main/java/de/intevation/flys/importer/AnnotationClassifier.java:
+	  New. Implements the classification.
+	  
+	* src/main/java/de/intevation/flys/importer/AnnotationsParser.java,
+	  src/main/java/de/intevation/flys/importer/InfoGewParser.java,
+	  src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Looped through the annotation type classification.
+
+2011-06-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Added method to find gauge by a position lying in its range.
+
+2011-06-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	First part of flys/issue18
+
+	* doc/schema/postgresql.sql: Add new table 'annotation_types'.
+	  To update existing databases:
+	    BEGIN;
+	      CREATE SEQUENCE ANNOTATION_TYPES_ID_SEQ;
+	      CREATE TABLE annotation_types (
+	          id    int PRIMARY KEY NOT NULL,
+	          name  VARCHAR(256)    NOT NULL UNIQUE
+	      );
+		  ALTER TABLE annotations ADD COLUMN type_id int REFERENCES annotation_types(id);
+	    COMMIT;
+
+	* doc/schema/postgresql-cleanup.sql: Removed. Hopeless out-dated.
+
+	* src/main/java/de/intevation/flys/model/AnnotationType.java:
+	  New. Hibernate model to access the type.
+
+	* src/main/java/de/intevation/flys/backend/SessionFactoryProvider.java:
+	  Register the new backend type.
+
+	* src/main/java/de/intevation/flys/model/Annotation.java:
+	  References the annotation type.
+
+	* src/main/java/de/intevation/flys/importer/ImportAnnotationType.java:
+	  New. Model to help import the annotation type.
+
+	* src/main/java/de/intevation/flys/importer/ImportAnnotation.java:
+	  Uses the import type.
+
+	* src/main/java/de/intevation/flys/importer/AnnotationsParser.java:
+	  Set the annotation type to 'null'. TODO: Do the classification!
+
+2011-06-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/AtFileParser.java:
+	  Fix for flys/issue110. start index was shifted by one.
+
+2011-06-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportAnnotation.java:
+	  Forgot to store reference to edge.
+
+2011-06-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	 * src/main/java/de/intevation/flys/backend/SessionFactoryProvider.java:
+	   Register backend model.
+
+	 * src/main/java/de/intevation/flys/importer/ImportEdge.java: New. Model
+	   for importing the edges.
+
+	 * src/main/java/de/intevation/flys/importer/AnnotationsParser.java:
+	   Parses the edges of an annotation, too.
+
+2011-06-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Added table edges to model 'Kanten' of an annotation.
+	  To update existing databases:
+	    BEGIN;
+	      CREATE SEQUENCE EDGES_ID_SEQ;
+	      CREATE TABLE edges (
+	        id     int PRIMARY KEY NOT NULL,
+	        top    NUMERIC,
+	        bottom NUMERIC);
+	      ALTER TABLE annotations ADD COLUMN edge_id int REFERENCES edges(id);
+	    COMMIT;
+
+	* src/main/java/de/intevation/flys/model/Edge.java: New. A 'Kanten'-Model.
+	* src/main/java/de/intevation/flys/model/Annotation.java: References the
+	  edges.
+	
+2011-06-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Added method to find gauge only by station position.
+
+2011-05-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Make search for gauges independent of from/to order.
+
+2011-05-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Added a new view to select qs of a WST.
+	  To update existing databases:
+
+	    CREATE VIEW wst_q_values AS
+	        SELECT wc.position AS column_pos,
+	               wqr.q       AS q, 
+	               r.a         AS a, 
+	               r.b         AS b,
+	               wc.wst_id   AS wst_id
+	        FROM wst_column_q_ranges wcqr
+	        JOIN wst_q_ranges wqr ON wcqr.wst_q_range_id = wqr.id
+	        JOIN ranges r         ON wqr.range_id        = r.id
+	        JOIN wst_columns wc   ON wcqr.wst_column_id  = wc.id
+	        ORDER BY wc.position, wcqr.wst_column_id, r.a;	  
+
+2011-05-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Added a new view to select ws of a WST.
+	  To update existing databases:
+
+	    CREATE VIEW wst_w_values AS
+	        SELECT wcv."position" AS km, 
+	               wcv.w          AS w,  
+	               wc."position"  AS column_pos, 
+	               w.id           AS wst_id
+	            FROM wst_column_values wcv
+	            JOIN wst_columns wc ON wcv.wst_column_id = wc.id
+	            JOIN wsts w         ON wc.wst_id = w.id
+	        ORDER BY wcv."position", wc."position";
+
+2011-05-23	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	 flys/issue76
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Close gaps between q ranges.
+
+2011-05-20  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.3.1
+
+2011-05-13  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.3
+
+2011-05-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* Changes: Changes for release 2.3 - see Changes file to get to know about
+	  the version numbers of this software.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Added a column which tells
+	  if a river counts its km up or downwards.
+	  To update existing databases:
+
+	    ALTER TABLE rivers ADD COLUMN km_up BOOLEAN NOT NULL DEFAULT true;
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Adjust Hibernate mapping of new column.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/AtFileParser.java:
+	  Fixed flys/issue11 and flys51.
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ValueKey.java:
+	  Add some unsharp comparison (eps = 1e-6) to avoid 
+	  numerical problems.
+
+	* src/main/java/de/intevation/flys/importer/IdValueKey.java:
+	  Removed. Not need any longer.
+
+	* src/main/java/de/intevation/flys/importer/ImporterSession.java:
+	  Use unsharp lookup.
+
+	* src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Flush more often. Hopefully this reduces hibernate sync
+	  problems?!
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImporterSession.java:
+	  Fixed silly bug. Set flush mode back to auto because
+	  manual flushing causes some undetermined problems.
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImporterSession.java:
+	  Do not load _all_ values from discharge tables and
+	  wst columns. This is extremly slow and will lead
+	  to OOM if more rivers are imported. Now only the
+	  last 20 columns und discharge tables are cached.
+
+	* src/main/java/de/intevation/flys/importer/ValueKey.java:
+	  New. Key for caching discharge table values and wst
+	  column values.
+
+	* src/main/java/de/intevation/flys/importer/IdValueKey.java:
+	  Fixed bug in equals().
+
+	* src/main/java/de/intevation/flys/importer/ImportWstColumn.java:
+	  Removed too eloquent debug output.
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/IdValueKey.java:
+	  Use BigDecimals as representation for the numeric components
+	  which prevents running into unique constraint problems
+	  caused by imprecision.
+
+	* src/main/java/de/intevation/flys/importer/ImportRange.java,
+	  src/main/java/de/intevation/flys/importer/ImporterSession.java:
+	  Ranges are now cached globally, too.
+
+	* src/main/java/de/intevation/flys/importer/ImportWstColumn.java:
+	  Improved logging.
+
+	* src/main/java/de/intevation/flys/importer/ImportDischargeTableValue.java:
+	  Removed superfluous imports.
+
+2011-05-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/StaFileParser.java:
+	  Only accept main value types 'Q', 'W', 'D' and 'T' by default.
+	  '-' is not handled any more because it causes too much constraint
+	  problems. You can overwrite the imported type with the
+	  system property flys.backend.main.value.types" (default "QWTD")
+	  
+	* src/main/java/de/intevation/flys/importer/ImporterSession.java:
+	  Set session flush mode to manual. Hopefully this improves the
+	  performance a bit.
+
+	* src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/ImportGauge.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumn.java,
+	  src/main/java/de/intevation/flys/importer/ImportRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportDischargeTable.java:
+	  Improved logging.
+
+2011-05-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstColumnValueKey.java: Deleted
+	* src/main/java/de/intevation/flys/importer/IdValueKey.java: Reinserted
+	  here in a more generalized form.
+
+	* src/main/java/de/intevation/flys/importer/ImporterSession.java:
+	  Cache the discharge table value, too.
+
+	* src/main/java/de/intevation/flys/importer/ImportDischargeTableValue.java:
+	  Use the global cache.
+
+2011-05-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImporterSession.java:
+	  New. Centralized caching in the thread local context. Importing
+	  the Elbe leads to OOM because the column values of the
+	  WST files where loaded separately for every file.
+
+	* src/main/java/de/intevation/flys/importer/ImportPosition.java,
+	  src/main/java/de/intevation/flys/importer/Importer.java,
+	  src/main/java/de/intevation/flys/importer/ImportAnnotation.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstQRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/ImportMainValue.java,
+	  src/main/java/de/intevation/flys/importer/ImportMainValueType.java,
+	  src/main/java/de/intevation/flys/importer/ImportNamedMainValue.java,
+	  src/main/java/de/intevation/flys/importer/ImportRiver.java,
+	  src/main/java/de/intevation/flys/importer/ImportGauge.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumnValue.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumnQRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumn.java,
+	  src/main/java/de/intevation/flys/importer/ImportRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportDischargeTableValue.java,
+	  src/main/java/de/intevation/flys/importer/ImportDischargeTable.java,
+	  src/main/java/de/intevation/flys/importer/ImportAttribute.java,
+	  src/main/java/de/intevation/flys/importer/ImportTimeInterval.java:
+	  Adjusted to use the new global context.
+
+2011-05-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Dropped constraint which
+	  forces discharge tables to have a unique time interval
+	  for a given gauge and kind.
+
+	  There are AT files (historical Mosel/Perl/Perl/1967-1981.at
+	  and Mosel/Perl/1967-1981-1.at) which violate this
+	  constraint. Its a technical question to the customer
+	  how to handle these cases.
+
+	  To adjust existing databases:
+
+	    ALTER TABLE discharge_tables DROP CONSTRAINT discharge_tables_gauge_id_key;
+
+2011-05-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  There are wst files where column names are not unique.
+	  Make them unique by appending (1), (2) and so on.
+
+2011-05-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Import the "HW-Schutzanlagen", too.
+
+2011-05-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Make import of historical discharge tables work.
+
+	* doc/schema/postgresql.sql: Added forgotten column 'description'.
+
+	  !!! You have to drop your database !!!
+
+	* src/main/java/de/intevation/flys/importer/ImportDischargeTable.java:
+	  src/main/java/de/intevation/flys/model/DischargeTable.java:
+	  Add the forgotten description property.
+
+	* src/main/java/de/intevation/flys/importer/AtFileParser.java:
+	  Fixed problems with date recognition.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Prefix the description of the historical discharge tables 
+	  with "Histor.Abflusstafeln".
+
+	* src/main/java/de/intevation/flys/importer/ImportTimeInterval.java:
+	  Fixed silly programming error.
+
+2011-05-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/AtFileParser.java:
+	  Inject 'kind' attribute.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Traverse the "Histor.Abflusstafeln" for the historical
+	  discharge tables. too. TODO: Store them in the database.
+
+	* src/main/java/de/intevation/flys/importer/ImportDischargeTable.java:
+	  Added convinience constructor to set the kind of the
+	  discharge table.
+	  
+2011-05-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Prefix "Zusätzliche Längsschnitte" with "Zus.Längsschnitte"
+	  in description.
+
+2011-05-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Import 'Hochwasser-Marken', too.
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Removed superfluous import.
+
+2011-05-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Ignore lines that contain km positions which were found
+	  before in the same file.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java: Added a method that
+	  returns all gauges of the river intersected by a given start and end
+	  point.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportTimeInterval.java:
+	  Forgot to fetch peer from result set.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportTimeInterval.java:
+	  New. Importer model help fetching the database peer.
+
+	* src/main/java/de/intevation/flys/model/TimeInterval.java:
+	  Add convinience constructor with start and stop time.
+
+	* src/main/java/de/intevation/flys/importer/AtFileParser.java:
+	  Attach a time interval to a discharge table if we find one.
+
+	* src/main/java/de/intevation/flys/importer/ImportDischargeTable.java:
+	  Store the reference to the importer model of the 
+	  time interval of the discharge table.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/AtFileParser.java:
+	  Try to extract time ranges from at files.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Parse and store "amtliche Linien" wst files.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Parse and store fixation wst files as well.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Parse the "zusaetzliche Laengsschnitte", too.
+
+	* src/main/java/de/intevation/flys/importer/ImportWst.java:
+	  Add getter/setter for column 'kind'.
+
+2011-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Fixed flys/issue19: Do not take km column in wst file as a water level.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Gauge.java: Introduced a 'scale'
+	  that is used to adjust the range of min/max W values.
+
+2011-05-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Removed superfluous import.
+
+2011-04-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstColumnValueKey.java:
+	  New. This class is used as distinct key of a WstColumnValue - e.g. as
+	  key in a map.
+
+	* src/main/java/de/intevation/flys/importer/ImportWst.java: A
+	  WstColumnValue cache is build up while initialization. This cache
+	  contains all WstColumnValues that exist in the database.
+
+	* src/main/java/de/intevation/flys/importer/ImportWstColumn.java: New
+	  constructor that takes the WstColumnValues cache. It is used to
+	  initialize new ImportWstColumnValue objects.
+
+	* src/main/java/de/intevation/flys/importer/ImportWstColumnValue.java:
+	  Speedup: A ImportWstColumnValue has a WstColumnValues cache that
+	  contains all WstColumnValues existing in the database. This makes it
+	  unnecessary to call an sql statement for each WstColumnValue to
+	  determine its existence in the database.
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql, doc/schema/postgresql-cleanup.sql:
+	  Added a view 'wst_value_table' which aggregates the data
+	  to build w/q value tables.
+
+	  To update existing databases:
+
+	  BEGIN;
+	    CREATE VIEW wst_value_table AS
+	        SELECT wcv.position AS position,
+	               w,
+	               (SELECT q
+	                FROM   wst_column_q_ranges wcqr
+	                       JOIN wst_q_ranges wqr
+	                         ON wcqr.wst_q_range_id = wqr.id
+	                       JOIN ranges r
+	                         ON r.id = wqr.range_id
+	                WHERE  wcqr.wst_column_id = wc.id
+	                       AND wcv.position BETWEEN r.a AND r.b) AS q,
+	               wc.position                                   AS column_pos,
+	               w.id                                          AS wst_id
+	        FROM   wst_column_values wcv
+	               JOIN wst_columns wc
+	                 ON wcv.wst_column_id = wc.id
+	               JOIN wsts w
+	                 ON wc.wst_id = w.id
+	        ORDER  BY wcv.position ASC,
+	              wc.position DESC;
+	  COMMIT;
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Wst.java:
+	  Add forgotten one to many relation Wst -> WstColumn.
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Import of q ranges of wst files was totally broken. :-/
+	  You have to reimport all your data.
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportDischargeTable.java:
+	  Forgot kind parameter in peer fetching query.
+	  
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java::
+	  Added a new method to determine the gauge based on a given start and end
+	  point of the river.
+
+2011-04-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Added 'position' column to wst_columns
+	  to allow order them by there column position in the original
+	  wst file.
+	
+	  Update existing database with:
+
+	    BEGIN;
+	      ALTER TABLE wst_columns ADD COLUMN position int;
+	      UPDATE wst_columns w SET 
+	        position = id - (SELECT min(id) FROM wst_columns WHERE wst_id = w.wst_id);
+	      ALTER TABLE wst_columns ADD CONSTRAINT wst_columns_wst_id_position_key
+	        UNIQUE (wst_id, position);
+	    COMMIT;
+
+	* src/main/java/de/intevation/flys/model/Wst.java,
+	  src/main/java/de/intevation/flys/model/WstColumn.java:
+	  Adjusted models.
+
+	* src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumn.java:
+	  Adjusted importer.
+
+2011-04-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Forget ',' in schema.
+
+2011-04-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Wst.java (determineMinMaxQ): 
+	  Fixed index problem when an empty list is returned.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Wst.java: A Wst is now able to
+	  return its min and max Q values.
+
+	* src/main/java/de/intevation/flys/model/Gauge.java: A Gauge is now able
+	  to return its min and max W values.
+
+2011-04-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Added new column 'kind' in
+	  discharge tables and wst to distinguish between different
+	  kinds like 'Haupt-WST', 'zusaetzliche Laengsschnitte',
+	  'amtliche Daten' and so on.
+
+	  Update existing databases with:
+	    BEGIN;
+	      ALTER TABLE discharge_tables ADD COLUMN kind int NOT NULL DEFAULT 0;
+	      ALTER TABLE wsts             ADD COLUMN kind int NOT NULL DEFAULT 0;
+	    COMMIT;
+
+	* src/main/java/de/intevation/flys/model/DischargeTable.java
+	  src/main/java/de/intevation/flys/model/Wst.java,
+	  src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/ImportDischargeTable.java:
+	  Adjusted the models.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/backend/SessionHolder.java: Moved to
+	  this module from flys-artifacts. This is necessary to get access to the
+	  current session in this module as well.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/River.java: Added a method that
+	  returns the min and max distance of a river.
+
+2011-04-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/**/*.java: Removed trailing whitespace.
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 0.1
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Range.java:
+	  Forgot to save the last change before commit.
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Range.java:
+	  Added methods to find out if two ranges intersects.
+
+2011-03-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Gauge.java:
+	  Added an one to many relation to the discharge tables of a gauge.
+
+2011-03-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Finished import of WSTs.
+
+	TODO 1: Speed it up! It takes on a high end machine over 7(!)
+	        minutes only for the data of the Saar.
+	TODO 2: Double precision floating point representations produced
+	        by the the parsers leed to unique constraint violations
+			in the backend on a second run. So the import is currently
+			only working on freshly initialized data bases.
+			More consequent working with BigDecimal and some
+			rounding may be of help here.
+
+	* src/main/java/de/intevation/flys/model/WstColumnValue.java:
+	  Added convinience constructors.
+
+	* src/main/java/de/intevation/flys/importer/ImportWstColumnValue.java:
+	  Added getPeer() method.
+
+	* src/main/java/de/intevation/flys/importer/ImportWstColumn.java:
+	  Add a list of the ImportWstColumnValues produced by the WST parser.
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java: Add
+	  the (km, w) values to the ImportWstColumns.
+
+2011-03-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Build models for wsts, wst columns and q ranges and
+	  store them in the backend. TODO: store the w values.
+
+	* src/main/java/de/intevation/flys/model/WstQRange.java
+	  src/main/java/de/intevation/flys/model/Wst.java,
+	  src/main/java/de/intevation/flys/model/Range.java,
+	  src/main/java/de/intevation/flys/model/WstColumnQRange.java,
+	  src/main/java/de/intevation/flys/model/WstColumn.java:
+	  Added convinience constructors.
+
+	* src/main/java/de/intevation/flys/importer/ImportWstQRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/ImportRiver.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumnQRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumn.java,
+	  src/main/java/de/intevation/flys/importer/ImportRange.java:
+	  Added getPeer() methods.
+
+2011-03-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  The unit extraction in the WST parser of desktop FLYS
+	  is broken! Add a hack here to repair this for our
+	  importer. Desktop FLYS needs a fix, too!
+
+2011-03-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Ported some stuff ver from WSTSource.java of desktop flys to
+	  parse WST files. TODO: create instances of the import models.
+
+	* src/main/java/de/intevation/flys/utils/StringUtil.java:
+	  Copied from desktop flys. Used for some string operations
+	  in WST parser.
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Added system property 'flys.backend.importer.dry.run'.
+	  Set to true only the parsing is done and no writing
+	  to the backend. Default: false.
+
+	*  src/main/java/de/intevation/flys/App.java,
+	   src/main/java/de/intevation/flys/model/MainValueType.java:
+	   Removed needless imports.
+
+2011-03-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/DischargeTableValue.java,
+	  src/main/java/de/intevation/flys/model/DischargeTable.java: Added new
+	  constructors.
+
+	* src/main/java/de/intevation/flys/importer/AtFileParser.java: New. This
+	  parser is used to '*.at' files.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java: Added code to
+	  import discharge tables.
+
+	* src/main/java/de/intevation/flys/importer/ImportDischargeTableValue.java,
+	  src/main/java/de/intevation/flys/importer/ImportDischargeTable.java: New.
+	  Helper models for import discharge tables.
+
+2011-03-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java,
+	  src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Added stub for WST parser.
+
+2011-03-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportWstQRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportWst.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumnValue.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumnQRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportWstColumn.java:
+	  Added importer helper model stubs for WST imports.
+	
+2011-03-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Second part of parsing/storing main values. Should be finished now.
+
+	* src/main/java/de/intevation/flys/importer/ImportNamedMainValue.java,
+	  src/main/java/de/intevation/flys/importer/ImportMainValue.java:
+	  New. Helper models for import main values,
+
+	* src/main/java/de/intevation/flys/model/MainValue.java,
+	  src/main/java/de/intevation/flys/model/NamedMainValue.java:
+	  Added convinience constructors.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Write main values to backend, too.
+
+	* src/main/java/de/intevation/flys/importer/StaFileParser.java:
+	  Build importer models for main values.
+
+	* src/main/java/de/intevation/flys/importer/ImportMainValueType.java:
+	  Data was called 'value'. Now it is 'name' to fit the
+	  schema.
+
+2011-03-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Fixed wrong unique constraint.
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Added some logging when storing gauges.
+
+2011-03-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Gauge.java:
+	  Add forgotten column river_id.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Small HQL fix.
+
+2011-03-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Gauge.java:
+	  Added convinience contructor.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Fixed getPeer() method.
+
+	* src/main/java/de/intevation/flys/importer/StaFileParser.java:
+	  Fixed parsing of STA files.
+
+2011-03-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java,
+	  src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Propagate river into storing of gauges.
+
+2011-03-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Added code to store rivers not only annotations.
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Added stub code to write gauges.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	First part of parsing main values.
+
+	* src/main/java/de/intevation/flys/App.java: Commented out
+	  creation of dummy rivers.
+
+	* src/main/java/de/intevation/flys/model/NamedMainValues.java:
+	  Moved to NamedMainValue.
+
+	* src/main/java/de/intevation/flys/model/NamedMainValue.java:
+	  New. Formerly NamedMainValues.
+
+	* src/main/java/de/intevation/flys/model/MainValue.java:
+	  New. Forgotten part of the model.
+
+	* src/main/java/de/intevation/flys/model/MainValueType.java:
+	  Data is String not BigDecimal
+
+	* src/main/java/de/intevation/flys/model/Range.java: Removed
+	  contructor with double arguments. Using BigDecimal now.
+
+	* src/main/java/de/intevation/flys/importer/PegelGltParser.java:
+	  Propagate BigDecimal usage.
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Removed needless import. Added TODO
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Parse the dependencies of the gauges, too.
+
+	* src/main/java/de/intevation/flys/importer/StaFileParser.java:
+	  New. Parser for STA files.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Call STA file parser.
+
+	* src/main/java/de/intevation/flys/importer/AnnotationsParser.java,
+	  src/main/java/de/intevation/flys/importer/ImportRange.java:
+	  Uses BigDecimal now.
+
+	* src/main/java/de/intevation/flys/importer/ImportAttribute.java:
+	  Fixed wrong type cast in equals.
+
+	* src/main/java/de/intevation/flys/importer/ImportMainValueType.java:
+	  New. Helper model for importing main value types.
+
+	* src/main/java/de/intevation/flys/backend/SessionFactoryProvider.java:
+	  Register forgotten MainValue model.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Store annotations in backend.
+
+	* src/main/java/de/intevation/flys/model/Annotation.java:
+	  New convinience constructor.
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Added toString() method.
+
+	* src/main/java/de/intevation/flys/model/Range.java:
+	  Fixed nasty mistake in @OneToOne annotatation.
+	  New convinience constructors.
+
+	* src/main/java/de/intevation/flys/importer/ImportPosition.java
+	  src/main/java/de/intevation/flys/importer/ImportAnnotation.java,
+	  src/main/java/de/intevation/flys/importer/ImportRange.java
+	  src/main/java/de/intevation/flys/importer/ImportAttribute.java:
+	  Make storing to backend work. It's a bit too slow. :-/
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Fetch peer from backend. Added method to store annotations.
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Stored annotations into backend. More eloquent SQL exception
+	  handling.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/Attribute.java,
+	  src/main/java/de/intevation/flys/model/Position.java:
+	  Added convinience constructors.
+
+	* src/main/java/de/intevation/flys/importer/ImportPosition.java,
+	  src/main/java/de/intevation/flys/importer/ImportAttribute.java:
+	  Bound them to there backend peers.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/AnnotationsParser.java:
+	  New. Added parser to read *.KM files.
+
+	* src/main/java/de/intevation/flys/importer/ImportPosition.java,
+	  src/main/java/de/intevation/flys/importer/PegelGltParser.java,
+	  src/main/java/de/intevation/flys/importer/ImportRiver.java,
+	  src/main/java/de/intevation/flys/importer/ImportAnnotation.java,
+	  src/main/java/de/intevation/flys/importer/ImportRange.java,
+	  src/main/java/de/intevation/flys/importer/InfoGewParser.java,
+	  src/main/java/de/intevation/flys/importer/ImportAttribute.java:
+	  Adjusted to load the annotations from *.KM files.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/ImportPosition.java,
+	  src/main/java/de/intevation/flys/importer/ImportRange.java,
+	  src/main/java/de/intevation/flys/importer/ImportAttribute.java,
+	  src/main/java/de/intevation/flys/importer/ImportAnnotation.java:
+	  New helper models for import.
+
+	* src/main/java/de/intevation/flys/importer/PegelGltParser.java,
+	  src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  Use new models.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/PegelGltParser.java:
+	  New. Parser for PEGEL.GLT files.
+
+	* src/main/java/de/intevation/flys/importer/ImportGauge.java:
+	  New. Import model for gauges.
+
+	* src/main/java/de/intevation/flys/utils/DBCPConnectionProvider.java:
+	  Removed needless imports.
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Added method to parse the gauges.
+
+	* src/main/java/de/intevation/flys/importer/InfoGewParser.java:
+	  Trigger pegel glt file parsing.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Used thread local pattern to make sharing of session easier.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Fixed error in HQL statement.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/DBCPConnectionProvider.java:
+	  Commented out a debug block because it leaks the db password.
+
+2011-03-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/InfoGewParser.java:
+	  Expose imported rivers.
+
+	* src/main/java/de/intevation/flys/importer/InfoGewParser.java:
+	  Store imported rivers into database. Needs testing!
+
+2011-03-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/backend/SessionFactoryProvider.java:
+	  Default connection parameters are now overwritable with
+	  system properties (defaults in brackets):
+	  - user name:     flys.backend.user     (flys)
+	  - user password: flys.backend.password (flys)
+	  - db dialect:    flys.backend.dialect  (org.hibernate.dialect.PostgreSQLDialect)
+	  - db driver:     flys.backend.driver   (org.postgresql.Driver)
+	  - db url:        flys.backend.url      (jdbc:postgresql://localhost:5432/flys)
+
+2011-03-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/backend/SessionFactoryProvider.java:
+	  Expose createSessionFactory() as public to be usable without
+	  a artifact database running.
+
+2011-03-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FileTools.java: 
+	  Tools for handling with filenames. Currently there is
+	  a repair(File) method with repairs letter case errors
+	  which is useful when reading windows filenames on a
+	  un*x platform.
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Standalone app to read data from the file system and
+	  store it in a database. Currently it does not store 
+	  anything. It only loads info gew files.
+
+	* src/main/java/de/intevation/flys/importer/InfoGewParser.java:
+	  Info gew parser.
+
+	* src/main/java/de/intevation/flys/importer/ImportRiver.java:
+	  Helper model of a river used produced by parsing the
+	  info gew files.
+
+2011-03-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/backend/SessionFactoryProvider.java:
+	  New. SessionFactoryProvider.getSessionFactory() provides a
+	  SessionFactory to use the Hibernate O/R mapper for the FLYS backend.
+
+2011-03-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency to artifacts-commons to
+	  be able to use the global configuration of the artifact database.
+
+2011-03-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/App.java: Wire all POJOs
+	  to corresponding factory.
+
+	* src/main/java/de/intevation/flys/model/*.java: Generate
+	  all foreign key constraints. TODO: name them correctly
+	  because the machine generated names are ugly and do
+	  not fit the PostgreSQL names.
+
+	* doc/schema/postgresql.sql: Small quantifier fix in descriptions
+	  of wst columns.
+
+2011-03-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql: Fixed wrong spelled 
+	  column references in foreign keys introduces with
+	  last change.
+
+2011-03-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/model/*.java: Added
+	  column annotations for simple fields. TODO: foreign keys.
+
+2011-03-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql, doc/schema/postgresql-cleanup.sql:
+	  Fixed inconsistent table names.
+
+	* src/main/java/de/intevation/flys/model/*.java: Added
+	  entity and id annotations.
+
+2011-03-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql.sql, doc/schema/postgresql-cleanup.sql:
+	  Added missing sequences.
+
+	* doc/schema/sqlite.sql: Deleted. Not longer supported.
+
+2011-03-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql-cleanup.sql: Forgot to add.
+
+	* src/main/java/de/intevation/flys/App.java: Use
+	  Apache Commons DBCP as Hibernate connection provide.
+
+	* src/main/java/de/intevation/flys/model/River.java:
+	  Added a constructor with string argument. Set the
+	  sequence increment to 1 (eat up 100 at a time before).
+
+	* pom.xml: Added PostgreSQL 8.4 driver as runtime dependency.
+
+2011-03-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/postgresql-cleanup.sql: New. Tear down schema
+	  for a postgres database.
+
+	* doc/schema/postgresql.sql: Added squence for 
+	  auto generating ids in river table. Cleaned up schema.
+
+	* src/main/java/de/intevation/flys/App.java: Simple
+	  test app to interact with hibernate. Needs to be removed
+	  because its only a toy.
+
+	* src/main/java/de/intevation/flys/utils/DBCPConnectionProvider.java:
+	  New. Binds Apache Commons to Hibernate.
+
+	* pom.xml: Added dependencies to log4j, commons dbcp,
+	  JPA of hibernate.
+
+	* src/main/java/de/intevation/flys/model/River.java: Added
+	  JPA annotations.
+
+	* src/main/java/de/intevation/flys/model/*.java: Replaced
+	  Long with Integer because column ids are only four bytes wide.
+
+2011-03-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/sqlite.sql, doc/schema/postgresql.sql: Fixed
+	  smaller issues in ddl.
+
+	* src/main/java/de/intevation/flys/model/*.java: Added POJOs
+	  of to be mapped. TODO: Map them!
+
+	* pom.xml: Added plugin config for hibernate.
+
+2011-03-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency (and corresponding repository) to
+	  Hibernate Core 3.6.1 Final
+
+2011-03-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml, src/**: Created a new empty maven project:
+	  $ mvn archetype:create         \
+	    -DgroupId=de.intevation.flys \
+		-DartifactId=flys-backend
+
+2011-03-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* README: New. Some setup instructions.
+
+	* doc/schema/postgresql.sql: New. Schema converted to PostgreSQL
+
+	* doc/schema/sqlite.sql: Fixed defect foreign key constraints.
+
+2011-03-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/schema/sqlite.sql: Factorized time intervals out into
+	  a separated table.
+
+2011-01-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/import-kms.py, contrib/import-gew.py: Initial scripts
+	to import data into SQLite database. They still need some work.
+
+2011-02-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>:
+
+	* doc/schema/sqlite.sql: Added initial schema for
+	FLYS database.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/Changes	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,78 @@
+2011-06-27      RELEASE 2.4
+
+    New:
+
+        * Improved the data model to support:
+          - edges ("Oberkante", "Unterkante")
+          - annotations
+
+        * Improved the importer to read:
+          - edges
+          - annotations
+
+        * Made search for gauges in River independent of from/to
+          kilometer order.
+
+
+    Fixes:
+
+        * flys/issue76 Close gaps between q ranges
+
+        * flys/issue110 Fixed import of ATs that skipped the first value.
+
+
+
+2011-05-13      RELEASE 2.3
+
+    New:
+
+        * Import of:
+          - "zusätzliche Längsschnitte"
+          - "Fixierungen"
+          - "amtliche Linien"
+          - "Hochwassermarken"
+          - "Historische Abflusskurven"
+          - "HW-Schutzanlagen"
+
+        * Improvements in the SQL schema:
+          - Added a 'kind' column to WSTs
+          - Added a 'position' column to WST columns to enable sorting WST columns.
+          - Added a 'km_up' column to rivers to determine the flow direction of rivers.
+
+        * Rivers are now able to:
+          - determine their min/max kilometer range
+          - determine the selected gauge(s) based on a kilometer range
+
+        * WSTs are able to determine their min/max Q range.
+
+        * Gauges are able to determine their min/max W range.
+
+        * Added a view 'wst_value_table' that aggregates the data to build w/q
+          value tables.
+
+        * Added one-to-many relation Wst -> WstColumn
+
+        * Speedup of the importer by using an internal caching mechanism.
+
+        * Sta-Parse just accepts the following main value types: 'Q', 'W', 'D' and 'T'
+
+
+    Fixes:
+
+        * Fixed import of Q ranges in wst files.
+
+        * Fixed flys/issue19: Do not take km column in wst file as a water level.
+
+        * Fixed flys/issue11 (Diagramm: Fehlerhafte Werte in Abflusskurven)
+
+        * Fixed flys/issue51 (WINFO: Fachdaten am Pegel Perl enthält Sprünge)
+
+
+    !!!
+
+    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-backend/README	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,10 @@
+For the artifact database
+# su - postgres
+$ createuser --no-createrole --no-superuser --pwprompt --no-createdb artifacts
+$ createdb --encoding=UTF-8 --owner artifacts artifactsdb
+
+For the flys database
+
+# su - postgres
+$ createuser --no-createrole --no-superuser --pwprompt --no-createdb flys
+$ createdb --encoding=UTF-8 --owner flys flystest1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/contrib/import-gew.py	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,223 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+import os
+import codecs
+import re
+
+HAUPTWERT  = re.compile(r"\s*([^\s]+)\s+([^\s+]+)\s+([QWDT-])")
+WHITESPACE = re.compile(r"\s+")
+
+class KM(object):
+
+    def __init__(self, filename):
+        self.filename = filename
+        self.load_values()
+
+    def load_values(self):
+        with codecs.open(self.filename, "r", "latin-1") as f:
+            for line in f:
+                line = line.strip()
+                if not line or line.startswith("*"):
+                    parts = [s.strip() for s in line.split(";")]
+                    # TODO: Use code from import-kms.py
+
+class AbflussTafel(object):
+
+    def __init__(self, filename):
+        self.filename = filename
+        self.name     = ""
+        self.values = []
+        self.load_values()
+
+    def load_values(self):
+        with codecs.open(self.filename, "r", "latin-1") as f:
+            first = True
+            for line in f:
+                line = line.strip()
+                if not line: continue
+                if line.startswith("#! name="):
+                    self.name = line[8:]
+                    continue
+                if line.startswith("#") or line.startswith("*"):
+                    continue
+                line = line.replace(",", ".")
+                splits = WHITESPACE.split(line)
+
+                if len(splits) < 2 or len(splits) > 11:
+                    continue
+
+                w = float(splits[0])
+
+                shift = 0
+
+                if len(splits) != 11 and first:
+                    shift = 11 - len(splits)
+
+                for idx, q in enumerate(splits[1:]):
+                    i_w = w + shift + idx
+                    i_q = float(q)
+                    w_q = (i_w/100.0, i_q/100.0)
+                    self.values.append(w_q)
+
+                first = False
+
+
+class Hauptwert(object):
+    def __init__(self, name, value, kind):
+        self.name  = name
+        self.extra = value
+        self.kind  = kind
+
+class Pegel(object):
+    def __init__(self, name, start, stop, sta, at, html):
+        self.name       = name
+        self.start      = start
+        self.stop       = stop
+        self.sta        = sta
+        self.at         = at
+        self.html       = html
+        self.aeo        = 0.0
+        self.nullpunkt  = 0.0
+        self.km         = 0.0
+        self.hauptwerte = []
+        self.load_hauptwerte()
+        self.at_data = AbflussTafel(self.at)
+
+    def load_hauptwerte(self):
+        with codecs.open(self.sta, "r", "latin-1") as f:
+            for line_no, line in enumerate(f):
+                line = line.rstrip()
+                if line_no == 0:
+                    first = False
+                    name = line[16:37].strip()
+                    line = [s.replace(",", ".") for s in line[37:].split()]
+                    self.aeo = float(line[0])
+                    self.nullpunkt = float(line[1])
+                    print >> sys.stderr, "pegel name: '%s'" % name
+                    print >> sys.stderr, "pegel aeo: '%f'" % self.aeo
+                    print >> sys.stderr, "pegel nullpunkt: '%f'" % self.nullpunkt
+                elif line_no == 1:
+                    self.km = float(line[29:36].strip().replace(",", "."))
+                    print >> sys.stderr, "km: '%f'" % self.km
+                else:
+                    if not line: continue
+                    line = line.replace(",", ".")
+                    m = HAUPTWERT.match(line)
+                    if not m: continue
+                    self.hauptwerte.append(Hauptwert(
+                        m.group(1), float(m.group(2)), m.group(3)))
+
+class Gewaesser(object):
+
+    def __init__(self, name=None, b_b=None, wst=None):
+        self.name = name
+        self.b_b = b_b
+        self.wst = wst
+        self.pegel = []
+
+    def load_pegel(self):
+        dir_name = os.path.dirname(self.wst)
+        pegel_glt = find_file(dir_name, "PEGEL.GLT")
+        if not pegel_glt:
+            print >> sys.stderr, "Missing PEGEL.GLT for %r" % self.name
+            return
+
+        print >> sys.stderr, "pegel_glt: %r" % pegel_glt
+
+        with codecs.open(pegel_glt, "r", "latin-1") as f:
+            for line in f:
+                line = line.strip()
+                if not line or line.startswith("#"):
+                    continue
+                # using re to cope with quoted columns,
+                # shlex has unicode problems.
+                parts = [p for p in re.split("( |\\\".*?\\\"|'.*?')", line) 
+                         if p.strip()]
+                if len(parts) < 7:
+                    print >> sys.stderr, "too less colums (need 7): %r" % line
+                    continue
+
+                print >> sys.stderr, "%r" % parts
+                self.pegel.append(Pegel(
+                    parts[0],
+                    min(float(parts[2]), float(parts[3])),
+                    max(float(parts[2]), float(parts[3])),
+                    norm_path(parts[4], dir_name),
+                    norm_path(parts[5], dir_name),
+                    parts[6]))
+
+
+    def __repr__(self):
+        return u"Gewaesser(name=%r, b_b=%r, wst=%r)" % (
+            self.name, self.b_b, self.wst)
+
+def norm_path(path, ref):
+    if not os.path.isabs(path):
+        path = os.path.normpath(os.path.join(ref, path))
+    return path
+
+def find_file(path, what):
+    what = what.lower()
+    for filename in os.listdir(path):
+        p = os.path.join(path, filename)
+        if os.path.isfile(p) and filename.lower() == what:
+            return p
+    return None
+    
+
+def read_gew(filename):
+
+    gewaesser = []
+
+    current = Gewaesser()
+
+    filename = os.path.abspath(filename)
+    dirname = os.path.dirname(filename)
+
+    with codecs.open(filename, "r", "latin-1") as f:
+        for line in f:
+            line = line.strip()
+            if not line or line.startswith("*"):
+                continue
+
+            if line.startswith(u"Gewässer:"):
+                if current.name:
+                    gewaesser.append(current)
+                    current = Gewaesser()
+                current.name = line[len(u"Gewässer:"):].strip()
+            elif line.startswith(u"B+B-Info:"):
+                current.b_b = norm_path(line[len(u"B+B-Info:"):].strip(),
+                                        dirname)
+            elif line.startswith(u"WSTDatei:"):
+                current.wst = norm_path(line[len(u"WSTDatei:"):].strip(),
+                                        dirname)
+
+        if current.name:
+            gewaesser.append(current)
+
+    return gewaesser
+
+def main():
+
+    if len(sys.argv) < 2:
+        print >> sys.stderr, "missing gew file"
+        sys.exit(1)
+
+    gew_filename = sys.argv[1]
+
+    if not os.path.isfile(gew_filename):
+        print >> sys.stderr, "'%s' is not a file" % gew_filename
+        sys.exit(1)
+
+    gewaesser = read_gew(gew_filename)
+
+    for gew in gewaesser:
+        gew.load_pegel()
+
+    
+
+if __name__ == '__main__':
+    main()
+# vim: set fileencoding=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/contrib/import-kms.py	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+
+import sys
+import logging
+import re
+import os
+
+import sqlite3 as db
+import locale
+import codecs
+
+from optparse import OptionParser
+
+log = logging.getLogger(__name__) 
+log.setLevel(logging.WARNING)
+log.addHandler(logging.StreamHandler(sys.stderr))
+
+RANGE = re.compile("([^#]*)#(.*)")
+DEFAULT_DATABASE = "flys.db"
+
+SQL_NEXT_ID   = "SELECT coalesce(max(id), -1) + 1 FROM %s"
+SQL_SELECT_ID = "SELECT id FROM %s WHERE %s = ?"
+SQL_INSERT_ID = "INSERT INTO %s (id, %s) VALUES (?, ?)"
+
+SQL_SELECT_RANGE_ID = """
+SELECT id FROM ranges WHERE river_id = ? AND a = ? AND b = ?
+"""
+SQL_INSERT_RANGE_ID = """
+INSERT INTO ranges (id, river_id, a, b) VALUES (?, ?, ?, ?)
+"""
+SQL_SELECT_ANNOTATION_ID = """
+SELECT id FROM annotations
+WHERE range_id = ? AND attribute_id = ? AND position_id = ?
+"""
+SQL_INSERT_ANNOTATION_ID = """
+INSERT INTO annotations (id, range_id, attribute_id, position_id) 
+VALUES (?, ?, ?, ?)
+"""
+
+def encode(s):
+    try:
+        return unicode(s, "latin-1")
+    except UnicodeDecodeError:
+        return unicode.encode(s, locale.getpreferredencoding())
+
+class hashabledict(dict):
+    def __key(self):
+        return tuple((k, self[k]) for k in sorted(self))
+    def __hash__(self):
+        return hash(self.__key())
+    def __eq__(self, other):
+        return self.__key() == other.__key()
+
+def cache(f):
+    def func(*args, **kw):
+        key = (args, hashabledict(kw))
+        try:
+            return f.__cache__[key]
+        except KeyError:
+            value = f(*args, **kw)
+            f.__cache__[key] = value
+            return value
+    f.__cache__ = {}
+    return func
+
+NEXT_IDS = {}
+def next_id(cur, relation):
+    idx = NEXT_IDS.get(relation)
+    if idx is None:
+        cur.execute(SQL_NEXT_ID % relation)
+        idx = cur.fetchone()[0]
+    NEXT_IDS[relation] = idx + 1
+    return idx
+
+def get_id(cur, relation, attribute, value):
+    select_stmt = SQL_SELECT_ID % (relation, attribute)
+    #log.debug(select_stmt)
+    cur.execute(select_stmt, (value,))
+    row = cur.fetchone()
+    if row: return row[0]
+    idx = next_id(cur, relation)
+    insert_stmnt = SQL_INSERT_ID % (relation, attribute)
+    #log.debug(insert_stmnt)
+    cur.execute(insert_stmnt, (idx, value))
+    cur.connection.commit()
+    log.debug("insert %s '%s' id: '%d'" % (relation, value, idx))
+    return idx
+
+#@cache
+def get_river_id(cur, name):
+    return get_id(cur, "rivers", "name", name)
+
+#@cache
+def get_attribute_id(cur, value):
+    return get_id(cur, "attributes", "value", value)
+
+#@cache
+def get_position_id(cur, value):
+    return get_id(cur, "positions", "value", value)
+
+#@cache
+def get_range_id(cur, river_id, a, b):
+    cur.execute(SQL_SELECT_RANGE_ID, (river_id, a, b))
+    row = cur.fetchone()
+    if row: return row[0]
+    idx = next_id(cur, "ranges")
+    cur.execute(SQL_INSERT_RANGE_ID, (idx, river_id, a, b))
+    cur.connection.commit()
+    return idx
+
+#@cache
+def get_annotation_id(cur, range_id, attribute_id, position_id):
+    cur.execute(SQL_SELECT_ANNOTATION_ID, (
+        range_id, attribute_id, position_id))
+    row = cur.fetchone()
+    if row: return row[0]
+    idx = next_id(cur, "annotations")
+    cur.execute(SQL_INSERT_ANNOTATION_ID, (
+        idx, range_id, attribute_id, position_id))
+    cur.connection.commit()
+    return idx
+
+def files(root, accept=lambda x: True):
+    if os.path.isfile(root):
+        if accept(root): yield root
+    elif os.path.isdir(root):
+        stack = [ root ]
+        while stack:
+            cur = stack.pop()
+            for f in os.listdir(cur):
+                p = os.path.join(cur, f)
+                if os.path.isdir(p):
+                    stack.append(p)
+                elif os.path.isfile(p) and accept(p):
+                    yield p
+
+def feed_km(cur, river_id, km_file):
+
+    log.info("processing: %s" % km_file)
+
+    for line in codecs.open(km_file, "r", "latin-1"):
+        line = line.strip()
+        if not line or line.startswith('*'):
+            continue
+        parts = [x.strip() for x in line.split(';')]
+        if len(parts) < 3:
+            log.error("cannot process: '%s'" % line)
+            continue
+        m = RANGE.match(parts[2])
+        try:
+            if m:
+                x = [float(x.replace(",", ".")) for x in m.groups()]
+                a, b = min(x), max(x)
+                if a == b: b = None
+            else:
+                a, b = float(parts[2].replace(",", ".")), None
+        except ValueError:
+            log.error("cannot process: '%s'" % line)
+            continue
+
+        attribute = parts[0]
+        position  = parts[1]
+        attribute_id = get_attribute_id(cur, attribute) if attribute else None
+        position_id  = get_position_id(cur, position) if position else None
+
+        range_id = get_range_id(cur, river_id, a, b)
+
+        get_annotation_id(cur, range_id, attribute_id, position_id)
+
+def main():
+
+    usage = "usage: %prog [options] river km-file ..."
+    parser = OptionParser(usage=usage)
+    parser.add_option(
+        "-v", "--verbose", action="store_true",
+        dest="verbose",
+        help="verbose output")
+    parser.add_option(
+        "-r", "--recursive", action="store_true",
+        dest="recursive", default=False,
+        help="recursive")
+    parser.add_option(
+        "-d", "--database", action="store",
+        dest="database",
+        help="database to connect with",
+        default=DEFAULT_DATABASE)
+
+    options, args = parser.parse_args()
+
+    if options.verbose:
+        log.setLevel(logging.INFO)
+    
+    if len(args) < 1:
+        log.error("missing river argument")
+        sys.exit(1)
+
+    river = unicode(args[0], locale.getpreferredencoding())
+
+    with db.connect(options.database) as con:
+        cur = con.cursor()
+        river_id = get_river_id(cur, river)
+
+        for arg in args[1:]:
+            if options.recursive:
+                for km_file in files(
+                    arg, lambda x: x.lower().endswith(".km")):
+                    feed_km(cur, river_id, km_file)
+            else:
+                feed_km(cur, river_id, arg)
+        
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/doc/annotation-types.xml	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<annotation>
+    <types>
+        <type name="Abzweigung"/>
+        <type name="Berechnungsstrecke"/>
+        <type name="Brücke"/>
+        <type name="Bundesland"/>
+        <type name="Deich"/>
+        <type name="Einmündung"/>
+        <type name="Fähre"/>
+        <type name="Gemeinde"/>
+        <type name="Grenze"/>
+        <type name="Hafen"/>
+        <type name="HW-Schutz"/>
+        <type name="Landkreis"/>
+        <type name="Meldestelle"/>
+        <type name="Messstelle"/>
+        <type name="Pegel"/>
+        <type name="Stauwehr"/>
+        <type name="Staatsgrenze"/>
+        <type name="Staat"/>
+        <type name="WSA"/>
+        <type name="Zufluß"/>
+        <type name="Sonstige" default="true"/>
+    </types>
+
+    <patterns>
+        <file pattern="^Brücken$" type="Brücke"/>
+        <file pattern="^Deich.*$" type="Deich"/>
+        <file pattern="^Hafen$" type="Hafen"/>
+        <file pattern="^Pegel-alle$" type="Pegel"/>
+        <file pattern="^Pegel$" type="Pegel"/>
+        <file pattern="^Wehr$" type="Stauwehr"/>
+        <file pattern="^Stauwehr$" type="Stauwehr"/>
+        <file pattern="^Zufluß$" type="Zufluß"/>
+
+        <line pattern="^Abz\.?[:\s].*$" type="Abzweigung"/>
+        <line pattern="^Berechnungsstrecke.*$" type="Berechnungsstrecke"/>
+        <line pattern="^Brücke[:\s].*$" type="Brücke"/>
+        <line pattern="^Bundesland[:\s].*$" type="Bundesland"/>
+        <line pattern="^Einmündung[:\s].*$" type="Einmündung"/>
+        <line pattern="^Fähre[:\s].*$" type="Abzweig"/>
+        <line pattern="^Gemeinde[:\s].*$" type="Gemeinde"/>
+        <line pattern="^Grenze[:\s].*$" type="Grenze"/>
+        <line pattern="^Hafen[:\s].*$" type="Hafen"/>
+        <line pattern="^HW-Schutz[:\s].*$" type="HW-Schutz"/>
+        <line pattern="^Landkreis[:\s].*$" type="Landkreis"/>
+        <line pattern="^Meldestelle[:\s].*$" type="Meldestelle"/>
+        <line pattern="^Messstelle[:\s].*$" type="Messstelle"/>
+        <line pattern="^Pegel[:\s].*$" type="Pegel"/>
+        <line pattern="^Staatsgrenze[:\s].*$" type="Staatsgrenze"/>
+        <line pattern="^Staat[:\s].*$" type="Staat"/>
+        <line pattern="^Wehr[:\s].*$" type="Stauwehr"/>
+        <line pattern="^WSA[:\s].*$" type="WSA"/>
+        <line pattern="^Zufluß[:\s].*$" type="Zufluß"/>
+    </patterns>
+</annotation>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/doc/schema/postgresql.sql	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,263 @@
+BEGIN;
+
+-- Gewaesser
+CREATE SEQUENCE RIVERS_ID_SEQ;
+
+CREATE TABLE rivers (
+    id    int PRIMARY KEY NOT NULL,
+    name  VARCHAR(256)    NOT NULL UNIQUE,
+    km_up BOOLEAN         NOT NULL DEFAULT true
+);
+
+-- Bruecke, Haefen, etc.
+CREATE SEQUENCE ATTRIBUTES_ID_SEQ;
+
+CREATE TABLE attributes (
+    id    int PRIMARY KEY NOT NULL,
+    value VARCHAR(256)    NOT NULL UNIQUE
+);
+
+-- segments from/to at a river
+CREATE SEQUENCE RANGES_ID_SEQ;
+
+CREATE TABLE ranges (
+    id       int PRIMARY KEY NOT NULL,
+    river_id int             NOT NULL REFERENCES rivers(id),
+    a        NUMERIC         NOT NULL,
+    b        NUMERIC,
+    UNIQUE (river_id, a, b)
+);
+
+-- Lage 'links', 'rechts', etc.
+CREATE SEQUENCE POSITIONS_ID_SEQ;
+
+CREATE TABLE positions (
+    id    int PRIMARY KEY NOT NULL,
+    value VARCHAR(256)    NOT NULL UNIQUE
+);
+
+-- Kante 'oben', 'unten'
+CREATE SEQUENCE EDGES_ID_SEQ;
+
+CREATE TABLE edges (
+    id     int PRIMARY KEY NOT NULL,
+    top    NUMERIC,
+    bottom NUMERIC
+);
+
+-- Types of annotatations (Hafen, Bruecke, Zufluss, ...)
+CREATE SEQUENCE ANNOTATION_TYPES_ID_SEQ;
+
+CREATE TABLE annotation_types (
+    id    int PRIMARY KEY NOT NULL,
+    name  VARCHAR(256)    NOT NULL UNIQUE
+);
+
+-- Some object (eg. Hafen) at a segment of river
+-- plus its position.
+CREATE SEQUENCE ANNOTATIONS_ID_SEQ;
+
+CREATE TABLE annotations (
+    id           int PRIMARY KEY NOT NULL,
+    range_id     int             NOT NULL REFERENCES ranges(id),
+    attribute_id int             NOT NULL REFERENCES attributes(id),
+    position_id  int REFERENCES positions(id),
+    edge_id      int REFERENCES edges(id),
+    type_id      int REFERENCES annotation_types(id)
+);
+
+-- Pegel
+CREATE SEQUENCE GAUGES_ID_SEQ;
+
+CREATE TABLE gauges (
+    id       int PRIMARY KEY NOT NULL,
+    name     VARCHAR(256)    NOT NULL,
+    river_id int             NOT NULL REFERENCES rivers(id),
+    station  NUMERIC         NOT NULL UNIQUE,
+    aeo      NUMERIC         NOT NULL,
+
+    -- Pegelnullpunkt
+    datum    NUMERIC NOT NULL,
+    -- Streckengueltigkeit
+    range_id int NOT NULL REFERENCES ranges (id),
+
+    UNIQUE (name, river_id),
+    UNIQUE (river_id, station)
+);
+
+-- Type of a Hauptwert 'W', 'Q', 'D', etc.
+CREATE SEQUENCE MAIN_VALUE_TYPES_ID_SEQ;
+
+CREATE TABLE main_value_types (
+    id   int PRIMARY KEY NOT NULL,
+    name VARCHAR(256)    NOT NULL UNIQUE
+);
+
+--  Named type of a Hauptwert (eg. HQ100)
+CREATE SEQUENCE NAMED_MAIN_VALUES_ID_SEQ;
+
+CREATE TABLE named_main_values (
+    id      int PRIMARY KEY NOT NULL,
+    name    VARCHAR(256)    NOT NULL UNIQUE,
+    type_id int NOT NULL REFERENCES main_value_types(id),
+    UNIQUE (name, type_id)
+);
+
+-- Table for time intervals
+CREATE SEQUENCE TIME_INTERVALS_ID_SEQ;
+
+CREATE TABLE time_intervals (
+    id         int PRIMARY KEY NOT NULL,
+    start_time TIMESTAMP       NOT NULL,
+    stop_time  TIMESTAMP,
+    CHECK (start_time <= stop_time)
+);
+
+-- Stammdaten
+CREATE SEQUENCE MAIN_VALUES_ID_SEQ;
+
+CREATE TABLE main_values (
+    id             int PRIMARY KEY NOT NULL,
+    gauge_id       int NOT NULL REFERENCES gauges(id),
+    named_value_id int NOT NULL REFERENCES named_main_values(id),
+    value          NUMERIC NOT NULL,
+
+    time_interval_id int REFERENCES time_intervals(id),
+
+    -- TODO: better checks
+    UNIQUE (gauge_id, named_value_id, time_interval_id)
+);
+
+-- Abflusstafeln
+CREATE SEQUENCE DISCHARGE_TABLES_ID_SEQ;
+
+CREATE TABLE discharge_tables (
+    id               int PRIMARY KEY NOT NULL,
+    gauge_id         int NOT NULL REFERENCES gauges(id),
+    description      VARCHAR(256) NOT NULL,
+    kind             int NOT NULL DEFAULT 0,
+    time_interval_id int REFERENCES time_intervals(id)
+
+    -- TODO: better checks
+    -- UNIQUE (gauge_id, kind, time_interval_id)
+);
+
+-- Values of the Abflusstafeln
+CREATE SEQUENCE DISCHARGE_TABLE_VALUES_ID_SEQ;
+
+CREATE TABLE discharge_table_values (
+    id       int PRIMARY KEY NOT NULL,
+    table_id int NOT NULL REFERENCES discharge_tables(id),
+    q        NUMERIC NOT NULL,
+    w        NUMERIC NOT NULL,
+
+    UNIQUE (table_id, q, w)
+);
+
+-- WST files
+CREATE SEQUENCE WSTS_ID_SEQ;
+
+CREATE TABLE wsts (
+    id          int PRIMARY KEY NOT NULL,
+    river_id    int NOT NULL REFERENCES rivers(id),
+    description VARCHAR(256) NOT NULL,
+    kind        int NOT NULL DEFAULT 0,
+    -- TODO: more meta infos
+    UNIQUE (river_id, description)
+);
+
+-- columns of WST files
+CREATE SEQUENCE WST_COLUMNS_ID_SEQ;
+
+CREATE TABLE wst_columns (
+    id          int PRIMARY KEY NOT NULL,
+    wst_id      int NOT NULL REFERENCES wsts(id),
+    name        VARCHAR(256) NOT NULL,
+    description VARCHAR(256),
+    position    int NOT NULL DEFAULT 0,
+
+    time_interval_id int REFERENCES time_intervals(id),
+
+    UNIQUE (wst_id, name),
+    UNIQUE (wst_id, position)
+);
+
+-- w values in  WST file column
+CREATE SEQUENCE WST_COLUMN_VALUES_ID_SEQ;
+
+CREATE TABLE wst_column_values (
+    id            int PRIMARY KEY NOT NULL,
+    wst_column_id int NOT NULL REFERENCES wst_columns(id),
+    position      NUMERIC NOT NULL,
+    w             NUMERIC NOT NULL,
+
+    UNIQUE (position, wst_column_id),
+    UNIQUE (position, wst_column_id, w)
+);
+
+-- bind q values to range
+CREATE SEQUENCE WST_Q_RANGES_ID_SEQ;
+
+CREATE TABLE wst_q_ranges (
+    id       int PRIMARY KEY NOT NULL,
+    range_id int NOT NULL REFERENCES ranges(id),
+    q        NUMERIC NOT NULL
+);
+
+-- bind q ranges to wst columns
+CREATE SEQUENCE WST_COLUMN_Q_RANGES_ID_SEQ;
+
+CREATE TABLE wst_column_q_ranges (
+    id             int PRIMARY KEY NOT NULL,
+    wst_column_id  int NOT NULL REFERENCES wst_columns(id),
+    wst_q_range_id int NOT NULL REFERENCES wst_q_ranges(id),
+
+    UNIQUE (wst_column_id, wst_q_range_id)
+);
+
+CREATE VIEW wst_value_table AS
+    SELECT wcv.position AS position,
+           w,
+           (SELECT q
+            FROM   wst_column_q_ranges wcqr
+                   JOIN wst_q_ranges wqr
+                     ON wcqr.wst_q_range_id = wqr.id
+                   JOIN ranges r
+                     ON r.id = wqr.range_id
+            WHERE  wcqr.wst_column_id = wc.id
+                   AND wcv.position BETWEEN r.a AND r.b) AS q,
+           wc.position                                   AS column_pos,
+           w.id                                          AS wst_id
+    FROM   wst_column_values wcv
+           JOIN wst_columns wc
+             ON wcv.wst_column_id = wc.id
+           JOIN wsts w
+             ON wc.wst_id = w.id
+    ORDER  BY wcv.position ASC,
+          wc.position DESC;
+
+-- view to select the w values of a WST
+CREATE VIEW wst_w_values AS
+    SELECT wcv."position" AS km, 
+           wcv.w          AS w,  
+           wc."position"  AS column_pos, 
+           w.id           AS wst_id
+        FROM wst_column_values wcv
+        JOIN wst_columns wc ON wcv.wst_column_id = wc.id
+        JOIN wsts w         ON wc.wst_id = w.id
+    ORDER BY wcv."position", wc."position";
+
+-- view to select the q values of a WST
+CREATE VIEW wst_q_values AS
+    SELECT wc.position AS column_pos,
+           wqr.q       AS q, 
+           r.a         AS a, 
+           r.b         AS b,
+           wc.wst_id   AS wst_id
+    FROM wst_column_q_ranges wcqr
+    JOIN wst_q_ranges wqr ON wcqr.wst_q_range_id = wqr.id
+    JOIN ranges r         ON wqr.range_id        = r.id
+    JOIN wst_columns wc   ON wcqr.wst_column_id  = wc.id
+    ORDER BY wc.position, wcqr.wst_column_id, r.a;
+
+COMMIT;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/pom.xml	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,90 @@
+<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</groupId>
+  <artifactId>flys-backend</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>flys-backend</name>
+  <url>http://maven.apache.org</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>hibernate3-maven-plugin</artifactId>
+        <version>2.2</version>
+        <!--
+        <configuration>
+            <componentProperties>
+                <propertyfile>src/main/config/hbm.properties</propertyfile>
+            </componentProperties>
+        </configuration>
+        -->
+      </plugin>
+      <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>de.intevation.artifacts.common</groupId>
+      <artifactId>artifacts-common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>    
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>hibernate-core</artifactId>
+      <version>3.6.2.Final</version>
+    </dependency>
+    <dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>hibernate-entitymanager</artifactId>
+      <version>3.6.2.Final</version>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <version>1.2.14</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-dbcp</groupId>
+      <artifactId>commons-dbcp</artifactId>
+      <version>1.2.2</version>
+    </dependency>
+    <dependency>
+      <groupId>postgresql</groupId>
+      <artifactId>postgresql</artifactId>
+      <version>8.4-702.jdbc4</version>
+      <scope>runtime</scope>
+    </dependency>
+  </dependencies>
+
+  <repositories>
+    <repository>
+      <id>repository.jboss.org/nexus</id>
+      <name>JBoss Repository - Nexus</name>
+      <url>http://repository.jboss.org/nexus/content/groups/public/</url>
+    </repository>
+  </repositories>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/src/main/java/de/intevation/flys/App.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,121 @@
+package de.intevation.flys;
+
+import org.hibernate.cfg.Configuration;
+
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.Attribute;
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.DischargeTableValue;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValueType;
+import de.intevation.flys.model.NamedMainValue;
+import de.intevation.flys.model.MainValue;
+import de.intevation.flys.model.Position;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.TimeInterval;
+import de.intevation.flys.model.WstColumn;
+import de.intevation.flys.model.WstColumnQRange;
+import de.intevation.flys.model.WstColumnValue;
+import de.intevation.flys.model.Wst;
+import de.intevation.flys.model.WstQRange;
+
+import org.hibernate.dialect.resolver.DialectFactory;
+
+import java.util.Properties;
+
+import org.hibernate.cfg.Environment;
+
+public class App
+{
+    private static final String USER =
+        System.getProperty("flys.user", "flys");
+
+    private static final String PASS =
+        System.getProperty("flys.pass", "flys");
+
+    public static void dumpSchema(Configuration cfg) {
+        System.out.println("BEGIN;");
+
+        String [] setupScript = cfg.generateSchemaCreationScript(
+            DialectFactory.constructDialect(
+                "org.hibernate.dialect.PostgreSQLDialect"));
+
+        for (String line: setupScript) {
+            System.out.println(line + ";");
+        }
+
+        System.out.println("COMMIT;");
+    }
+
+    public static void main(String [] args)
+    throws Exception
+    {
+        Configuration cfg = new Configuration();
+
+        cfg.addAnnotatedClass(Annotation.class);
+        cfg.addAnnotatedClass(Attribute.class);
+        cfg.addAnnotatedClass(DischargeTable.class);
+        cfg.addAnnotatedClass(DischargeTableValue.class);
+        cfg.addAnnotatedClass(Gauge.class);
+        cfg.addAnnotatedClass(MainValueType.class);
+        cfg.addAnnotatedClass(NamedMainValue.class);
+        cfg.addAnnotatedClass(MainValue.class);
+        cfg.addAnnotatedClass(Position.class);
+        cfg.addAnnotatedClass(Range.class);
+        cfg.addAnnotatedClass(River.class);
+        cfg.addAnnotatedClass(TimeInterval.class);
+        cfg.addAnnotatedClass(WstColumn.class);
+        cfg.addAnnotatedClass(WstColumnQRange.class);
+        cfg.addAnnotatedClass(WstColumnValue.class);
+        cfg.addAnnotatedClass(Wst.class);
+        cfg.addAnnotatedClass(WstQRange.class);
+        Properties props = new Properties();
+
+        dumpSchema(cfg);
+
+        props.setProperty(
+            Environment.DIALECT,
+            "org.hibernate.dialect.PostgreSQLDialect");
+
+        props.setProperty(
+            "hibernate.connection.provider_class",
+            "org.hibernate.connection.DBCPConnectionProvider");
+
+        props.setProperty(
+            Environment.USER,
+            USER);
+
+        props.setProperty(
+            Environment.PASS,
+            PASS);
+
+        props.setProperty(
+            Environment.DRIVER,
+            "org.postgresql.Driver");
+
+        props.setProperty(
+            Environment.URL,
+            "jdbc:postgresql://localhost:54321/flystest1");
+
+        cfg.mergeProperties(props);
+
+        /*
+
+        SessionFactory sessionFactory = cfg.buildSessionFactory();
+
+        Session session = sessionFactory.openSession();
+        session.beginTransaction();
+
+        River river = new River("Hase-" + new java.util.Date());
+
+        session.save(river);
+
+        System.out.println("river id: " + river.getId());
+
+        session.getTransaction().commit();
+        session.close();
+        */
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/backend/SessionFactoryProvider.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,162 @@
+package de.intevation.flys.backend;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import java.util.Properties;
+
+import org.hibernate.SessionFactory;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.AnnotationType;
+import de.intevation.flys.model.Attribute;
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.DischargeTableValue;
+import de.intevation.flys.model.Edge;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValueType;
+import de.intevation.flys.model.NamedMainValue;
+import de.intevation.flys.model.MainValue;
+import de.intevation.flys.model.Position;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.TimeInterval;
+import de.intevation.flys.model.WstColumn;
+import de.intevation.flys.model.WstColumnQRange;
+import de.intevation.flys.model.WstColumnValue;
+import de.intevation.flys.model.Wst;
+import de.intevation.flys.model.WstQRange;
+
+import org.apache.log4j.Logger;
+
+public final class SessionFactoryProvider
+{
+    private static Logger log = Logger.getLogger(SessionFactoryProvider.class);
+
+    public static final String XPATH_USER =
+        "/artifact-database/backend-database/user/text()";
+
+    public static final String XPATH_PASSWORD =
+        "/artifact-database/backend-database/password/text()";
+
+    public static final String XPATH_DIALECT =
+        "/artifact-database/backend-database/dialect/text()";
+
+    public static final String XPATH_DRIVER =
+        "/artifact-database/backend-database/driver/text()";
+
+    public static final String XPATH_URL =
+        "/artifact-database/backend-database/url/text()";
+
+    public static final String DEFAULT_USER =
+        System.getProperty("flys.backend.user", "flys");
+
+    public static final String DEFAULT_PASSWORD =
+        System.getProperty("flys.backend.password", "flys");
+
+    public static final String DEFAULT_DIALECT =
+        System.getProperty(
+            "flys.backend.dialect",
+            "org.hibernate.dialect.PostgreSQLDialect");
+
+    public static final String DEFAULT_DRIVER =
+        System.getProperty(
+            "flys.backend.driver",
+            "org.postgresql.Driver");
+
+    public static final String DEFAULT_URL =
+        System.getProperty(
+            "flys.backend.url",
+            "jdbc:postgresql://localhost:5432/flys");
+
+    private static SessionFactory sessionFactory;
+
+    private SessionFactoryProvider() {
+    }
+
+    public static synchronized SessionFactory getSessionFactory() {
+        if (sessionFactory == null) {
+            String user =
+                Config.getStringXPath(XPATH_USER, DEFAULT_USER);
+            String password =
+                Config.getStringXPath(XPATH_PASSWORD, DEFAULT_PASSWORD);
+            String dialect =
+                Config.getStringXPath(XPATH_DIALECT, DEFAULT_DIALECT);
+            String driver =
+                Config.getStringXPath(XPATH_DRIVER, DEFAULT_DRIVER);
+            String url =
+                Config.getStringXPath(XPATH_URL, DEFAULT_URL);
+
+            sessionFactory = createSessionFactory(
+                user, password, dialect, driver, url);
+        }
+        return sessionFactory;
+    }
+
+    public static SessionFactory createSessionFactory() {
+        return createSessionFactory(
+            DEFAULT_USER,
+            DEFAULT_PASSWORD,
+            DEFAULT_DIALECT,
+            DEFAULT_DRIVER,
+            DEFAULT_URL);
+    }
+
+    public static SessionFactory createSessionFactory(
+        String user,
+        String password,
+        String dialect,
+        String driver,
+        String url
+    ) {
+        Configuration cfg = new Configuration();
+
+        // TODO: Use package reflection here.
+        cfg.addAnnotatedClass(Annotation.class);
+        cfg.addAnnotatedClass(AnnotationType.class);
+        cfg.addAnnotatedClass(Attribute.class);
+        cfg.addAnnotatedClass(DischargeTable.class);
+        cfg.addAnnotatedClass(DischargeTableValue.class);
+        cfg.addAnnotatedClass(Edge.class);
+        cfg.addAnnotatedClass(Gauge.class);
+        cfg.addAnnotatedClass(MainValueType.class);
+        cfg.addAnnotatedClass(NamedMainValue.class);
+        cfg.addAnnotatedClass(MainValue.class);
+        cfg.addAnnotatedClass(Position.class);
+        cfg.addAnnotatedClass(Range.class);
+        cfg.addAnnotatedClass(River.class);
+        cfg.addAnnotatedClass(TimeInterval.class);
+        cfg.addAnnotatedClass(WstColumn.class);
+        cfg.addAnnotatedClass(WstColumnQRange.class);
+        cfg.addAnnotatedClass(WstColumnValue.class);
+        cfg.addAnnotatedClass(Wst.class);
+        cfg.addAnnotatedClass(WstQRange.class);
+
+        if (log.isDebugEnabled()) {
+            log.debug("user: "    + user);
+            log.debug("dialect: " + dialect);
+            log.debug("driver: "  + driver);
+            log.debug("url: "     + url);
+        }
+
+        Properties props = new Properties();
+
+        // We rely on our own connection pool
+        props.setProperty(
+            "hibernate.connection.provider_class",
+            "org.hibernate.connection.DBCPConnectionProvider");
+
+        props.setProperty(Environment.DIALECT, dialect);
+        props.setProperty(Environment.USER,    user);
+        props.setProperty(Environment.PASS,    password);
+        props.setProperty(Environment.DRIVER,  driver);
+        props.setProperty(Environment.URL,     url);
+
+        cfg.mergeProperties(props);
+
+        return cfg.buildSessionFactory();
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/backend/SessionHolder.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,43 @@
+package de.intevation.flys.backend;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+
+
+public class SessionHolder
+{
+    private static Logger logger =
+        Logger.getLogger(SessionHolder.class);
+
+    public static final ThreadLocal<Session> HOLDER =
+        new ThreadLocal<Session>() {
+            protected Session initialValue() {
+                return create();
+            }
+        };
+
+    private SessionHolder() {
+    }
+
+    public synchronized static Session create() {
+        logger.debug("create");
+        SessionFactory sessionFactory =
+            SessionFactoryProvider.getSessionFactory();
+        return sessionFactory.openSession();
+    }
+
+    public static Session acquire() {
+        logger.debug("acquire");
+        Session session = create();
+        HOLDER.set(session);
+        return session;
+    }
+
+    public static void release() {
+        logger.debug("release");
+        HOLDER.remove();
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/AnnotationClassifier.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,229 @@
+package de.intevation.flys.importer;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Element;
+
+import javax.xml.xpath.XPathConstants;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+public class AnnotationClassifier
+{
+    private static Logger log = Logger.getLogger(Importer.class);
+
+    public static final String TYPES_XPATH =
+        "/annotation/types/type";
+
+    public static final String FILE_PATTERNS_XPATH =
+        "/annotation/patterns/file";
+
+    public static final String DESCRIPTION_PATTERNS_XPATH =
+        "/annotation/patterns/line";
+
+
+    public static class Pair {
+
+        protected Pattern              pattern;
+        protected ImportAnnotationType annType;
+
+        public Pair(Pattern pattern, ImportAnnotationType annType) {
+            this.pattern  = pattern;
+            this.annType = annType;
+        }
+
+        public ImportAnnotationType match(String s) {
+            Matcher m = pattern.matcher(s);
+            return m.matches() ? annType : null;
+        }
+    } // class Pair
+
+
+    protected Map<String, ImportAnnotationType> types;
+    protected List<Pair>                        filePatterns;
+    protected List<Pair>                        descPatterns;
+
+    protected ImportAnnotationType defaultType;
+
+    public AnnotationClassifier() {
+    }
+
+    public AnnotationClassifier(Document rules) {
+        types        = new HashMap<String, ImportAnnotationType>();
+        filePatterns = new ArrayList<Pair>();
+        descPatterns = new ArrayList<Pair>();
+
+        buildRules(rules);
+    }
+
+    protected void buildRules(Document rules) {
+        buildTypes(rules);
+        buildFilePatterns(rules);
+        buildDescriptionPatterns(rules);
+    }
+
+    protected void buildTypes(Document rules) {
+
+        NodeList typeList = (NodeList)XMLUtils.xpath(
+            rules,
+            TYPES_XPATH,
+            XPathConstants.NODESET,
+            null);
+
+        if (typeList == null) {
+            log.info("no rules found.");
+            return;
+        }
+
+        for (int i = 0, N = typeList.getLength(); i < N; ++i) {
+            Element typeElement = (Element)typeList.item(i);
+            String name = typeElement.getAttribute("name");
+            if (name.length() == 0) {
+                log.warn("rule has no name");
+                continue;
+            }
+
+            ImportAnnotationType aic = new ImportAnnotationType(name);
+
+            types.put(name, aic);
+
+            if (typeElement.getAttribute("default").equals("true")) {
+                defaultType = aic;
+            }
+        }
+    }
+
+    protected void buildFilePatterns(Document rules) {
+
+        NodeList patternList = (NodeList)XMLUtils.xpath(
+            rules,
+            FILE_PATTERNS_XPATH,
+            XPathConstants.NODESET,
+            null);
+
+        if (patternList == null) {
+            log.info("no file patterns found.");
+            return;
+        }
+
+        for (int i = 0, N = patternList.getLength(); i < N; ++i) {
+            Element element = (Element)patternList.item(i);
+            Pair pair = buildPair(element);
+            if (pair != null) {
+                filePatterns.add(pair);
+            }
+        }
+    }
+
+    protected void buildDescriptionPatterns(Document rules) {
+
+        NodeList patternList = (NodeList)XMLUtils.xpath(
+            rules,
+            DESCRIPTION_PATTERNS_XPATH,
+            XPathConstants.NODESET,
+            null);
+
+        if (patternList == null) {
+            log.info("no line patterns found.");
+            return;
+        }
+
+        for (int i = 0, N = patternList.getLength(); i < N; ++i) {
+            Element element = (Element)patternList.item(i);
+            Pair pair = buildPair(element);
+            if (pair != null) {
+                descPatterns.add(pair);
+            }
+        }
+    }
+
+    protected Pair buildPair(Element element) {
+        String pattern = element.getAttribute("pattern");
+        String type    = element.getAttribute("type");
+
+        if (pattern.length() == 0) {
+            log.warn("pattern has no 'pattern' attribute.");
+            return null;
+        }
+
+        if (type.length() == 0) {
+            log.warn("pattern has no 'type' attribute.");
+            return null;
+        }
+
+        ImportAnnotationType annType = types.get(type);
+
+        if (annType == null) {
+            log.warn("pattern has unknown type '" + type + "'");
+            return null;
+        }
+
+        Pattern p;
+
+        try {
+            p = Pattern.compile(pattern,
+                    Pattern.CASE_INSENSITIVE|Pattern.UNICODE_CASE);
+        }
+        catch (IllegalArgumentException iae) {
+            log.warn("pattern '" + pattern + "' is invalid.", iae);
+            return null;
+        }
+
+        return new Pair(p, annType);
+    }
+
+    public ImportAnnotationType getDefaultType() {
+        return defaultType;
+    }
+
+    public ImportAnnotationType classifyFile(String filename) {
+        return classifyFile(filename, null);
+    }
+
+    public ImportAnnotationType classifyFile(
+        String                filename,
+        ImportAnnotationType def
+    ) {
+        if (filename.toLowerCase().endsWith(".km")) {
+            filename = filename.substring(0, filename.length()-3);
+        }
+
+        for (Pair pair: filePatterns) {
+            ImportAnnotationType annType = pair.match(filename);
+            if (annType != null) {
+                return annType;
+            }
+        }
+
+        return def;
+    }
+
+    public ImportAnnotationType classifyDescription(String description) {
+        return classifyDescription(description, null);
+    }
+
+    public ImportAnnotationType classifyDescription(
+        String                description,
+        ImportAnnotationType def
+    ) {
+        for (Pair pair: descPatterns) {
+            ImportAnnotationType annType = pair.match(description);
+            if (annType != null) {
+                return annType;
+            }
+        }
+
+        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-backend/src/main/java/de/intevation/flys/importer/AnnotationsParser.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,189 @@
+package de.intevation.flys.importer;
+
+import java.util.HashMap;
+import java.util.TreeSet;
+import java.util.List;
+import java.util.ArrayList;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.LineNumberReader;
+import java.io.InputStreamReader;
+import java.io.FileInputStream;
+
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.utils.FileTools;
+
+public class AnnotationsParser
+{
+    private static Logger log = Logger.getLogger(AnnotationsParser.class);
+
+    public static final String ENCODING = "ISO-8859-1";
+
+    public static final String [] TO_SCAN = {
+        "Basisdaten",
+        "Streckendaten"
+    };
+
+    protected HashMap<String, ImportAttribute> attributes;
+    protected HashMap<String, ImportPosition>  positions;
+    protected TreeSet<ImportAnnotation>        annotations;
+    protected AnnotationClassifier             classifier;
+
+    public AnnotationsParser() {
+        this(null);
+    }
+
+    public AnnotationsParser(AnnotationClassifier classifier) {
+        attributes  = new HashMap<String, ImportAttribute>();
+        positions   = new HashMap<String, ImportPosition>();
+        annotations = new TreeSet<ImportAnnotation>();
+        this.classifier = classifier;
+    }
+
+    public void parseFile(File file) throws IOException {
+        log.info("parsing km file: '" + file + "'");
+
+        ImportAnnotationType defaultIAT = null;
+
+        if (classifier != null) {
+            defaultIAT = classifier.classifyFile(
+                file.getName(),
+                classifier.getDefaultType());
+        }
+
+        LineNumberReader in = null;
+        try {
+            in =
+                new LineNumberReader(
+                new InputStreamReader(
+                new FileInputStream(file), ENCODING));
+
+            String line = null;
+            while ((line = in.readLine()) != null) {
+                if ((line = line.trim()).length() == 0
+                || line.startsWith("*")) {
+                    continue;
+                }
+
+                String [] parts = line.split("\\s*;\\s*");
+
+                if (parts.length < 3) {
+                    log.warn("not enough columns in line "
+                        + in.getLineNumber());
+                    continue;
+                }
+
+                ImportPosition position = positions.get(parts[0]);
+                if (position == null) {
+                    position = new ImportPosition(parts[0]);
+                    positions.put(parts[0], position);
+                }
+
+                ImportAttribute attribute = attributes.get(parts[1]);
+                if (attribute == null) {
+                    attribute = new ImportAttribute(parts[1]);
+                    attributes.put(parts[1], attribute);
+                }
+
+                String [] r = parts[2].replace(",", ".").split("\\s*#\\s*");
+
+                BigDecimal from, to;
+
+                try {
+                    from = new BigDecimal(r[0]);
+                    to   = r.length < 2 ? null : new BigDecimal(r[1]);
+                    if (to != null && from.compareTo(to) > 0) {
+                        BigDecimal t = from; from = to; to = t;
+                    }
+                }
+                catch (NumberFormatException nfe) {
+                    log.warn("invalid number in line " + in.getLineNumber());
+                    continue;
+                }
+
+                ImportEdge edge = null;
+
+                if (parts.length == 4) { // Only 'Unterkante'
+                    try {
+                        edge = new ImportEdge(
+                            null,
+                            new BigDecimal(parts[3].trim().replace(',', '.')));
+                    }
+                    catch (NumberFormatException nfe) {
+                        log.warn("cannot parse 'Unterkante' in line " +
+                            in.getLineNumber());
+                    }
+                }
+                else if (parts.length > 4) { // 'Unterkante' and 'Oberkante'
+                    String bottom = parts[3].trim().replace(',', '.');
+                    String top    = parts[4].trim().replace(',', '.');
+                    try {
+                        BigDecimal b = bottom.length() == 0
+                            ? null
+                            : new BigDecimal(bottom);
+                        BigDecimal t = top.length() == 0
+                            ? null
+                            : new BigDecimal(top);
+                        edge = new ImportEdge(t, b);
+                    }
+                    catch (NumberFormatException nfe) {
+                        log.warn(
+                            "cannot parse 'Unterkante' or 'Oberkante' in line "
+                            + in.getLineNumber());
+                    }
+                }
+
+                ImportRange range = new ImportRange(from, to);
+
+                ImportAnnotationType type = classifier != null
+                    ? classifier.classifyDescription(line, defaultIAT)
+                    : null;
+
+                ImportAnnotation annotation = new ImportAnnotation(
+                    attribute, position, range, edge, type);
+
+                if (!annotations.add(annotation)) {
+                    log.warn("duplicated annotation '" + parts[0] +
+                        "' in line " + in.getLineNumber());
+                }
+            }
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    public void parse(File root) throws IOException {
+
+        for (String toScan: TO_SCAN) {
+            File directory = FileTools.repair(new File(root, toScan));
+            if (!directory.isDirectory()) {
+                log.warn("'" + directory + "' is not a directory.");
+                continue;
+            }
+            File [] files = directory.listFiles();
+            if (files == null) {
+                log.warn("cannot list directory '" + directory + "'");
+                continue;
+            }
+
+            for (File file: files) {
+                if (file.isFile() && file.canRead()
+                && file.getName().toLowerCase().endsWith(".km")) {
+                    parseFile(file);
+                }
+            }
+        } // for all directories to scan
+    }
+
+    public List<ImportAnnotation> getAnnotations() {
+        return new ArrayList<ImportAnnotation>(annotations);
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/AtFileParser.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,204 @@
+package de.intevation.flys.importer;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.importer.ImportDischargeTable;
+import de.intevation.flys.importer.ImportDischargeTableValue;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.util.Date;
+import java.util.Calendar;
+
+public class AtFileParser {
+
+    public static final String ENCODING = "ISO-8859-1";
+
+    private static Logger logger = Logger.getLogger(AtFileParser.class);
+
+
+    // regular expression from hell to find out time range
+    public static final Pattern DATE_LINE = Pattern.compile(
+        "^\\*\\s*Abflu[^t]+tafel?\\s*([^\\d]+)"  +
+        "(\\d{1,2})?\\.?(\\d{1,2})?\\.?(\\d{2,4})\\s*(?:(?:bis)|-)?\\s*" +
+        "(?:(\\d{1,2})?\\.?(\\d{1,2})?\\.?(\\d{2,4}))?\\s*.*$");
+
+    public AtFileParser() {
+    }
+
+
+    public ImportDischargeTable parse(File file) throws IOException {
+        return parse(file, "", 0);
+    }
+
+    public ImportDischargeTable parse(
+        File   file,
+        String prefix,
+        int    kind
+    )
+    throws IOException {
+
+        logger.info("parsing AT file: " + file);
+
+        BufferedReader br = null;
+
+        String line       = null;
+
+        boolean beginning = true;
+
+        ImportDischargeTable dischargeTable =
+            new ImportDischargeTable(kind, prefix + file.getName());
+
+        Date from = null;
+        Date to   = null;
+
+        try {
+            br = new BufferedReader(
+                 new InputStreamReader(
+                 new FileInputStream(file), ENCODING));
+
+            while ((line = br.readLine()) != null) {
+
+                String tmp = line.trim();
+
+                if (tmp.length() == 0) {
+                    continue;
+                }
+
+                Matcher m = DATE_LINE.matcher(tmp);
+                if (m.matches()) {
+                    from = guessDate(m.group(2), m.group(3), m.group(4));
+                    to   = guessDate(m.group(5), m.group(6), m.group(7));
+                    if (from == null) {
+                        Date t = from; from = to; to = t;
+                    }
+                    continue;
+                }
+
+                if (tmp.startsWith("#! name=")) {
+                    // XXX Skip the name,  because we don't know where to save
+                    // it at the moment
+
+                    //String name = tmp.substring(8);
+                    continue;
+                }
+
+                if (tmp.startsWith("#") || tmp.startsWith("*")) {
+                    continue;
+                }
+
+                String[] splits = tmp.replace(',', '.').split("\\s+");
+
+                if ((splits.length < 2) || (splits.length > 11)) {
+                    logger.warn("Found an invalid row in the AT file.");
+                    continue;
+                }
+
+                String strW = splits[0].trim();
+                double W    = Double.parseDouble(strW);
+
+                /* shift is used to differenciate between lines with
+                 * exactly 10 Qs and lines with less than 10 Qs. The shift
+                 * is only modified when it is the first line.
+                 */
+                int shift = -1;
+
+                if (splits.length != 11 && beginning) {
+                    shift = 10 - splits.length;
+                }
+
+
+                for (int i = 1; i < splits.length; i++) {
+                    double iW = W + shift + i;
+                    double iQ = Double.parseDouble(splits[i].trim());
+
+                    dischargeTable.addDischargeTableValue(
+                        new ImportDischargeTableValue(
+                            new BigDecimal(iQ/100.0),
+                            new BigDecimal(iW/100.0)));
+                }
+
+                beginning = false;
+            }
+        }
+        catch (NumberFormatException pe) {
+            logger.warn(pe.getMessage());
+        }
+        finally {
+            if (br != null) {
+                br.close();
+            }
+        }
+
+        if (from != null) {
+            if (to != null && from.compareTo(to) > 0) {
+                Date t = from; from = to; to = t;
+            }
+            logger.info("from: " + from + " to: " + to);
+            ImportTimeInterval interval = new ImportTimeInterval(from, to);
+            dischargeTable.setTimeInterval(interval);
+        }
+
+        logger.info("Finished parsing AT file: " + file);
+
+        return dischargeTable;
+    }
+
+    public static Date guessDate(String day, String month, String year) {
+        if (day == null && month == null && year == null) {
+            return null;
+        }
+
+        logger.debug("day: " + day + " month: " + month + " year: " + year);
+
+        int dayI = 15;
+        if (day != null) {
+            try {
+                dayI = Integer.parseInt(day.trim());
+            }
+            catch (NumberFormatException nfe) {
+            }
+        }
+
+        int monthI = 6;
+        if (month != null) {
+            try {
+                monthI = Integer.parseInt(month.trim());
+            }
+            catch (NumberFormatException nfe) {
+            }
+        }
+
+        int yearI = 1900;
+        if (year != null) {
+            try {
+                yearI = Integer.parseInt(year.trim());
+                if (yearI < 100) {
+                    if (yearI < 20) {
+                        yearI += 2000;
+                    }
+                    else {
+                        yearI += 1900;
+                    }
+                }
+            }
+            catch (NumberFormatException nfe) {
+            }
+        }
+
+        Calendar cal = Calendar.getInstance();
+        cal.set(yearI, monthI-1, dayI, 12, 0, 0);
+        long ms = cal.getTimeInMillis();
+        cal.setTimeInMillis(ms - ms%1000);
+        return cal.getTime();
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportAnnotation.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,146 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.AnnotationType;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.Position;
+import de.intevation.flys.model.Attribute;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Edge;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+public class ImportAnnotation
+implements   Comparable<ImportAnnotation>
+{
+    protected ImportAttribute      attribute;
+    protected ImportPosition       position;
+    protected ImportRange          range;
+    protected ImportEdge           edge;
+    protected ImportAnnotationType type;
+
+    protected Annotation      peer;
+
+    public ImportAnnotation() {
+    }
+
+    public ImportAnnotation(
+        ImportAttribute      attribute,
+        ImportPosition       position,
+        ImportRange          range,
+        ImportEdge           edge,
+        ImportAnnotationType type
+    ) {
+        this.attribute = attribute;
+        this.position  = position;
+        this.range     = range;
+        this.edge      = edge;
+        this.type      = type;
+    }
+
+    public int compareTo(ImportAnnotation other) {
+        int d = attribute.compareTo(other.attribute);
+        if (d != 0) {
+            return d;
+        }
+
+        if ((d = position.compareTo(other.position)) != 0) {
+            return d;
+        }
+
+        if ((d = range.compareTo(other.range)) != 0) {
+            return d;
+        }
+
+        if (edge == null && other.edge != null) return -1;
+        if (edge != null && other.edge == null) return +1;
+        if (edge == null && other.edge == null) return 0;
+
+        if ((d = edge.compareTo(other.edge)) != 0) {
+            return d;
+        }
+
+        if (type == null && other.type != null) return -1;
+        if (type != null && other.type == null) return +1;
+        if (type == null && other.type == null) return 0;
+
+        return type.compareTo(other.type);
+    }
+
+    public ImportAttribute getAttribute() {
+        return attribute;
+    }
+
+    public void setAttribute(ImportAttribute attribute) {
+        this.attribute = attribute;
+    }
+
+    public ImportPosition getPosition() {
+        return position;
+    }
+
+    public void setPosition(ImportPosition position) {
+        this.position = position;
+    }
+
+    public ImportRange getRange() {
+        return range;
+    }
+
+    public void setRange(ImportRange range) {
+        this.range = range;
+    }
+
+    public ImportEdge getEdge() {
+        return edge;
+    }
+
+    public void setEdge(ImportEdge edge) {
+        this.edge = edge;
+    }
+
+    public ImportAnnotationType getType() {
+        return type;
+    }
+
+    public void setType(ImportAnnotationType type) {
+        this.type = type;
+    }
+
+    public Annotation getPeer(River river) {
+        if (peer == null) {
+            Range          r = range.getPeer(river);
+            Attribute      a = attribute.getPeer();
+            Position       p = position.getPeer();
+            Edge           e = edge != null ? edge.getPeer() : null;
+            AnnotationType t = type != null ? type.getPeer() : null;
+
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from Annotation where "    +
+                "range=:range and "         +
+                "attribute=:attribute and " +
+                "position=:position and "   +
+                "edge=:edge and "           +
+                "type=:type");
+            query.setParameter("range",     r);
+            query.setParameter("attribute", a);
+            query.setParameter("position",  p);
+            query.setParameter("edge",      e);
+            query.setParameter("type",      t);
+            List<Annotation> annotations = query.list();
+            if (annotations.isEmpty()) {
+                peer = new Annotation(r, a, p, e, t);
+                session.save(peer);
+            }
+            else {
+                peer = annotations.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportAnnotationType.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,54 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.AnnotationType;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+public class ImportAnnotationType
+implements   Comparable<ImportAnnotationType>
+{
+    protected String         name;
+    protected AnnotationType peer;
+
+    public ImportAnnotationType() {
+    }
+
+    public ImportAnnotationType(String name) {
+        this.name = name;
+    }
+
+    public int compareTo(ImportAnnotationType other) {
+        return name.compareTo(other.name);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+    public AnnotationType getPeer() {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from AnnotationType where name=:name");
+            query.setParameter("name", name);
+            List<AnnotationType> types = query.list();
+            if (types.isEmpty()) {
+                peer = new AnnotationType(name);
+                session.save(peer);
+            }
+            else {
+                peer = types.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportAttribute.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,65 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.Attribute;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+public class ImportAttribute
+implements   Comparable<ImportAttribute>
+{
+    protected String value;
+
+    protected Attribute peer;
+
+    public ImportAttribute() {
+    }
+
+    public ImportAttribute(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public int compareTo(ImportAttribute other) {
+        return value.compareTo(other.value);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) return true;
+        if (!(other instanceof ImportAttribute)) return false;
+        return value.equals(((ImportAttribute)other).value);
+    }
+
+    @Override
+    public int hashCode() {
+        return value.hashCode();
+    }
+
+    public Attribute getPeer() {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery("from Attribute where value=:value");
+            query.setString("value", value);
+            List<Attribute> attributes = query.list();
+            if (attributes.isEmpty()) {
+                peer = new Attribute(value);
+                session.save(peer);
+            }
+            else {
+                peer = attributes.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportDischargeTable.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,116 @@
+package de.intevation.flys.importer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.TimeInterval;
+
+import org.apache.log4j.Logger;
+
+public class ImportDischargeTable
+{
+    private static Logger log = Logger.getLogger(ImportDischargeTable.class);
+
+    protected DischargeTable peer;
+
+    protected String         description;
+
+    protected Integer        kind;
+
+    protected List<ImportDischargeTableValue> dischargeTableValues;
+
+    protected ImportTimeInterval timeInterval;
+
+    public ImportDischargeTable() {
+        this(0, null);
+    }
+
+    public ImportDischargeTable(int kind, String description) {
+        this.kind            = kind;
+        this.description     = description;
+        dischargeTableValues = new ArrayList<ImportDischargeTableValue>();
+    }
+
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+
+    public void addDischargeTableValue(ImportDischargeTableValue value) {
+        dischargeTableValues.add(value);
+    }
+
+
+    public void setDischargeTableValues(List<ImportDischargeTableValue> values){
+        this.dischargeTableValues = values;
+    }
+
+
+    public List<ImportDischargeTableValue> getDischargeTableValues() {
+        return dischargeTableValues;
+    }
+
+    public ImportTimeInterval getTimeInterval() {
+        return timeInterval;
+    }
+
+    public void setTimeInterval(ImportTimeInterval timeInterval) {
+        this.timeInterval = timeInterval;
+    }
+
+
+    public DischargeTable getPeer(Gauge gauge) {
+        if (peer == null) {
+            TimeInterval ti = timeInterval != null
+                ? timeInterval.getPeer()
+                : null;
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+
+            Query query = session.createQuery(
+                "from DischargeTable where " +
+                "gauge.id=:gauge and kind=:kind and " +
+                "description=:description and timeInterval=:interval");
+            query.setParameter("gauge",       gauge.getId());
+            query.setParameter("description", description);
+            query.setParameter("kind",        kind);
+            query.setParameter("interval",    ti);
+
+            List<DischargeTable> dischargeTables = query.list();
+            if (dischargeTables.isEmpty()) {
+                peer = new DischargeTable(gauge, description, kind, ti);
+                session.save(peer);
+            }
+            else {
+                peer = dischargeTables.get(0);
+            }
+        }
+
+        return peer;
+    }
+
+
+    public void storeDependencies(Gauge gauge) {
+        log.info("store discharge table '" + description + "'");
+        storeDischargeTableValues(gauge);
+    }
+
+
+    public void storeDischargeTableValues(Gauge gauge) {
+        DischargeTable dischargeTable = getPeer(gauge);
+
+        for (ImportDischargeTableValue value: dischargeTableValues) {
+            value.getPeer(dischargeTable);
+        }
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportDischargeTableValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,34 @@
+package de.intevation.flys.importer;
+
+import java.math.BigDecimal;
+
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.DischargeTableValue;
+
+
+public class ImportDischargeTableValue
+{
+    private BigDecimal q;
+    private BigDecimal w;
+
+    private DischargeTableValue peer;
+
+    public ImportDischargeTableValue() {
+    }
+
+
+    public ImportDischargeTableValue(BigDecimal q, BigDecimal w) {
+        this.q = q;
+        this.w = w;
+    }
+
+
+    public DischargeTableValue getPeer(DischargeTable dischargeTable) {
+        if (peer == null) {
+            peer = ImporterSession.getInstance()
+                .getDischargeTableValue(dischargeTable, q, w);
+        }
+
+        return peer;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/src/main/java/de/intevation/flys/importer/ImportEdge.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,75 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.Edge;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+import java.math.BigDecimal;
+
+public class ImportEdge
+implements   Comparable<ImportEdge>
+{
+    protected BigDecimal top;
+    protected BigDecimal bottom;
+
+    protected Edge peer;
+
+    public ImportEdge() {
+    }
+
+    public ImportEdge(BigDecimal top, BigDecimal bottom) {
+        this.top    = top;
+        this.bottom = bottom;
+    }
+
+    public BigDecimal getTop() {
+        return top;
+    }
+
+    public void setTop(BigDecimal top) {
+        this.top = top;
+    }
+
+    public BigDecimal getBottom() {
+        return bottom;
+    }
+
+    public void setBottom(BigDecimal bottom) {
+        this.bottom = bottom;
+    }
+
+    private static final int compare(BigDecimal a, BigDecimal b) {
+        if (a == null && b != null) return -1;
+        if (a != null && b == null) return +1;
+        if (a == null && b == null) return  0;
+        return a.compareTo(b);
+    }
+
+    public int compareTo(ImportEdge other) {
+        int cmp = compare(top, other.top);
+        return cmp != 0 ? cmp : compare(bottom, other.bottom);
+    }
+
+    public Edge getPeer() {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from Edge where top=:top and bottom=:bottom");
+            query.setParameter("top", top);
+            query.setParameter("bottom", bottom);
+            List<Edge> edges = query.list();
+            if (edges.isEmpty()) {
+                peer = new Edge(top, bottom);
+                session.save(peer);
+            }
+            else {
+                peer = edges.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportGauge.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,255 @@
+package de.intevation.flys.importer;
+
+import java.io.File;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import java.math.BigDecimal;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Gauge;
+
+import de.intevation.flys.utils.FileTools;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+public class ImportGauge
+{
+    private static Logger log = Logger.getLogger(ImportGauge.class);
+
+    public static final String HISTORICAL_DISCHARGE_TABLES =
+        "Histor.Abflusstafeln";
+
+    protected ImportRange range;
+
+    protected File        staFile;
+    protected File        atFile;
+
+    protected String      name;
+    protected BigDecimal  aeo;
+    protected BigDecimal  datum;
+    protected BigDecimal  station;
+
+    protected Gauge  peer;
+
+    protected ImportDischargeTable dischargeTable;
+
+    protected List<ImportMainValueType>  mainValueTypes;
+    protected List<ImportNamedMainValue> namedMainValues;
+    protected List<ImportMainValue>      mainValues;
+    protected List<ImportDischargeTable> historicalDischargeTables;
+
+    public ImportGauge() {
+        historicalDischargeTables = new ArrayList<ImportDischargeTable>();
+    }
+
+    public ImportGauge(ImportRange range, File staFile, File atFile) {
+        this();
+        this.range   = range;
+        this.staFile = staFile;
+        this.atFile  = atFile;
+    }
+
+    public void setRange(ImportRange range) {
+        this.range = range;
+    }
+
+    public void setStaFile(File staFile) {
+        this.staFile = staFile;
+    }
+
+    public File getStaFile() {
+        return staFile;
+    }
+
+    public void setAtFile(File atFile) {
+        this.atFile = atFile;
+    }
+
+    public File getAtFile() {
+        return atFile;
+    }
+
+    public BigDecimal getAeo() {
+        return aeo;
+    }
+
+    public void setAeo(BigDecimal aeo) {
+        this.aeo = aeo;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public BigDecimal getDatum() {
+        return datum;
+    }
+
+    public void setDatum(BigDecimal datum) {
+        this.datum = datum;
+    }
+
+    public BigDecimal getStation() {
+        return station;
+    }
+
+    public void setStation(BigDecimal station) {
+        this.station = station;
+    }
+
+    public ImportDischargeTable getDischargeTable() {
+        return dischargeTable;
+    }
+
+    public void setDischargeTable(ImportDischargeTable dischargeTable) {
+        this.dischargeTable = dischargeTable;
+    }
+
+    public List<ImportMainValueType> getMainValueTypes() {
+        return mainValueTypes;
+    }
+
+    public void setMainValueTypes(List<ImportMainValueType> mainValueTypes) {
+        this.mainValueTypes = mainValueTypes;
+    }
+
+    public List<ImportNamedMainValue> getNamedMainValues() {
+        return namedMainValues;
+    }
+
+    public void setNamedMainValues(List<ImportNamedMainValue> namedMainValues) {
+        this.namedMainValues = namedMainValues;
+    }
+
+    public List<ImportMainValue> getMainValues() {
+        return mainValues;
+    }
+
+    public void setMainValues(List<ImportMainValue> mainValues) {
+        this.mainValues = mainValues;
+    }
+
+    public void parseDependencies() throws IOException {
+        StaFileParser sfp = new StaFileParser();
+        sfp.parse(this);
+
+        AtFileParser afp = new AtFileParser();
+        setDischargeTable(afp.parse(getAtFile()));
+        parseHistoricalDischargeTables();
+    }
+
+    public void parseHistoricalDischargeTables() throws IOException {
+        log.info("parse historical discharge tables");
+
+        File riverDir = atFile.getParentFile().getParentFile();
+
+        File histDischargeDir = FileTools.repair(
+            new File(riverDir, HISTORICAL_DISCHARGE_TABLES));
+
+        if (!histDischargeDir.isDirectory() || !histDischargeDir.canRead()) {
+            log.info("cannot find '" + histDischargeDir + "'");
+            return;
+        }
+
+        histDischargeDir = FileTools.repair(
+            new File(histDischargeDir, getName()));
+
+        if (!histDischargeDir.isDirectory() || !histDischargeDir.canRead()) {
+            log.info("cannot find '" + histDischargeDir + "'");
+            return;
+        }
+
+        File [] files = histDischargeDir.listFiles();
+
+        if (files == null) {
+            log.info("cannot read directory '" + histDischargeDir + "'");
+            return;
+        }
+
+        for (File file: files) {
+            if (!file.isFile() || !file.canRead()) {
+                continue;
+            }
+            String name = file.getName().toLowerCase();
+            if (!name.endsWith(".at")) {
+                continue;
+            }
+            log.info("found at file '" + file.getName() + "'");
+
+            AtFileParser afp = new AtFileParser();
+            historicalDischargeTables.add(
+                afp.parse(file, HISTORICAL_DISCHARGE_TABLES + "/", 1));
+        }
+    }
+
+    public void storeDependencies(River river) {
+
+        Gauge gauge = getPeer(river);
+
+        log.info("store main value types");
+        for (ImportMainValueType mainValueType: mainValueTypes) {
+            mainValueType.getPeer();
+        }
+
+        log.info("store named main values");
+        for (ImportNamedMainValue namedMainValue: namedMainValues) {
+            namedMainValue.getPeer();
+        }
+
+        log.info("store main values");
+        for (ImportMainValue mainValue: mainValues) {
+            mainValue.getPeer(river);
+        }
+
+        storeDischargeTable(gauge);
+        storeHistoricalDischargeTable(gauge);
+    }
+
+    public void storeDischargeTable(Gauge gauge) {
+        log.info("store discharge table");
+        dischargeTable.getPeer(gauge);
+        dischargeTable.storeDependencies(gauge);
+    }
+
+    public void storeHistoricalDischargeTable(Gauge gauge) {
+        log.info("store historical discharge tables");
+        for (ImportDischargeTable hdt: historicalDischargeTables) {
+            hdt.storeDependencies(gauge);
+        }
+    }
+
+    public Gauge getPeer(River river) {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from Gauge where name=:name " +
+                "and river.id=:river");
+            query.setString("name", name);
+            query.setParameter("river", river.getId());
+            List<Gauge> gauges = query.list();
+            if (gauges.isEmpty()) {
+                peer = new Gauge(
+                    name, river,
+                    station, aeo, datum,
+                    range.getPeer(river));
+                session.save(peer);
+            }
+            else {
+                peer = gauges.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportMainValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,83 @@
+package de.intevation.flys.importer;
+
+import java.util.List;
+
+import java.math.BigDecimal;
+
+import de.intevation.flys.model.MainValue;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.NamedMainValue;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+public class ImportMainValue
+{
+    protected ImportGauge          gauge;
+    protected ImportNamedMainValue mainValue;
+    protected BigDecimal           value;
+
+    protected MainValue peer;
+
+    public ImportMainValue() {
+    }
+
+    public ImportMainValue(
+        ImportGauge          gauge,
+        ImportNamedMainValue mainValue,
+        BigDecimal           value
+    ) {
+        this.gauge     = gauge;
+        this.mainValue = mainValue;
+        this.value     = value;
+    }
+
+    public ImportGauge getGauge() {
+        return gauge;
+    }
+
+    public void setGauge(ImportGauge gauge) {
+        this.gauge = gauge;
+    }
+
+    public ImportNamedMainValue getMainValue() {
+        return mainValue;
+    }
+
+    public void setMainValue(ImportNamedMainValue mainValue) {
+        this.mainValue = mainValue;
+    }
+
+    public BigDecimal getValue() {
+        return value;
+    }
+
+    public void setValue(BigDecimal value) {
+        this.value = value;
+    }
+
+    public MainValue getPeer(River river) {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery("from MainValue where "
+                + "gauge.id=:gauge_id and mainValue.id=:name_id "
+                + "and value=:value");
+            Gauge          g = gauge.getPeer(river);
+            NamedMainValue n = mainValue.getPeer();
+            query.setParameter("gauge_id", g.getId());
+            query.setParameter("name_id",  n.getId());
+            query.setParameter("value",    value);
+            List<MainValue> values = query.list();
+            if (values.isEmpty()) {
+                peer = new MainValue(g, n, value, null);
+                session.save(peer);
+            }
+            else {
+                peer = values.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportMainValueType.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,65 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.MainValueType;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+public class ImportMainValueType
+implements   Comparable<ImportMainValueType>
+{
+    protected String name;
+
+    protected MainValueType peer;
+
+    public ImportMainValueType() {
+    }
+
+    public ImportMainValueType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int compareTo(ImportMainValueType other) {
+        return name.compareTo(other.name);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) return true;
+        if (!(other instanceof ImportMainValueType)) return false;
+        return name.equals(((ImportMainValueType)other).name);
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    public MainValueType getPeer() {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery("from MainValueType where name=:name");
+            query.setString("name", name);
+            List<MainValueType> values = query.list();
+            if (values.isEmpty()) {
+                peer = new MainValueType(name);
+                session.save(peer);
+            }
+            else {
+                peer = values.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportNamedMainValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,66 @@
+package de.intevation.flys.importer;
+
+import java.util.List;
+
+import de.intevation.flys.model.NamedMainValue;
+import de.intevation.flys.model.MainValueType;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+public class ImportNamedMainValue
+{
+    protected ImportMainValueType mainValueType;
+    protected String              name;
+
+    protected NamedMainValue      peer;
+
+    public ImportNamedMainValue() {
+    }
+
+    public ImportNamedMainValue(
+        ImportMainValueType mainValueType,
+        String              name
+    ) {
+        this.mainValueType = mainValueType;
+        this.name          = name;
+    }
+
+    public ImportMainValueType getMainValueType() {
+        return mainValueType;
+    }
+
+    public void setMainValueType(ImportMainValueType mainValueType) {
+        this.mainValueType = mainValueType;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public NamedMainValue getPeer() {
+        if (peer == null) {
+            MainValueType type = mainValueType.getPeer();
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from NamedMainValue where " +
+                "name=:name and type.id=:id");
+            query.setString("name", name);
+            query.setParameter("id", type.getId());
+            List<NamedMainValue> named = query.list();
+            if (named.isEmpty()) {
+                peer = new NamedMainValue(name, type);
+                session.save(peer);
+            }
+            else {
+                peer = named.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportPosition.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,54 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.Position;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+public class ImportPosition
+implements   Comparable<ImportPosition>
+{
+    protected String value;
+
+    protected Position peer;
+
+    public ImportPosition() {
+    }
+
+    public ImportPosition(String value) {
+        this.value = value;
+    }
+
+    public int compareTo(ImportPosition other) {
+        return value.compareTo(other.value);
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public Position getPeer() {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery("from Position where value=:value");
+            query.setString("value", value);
+            List<Position> positions = query.list();
+            if (positions.isEmpty()) {
+                peer = new Position(value);
+                session.save(peer);
+            }
+            else {
+                peer = positions.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportRange.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,70 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+public class ImportRange
+implements   Comparable<ImportRange>
+{
+    private static Logger log = Logger.getLogger(ImportRange.class);
+
+    protected BigDecimal a;
+    protected BigDecimal b;
+
+    protected Range peer;
+
+    public ImportRange() {
+    }
+
+    public ImportRange(BigDecimal a, BigDecimal b) {
+        this.a = a;
+        this.b = b;
+    }
+
+    private static final int compare(BigDecimal a, BigDecimal b) {
+        if (a == null && b == null) {
+            return 0;
+        }
+        if (a == null && b != null) {
+            return -1;
+        }
+        if (a != null && b == null) {
+            return +1;
+        }
+        return a.compareTo(b);
+    }
+
+    public int compareTo(ImportRange other) {
+        int cmp = compare(a, other.a);
+        if (cmp != 0) return cmp;
+        return compare(b, other.b);
+    }
+
+    public BigDecimal getA() {
+        return a;
+    }
+
+    public void setA(BigDecimal a) {
+        this.a = a;
+    }
+
+    public BigDecimal getB() {
+        return b;
+    }
+
+    public void setB(BigDecimal b) {
+        this.b = b;
+    }
+
+    public Range getPeer(River river) {
+        if (peer == null) {
+            peer = ImporterSession.getInstance().getRange(river, a, b);
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportRiver.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,445 @@
+package de.intevation.flys.importer;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.utils.FileTools;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+public class ImportRiver
+{
+    private static Logger log = Logger.getLogger(ImportRiver.class);
+
+    public static final String PEGEL_GLT = "PEGEL.GLT";
+
+    public static final String FIXATIONS = "Fixierungen";
+
+    public static final String EXTRA_LONGITUDINALS =
+        "Zus.L\u00e4ngsschnitte";
+
+    public static final String [] OFFICIAL_LINES_FOLDERS = {
+        "Basisdaten",
+        "Fixierungen" };
+
+    public static final String OFFICIAL_LINES =
+        "Amtl_Linien.wst";
+
+    public static final String FLOOD_WATER = "HW-Marken";
+
+    public static final String FLOOD_PROTECTION =
+        "HW-Schutzanlagen";
+
+    protected String name;
+
+    protected File   wstFile;
+
+    protected File   bbInfoFile;
+
+    protected List<ImportGauge> gauges;
+
+    protected List<ImportAnnotation> annotations;
+
+    protected List<ImportWst> extraWsts;
+
+    protected List<ImportWst> fixations;
+
+    protected List<ImportWst> officialLines;
+
+    protected List<ImportWst> floodWater;
+
+    protected List<ImportWst> floodProtection;
+
+    protected ImportWst wst;
+
+    protected AnnotationClassifier annotationClassifier;
+
+    protected River peer;
+
+    public ImportRiver() {
+        extraWsts       = new ArrayList<ImportWst>();
+        fixations       = new ArrayList<ImportWst>();
+        officialLines   = new ArrayList<ImportWst>();
+        floodWater      = new ArrayList<ImportWst>();
+        floodProtection = new ArrayList<ImportWst>();
+    }
+
+    public ImportRiver(
+        String               name,
+        File                 wstFile,
+        File                 bbInfoFile,
+        AnnotationClassifier annotationClassifier
+    ) {
+        this();
+        this.name                 = name;
+        this.wstFile              = wstFile;
+        this.bbInfoFile           = bbInfoFile;
+        this.annotationClassifier = annotationClassifier;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public File getWstFile() {
+        return wstFile;
+    }
+
+    public void setWstFile(File wstFile) {
+        this.wstFile = wstFile;
+    }
+
+    public File getBBInfo() {
+        return bbInfoFile;
+    }
+
+    public void setBBInfo(File bbInfoFile) {
+        this.bbInfoFile = bbInfoFile;
+    }
+
+    public ImportWst getWst() {
+        return wst;
+    }
+
+    public void setWst(ImportWst wst) {
+        this.wst = wst;
+    }
+
+    public void parseDependencies() throws IOException {
+        parseGauges();
+        parseAnnotations();
+        parseWst();
+        parseExtraWsts();
+        parseFixations();
+        parseOfficialLines();
+        parseFloodWater();
+        parseFloodProtection();
+    }
+
+    public void parseFloodProtection() throws IOException {
+        log.info("Parse flood protection wst file");
+
+        File riverDir = wstFile.getParentFile().getParentFile();
+
+        File dir = FileTools.repair(new File(riverDir, FLOOD_PROTECTION));
+
+        if (!dir.isDirectory() || !dir.canRead()) {
+            log.info("no directory '" + dir + "' found");
+            return;
+        }
+
+        File [] files = dir.listFiles();
+
+        if (files == null) {
+            log.warn("cannot read '" + dir + "'");
+            return;
+        }
+
+        for (File file: files) {
+            if (!file.isFile() || !file.canRead()) {
+                continue;
+            }
+            String name = file.getName().toLowerCase();
+            if (!(name.endsWith(".zus") || name.endsWith(".wst"))) {
+                continue;
+            }
+            log.info("found file '" + file.getName() + "'");
+            WstParser wstParser = new WstParser();
+            wstParser.parse(file);
+            ImportWst iw = wstParser.getWst();
+            iw.setKind(5);
+            iw.setDescription(FLOOD_PROTECTION + "/" + iw.getDescription());
+            floodProtection.add(iw);
+        }
+    }
+
+    public void parseFloodWater() throws IOException {
+        log.info("Parse flood water wst file");
+
+        File riverDir = wstFile.getParentFile().getParentFile();
+
+        File dir = FileTools.repair(new File(riverDir, FLOOD_WATER));
+
+        if (!dir.isDirectory() || !dir.canRead()) {
+            log.info("no directory '" + dir + "' found");
+            return;
+        }
+
+        File [] files = dir.listFiles();
+
+        if (files == null) {
+            log.warn("cannot read '" + dir + "'");
+            return;
+        }
+
+        for (File file: files) {
+            if (!file.isFile() || !file.canRead()) {
+                continue;
+            }
+            String name = file.getName().toLowerCase();
+            if (!(name.endsWith(".zus") || name.endsWith(".wst"))) {
+                continue;
+            }
+            log.info("found file '" + file.getName() + "'");
+            WstParser wstParser = new WstParser();
+            wstParser.parse(file);
+            ImportWst iw = wstParser.getWst();
+            iw.setKind(4);
+            iw.setDescription(FLOOD_WATER + "/" + iw.getDescription());
+            floodWater.add(iw);
+        }
+    }
+
+    public void parseOfficialLines() throws IOException {
+        log.info("Parse official wst files");
+
+        File riverDir = wstFile.getParentFile().getParentFile();
+
+        for (String folder: OFFICIAL_LINES_FOLDERS) {
+            File dir = FileTools.repair(new File(riverDir, folder));
+
+            if (!dir.isDirectory() || !dir.canRead()) {
+                log.info("no directory '" + folder + "' found");
+                continue;
+            }
+
+            File file = FileTools.repair(new File(dir, OFFICIAL_LINES));
+            if (!file.isFile() || !file.canRead()) {
+                log.warn("no official lines wst file found");
+                continue;
+            }
+            log.debug("Found WST file: " + file);
+
+            WstParser wstParser = new WstParser();
+            wstParser.parse(file);
+            ImportWst iw = wstParser.getWst();
+            iw.setKind(3);
+            iw.setDescription(folder + "/" + iw.getDescription());
+            officialLines.add(iw);
+        } // for all folders
+
+    }
+
+    public void parseFixations() throws IOException {
+        log.info("Parse fixation wst files");
+
+        File riverDir = wstFile.getParentFile().getParentFile();
+
+        File fixDir = FileTools.repair(
+            new File(riverDir, FIXATIONS));
+
+        if (!fixDir.isDirectory() || !fixDir.canRead()) {
+            log.info("no fixation wst file directory found");
+            return;
+        }
+
+        File [] files = fixDir.listFiles();
+
+        if (files == null) {
+            log.warn("cannot read fixations wst file directory");
+            return;
+        }
+
+        for (File file: files) {
+            if (!file.isFile() || !file.canRead()) {
+                continue;
+            }
+            String name = file.getName().toLowerCase();
+            if (!name.endsWith(".wst")) {
+                continue;
+            }
+            log.debug("Found WST file: " + file);
+
+            WstParser wstParser = new WstParser();
+            wstParser.parse(file);
+            ImportWst iw = wstParser.getWst();
+            iw.setKind(2);
+            iw.setDescription(FIXATIONS+ "/" + iw.getDescription());
+            fixations.add(iw);
+        }
+    }
+
+    public void parseExtraWsts() throws IOException {
+        log.info("Parse extra longitudinal wst files");
+
+        File riverDir = wstFile.getParentFile().getParentFile();
+
+        File extraDir = FileTools.repair(
+            new File(riverDir, EXTRA_LONGITUDINALS));
+
+        if (!extraDir.isDirectory() || !extraDir.canRead()) {
+            log.info("no extra longitudinal wst file directory found");
+            return;
+        }
+
+        File [] files = extraDir.listFiles();
+
+        if (files == null) {
+            log.warn("cannot read extra longitudinal wst file directory");
+            return;
+        }
+
+        for (File file: files) {
+            if (!file.isFile() || !file.canRead()) {
+                continue;
+            }
+            String name = file.getName().toLowerCase();
+            if (!(name.endsWith(".zus") || name.endsWith(".wst"))) {
+                continue;
+            }
+            log.debug("Found WST file: " + file);
+
+            WstParser wstParser = new WstParser();
+            wstParser.parse(file);
+            ImportWst iw = wstParser.getWst();
+            iw.setKind(1);
+            iw.setDescription(EXTRA_LONGITUDINALS + "/" + iw.getDescription());
+            extraWsts.add(iw);
+        }
+
+    }
+
+    public void parseWst() throws IOException {
+        WstParser wstParser = new WstParser();
+        wstParser.parse(wstFile);
+        wst = wstParser.getWst();
+    }
+
+    public void parseGauges() throws IOException {
+        File gltFile = new File(wstFile.getParentFile(), PEGEL_GLT);
+        gltFile = FileTools.repair(gltFile);
+
+        if (!gltFile.isFile() || !gltFile.canRead()) {
+            log.warn("cannot read gauges from '" + gltFile + "'");
+            return;
+        }
+
+        PegelGltParser pgltp = new PegelGltParser();
+        pgltp.parse(gltFile);
+
+        gauges = pgltp.getGauges();
+
+        for (ImportGauge gauge: gauges) {
+            gauge.parseDependencies();
+        }
+    }
+
+    public void parseAnnotations() throws IOException {
+        File riverDir = wstFile.getParentFile().getParentFile();
+        AnnotationsParser aparser =
+            new AnnotationsParser(annotationClassifier);
+        aparser.parse(riverDir);
+
+        annotations = aparser.getAnnotations();
+    }
+
+    public void storeDependencies() {
+        storeAnnotations();
+        storeGauges();
+        storeWst();
+        storeExtraWsts();
+        storeFixations();
+        storeOfficialLines();
+        storeFloodWater();
+        storeFloodProtection();
+    }
+
+    public void storeWst() {
+        River river = getPeer();
+        wst.storeDependencies(river);
+    }
+
+    public void storeFixations() {
+        log.info("store fixation wsts");
+        River river = getPeer();
+        for (ImportWst wst: fixations) {
+            log.debug("name: " + wst.getDescription());
+            wst.storeDependencies(river);
+        }
+    }
+
+    public void storeExtraWsts() {
+        log.info("store extra wsts");
+        River river = getPeer();
+        for (ImportWst wst: extraWsts) {
+            log.debug("name: " + wst.getDescription());
+            wst.storeDependencies(river);
+        }
+    }
+
+    public void storeOfficialLines() {
+        log.info("store official lines wsts");
+        River river = getPeer();
+        for (ImportWst wst: officialLines) {
+            log.debug("name: " + wst.getDescription());
+            wst.storeDependencies(river);
+        }
+    }
+
+    public void storeFloodWater() {
+        log.info("store flood water wsts");
+        River river = getPeer();
+        for (ImportWst wst: floodWater) {
+            log.debug("name: " + wst.getDescription());
+            wst.storeDependencies(river);
+        }
+    }
+
+    public void storeFloodProtection() {
+        log.info("store flood protection wsts");
+        River river = getPeer();
+        for (ImportWst wst: floodProtection) {
+            log.debug("name: " + wst.getDescription());
+            wst.storeDependencies(river);
+        }
+    }
+
+    public void storeAnnotations() {
+        River river = getPeer();
+        for (ImportAnnotation annotation: annotations) {
+            annotation.getPeer(river);
+        }
+    }
+
+    public void storeGauges() {
+        log.info("store gauges:");
+        River river = getPeer();
+        Session session = ImporterSession.getInstance().getDatabaseSession();
+        for (ImportGauge gauge: gauges) {
+            log.info("\tgauge: " + gauge.getName());
+            gauge.storeDependencies(river);
+            ImporterSession.getInstance().getDatabaseSession();
+            session.flush();
+        }
+    }
+
+    public River getPeer() {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery("from River where name=:name");
+            query.setString("name", name);
+            List<River> rivers = query.list();
+            if (rivers.isEmpty()) {
+                peer = new River(name);
+                session.save(peer);
+            }
+            else {
+                peer = rivers.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportTimeInterval.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,65 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.TimeInterval;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+import java.util.Date;
+
+import org.apache.log4j.Logger;
+
+public class ImportTimeInterval
+{
+    private static Logger log = Logger.getLogger(ImportTimeInterval.class);
+
+    protected Date startTime;
+    protected Date stopTime;
+
+    protected TimeInterval peer;
+
+    public ImportTimeInterval() {
+    }
+
+    public ImportTimeInterval(Date startTime, Date stopTime) {
+        this.startTime = startTime;
+        this.stopTime  = stopTime;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getStopTime() {
+        return stopTime;
+    }
+
+    public void setStopTime(Date stopTime) {
+        this.stopTime = stopTime;
+    }
+
+    public TimeInterval getPeer() {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from TimeInterval where startTime=:a and stopTime=:b");
+            query.setParameter("a", startTime);
+            query.setParameter("b",  stopTime);
+            List<TimeInterval> intervals = query.list();
+            if (intervals.isEmpty()) {
+                peer = new TimeInterval(startTime, stopTime);
+                session.save(peer);
+            }
+            else {
+                peer = intervals.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportWst.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,101 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.Wst;
+import de.intevation.flys.model.River;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ImportWst
+{
+    private static Logger log = Logger.getLogger(ImportWst.class);
+
+    protected String description;
+
+    protected Integer kind;
+
+    protected List<ImportWstColumn> columns;
+
+    protected Wst peer;
+
+    public ImportWst() {
+        kind = 0;
+        columns = new ArrayList<ImportWstColumn>();
+    }
+
+    public ImportWst(String description) {
+        this();
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Integer getKind() {
+        return kind;
+    }
+
+    public void setKind(Integer kind) {
+        this.kind = kind;
+    }
+
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void setNumberColumns(int numColumns) {
+        for (int i = 0; i < numColumns; ++i) {
+            columns.add(new ImportWstColumn(this, null, null, i));
+        }
+    }
+
+    public int getNumberColumns() {
+        return columns.size();
+    }
+
+    public ImportWstColumn getColumn(int index) {
+        return columns.get(index);
+    }
+
+    public void storeDependencies(River river) {
+
+        log.info("store '" + description + "'");
+        Wst wst = getPeer(river);
+
+        for (ImportWstColumn column: columns) {
+            column.storeDependencies(river);
+        }
+        Session session = ImporterSession.getInstance().getDatabaseSession();
+        session.flush();
+    }
+
+    public Wst getPeer(River river) {
+        if (peer == null) {
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from Wst where " +
+                "river=:river and description=:description and kind=:kind");
+            query.setParameter("river",       river);
+            query.setParameter("description", description);
+            query.setParameter("kind",        kind);
+            List<Wst> wsts = query.list();
+            if (wsts.isEmpty()) {
+                peer = new Wst(river, description, kind);
+                session.save(peer);
+            }
+            else {
+                peer = wsts.get(0);
+            }
+
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportWstColumn.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,128 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.Wst;
+import de.intevation.flys.model.WstColumn;
+import de.intevation.flys.model.River;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+public class ImportWstColumn
+{
+    private static Logger log = Logger.getLogger(ImportWstColumn.class);
+
+    protected ImportWst wst;
+    protected String    name;
+    protected String    description;
+    protected Integer   position;
+
+    protected List<ImportWstColumnQRange> columnQRanges;
+    protected List<ImportWstColumnValue>  columnValues;
+
+    protected WstColumn peer;
+
+    public ImportWstColumn() {
+        columnQRanges = new ArrayList<ImportWstColumnQRange>();
+        columnValues  = new ArrayList<ImportWstColumnValue>();
+    }
+
+    public ImportWstColumn(
+        ImportWst wst,
+        String    name,
+        String    description,
+        Integer   position
+    ) {
+        this();
+        this.wst         = wst;
+        this.name        = name;
+        this.description = description;
+        this.position    = position;
+    }
+
+    public ImportWst getWst() {
+        return wst;
+    }
+
+    public void setWst(ImportWst wst) {
+        this.wst = wst;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Integer getPosition() {
+        return position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
+    public void addColumnValue(BigDecimal position, BigDecimal w) {
+        columnValues.add(
+            new ImportWstColumnValue(this, position, w));
+    }
+
+    public void addColumnQRange(ImportWstQRange columnQRange) {
+        columnQRanges.add(
+            new ImportWstColumnQRange(this, columnQRange));
+    }
+
+    public void storeDependencies(River river) {
+        log.info("store column '" + name + "'");
+        WstColumn column = getPeer(river);
+
+        for (ImportWstColumnQRange columnQRange: columnQRanges) {
+            columnQRange.getPeer(river);
+        }
+
+        for (ImportWstColumnValue columnValue: columnValues) {
+            columnValue.getPeer(river);
+        }
+    }
+
+    public WstColumn getPeer(River river) {
+        if (peer == null) {
+            Wst w = wst.getPeer(river);
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from WstColumn where " +
+                "wst=:wst and name=:name and description=:description" +
+                " and position=:position");
+            query.setParameter("wst",         w);
+            query.setParameter("name",        name);
+            query.setParameter("description", description);
+            query.setParameter("position",    position);
+            List<WstColumn> columns = query.list();
+            if (columns.isEmpty()) {
+                peer = new WstColumn(w, name, description, position, null);
+                session.save(peer);
+            }
+            else {
+                peer = columns.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportWstColumnQRange.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,69 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.WstColumnQRange;
+import de.intevation.flys.model.WstQRange;
+import de.intevation.flys.model.WstColumn;
+import de.intevation.flys.model.River;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+public class ImportWstColumnQRange
+{
+    protected ImportWstColumn wstColumn;
+    protected ImportWstQRange qRange;
+
+    protected WstColumnQRange peer;
+
+    public ImportWstColumnQRange() {
+    }
+
+    public ImportWstColumnQRange(
+        ImportWstColumn wstColumn,
+        ImportWstQRange qRange
+    ) {
+        this.wstColumn = wstColumn;
+        this.qRange    = qRange;
+    }
+
+    public ImportWstColumn getWstColumn() {
+        return wstColumn;
+    }
+
+    public void setWstColumn(ImportWstColumn wstColumn) {
+        this.wstColumn = wstColumn;
+    }
+
+    public ImportWstQRange getQRange() {
+        return qRange;
+    }
+
+    public void setQRange(ImportWstQRange qRange) {
+        this.qRange = qRange;
+    }
+
+    public WstColumnQRange getPeer(River river) {
+        if (peer == null) {
+            WstColumn c = wstColumn.getPeer(river);
+            WstQRange q = qRange.getPeer(river);
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from WstColumnQRange where " +
+                "wstColumn=:c and wstQRange=:q");
+            query.setParameter("c", c);
+            query.setParameter("q", q);
+            List<WstColumnQRange> cols = query.list();
+            if (cols.isEmpty()) {
+                peer = new WstColumnQRange(c, q);
+                session.save(peer);
+            }
+            else {
+                peer = cols.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportWstColumnValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,68 @@
+package de.intevation.flys.importer;
+
+import de.intevation.flys.model.WstColumnValue;
+import de.intevation.flys.model.WstColumn;
+import de.intevation.flys.model.River;
+
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+public class ImportWstColumnValue
+{
+    protected Logger logger = Logger.getLogger(ImportWstColumnValue.class);
+
+    protected BigDecimal      position;
+    protected BigDecimal      w;
+    protected ImportWstColumn wstColumn;
+
+    protected WstColumnValue  peer;
+
+    public ImportWstColumnValue() {
+    }
+
+    public ImportWstColumnValue(
+        ImportWstColumn wstColumn,
+        BigDecimal      position,
+        BigDecimal      w
+    ) {
+        this.wstColumn = wstColumn;
+        this.position  = position;
+        this.w         = w;
+    }
+
+    public BigDecimal getPosition() {
+        return position;
+    }
+
+    public void setPosition(BigDecimal position) {
+        this.position = position;
+    }
+
+    public BigDecimal getW() {
+        return w;
+    }
+
+    public void setW(BigDecimal w) {
+        this.w = w;
+    }
+
+    public ImportWstColumn getWstColumn() {
+        return wstColumn;
+    }
+
+    public void setWstColumn(ImportWstColumn wstColumn) {
+        this.wstColumn = wstColumn;
+    }
+
+    public WstColumnValue getPeer(River river) {
+        if (peer == null) {
+            WstColumn c = wstColumn.getPeer(river);
+            peer = ImporterSession.getInstance().getWstColumnValue(
+                c, position, w);
+        }
+
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImportWstQRange.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,69 @@
+package de.intevation.flys.importer;
+
+import java.math.BigDecimal;
+
+import de.intevation.flys.model.WstQRange;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Range;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import java.util.List;
+
+public class ImportWstQRange
+{
+    protected ImportRange range;
+    protected BigDecimal  q;
+
+    protected WstQRange peer;
+
+    public ImportWstQRange() {
+    }
+
+    public ImportWstQRange(
+        ImportRange range,
+        BigDecimal  q
+    ) {
+        this.range = range;
+        this.q     = q;
+    }
+
+    public ImportRange getRange() {
+        return range;
+    }
+
+    public void setRange(ImportRange range) {
+        this.range = range;
+    }
+
+    public BigDecimal getQ() {
+        return q;
+    }
+
+    public void setQ(BigDecimal q) {
+        this.q = q;
+    }
+
+    public WstQRange getPeer(River river) {
+        if (peer == null) {
+            Range r = range.getPeer(river);
+            Session session = ImporterSession.getInstance().getDatabaseSession();
+            Query query = session.createQuery(
+                "from WstQRange where " +
+                "range=:range and q=:q");
+            query.setParameter("range", r);
+            query.setParameter("q",     q);
+            List<WstQRange> wstQRanges = query.list();
+            if (wstQRanges.isEmpty()) {
+                peer = new WstQRange(r, q);
+                session.save(peer);
+            }
+            else {
+                peer = wstQRanges.get(0);
+            }
+        }
+        return peer;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/Importer.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,136 @@
+package de.intevation.flys.importer;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.List;
+
+import java.sql.SQLException;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Transaction;
+import org.hibernate.HibernateException;
+
+import org.w3c.dom.Document;
+
+public class Importer
+{
+    private static Logger log = Logger.getLogger(Importer.class);
+
+    public static final boolean DRY_RUN =
+        Boolean.getBoolean("flys.backend.importer.dry.run");
+
+    public static final String ANNOTATION_TYPES =
+        "flys.backend.importer.annotation.types";
+
+    protected List<ImportRiver> rivers;
+
+    public Importer() {
+    }
+
+    public Importer(List<ImportRiver> rivers) {
+        this.rivers = rivers;
+    }
+
+    public List<ImportRiver> getRivers() {
+        return rivers;
+    }
+
+    public void setRivers(List<ImportRiver> rivers) {
+        this.rivers = rivers;
+    }
+
+    public void writeRivers() {
+        log.debug("write rivers started");
+
+        for (ImportRiver river: rivers) {
+            log.debug("writing river '" + river.getName() + "'");
+            river.storeDependencies();
+            ImporterSession.getInstance().getDatabaseSession().flush();
+        }
+
+        log.debug("write rivers finished");
+    }
+
+    public void writeToDatabase() {
+
+        Transaction tx = null;
+
+        try {
+            tx = ImporterSession.getInstance()
+                .getDatabaseSession().beginTransaction();
+
+            try {
+                writeRivers();
+            }
+            catch (HibernateException he) {
+                Throwable t = he.getCause();
+                while (t instanceof SQLException) {
+                    SQLException sqle = (SQLException)t;
+                    log.error("SQL exeception chain:", sqle);
+                    t = sqle.getNextException();
+                }
+                throw he;
+            }
+
+            tx.commit();
+        }
+        catch (RuntimeException re) {
+            if (tx != null) {
+                tx.rollback();
+            }
+            throw re;
+        }
+    }
+
+    public static AnnotationClassifier getAnnotationClassifier() {
+        String annotationTypes = System.getProperty(ANNOTATION_TYPES);
+
+        if (annotationTypes == null) {
+            log.info("no annotation types file configured.");
+            return null;
+        }
+
+        File file = new File(annotationTypes);
+
+        log.info("use annotation types file '" + file + "'");
+
+        if (!(file.isFile() && file.canRead())) {
+            log.warn("annotation type file '" + file + "' is not readable.");
+            return null;
+        }
+
+        Document rules = XMLUtils.parseDocument(file);
+
+        if (rules == null) {
+            log.warn("cannot parse annotation types file.");
+            return null;
+        }
+
+        return new AnnotationClassifier(rules);
+    }
+
+    public static void main(String [] args) {
+
+        InfoGewParser infoGewParser = new InfoGewParser(
+            getAnnotationClassifier());
+
+        for (String gew: args) {
+            log.info("parsing info gew file: " + gew);
+            try {
+                infoGewParser.parse(new File(gew));
+            }
+            catch (IOException ioe) {
+                log.error("cannot while parsing: " + gew);
+            }
+        }
+
+        if (!DRY_RUN) {
+            new Importer(infoGewParser.getRivers()).writeToDatabase();
+        }
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/ImporterSession.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,195 @@
+package de.intevation.flys.importer;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.LinkedHashMap;
+
+import java.math.BigDecimal;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.Session;
+import org.hibernate.Query;
+import org.hibernate.FlushMode;
+
+import de.intevation.flys.backend.SessionFactoryProvider;
+
+import de.intevation.flys.model.WstColumnValue;
+import de.intevation.flys.model.WstColumn;
+import de.intevation.flys.model.DischargeTableValue;
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import org.apache.log4j.Logger;
+
+public class ImporterSession
+{
+    private static Logger log = Logger.getLogger(ImporterSession.class);
+
+    public static final int CACHE_SIZE = 25;
+
+    public static class Cache<K, V> extends LinkedHashMap<K, V> {
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+            return size() > CACHE_SIZE;
+        }
+    };
+
+    private static final ThreadLocal<ImporterSession> SESSION =
+        new ThreadLocal<ImporterSession>() {
+            @Override
+            protected ImporterSession initialValue() {
+                return new ImporterSession();
+            }
+        };
+
+    protected Session databaseSession;
+
+    protected Cache<Integer, Map<ValueKey, WstColumnValue>>
+        wstColumnValues;
+
+    protected Cache<Integer, Map<ValueKey, DischargeTableValue>>
+        dischargeTableValues;
+
+    protected Cache<Integer, Map<ValueKey, Range>>
+        ranges;
+
+    public static ImporterSession getInstance() {
+        return SESSION.get();
+    }
+
+    public ImporterSession() {
+        SessionFactory sessionFactory =
+            SessionFactoryProvider.createSessionFactory();
+        databaseSession = sessionFactory.openSession();
+        //databaseSession.setFlushMode(FlushMode.MANUAL);
+
+        wstColumnValues =
+            new Cache<Integer, Map<ValueKey, WstColumnValue>>();
+
+        dischargeTableValues =
+            new Cache<Integer, Map<ValueKey, DischargeTableValue>>();
+
+        ranges = new Cache<Integer, Map<ValueKey, Range>>();
+    }
+
+    public Session getDatabaseSession() {
+        return databaseSession;
+    }
+
+    public WstColumnValue getWstColumnValue(
+        WstColumn  column,
+        BigDecimal position,
+        BigDecimal w
+    ) {
+        Integer c = column.getId();
+
+        Map<ValueKey, WstColumnValue> map = wstColumnValues.get(c);
+
+        if (map == null) {
+            map = new TreeMap<ValueKey, WstColumnValue>(
+                ValueKey.EPSILON_COMPARATOR);
+            wstColumnValues.put(c, map);
+            Query query = databaseSession.createQuery(
+                "from WstColumnValue where wstColumn.id=:cid");
+            query.setParameter("cid", c);
+            for (Iterator iter = query.iterate(); iter.hasNext();) {
+                WstColumnValue wcv = (WstColumnValue)iter.next();
+                map.put(new ValueKey(wcv.getPosition(), wcv.getW()), wcv);
+            }
+        }
+
+        ValueKey key = new ValueKey(position, w);
+
+        WstColumnValue wcv = map.get(key);
+
+        if (wcv != null) {
+            return wcv;
+        }
+
+        wcv = new WstColumnValue(column, position, w);
+
+        databaseSession.save(wcv);
+
+        map.put(key, wcv);
+
+        return wcv;
+    }
+
+    public DischargeTableValue getDischargeTableValue(
+        DischargeTable table,
+        BigDecimal     q,
+        BigDecimal     w
+    ) {
+        Integer t = table.getId();
+
+        Map<ValueKey, DischargeTableValue> map =
+            dischargeTableValues.get(t);
+
+        if (map == null) {
+            map = new TreeMap<ValueKey, DischargeTableValue>(
+                ValueKey.EPSILON_COMPARATOR);
+            dischargeTableValues.put(t, map);
+            Query query = databaseSession.createQuery(
+                "from DischargeTableValue where dischargeTable.id=:tid");
+            query.setParameter("tid", t);
+            for (Iterator iter = query.iterate(); iter.hasNext();) {
+                DischargeTableValue dctv = (DischargeTableValue)iter.next();
+                map.put(new ValueKey(dctv.getQ(), dctv.getW()), dctv);
+            }
+        }
+
+        ValueKey key = new ValueKey(q, w);
+
+        DischargeTableValue dctv = map.get(key);
+
+        if (dctv != null) {
+            return dctv;
+        }
+
+        dctv = new DischargeTableValue(table, q, w);
+
+        databaseSession.save(dctv);
+
+        map.put(key, dctv);
+
+        return dctv;
+    }
+
+    public Range getRange(River river, BigDecimal a, BigDecimal b) {
+        Integer r = river.getId();
+
+        Map<ValueKey, Range> map = ranges.get(r);
+
+        if (map == null) {
+            map = new TreeMap<ValueKey, Range>(
+                ValueKey.EPSILON_COMPARATOR);
+            ranges.put(r, map);
+            Query query = databaseSession.createQuery(
+                "from Range where river.id=:rid");
+            query.setParameter("rid", r);
+            for (Iterator iter = query.iterate(); iter.hasNext();) {
+                Range range = (Range)iter.next();
+                map.put(new ValueKey(range.getA(), range.getB()), range);
+            }
+        }
+
+        ValueKey key = new ValueKey(a, b);
+
+        Range range = map.get(key);
+
+        if (range != null) {
+            return range;
+        }
+
+        range = new Range(a, b, river);
+
+        databaseSession.save(range);
+
+        map.put(key, range);
+
+        return range;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/InfoGewParser.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,133 @@
+package de.intevation.flys.importer;
+
+import java.io.File;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.utils.FileTools;
+
+public class InfoGewParser
+{
+    private static Logger log = Logger.getLogger(InfoGewParser.class);
+
+    public static final String ENCODING = "ISO-8859-1";
+
+    public static final Pattern GEWAESSER =
+        Pattern.compile("^\\s*Gew\u00e4sser\\s*:\\s*(.+)");
+
+    public static final Pattern WST_DATEI =
+        Pattern.compile("^\\s*WSTDatei\\s*:\\s*(.+)");
+
+    public static final Pattern BB_INFO =
+        Pattern.compile("^\\s*B\\+B-Info\\s*:\\s*(.+)");
+
+    protected ArrayList<ImportRiver> rivers;
+
+    protected AnnotationClassifier annotationClassifier;
+
+    public InfoGewParser() {
+        this(null);
+    }
+
+    public InfoGewParser(AnnotationClassifier annotationClassifier) {
+        rivers = new ArrayList<ImportRiver>();
+        this.annotationClassifier = annotationClassifier;
+    }
+
+    public List<ImportRiver> getRivers() {
+        return rivers;
+    }
+
+    public static final String normalize(String f) {
+        return f.replace("\\", "/").replace("/", File.separator);
+    }
+
+    public void parse(File file) throws IOException {
+
+        LineNumberReader in = null;
+
+        File root = file.getParentFile();
+
+        try {
+            in =
+                new LineNumberReader(
+                new InputStreamReader(
+                new FileInputStream(file), ENCODING));
+
+            String line = null;
+
+            String riverName  = null;
+            File   wstFile    = null;
+            File   bbInfoFile = null;
+
+            while ((line = in.readLine()) != null) {
+                if ((line = line.trim()).length() == 0) {
+                    continue;
+                }
+                Matcher m = GEWAESSER.matcher(line);
+
+                if (m.matches()) {
+                    String river = m.group(1);
+                    log.info("Found river '" + river + "'");
+                    if (riverName != null) {
+                        rivers.add(new ImportRiver(
+                            riverName,
+                            wstFile,
+                            bbInfoFile,
+                            annotationClassifier));
+                    }
+                    riverName  = river;
+                    wstFile    = null;
+                    bbInfoFile = null;
+                }
+                else if ((m = WST_DATEI.matcher(line)).matches()) {
+                    String wstFilename = m.group(1);
+                    File wst = new File(wstFilename = normalize(wstFilename));
+                    if (!wst.isAbsolute()) {
+                        wst = new File(root, wstFilename);
+                    }
+                    wst = FileTools.repair(wst);
+                    log.info("Found wst file '" + wst + "'");
+                    if (!wst.isFile() || !wst.canRead()) {
+                        log.warn("cannot access WST file '" + wstFilename + "'");
+                        continue;
+                    }
+                    wstFile = wst;
+                }
+                else if ((m = BB_INFO.matcher(line)).matches()) {
+                    //TODO: Make it relative to the wst file.
+                    String bbInfo = m.group(1);
+                    bbInfoFile = new File(normalize(bbInfo));
+                }
+            }
+            if (riverName != null) {
+                rivers.add(new ImportRiver(
+                    riverName,
+                    wstFile,
+                    bbInfoFile,
+                    annotationClassifier));
+            }
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+
+        for (ImportRiver river: rivers) {
+            river.parseDependencies();
+        }
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/PegelGltParser.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,99 @@
+package de.intevation.flys.importer;
+
+import java.io.File;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.utils.FileTools;
+
+public class PegelGltParser
+{
+    private static Logger log = Logger.getLogger(PegelGltParser.class);
+
+    public static final String ENCODING = "ISO-8859-1";
+
+    public static final String KM = "km:";
+
+    protected List<ImportGauge> gauges;
+
+    public PegelGltParser() {
+        gauges = new ArrayList<ImportGauge>();
+    }
+
+    public List<ImportGauge> getGauges() {
+        return gauges;
+    }
+
+    public void parse(File file) throws IOException {
+
+        File parent = file.getParentFile();
+
+        log.info("parsing GLT file '" + file + "'");
+        LineNumberReader in = null;
+        try {
+            in =
+                new LineNumberReader(
+                new InputStreamReader(
+                new FileInputStream(file), ENCODING));
+
+            String line = null;
+            while ((line = in.readLine()) != null) {
+                if ((line = line.trim()).length() == 0) {
+                    continue;
+                }
+
+                int kmPos = line.indexOf(KM);
+                if (kmPos < 0) {
+                    log.warn("no gauge found in line " + in.getLineNumber());
+                    continue;
+                }
+
+                String gaugeName = line.substring(0, kmPos).trim();
+                log.info("Found gauge '" + gaugeName + "'");
+
+                line = line.substring(kmPos + KM.length()).trim();
+
+                String [] parts = line.split("\\s+");
+                if (parts.length < 4) {
+                    log.warn("line " + in.getLineNumber()
+                        + " has not enough columns");
+                    continue;
+                }
+
+                BigDecimal from = new BigDecimal(parts[0].replace(",", "."));
+                BigDecimal to   = new BigDecimal(parts[1].replace(",", "."));
+                if (from.compareTo(from) > 0) {
+                    BigDecimal t = from; from = to; to = t;
+                }
+                ImportRange range = new ImportRange(from, to);
+                File staFile = FileTools.repair(new File(parent, parts[2]));
+                File atFile  = FileTools.repair(new File(parent, parts[3]));
+
+                if (log.isDebugEnabled()) {
+                    log.debug("\tfrom: " + from);
+                    log.debug("\tto: " + to);
+                    log.debug("\tsta: " + staFile);
+                    log.debug("\tat: " + atFile);
+                }
+
+                gauges.add(new ImportGauge(range, staFile, atFile));
+            }
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/StaFileParser.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,160 @@
+package de.intevation.flys.importer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+
+import java.math.BigDecimal;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+
+import org.apache.log4j.Logger;
+
+public class StaFileParser
+{
+    private static Logger log = Logger.getLogger(StaFileParser.class);
+
+    public static final String ENCODING = "ISO-8859-1";
+
+    public static final String TYPES =
+        System.getProperty("flys.backend.main.value.types", "QWTD");
+
+    public static final Pattern QWTD_ =
+        Pattern.compile("\\s*([^\\s]+)\\s+([^\\s]+)\\s+([" +
+            Pattern.quote(TYPES) + "]).*");
+
+    public StaFileParser() {
+    }
+
+    public boolean parse(ImportGauge gauge) throws IOException {
+
+        File file = gauge.getStaFile();
+
+        log.info("parsing STA file: " + file);
+        LineNumberReader in = null;
+        try {
+            in =
+                new LineNumberReader(
+                new InputStreamReader(
+                new FileInputStream(file), ENCODING));
+
+            String line = in.readLine();
+
+            if (line == null) {
+                log.warn("STA file is empty.");
+                return false;
+            }
+
+            if (line.length() < 37) {
+                log.warn("first line in STA file is too short.");
+                return false;
+            }
+
+            gauge.setName(line.substring(16, 37).trim());
+
+            String [] values = line.substring(38).trim().split("\\s+", 2);
+
+            if (values.length < 2) {
+                log.warn("Not enough columns for aeo and datum");
+            }
+            try {
+                gauge.setAeo(new BigDecimal(values[0].replace(",", ".")));
+                gauge.setDatum(new BigDecimal(values[1].replace(",", ".")));
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("cannot parse aeo or datum");
+                return false;
+            }
+
+            line = in.readLine();
+
+            if (line == null) {
+                log.warn("STA file has not enough lines");
+                return false;
+            }
+
+            if (line.length() < 36) {
+                log.warn("second line is too short");
+                return false;
+            }
+
+            try {
+                gauge.setStation(
+                    new BigDecimal(line.substring(29, 36).trim()));
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("parsing of the datum of the gauge failed");
+                return false;
+            }
+
+            // overread the next six lines
+            for (int i = 0; i < 6; ++i) {
+                if ((line = in.readLine()) == null) {
+                    log.warn("STA file is too short");
+                    return false;
+                }
+            }
+
+            HashMap<String, ImportMainValueType> types =
+                new HashMap<String, ImportMainValueType>();
+
+            ArrayList<ImportNamedMainValue> namedMainValues =
+                new ArrayList<ImportNamedMainValue>();
+
+            ArrayList<ImportMainValue> mainValues =
+                new ArrayList<ImportMainValue>();
+
+            while ((line = in.readLine()) != null) {
+                Matcher m = QWTD_.matcher(line);
+                if (m.matches()) {
+                    BigDecimal value;
+                    try {
+                        value = new BigDecimal(m.group(2).replace(",", "."));
+                    }
+                    catch (NumberFormatException nfe) {
+                        log.warn("value not parseable in line "
+                            + in.getLineNumber());
+                        continue;
+                    }
+                    String typeString = m.group(3);
+                    log.debug("\t type: " + typeString);
+                    ImportMainValueType type = types.get(typeString);
+                    if (type == null) {
+                        type = new ImportMainValueType(typeString);
+                        types.put(typeString, type);
+                    }
+                    String name = m.group(1);
+                    ImportNamedMainValue namedMainValue =
+                        new ImportNamedMainValue(type, name);
+                    namedMainValues.add(namedMainValue);
+
+                    ImportMainValue mainValue =
+                        new ImportMainValue(gauge, namedMainValue, value);
+
+                    mainValues.add(mainValue);
+                }
+                else {
+                    // TODO: treat as a comment
+                }
+            }
+            gauge.setMainValueTypes(
+                new ArrayList<ImportMainValueType>(types.values()));
+            gauge.setNamedMainValues(namedMainValues);
+            gauge.setMainValues(mainValues);
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+        log.info("finished parsing STA file: " + file);
+        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-backend/src/main/java/de/intevation/flys/importer/ValueKey.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,63 @@
+package de.intevation.flys.importer;
+
+import java.math.BigDecimal;
+
+import java.util.Comparator;
+
+public class ValueKey
+{
+    public static final double EPSILON = 1e-6;
+
+    public static final Comparator<ValueKey> EPSILON_COMPARATOR =
+        new Comparator<ValueKey>()
+    {
+        public int compare(ValueKey x, ValueKey y) {
+            int cmp = ValueKey.compare(x.a, y.a);
+            if (cmp != 0) return cmp;
+            return ValueKey.compare(x.b, y.b);
+        }
+    };
+
+    public static int compare(BigDecimal a, BigDecimal b) {
+        if (a == null && b == null) return  0;
+        if (a != null && b == null) return +1;
+        if (a == null && b != null) return -1;
+
+        double diff = a.doubleValue() - b.doubleValue();
+        if (diff < -EPSILON) return -1;
+        return diff > EPSILON ? +1 : 0;
+    }
+
+    protected BigDecimal a;
+    protected BigDecimal b;
+
+    public ValueKey() {
+    }
+
+    public ValueKey(BigDecimal a, BigDecimal b) {
+        this.a = a;
+        this.b = b;
+    }
+
+    @Override
+    public int hashCode() {
+        return ((a != null ? a.hashCode() : 0) << 16)
+              | (b != null ? b.hashCode() : 0);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof ValueKey)) {
+            return false;
+        }
+        ValueKey o = (ValueKey)other;
+        return !(
+               (a == null && o.a != null)
+            || (a != null && o.a == null)
+            || (a != null && !a.equals(o.a))
+            || (b == null && o.b != null)
+            || (b != null && o.b == null)
+            || (b != null && !b.equals(o.b)));
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/importer/WstParser.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,425 @@
+package de.intevation.flys.importer;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.InputStreamReader;
+import java.io.FileInputStream;
+
+import java.text.NumberFormat;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.utils.StringUtil;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.math.BigDecimal;
+
+public class WstParser
+{
+    private static Logger log = Logger.getLogger(WstParser.class);
+
+    public static final String COLUMN_BEZ_TEXT   = "column-bez-text";
+    public static final String COLUMN_BEZ_BREITE = "column-bez-breite";
+    public static final String COLUMN_QUELLE     = "column-quelle";
+    public static final String COLUMN_DATUM      = "column-datum";
+
+    public static final BigDecimal UNDEFINED_ZERO =
+        new BigDecimal(0.0);
+    public static final BigDecimal MIN_RANGE =
+        new BigDecimal(-Double.MAX_VALUE);
+    public static final BigDecimal MAX_RANGE =
+        new BigDecimal(Double.MAX_VALUE);
+
+    public static final String ENCODING = "ISO-8859-1";
+
+    public static final Pattern UNIT_COMMENT =
+        Pattern.compile("\\*\\s*[kK][mM]\\s+(.+)");
+
+    public static final Pattern UNIT =
+        Pattern.compile("[^\\[]*\\[([^]]+)\\].*");
+
+    public static final BigDecimal INTERVAL_GAP =
+        new BigDecimal(0.00001);
+
+    protected ImportWst wst;
+
+    protected ImportRange lastRange;
+
+    public WstParser() {
+    }
+
+    public ImportWst getWst() {
+        return wst;
+    }
+
+    public void setWst(ImportWst wst) {
+        this.wst = wst;
+    }
+
+    public void parse(File file) throws IOException {
+
+        log.info("Parsing WST file '" + file + "'");
+
+        wst = new ImportWst(file.getName());
+
+        LineNumberReader in = null;
+        try {
+            in =
+                new LineNumberReader(
+                new InputStreamReader(
+                new FileInputStream(file), ENCODING));
+
+            String input;
+            boolean first = true;
+            int columnCount = 0;
+
+            String [] lsBezeichner   = null;
+            String [] langBezeichner = null;
+            int    [] colNaWidths    = null;
+            String [] quellen        = null;
+            String [] daten          = null;
+
+            BigDecimal [] aktAbfluesse   = null;
+            BigDecimal [] firstAbfluesse = null;
+
+            BigDecimal minKm = MAX_RANGE;
+            BigDecimal maxKm = MIN_RANGE;
+
+            boolean columnHeaderChecked = false;
+
+            String einheit = "Wasserstand [NN + m]";
+
+            HashSet<BigDecimal> kms = new HashSet<BigDecimal>();
+
+            while ((input = in.readLine()) != null) {
+                String line = input;
+                if (first) { // fetch number of columns
+                    if ((line = line.trim()).length() == 0) {
+                        continue;
+                    }
+                    try {
+                        columnCount = Integer.parseInt(line);
+                        if (columnCount <= 0) {
+                            throw new NumberFormatException(
+                                "number columns <= 0");
+                        }
+                        log.debug("Number of columns: " + columnCount);
+                        wst.setNumberColumns(columnCount);
+                        lsBezeichner = new String[columnCount];
+                    }
+                    catch (NumberFormatException nfe) {
+                        log.warn(nfe);
+                        continue;
+                    }
+                    first = false;
+                    continue;
+                }
+
+                line = line.replace(',', '.');
+
+                if (line.startsWith("*\u001f")) {
+                    BigDecimal [] data =
+                        parseLineAsDouble(line, columnCount, false, true);
+
+                    if (aktAbfluesse != null) {
+                        addInterval(minKm, maxKm, aktAbfluesse);
+                        minKm = MAX_RANGE;
+                        maxKm = MIN_RANGE;
+                    }
+
+                    aktAbfluesse = new BigDecimal[columnCount];
+                    log.debug("new q range: " + columnCount);
+                    for (int i = 0; i < Math.min(columnCount, data.length); ++i) {
+                        if (data[i] != null) {
+                            log.debug("  column: " + data[i]);
+                            aktAbfluesse[i] = data[i];
+                        }
+                    }
+
+                    if (firstAbfluesse == null) {
+                        firstAbfluesse = (BigDecimal [])aktAbfluesse.clone();
+                    }
+                    continue;
+                }
+
+                if (line.startsWith("*!")) {
+                    String spezial = line.substring(2).trim();
+
+                    if (spezial.length() == 0) {
+                        continue;
+                    }
+
+                    if (spezial.startsWith(COLUMN_BEZ_TEXT)) {
+                        spezial = spezial.substring(COLUMN_BEZ_TEXT.length()).trim();
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+                        langBezeichner = StringUtil.splitQuoted(spezial, '"');
+                    }
+                    else if (spezial.startsWith(COLUMN_BEZ_BREITE)) {
+                        spezial = spezial.substring(COLUMN_BEZ_BREITE.length()).trim();
+
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+
+                        String[] split = spezial.split("\\s+");
+
+                        colNaWidths = new int[split.length];
+                        for (int i=0; i < split.length; i++) {
+                            colNaWidths[i] = Integer.parseInt(split[i]);
+                        }
+                    }
+                    else if (spezial.startsWith(COLUMN_QUELLE)) {
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+                        quellen = StringUtil.splitQuoted(spezial, '"');
+                    }
+                    else if (spezial.startsWith(COLUMN_DATUM)) {
+                        spezial = spezial.substring(COLUMN_DATUM.length()).trim();
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+                        daten = StringUtil.splitQuoted(spezial, '"');
+                    }
+                    continue;
+                }
+
+                if (line.length() < 11) {
+                    continue;
+                }
+
+                if (line.startsWith("*")) {
+                    Matcher m = UNIT_COMMENT.matcher(line);
+                    if (m.matches()) {
+                        log.debug("unit comment found");
+                        // XXX: This hack is needed because desktop
+                        // FLYS is broken figuring out the unit
+                        String [] units = m.group(1).split("\\s{2,}");
+                        m = UNIT.matcher(units[0]);
+                        einheit = m.matches() ? m.group(1) : units[0];
+                        log.debug("unit: " + einheit);
+                    }
+                    continue;
+                }
+
+                if (firstAbfluesse != null) {
+                    if (!columnHeaderChecked) {
+                        int unknownCount = 0;
+                        HashSet<String> uniqueColumnNames =
+                            new HashSet<String>();
+                        for (int i = 0; i < lsBezeichner.length; ++i) {
+                            if (lsBezeichner[i] == null
+                            || lsBezeichner[i].length() == 0) {
+                                double q = firstAbfluesse[i].doubleValue();
+                                if (q < 0.001) {
+                                    lsBezeichner[i] =
+                                        "<unbekannt #" + unknownCount + ">";
+                                    ++unknownCount;
+                                }
+                                else {
+                                    lsBezeichner[i] = "Q="+format(q);
+                                }
+                            }
+                            String candidate = lsBezeichner[i];
+                            int collision = 1;
+                            while (!uniqueColumnNames.add(candidate)) {
+                                candidate = lsBezeichner[i] +
+                                    " (" + collision + ")";
+                                ++collision;
+                            }
+                            wst.getColumn(i).setName(candidate);
+                        }
+                        columnHeaderChecked = true;
+                    }
+
+                    BigDecimal [] data =
+                        parseLineAsDouble(line, columnCount, true, false);
+
+                    BigDecimal kaem = data[0];
+
+                    if (!kms.add(kaem)) {
+                        log.warn(
+                            "km " + kaem +
+                            " (line " + in.getLineNumber() +
+                            ") found more than once. -> ignored");
+                        continue;
+                    }
+
+                    if (kaem.compareTo(minKm) < 0) {
+                        minKm = kaem;
+                    }
+                    if (kaem.compareTo(maxKm) > 0) {
+                        maxKm = kaem;
+                    }
+
+                    // extract values
+                    for (int i = 0; i < columnCount; ++i) {
+                        addValue(kaem, data[i+1], i);
+                    }
+
+                }
+                else { // firstAbfluesse == null
+                    if (langBezeichner != null) {
+                        lsBezeichner = StringUtil.fitArray(
+                            langBezeichner, lsBezeichner);
+                    }
+                    else if (colNaWidths != null) {
+                        for (int j = 0, i = 0, N = input.length();
+                             j < colNaWidths.length && i < N;
+                             i += colNaWidths[j++]
+                        ) {
+                            lsBezeichner[j] = input.substring(
+                                i, i+colNaWidths[j]).trim();
+                        }
+                    }
+                    else {
+                        // first column begins at position 8 in line
+                        for (int i = 8, col = 0; i < input.length(); i += 9) {
+                            if ((i + 9) > input.length()) {
+                                i = input.length() - 10;
+                            }
+                            // one column header is 9 chars wide
+                            lsBezeichner[col++] =
+                                input.substring(i, i + 9).trim();
+
+                            if (col == lsBezeichner.length) {
+                                break;
+                            }
+                        }
+                    }
+                }
+
+            }
+            addInterval(minKm, maxKm, aktAbfluesse);
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    protected void addValue(BigDecimal km, BigDecimal w, int index) {
+        if (w != null) {
+            ImportWstColumn column = wst.getColumn(index);
+            column.addColumnValue(km, w);
+        }
+    }
+
+    private static final NumberFormat NF = getNumberFormat();
+
+    private static final NumberFormat getNumberFormat() {
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setMinimumFractionDigits(2);
+        nf.setMaximumFractionDigits(2);
+        return nf;
+    }
+
+    protected static String format(double value) {
+        return NF.format(value);
+    }
+
+    protected void addInterval(
+        BigDecimal    from,
+        BigDecimal    to,
+        BigDecimal [] values
+    ) {
+        log.debug("addInterval: " + from + " " + to);
+
+        if (values == null || from == MAX_RANGE) {
+            return;
+        }
+
+        if (to.compareTo(from) < 0) {
+            BigDecimal t = from; from = to; to = t;
+        }
+
+        ImportRange range = new ImportRange(from, to);
+
+        // little workaround to make the q ranges tightly fit.
+        // Leave a very small gap to ensure that the range queries
+        // still work.
+
+        if (lastRange != null) {
+            double d1 = Math.abs(
+                lastRange.getB().doubleValue() - range.getA().doubleValue());
+            double d2 = Math.abs(
+                range.getB().doubleValue() - lastRange.getA().doubleValue());
+
+            if (d1 < d2) {
+                lastRange.setB(range.getA().subtract(INTERVAL_GAP));
+            }
+            else {
+                range.setA(lastRange.getB().subtract(INTERVAL_GAP));
+            }
+        }
+
+        for (int i = 0; i < values.length; ++i) {
+            ImportWstColumn column = wst.getColumn(i);
+            ImportWstQRange wstQRange = new ImportWstQRange(range, values[i]);
+            column.addColumnQRange(wstQRange);
+        }
+
+        lastRange = range;
+    }
+
+    private static final BigDecimal [] parseLineAsDouble(
+        String  line,
+        int     count,
+        boolean bStation,
+        boolean bParseEmptyAsZero
+    ) {
+        String [] tokens = parseLine(line, count, bStation);
+
+        BigDecimal [] doubles = new BigDecimal[tokens.length];
+
+        for (int i = 0; i < doubles.length; ++i) {
+            String token = tokens[i].trim();
+            if (token.length() != 0) {
+                doubles[i] = new BigDecimal(token);
+            }
+            else if (bParseEmptyAsZero) {
+                doubles[i] = UNDEFINED_ZERO;
+            }
+        }
+
+        return doubles;
+    }
+
+    private static String [] parseLine(
+        String  line,
+        int     tokenCount,
+        boolean bParseStation
+    ) {
+        ArrayList<String> strings = new ArrayList<String>();
+
+        if (bParseStation) {
+            if (line.length() < 8) {
+                throw new IllegalArgumentException("station too short");
+            }
+            strings.add(line.substring(0, 8));
+        }
+
+        int pos = 9;
+        for (int i = 0; i < tokenCount; ++i) {
+            if (line.length() >= pos + 8) {
+                strings.add(line.substring(pos, pos + 8));
+            }
+            else {
+                strings.add("");
+            }
+            pos += 9;
+        }
+
+        return strings.toArray(new String[strings.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-backend/src/main/java/de/intevation/flys/model/Annotation.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,111 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+
+@Entity
+@Table(name = "annotations")
+public class Annotation
+implements   Serializable
+{
+    private Integer        id;
+    private Range          range;
+    private Attribute      attribute;
+    private Position       position;
+    private Edge           edge;
+    private AnnotationType type;
+
+    public Annotation() {
+    }
+
+    public Annotation(
+        Range          range,
+        Attribute      attribute,
+        Position       position,
+        Edge           edge,
+        AnnotationType type
+    ) {
+        this.range     = range;
+        this.attribute = attribute;
+        this.position  = position;
+        this.edge      = edge;
+        this.type      = type;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_ANNOTATIONS_ID_SEQ",
+        sequenceName   = "ANNOTATIONS_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_ANNOTATIONS_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "range_id")
+    public Range getRange() {
+        return range;
+    }
+
+    public void setRange(Range range) {
+        this.range = range;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "attribute_id")
+    public Attribute getAttribute() {
+        return attribute;
+    }
+
+    public void setAttribute(Attribute attribute) {
+        this.attribute = attribute;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "position_id")
+    public Position getPosition() {
+        return position;
+    }
+
+    public void setPosition(Position position) {
+        this.position = position;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "edge_id")
+    public Edge getEdge() {
+        return edge;
+    }
+
+    public void setEdge(Edge edge) {
+        this.edge = edge;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "type_id")
+    public AnnotationType getType() {
+        return type;
+    }
+
+    public void setType(AnnotationType type) {
+        this.type = type;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/AnnotationType.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,54 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+
+@Entity
+@Table(name = "annotation_types")
+public class AnnotationType
+implements   Serializable
+{
+    private Integer id;
+    private String  name;
+
+    public AnnotationType() {
+    }
+
+    public AnnotationType(String name) {
+        this.name = name;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_ANNOTATION_TYPES_ID_SEQ",
+        sequenceName   = "ANNOTATION_TYPES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_ANNOTATION_TYPES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/Attribute.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,55 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+
+@Entity
+@Table(name = "attributes")
+public class Attribute
+implements   Serializable
+{
+    private Integer id;
+
+    private String  value;
+
+    public Attribute() {
+    }
+
+    public Attribute(String value) {
+        this.value = value;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_ATTRIBUTES_ID_SEQ",
+        sequenceName   = "ATTRIBUTES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_ATTRIBUTES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "value")
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = 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-backend/src/main/java/de/intevation/flys/model/DischargeTable.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,118 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+
+import java.util.List;
+
+@Entity
+@Table(name = "discharge_tables")
+public class DischargeTable
+implements   Serializable
+{
+    private Integer      id;
+    private Gauge        gauge;
+    private String       description;
+    private Integer      kind;
+    private TimeInterval timeInterval;
+
+    private List<DischargeTableValue> dischargeTableValues;
+
+    public DischargeTable() {
+        kind = 0;
+    }
+
+    public DischargeTable(Gauge gauge) {
+        this(gauge, null, 0, null);
+    }
+
+    public DischargeTable(
+        Gauge        gauge,
+        String       description,
+        Integer      kind,
+        TimeInterval timeInterval
+    ) {
+        this.gauge        = gauge;
+        this.description  = description;
+        this.kind         = kind;
+        this.timeInterval = timeInterval;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_DISCHARGE_TABLES_ID_SEQ",
+        sequenceName   = "DISCHARGE_TABLES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_DISCHARGE_TABLES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "gauge_id" )
+    public Gauge getGauge() {
+        return gauge;
+    }
+
+    public void setGauge(Gauge gauge) {
+        this.gauge = gauge;
+    }
+
+    @Column(name = "description")
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Column(name = "kind")
+    public Integer getKind() {
+        return kind;
+    }
+
+    public void setKind(Integer kind) {
+        this.kind = kind;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "time_interval_id" )
+    public TimeInterval getTimeInterval() {
+        return timeInterval;
+    }
+
+    public void setTimeInterval(TimeInterval timeInterval) {
+        this.timeInterval = timeInterval;
+    }
+
+    @OneToMany
+    @JoinColumn(name = "table_id")
+    public List<DischargeTableValue> getDischargeTableValues() {
+        return dischargeTableValues;
+    }
+
+    public void setDischargeTableValues(
+        List<DischargeTableValue> dischargeTableValues
+    ) {
+        this.dischargeTableValues = dischargeTableValues;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/DischargeTableValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,84 @@
+package de.intevation.flys.model;
+
+import java.math.BigDecimal;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+
+@Entity
+@Table(name = "discharge_table_values")
+public class DischargeTableValue
+implements   Serializable
+{
+    private Integer        id;
+    private DischargeTable dischargeTable;
+    private BigDecimal     q;
+    private BigDecimal     w;
+
+    public DischargeTableValue() {
+    }
+
+    public DischargeTableValue(
+        DischargeTable dischargeTable, BigDecimal q, BigDecimal w)
+    {
+        this.dischargeTable = dischargeTable;
+        this.q              = q;
+        this.w              = w;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_DISCHARGE_TABLE_VALUES_ID_SEQ",
+        sequenceName   = "DISCHARGE_TABLE_VALUES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_DISCHARGE_TABLE_VALUES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "table_id" )
+    public DischargeTable getDischargeTable() {
+        return dischargeTable;
+    }
+
+    public void setDischargeTable(DischargeTable dischargeTable) {
+        this.dischargeTable = dischargeTable;
+    }
+
+
+    @Column(name = "q")
+    public BigDecimal getQ() {
+        return q;
+    }
+
+    public void setQ(BigDecimal q) {
+        this.q = q;
+    }
+
+    @Column(name = "w")
+    public BigDecimal getW() {
+        return w;
+    }
+
+    public void setW(BigDecimal w) {
+        this.w = w;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/Edge.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,67 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+
+@Entity
+@Table(name = "edges")
+public class Edge
+implements   Serializable
+{
+    private Integer    id;
+    private BigDecimal top;
+    private BigDecimal bottom;
+
+    public Edge() {
+    }
+
+    public Edge(BigDecimal top, BigDecimal bottom) {
+        this.top    = top;
+        this.bottom = bottom;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_EDGES_ID_SEQ",
+        sequenceName   = "EDGES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_EDGES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "top")
+    public BigDecimal getTop() {
+        return top;
+    }
+
+    public void setTop(BigDecimal top) {
+        this.top = top;
+    }
+
+    @Column(name = "bottom")
+    public BigDecimal getBottom() {
+        return bottom;
+    }
+
+    public void setBottom(BigDecimal bottom) {
+        this.bottom = bottom;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/Gauge.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,193 @@
+package de.intevation.flys.model;
+
+import java.math.BigDecimal;
+
+import java.io.Serializable;
+
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.OneToMany;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import de.intevation.flys.backend.SessionHolder;
+
+
+@Entity
+@Table(name = "gauges")
+public class Gauge
+implements   Serializable
+{
+    public static final int DEFAULT_SCALE = 100;
+
+    private Integer    id;
+    private String     name;
+    private River      river;
+    private BigDecimal station;
+    private BigDecimal aeo;
+    private BigDecimal datum;
+    private Range      range;
+
+    private List<DischargeTable> dischargeTables;
+
+    public Gauge() {
+    }
+
+    public Gauge(
+        String     name,
+        River      river,
+        BigDecimal station,
+        BigDecimal aeo,
+        BigDecimal datum,
+        Range      range
+    ) {
+        this.name    = name;
+        this.river   = river;
+        this.station = station;
+        this.aeo     = aeo;
+        this.datum   = datum;
+        this.range   = range;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_GAUGES_ID_SEQ",
+        sequenceName   = "GAUGES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_GAUGES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "river_id" )
+    public River getRiver() {
+        return river;
+    }
+
+    public void setRiver(River river) {
+        this.river = river;
+    }
+
+    @Column(name = "name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Column(name = "station") // FIXME: type mapping needed
+    public BigDecimal getStation() {
+        return station;
+    }
+
+    public void setStation(BigDecimal station) {
+        this.station = station;
+    }
+
+    @Column(name = "aeo") // FIXME: type mapping needed
+    public BigDecimal getAeo() {
+        return aeo;
+    }
+
+    public void setAeo(BigDecimal aeo) {
+        this.aeo = aeo;
+    }
+
+    @Column(name = "datum") // FIXME: type mapping needed
+    public BigDecimal getDatum() {
+        return datum;
+    }
+
+    public void setDatum(BigDecimal datum) {
+        this.datum = datum;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "range_id" )
+    public Range getRange() {
+        return range;
+    }
+
+    public void setRange(Range range) {
+        this.range = range;
+    }
+
+    @OneToMany
+    @JoinColumn(name = "gauge_id")
+    public List<DischargeTable> getDischargeTables() {
+        return dischargeTables;
+    }
+
+    public void setDischargeTables(List<DischargeTable> dischargeTables) {
+        this.dischargeTables = dischargeTables;
+    }
+
+
+    /**
+     * Returns min and max W values of this gauge based with a DEFAULT_SCALE.
+     *
+     * @return min and max W value of this gauge [min,max].
+     */
+    public double[] determineMinMaxW() {
+        return determineMinMaxW(DEFAULT_SCALE);
+    }
+
+
+    /**
+     * Returns min and max W values of this gauge.
+     *
+     *  @return the min and max W value of this gauge [min,max].
+     */
+    public double[] determineMinMaxW(int scale) {
+        Session session = SessionHolder.HOLDER.get();
+
+        List<DischargeTable> tables   = getDischargeTables();
+        DischargeTable dischargeTable = null;
+
+        for (DischargeTable tmp: tables) {
+            if (tmp.getKind() == 0) {
+                dischargeTable = tmp;
+                break;
+            }
+        }
+
+        if (dischargeTable == null) {
+            return null;
+        }
+
+        Query query  = session.createQuery(
+            "select min(w) as min, max(w) as max from DischargeTableValue " +
+            "where table_id =:table");
+        query.setParameter("table", dischargeTable.getId());
+
+        List     results = query.list();
+        Object[] result  = (Object[]) results.get(0);
+
+        return result != null
+            ? new double[] {
+                ((BigDecimal) result[0]).doubleValue() * scale,
+                ((BigDecimal) result[1]).doubleValue() * scale}
+            : 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-backend/src/main/java/de/intevation/flys/model/MainValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,103 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+import javax.persistence.GenerationType;
+
+import java.math.BigDecimal;
+
+@Entity
+@Table(name = "main_values")
+public class MainValue
+implements   Serializable
+{
+    private Integer        id;
+
+    private Gauge          gauge;
+
+    private NamedMainValue mainValue;
+
+    private BigDecimal     value;
+
+    private TimeInterval   timeInterval;
+
+    public MainValue() {
+    }
+
+    public MainValue(
+        Gauge          gauge,
+        NamedMainValue mainValue,
+        BigDecimal     value,
+        TimeInterval   timeInterval
+    ) {
+        this.gauge        = gauge;
+        this.mainValue    = mainValue;
+        this.value        = value;
+        this.timeInterval = timeInterval;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_MAIN_VALUES_ID_SEQ",
+        sequenceName   = "MAIN_VALUES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_MAIN_VALUES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "gauge_id")
+    public Gauge getGauge() {
+        return gauge;
+    }
+
+    public void setGauge(Gauge gauge) {
+        this.gauge = gauge;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "named_value_id")
+    public NamedMainValue getMainValue() {
+        return mainValue;
+    }
+
+    public void setMainValue(NamedMainValue mainValue) {
+        this.mainValue = mainValue;
+    }
+
+    @Column(name = "value") // FIXME: type mapping needed?
+    public BigDecimal getValue() {
+        return value;
+    }
+
+    public void setValue(BigDecimal value) {
+        this.value = value;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "time_interval_id")
+    public TimeInterval getTimeInterval() {
+        return timeInterval;
+    }
+
+    public void setTimeInterval(TimeInterval timeInterval) {
+        this.timeInterval = timeInterval;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/MainValueType.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,54 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+
+@Entity
+@Table(name = "main_value_types")
+public class MainValueType
+implements   Serializable
+{
+    private Integer id;
+    private String  name;
+
+    public MainValueType() {
+    }
+
+    public MainValueType(String name) {
+        this.name = name;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_MAIN_VALUE_TYPES_ID_SEQ",
+        sequenceName   = "MAIN_VALUE_TYPES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_MAIN_VALUE_TYPES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "name") // FIXME: Type conversion needed?
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/NamedMainValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,68 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+
+@Entity
+@Table(name = "named_main_values")
+public class NamedMainValue
+implements   Serializable
+{
+    private Integer       id;
+    private String        name;
+    private MainValueType type;
+
+    public NamedMainValue() {
+    }
+
+    public NamedMainValue(String name, MainValueType type) {
+        this.name = name;
+        this.type = type;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_NAMED_MAIN_VALUES_ID_SEQ",
+        sequenceName   = "NAMED_MAIN_VALUES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_NAMED_MAIN_VALUES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "type_id" )
+    public MainValueType getType() {
+        return type;
+    }
+
+    public void setType(MainValueType type) {
+        this.type = type;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/Position.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,71 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.OneToMany;
+import javax.persistence.JoinColumn;
+import javax.persistence.GenerationType;
+
+import java.util.List;
+
+@Entity
+@Table(name = "positions")
+public class Position
+implements   Serializable
+{
+    private Integer id;
+
+    private String  value;
+
+    private List<Annotation> annotations;
+
+    public Position() {
+    }
+
+    public Position(String value) {
+        this.value = value;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_POSITIONS_ID_SEQ",
+        sequenceName   = "POSITIONS_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_POSITIONS_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "value")
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    @OneToMany
+    @JoinColumn(name="position_id")
+    public List<Annotation> getAnnotations() {
+        return annotations;
+    }
+
+    public void setAnnotations(List<Annotation> annotations) {
+        this.annotations = annotations;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/Range.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,144 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+
+@Entity
+@Table(name = "ranges")
+public class Range
+implements   Serializable
+{
+    private Integer    id;
+    private BigDecimal a;
+    private BigDecimal b;
+
+    private River      river;
+
+    public Range() {
+    }
+
+    public Range(double a, double b, River river) {
+        this(new BigDecimal(a), new BigDecimal(b), river);
+    }
+
+    public Range(BigDecimal a, BigDecimal b, River river) {
+        this.a     = a;
+        this.b     = b;
+        this.river = river;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_RANGES_ID_SEQ",
+        sequenceName   = "RANGES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_RANGES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "a") // FIXME: type mapping needed?
+    public BigDecimal getA() {
+        return a;
+    }
+
+    public void setA(BigDecimal a) {
+        this.a = a;
+    }
+
+    @Column(name = "b") // FIXME: type mapping needed?
+    public BigDecimal getB() {
+        return b;
+    }
+
+    public void setB(BigDecimal b) {
+        this.b = b;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "river_id")
+    public River getRiver() {
+        return river;
+    }
+
+    public void setRiver(River river) {
+        this.river = river;
+    }
+
+    public int code() {
+        int code = 0;
+        if (a != null) code  = 1;
+        if (b != null) code |= 2;
+        return code;
+    }
+
+    public boolean intersects(BigDecimal c) {
+        return !(a.compareTo(c) > 0 || b.compareTo(c) < 0);
+    }
+
+    public boolean intersects(Range other) {
+
+        int code  = code();
+        int ocode = other.code();
+
+        if (code == 0 || ocode == 0) {
+            return false;
+        }
+
+        switch (code) {
+            case 1: // has a
+                switch (ocode) {
+                    case 1: // has a
+                        return a.compareTo(other.a) == 0;
+                    case 2: // has b
+                        return a.compareTo(other.b) == 0;
+                    case 3: // has range
+                        return other.intersects(a);
+                }
+                break;
+            case 2: // has b
+                switch (ocode) {
+                    case 1: // has a
+                        return b.compareTo(other.a) == 0;
+                    case 2: // has b
+                        return b.compareTo(other.b) == 0;
+                    case 3: // has range
+                        return other.intersects(b);
+                }
+                break;
+            case 3: // has range
+                switch (ocode) {
+                    case 1: // has a
+                        return intersects(other.a);
+                    case 2: // has b
+                        return intersects(other.b);
+                    case 3: // has range
+                        return !(other.b.compareTo(a) < 0
+                               ||other.a.compareTo(b) > 0);
+                }
+                break;
+
+        }
+
+        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-backend/src/main/java/de/intevation/flys/model/River.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,237 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.OneToMany;
+import javax.persistence.JoinColumn;
+import javax.persistence.GenerationType;
+
+import java.util.List;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import de.intevation.flys.backend.SessionHolder;
+
+
+@Entity
+@Table(name = "rivers")
+public class River
+implements   Serializable
+{
+    public static final MathContext PRECISION = new MathContext(6);
+
+    private Integer id;
+
+    private String  name;
+
+    private boolean kmUp;
+
+    private List<Gauge> gauges;
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_RIVERS_ID_SEQ",
+        sequenceName   = "RIVERS_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_RIVERS_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Column(name = "km_up")
+    public boolean getKmUp() {
+        return kmUp;
+    }
+
+    public void setKmUp(boolean kmUp) {
+        this.kmUp = kmUp;
+    }
+
+    public River() {
+    }
+
+    public River(String name) {
+        this.name = name;
+    }
+
+    @OneToMany
+    @JoinColumn(name="river_id")
+    public List<Gauge> getGauges() {
+        return gauges;
+    }
+
+    public void setGauges(List<Gauge> gauges) {
+        this.gauges = gauges;
+    }
+
+    public String toString() {
+        return name != null ? name : "";
+    }
+
+
+    /**
+     * This method returns the gauges that intersect with <i>a</i> and
+     * <i>b</i>,
+     *
+     * @param a A start point.
+     * @param b An end point.
+     *
+     * @return the intersecting gauges.
+     */
+    public List<Gauge> determineGauges(double a, double b) {
+        Session session = SessionHolder.HOLDER.get();
+
+        if (a > b) { double t = a; a = b; b = t; }
+
+        Query query = session.createQuery(
+            "from Gauge where river=:river " +
+            "and not (range.a > :b or range.b < :a) order by a");
+        query.setParameter("river", this);
+        query.setParameter("a", new BigDecimal(a, PRECISION));
+        query.setParameter("b", new BigDecimal(b, PRECISION));
+
+        return query.list();
+    }
+
+    public Gauge maxOverlap(double a, double b) {
+        List<Gauge> gauges = determineGauges(a, b);
+        if (gauges == null) {
+            return null;
+        }
+
+        if (a > b) { double t = a; a = b; b = t; }
+
+        double max = -Double.MAX_VALUE;
+
+        Gauge result = null;
+
+        for (Gauge gauge: gauges) {
+            Range  r = gauge.getRange();
+            double c = r.getA().doubleValue();
+            double d = r.getB().doubleValue();
+
+            double start = c >= a ? c : a;
+            double stop  = d <= b ? d : b;
+
+            double length = stop - start;
+
+            if (length > max) {
+                max = length;
+                result = gauge;
+            }
+        }
+
+        return result;
+    }
+
+    public Gauge determineGaugeByName(String name) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from Gauge where river=:river and name=:name");
+        query.setParameter("river", this);
+        query.setParameter("name", name);
+        List<Gauge> gauges = query.list();
+        return gauges.isEmpty() ? null : gauges.get(0);
+    }
+
+    public Gauge determineGaugeByPosition(double p) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from Gauge g where river=:river "  +
+            "and :p between g.range.a and g.range.b");
+        query.setParameter("river", this);
+        query.setParameter("p", new BigDecimal(p, PRECISION));
+        List<Gauge> gauges = query.list();
+        return gauges.isEmpty() ? null : gauges.get(0);
+    }
+
+    public Gauge determineGaugeByStation(double a, double b) {
+
+        if (a > b) { double t = a; a = b; b = t; }
+
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from Gauge where river.id=:river " +
+            "and station between :a and :b");
+        query.setParameter("river", getId());
+        query.setParameter("a", new BigDecimal(a));
+        query.setParameter("b", new BigDecimal(b));
+
+        List<Gauge> gauges = query.list();
+        return gauges.isEmpty() ? null : gauges.get(0);
+    }
+
+
+    /**
+     * This method returns the first gauge that is intersected by <i>a</i> and
+     * <i>b</i>,
+     *
+     * @param a A start point.
+     * @param b An end point.
+     *
+     * @return the first intersecting gauge.
+     */
+    public Gauge determineGauge(double a, double b) {
+        List<Gauge> gauges = determineGauges(a, b);
+
+        return gauges.isEmpty() ? null : gauges.get(0);
+    }
+
+    /**
+     * Returns the min and max distance of this river. The first position in the
+     * resulting array contains the min distance, the second position the max
+     * distance.
+     *
+     * @return the min and max distance of this river.
+     */
+    public double[] determineMinMaxDistance() {
+        if (gauges == null) {
+            return null;
+        }
+
+        double minmax[] = new double[] { Double.MAX_VALUE, Double.MIN_VALUE };
+
+        for (Gauge g: gauges) {
+            Range r = g.getRange();
+
+            double a  = r.getA().doubleValue();
+            minmax[0] = minmax[0] < a ? minmax[0] : a;
+
+            BigDecimal bigB = r.getB();
+            if (bigB != null) {
+                double b  = bigB.doubleValue();
+                minmax[1] = minmax[1] > b ? minmax[1] : b;
+            }
+        }
+
+        return minmax;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/TimeInterval.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,67 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+
+@Entity
+@Table(name = "time_intervals")
+public class TimeInterval
+implements   Serializable
+{
+    private Integer id;
+    private Date    startTime;
+    private Date    stopTime;
+
+    public TimeInterval() {
+    }
+
+    public TimeInterval(Date startTime, Date stopTime) {
+        this.startTime = startTime;
+        this.stopTime  = stopTime;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_TIME_INTERVALS_ID_SEQ",
+        sequenceName   = "TIME_INTERVALS_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_TIME_INTERVALS_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Column(name = "start_time") // FIXME: type mapping needed?
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    @Column(name = "stop_time") // FIXME: type mapping needed?
+    public Date getStopTime() {
+        return stopTime;
+    }
+
+    public void setStopTime(Date stopTime) {
+        this.stopTime = stopTime;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/Wst.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,158 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.OneToMany;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import de.intevation.flys.backend.SessionHolder;
+
+
+@Entity
+@Table(name = "wsts")
+public class Wst
+implements   Serializable
+{
+    private static Logger logger = Logger.getLogger(Wst.class);
+
+    private Integer id;
+    private River   river;
+    private String  description;
+    private Integer kind;
+
+    private List<WstColumn> columns;
+
+    public Wst() {
+    }
+
+    public Wst(River river, String description) {
+        this(river, description, 0);
+    }
+
+    public Wst(River river, String description, Integer kind) {
+        this.river       = river;
+        this.description = description;
+        this.kind        = kind;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_WSTS_ID_SEQ",
+        sequenceName   = "WSTS_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_WSTS_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "river_id" )
+    public River getRiver() {
+        return river;
+    }
+
+    public void setRiver(River river) {
+        this.river = river;
+    }
+
+    @Column(name = "description")
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Column(name = "kind")
+    public Integer getKind() {
+        return kind;
+    }
+
+    public void setKind(Integer kind) {
+        this.kind = kind;
+    }
+
+    @OneToMany
+    @JoinColumn(name="wst_id")
+    public List<WstColumn> getColumns() {
+        return columns;
+    }
+
+    public void setColumns(List<WstColumn> columns) {
+        this.columns = columns;
+    }
+
+    /**
+     * Determines the min and max Q values of this WST. The min value is placed
+     * in the first field of the resulting array - the max value is placed in
+     * the second field.
+     *
+     * @return the min and max Q values of this WST.
+     */
+    public double[] determineMinMaxQ() {
+        double[] ab = river.determineMinMaxDistance();
+        return determineMinMaxQ(new Range(ab[0], ab[1], river));
+    }
+
+
+    /**
+     * Determines the min and max Q values of this WST in the given range. The
+     * min value is placed in the first field of the resulting array - the max
+     * value is placed in the second field.
+     *
+     * @param range The range used for querying the Q values.
+     *
+     * @return the min and max Q values of this WST.
+     */
+    public double[] determineMinMaxQ(Range range) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+          "select min(q), max(q) from WstQRange where " +
+          " id in " +
+          "  (select wstQRange.id from WstColumnQRange where " +
+          "    wstColumn.id in (select id from WstColumn where wst.id = :wst)) " +
+          " and range.id in " +
+          "  (select id from Range where not (a > :end or b < :start))");
+
+        query.setParameter("wst",   getId());
+        query.setParameter("start", range.getA());
+        query.setParameter("end",   range.getB());
+
+        List<Object []> results = query.list();
+
+        if (results.isEmpty()) {
+            return null;
+        }
+
+        Object [] result = results.get(0);
+
+        return new double [] {
+            ((BigDecimal)result[0]).doubleValue(),
+            ((BigDecimal)result[1]).doubleValue() };
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/WstColumn.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,134 @@
+package de.intevation.flys.model;
+
+import java.util.List;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.OneToMany;
+
+@Entity
+@Table(name = "wst_columns")
+public class WstColumn
+implements   Serializable
+{
+    private Integer               id;
+    private Wst                   wst;
+    private String                name;
+    private String                description;
+    private Integer               position;
+    private TimeInterval          timeInterval;
+
+    private List<WstColumnQRange> columnQRanges;
+    private List<WstColumnValue>  columnValues;
+
+    public WstColumn() {
+    }
+
+    public WstColumn(
+        Wst          wst,
+        String       name,
+        String       description,
+        Integer      position,
+        TimeInterval timeInterval
+    ) {
+        this.wst          = wst;
+        this.name         = name;
+        this.description  = description;
+        this.position     = position;
+        this.timeInterval = timeInterval;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_WST_COLUMNS_ID_SEQ",
+        sequenceName   = "WST_COLUMNS_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_WST_COLUMNS_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "wst_id" )
+    public Wst getWst() {
+        return wst;
+    }
+
+    public void setWst(Wst wst) {
+        this.wst = wst;
+    }
+
+    @Column(name = "name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Column(name = "description")
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Column(name = "position")
+    public Integer getPosition() {
+        return position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "time_interval_id" )
+    public TimeInterval getTimeInterval() {
+        return timeInterval;
+    }
+
+    public void setTimeInterval(TimeInterval timeInterval) {
+        this.timeInterval = timeInterval;
+    }
+
+    @OneToMany
+    @JoinColumn(name="wst_column_id")
+    public List<WstColumnQRange> getColumnQRanges() {
+        return columnQRanges;
+    }
+
+    public void setColumnQRanges(List<WstColumnQRange> columnQRanges) {
+        this.columnQRanges = columnQRanges;
+    }
+
+    @OneToMany
+    @JoinColumn(name="wst_column_id")
+    public List<WstColumnValue> getColumnValues() {
+        return columnValues;
+    }
+
+    public void setColumnValues(List<WstColumnValue> columnValues) {
+        this.columnValues = columnValues;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/WstColumnQRange.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,73 @@
+package de.intevation.flys.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+
+@Entity
+@Table(name = "wst_column_q_ranges")
+public class WstColumnQRange
+implements   Serializable
+{
+    private Integer   id;
+    private WstColumn wstColumn;
+    private WstQRange wstQRange;
+
+    public WstColumnQRange() {
+    }
+
+    public WstColumnQRange(
+        WstColumn wstColumn,
+        WstQRange wstQRange
+    ) {
+        this.wstColumn = wstColumn;
+        this.wstQRange = wstQRange;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_WST_Q_RANGES_ID_SEQ",
+        sequenceName   = "WST_Q_RANGES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_WST_Q_RANGES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "wst_column_id" )
+    public WstColumn getWstColumn() {
+        return wstColumn;
+    }
+
+    public void setWstColumn(WstColumn wstColumn) {
+        this.wstColumn = wstColumn;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "wst_q_range_id" )
+    public WstQRange getWstQRange() {
+        return wstQRange;
+    }
+
+    public void setWstQRange(WstQRange wstQRange) {
+        this.wstQRange = wstQRange;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/WstColumnValue.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,85 @@
+package de.intevation.flys.model;
+
+import java.math.BigDecimal;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+
+@Entity
+@Table(name = "wst_column_values")
+public class WstColumnValue
+implements   Serializable
+{
+    private Integer    id;
+    private WstColumn  wstColumn;
+    private BigDecimal position;
+    private BigDecimal w;
+
+    public WstColumnValue() {
+    }
+
+    public WstColumnValue(
+        WstColumn  wstColumn,
+        BigDecimal position,
+        BigDecimal w
+    ) {
+        this.wstColumn = wstColumn;
+        this.position  = position;
+        this.w         = w;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_WST_COLUMN_VALUES_ID_SEQ",
+        sequenceName   = "WST_COLUMN_VALUES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_WST_COLUMN_VALUES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "wst_column_id")
+    public WstColumn getWstColumn() {
+        return wstColumn;
+    }
+
+    public void setWstColumn(WstColumn wstColumn) {
+        this.wstColumn = wstColumn;
+    }
+
+    @Column(name = "position") // FIXME: type mapping needed?
+    public BigDecimal getPosition() {
+        return position;
+    }
+
+    public void setPosition(BigDecimal position) {
+        this.position = position;
+    }
+
+    @Column(name = "w") // FIXME: type mapping needed?
+    public BigDecimal getW() {
+        return w;
+    }
+
+    public void setW(BigDecimal w) {
+        this.w = w;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/model/WstQRange.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,70 @@
+package de.intevation.flys.model;
+
+import java.math.BigDecimal;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.GenerationType;
+import javax.persistence.OneToOne;
+import javax.persistence.JoinColumn;
+
+@Entity
+@Table(name = "wst_q_ranges")
+public class WstQRange
+implements   Serializable
+{
+    private Integer    id;
+    private Range      range;
+    private BigDecimal q;
+
+    public WstQRange() {
+    }
+
+    public WstQRange(Range range, BigDecimal q) {
+        this.range = range;
+        this.q     = q;
+    }
+
+    @Id
+    @SequenceGenerator(
+        name           = "SEQUENCE_WST_Q_RANGES_ID_SEQ",
+        sequenceName   = "WST_Q_RANGES_ID_SEQ",
+        allocationSize = 1)
+    @GeneratedValue(
+        strategy  = GenerationType.SEQUENCE,
+        generator = "SEQUENCE_WST_Q_RANGES_ID_SEQ")
+    @Column(name = "id")
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @OneToOne
+    @JoinColumn(name = "range_id" )
+    public Range getRange() {
+        return range;
+    }
+
+    public void setRange(Range range) {
+        this.range = range;
+    }
+
+    @Column(name = "q") // FIXME: type mapping needed?!
+    public BigDecimal getQ() {
+        return q;
+    }
+
+    public void setQ(BigDecimal q) {
+        this.q = 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-backend/src/main/java/de/intevation/flys/utils/DBCPConnectionProvider.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hibernate.connection;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Map;
+
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.dbcp.BasicDataSourceFactory;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.HibernateException;
+
+import org.hibernate.connection.ConnectionProviderFactory;
+import org.hibernate.connection.ConnectionProvider;
+
+import org.hibernate.cfg.Environment;
+
+/**
+ * <p>A connection provider that uses an Apache commons DBCP connection pool.</p>
+ *
+ * <p>To use this connection provider set:<br>
+ * <code>hibernate.connection.provider_class&nbsp;org.hibernate.connection.DBCPConnectionProvider</code></p>
+ *
+ * <pre>Supported Hibernate properties:
+ *   hibernate.connection.driver_class
+ *   hibernate.connection.url
+ *   hibernate.connection.username
+ *   hibernate.connection.password
+ *   hibernate.connection.isolation
+ *   hibernate.connection.autocommit
+ *   hibernate.connection.pool_size
+ *   hibernate.connection (JDBC driver properties)</pre>
+ * <br>
+ * All DBCP properties are also supported by using the hibernate.dbcp prefix.
+ * A complete list can be found on the DBCP configuration page:
+ * <a href="http://jakarta.apache.org/commons/dbcp/configuration.html">http://jakarta.apache.org/commons/dbcp/configuration.html</a>.
+ * <br>
+ * <pre>Example:
+ *   hibernate.connection.provider_class org.hibernate.connection.DBCPConnectionProvider
+ *   hibernate.connection.driver_class org.hsqldb.jdbcDriver
+ *   hibernate.connection.username sa
+ *   hibernate.connection.password
+ *   hibernate.connection.url jdbc:hsqldb:test
+ *   hibernate.connection.pool_size 20
+ *   hibernate.dbcp.initialSize 10
+ *   hibernate.dbcp.maxWait 3000
+ *   hibernate.dbcp.validationQuery select 1 from dual</pre>
+ *
+ * <p>More information about configuring/using DBCP can be found on the
+ * <a href="http://jakarta.apache.org/commons/dbcp/">DBCP website</a>.
+ * There you will also find the DBCP wiki, mailing lists, issue tracking
+ * and other support facilities</p>
+ *
+ * @see org.hibernate.connection.ConnectionProvider
+ * @author Dirk Verbeeck
+ */
+public class DBCPConnectionProvider
+implements   ConnectionProvider
+{
+    private static Logger log = Logger.getLogger(DBCPConnectionProvider.class);
+
+    private static final String PREFIX = "hibernate.dbcp.";
+
+    private BasicDataSource ds;
+
+    // Old Environment property for backward-compatibility
+    // (property removed in Hibernate3)
+    private static final String DBCP_PS_MAXACTIVE =
+        "hibernate.dbcp.ps.maxActive";
+
+    // Property doesn't exists in Hibernate2
+    private static final String AUTOCOMMIT =
+        "hibernate.connection.autocommit";
+
+    public void configure(Properties props) throws HibernateException {
+        try {
+            log.debug("Configure DBCPConnectionProvider");
+
+            // DBCP properties used to create the BasicDataSource
+            Properties dbcpProperties = new Properties();
+
+            // DriverClass & url
+            String jdbcDriverClass = props.getProperty(Environment.DRIVER);
+            String jdbcUrl = props.getProperty(Environment.URL);
+            dbcpProperties.put("driverClassName", jdbcDriverClass);
+            dbcpProperties.put("url", jdbcUrl);
+
+            // Username / password
+            String username = props.getProperty(Environment.USER);
+            String password = props.getProperty(Environment.PASS);
+            dbcpProperties.put("username", username);
+            dbcpProperties.put("password", password);
+
+            // Isolation level
+            String isolationLevel = props.getProperty(Environment.ISOLATION);
+            if (isolationLevel != null
+            && (isolationLevel = isolationLevel.trim()).length() > 0) {
+                dbcpProperties.put("defaultTransactionIsolation", isolationLevel);
+            }
+
+            // Turn off autocommit (unless autocommit property is set)
+            String autocommit = props.getProperty(AUTOCOMMIT);
+            if (autocommit != null
+            && (autocommit = autocommit.trim()).length() > 0) {
+                dbcpProperties.put("defaultAutoCommit", autocommit);
+            } else {
+                dbcpProperties.put("defaultAutoCommit", String.valueOf(Boolean.FALSE));
+            }
+
+            // Pool size
+            String poolSize = props.getProperty(Environment.POOL_SIZE);
+            if (poolSize != null
+            && (poolSize = poolSize.trim()).length() > 0
+            && Integer.parseInt(poolSize) > 0)  {
+                dbcpProperties.put("maxActive", poolSize);
+            }
+
+            // Copy all "driver" properties into "connectionProperties"
+            Properties driverProps =
+                ConnectionProviderFactory.getConnectionProperties(props);
+
+            if (driverProps.size() > 0) {
+                StringBuilder connectionProperties = new StringBuilder();
+                for (Iterator iter = driverProps.entrySet().iterator();
+                    iter.hasNext();
+                ) {
+                    Map.Entry entry = (Map.Entry)iter.next();
+                    String    key   = (String)entry.getKey();
+                    String    value = (String)entry.getValue();
+                    connectionProperties
+                        .append(key)
+                        .append('=')
+                        .append(value);
+                    if (iter.hasNext()) {
+                        connectionProperties.append(';');
+                    }
+                }
+                dbcpProperties.put(
+                    "connectionProperties", connectionProperties.toString());
+            }
+
+            // Copy all DBCP properties removing the prefix
+            for (Iterator iter = props.entrySet().iterator() ; iter.hasNext() ;) {
+                Map.Entry entry = (Map.Entry)iter.next();
+                String    key   = (String)entry.getKey();
+                if (key.startsWith(PREFIX)) {
+                    String property = key.substring(PREFIX.length());
+                    String value    = (String)entry.getValue();
+                    dbcpProperties.put(property, value);
+                }
+            }
+
+            // Backward-compatibility
+            if (props.getProperty(DBCP_PS_MAXACTIVE) != null) {
+                dbcpProperties.put(
+                    "poolPreparedStatements",
+                    String.valueOf(Boolean.TRUE));
+                dbcpProperties.put(
+                    "maxOpenPreparedStatements",
+                    props.getProperty(DBCP_PS_MAXACTIVE));
+            }
+
+            // Some debug info
+            /* // commented out, because it leaks the password
+            if (log.isDebugEnabled()) {
+                log.debug("Creating a DBCP BasicDataSource" +
+                          " with the following DBCP factory properties:");
+                StringWriter sw = new StringWriter();
+                dbcpProperties.list(new PrintWriter(sw, true));
+                log.debug(sw.toString());
+            }
+            */
+
+            // Let the factory create the pool
+            ds = (BasicDataSource)BasicDataSourceFactory
+                .createDataSource(dbcpProperties);
+
+            // The BasicDataSource has lazy initialization
+            // borrowing a connection will start the DataSource
+            // and make sure it is configured correctly.
+
+            // Connection conn = ds.getConnection();
+            // conn.close();
+        }
+        catch (Exception e) {
+            String message = "Could not create a DBCP pool";
+            log.fatal(message, e);
+            if (ds != null) {
+                BasicDataSource x = ds; ds = null;
+                try {
+                    x.close();
+                }
+                catch (SQLException sqle) {
+                }
+            }
+            throw new HibernateException(message, e);
+        }
+        log.debug("Configure DBCPConnectionProvider complete");
+    }
+
+    public Connection getConnection() throws SQLException {
+        return ds.getConnection();
+    }
+
+    public void closeConnection(Connection conn) throws SQLException {
+        conn.close();
+    }
+
+    public void close() throws HibernateException {
+        try {
+            if (ds != null) {
+                BasicDataSource x = ds; ds = null;
+                x.close();
+            }
+        }
+        catch (SQLException sqle) {
+            throw new HibernateException("Could not close DBCP pool", sqle);
+        }
+    }
+
+    public boolean supportsAggressiveRelease() {
+        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-backend/src/main/java/de/intevation/flys/utils/FileTools.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,78 @@
+package de.intevation.flys.utils;
+
+import java.io.File;
+
+import java.util.Stack;
+
+import org.apache.log4j.Logger;
+
+public class FileTools
+{
+    private static Logger log = Logger.getLogger(FileTools.class);
+
+    private FileTools() {
+    }
+
+    public static File repair(File file) {
+        file = file.getAbsoluteFile();
+        if (file.exists()) {
+            return file;
+        }
+        Stack<String> parts = new Stack<String>();
+        File curr = file;
+        while (curr != null) {
+            String name = curr.getName();
+            if (name.length() > 0) {
+                parts.push(curr.getName());
+            }
+            curr = curr.getParentFile();
+        }
+
+        curr = null;
+        OUTER: while (!parts.isEmpty()) {
+            String f = parts.pop();
+            log.debug("fixing: '" + f + "'");
+            if (curr == null) {
+                // XXX: Not totaly correct because there
+                // more than one root on none unix systems.
+                for (File root: File.listRoots()) {
+                    File [] files = root.listFiles();
+                    if (files == null) {
+                        log.warn("cannot list '" + root);
+                        continue;
+                    }
+                    for (File candidate: files) {
+                        if (candidate.getName().equalsIgnoreCase(f)) {
+                            curr = new File(root, candidate.getName());
+                            continue OUTER;
+                        }
+                    }
+                }
+                break;
+            }
+            else {
+                File [] files = curr.listFiles();
+                if (files == null) {
+                    log.warn("cannot list: '" + curr + "'");
+                    return file;
+                }
+                for (File candidate: files) {
+                    if (candidate.getName().equalsIgnoreCase(f)) {
+                        curr = new File(curr, candidate.getName());
+                        continue OUTER;
+                    }
+                }
+                curr = null;
+                break;
+            }
+        }
+
+        if (curr == null) {
+            log.warn("cannot repair path '" + file + "'");
+            return file;
+        }
+
+        return curr;
+    }
+}
+// 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-backend/src/main/java/de/intevation/flys/utils/StringUtil.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,787 @@
+package de.intevation.flys.utils;
+
+/**
+ * Copyright (c) 2006 by Intevation GmbH
+ *
+ * @author Sascha L. Teichmann (teichmann@intevation.de)
+ * @author Ludwig Reiter       (ludwig@intevation.de)
+ *
+ * This program is free software under the LGPL (&gt;=v2.1)
+ * Read the file LGPL coming with FLYS for details.
+ */
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import java.net.URLEncoder;
+import java.net.URLDecoder;
+
+import java.io.UnsupportedEncodingException;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+
+public final class StringUtil {
+    final static String NUMBER_SEPERATOR = ";";
+    final static String LINE_SEPERATOR = ":";
+
+    private StringUtil() {
+    }
+
+    public static final String double2DArrayToString(double[][] values) {
+
+        if (values == null) {
+            throw new IllegalArgumentException("keine double[][]-Werte");
+        }
+
+        StringBuilder strbuf = new StringBuilder();
+
+        for (int i=0; i < values.length; i++) {
+            if (i>0) {
+                strbuf.append(LINE_SEPERATOR);
+            }
+            for (int j=0; j < values[i].length; j++) {
+                if (j > 0) {
+                    strbuf.append(NUMBER_SEPERATOR);
+                }
+                strbuf.append(values[i][j]);
+            }
+        }
+
+        return strbuf.toString();
+    }
+
+    public static final double[][] stringToDouble2DArray(String str) {
+        if (str == null || str.length() == 0) {
+            return null;
+        }
+
+        String[] lineSplit = str.split(LINE_SEPERATOR);
+        double[][] array2D = new double[lineSplit.length][];
+        for (int i=0; i < lineSplit.length; i++) {
+            String[] numberSplit =  lineSplit[i].split(NUMBER_SEPERATOR);
+
+            double[] numbers = new double[numberSplit.length];
+            for (int j=0; j < numberSplit.length; j++) {
+                numbers[j] = Double.valueOf(numberSplit[j]).doubleValue();
+            }
+
+            array2D[i] = numbers;
+        }
+
+        return array2D;
+    }
+
+    public static final String [] splitLines(String s) {
+        if (s == null) {
+            return null;
+        }
+        ArrayList<String> list = new ArrayList<String>();
+
+        BufferedReader in = null;
+
+        try {
+            in =
+                new BufferedReader(
+                new StringReader(s));
+
+            String line;
+
+            while ((line = in.readLine()) != null) {
+                list.add(line);
+            }
+        }
+        catch (IOException ioe) {
+            return null;
+        }
+        finally {
+            if (in != null)
+                try {
+                    in.close();
+                }
+                catch (IOException ioe) {}
+        }
+
+        return list.toArray(new String[list.size()]);
+    }
+
+    public static final String concat(String [] s) {
+        return concat(s, null);
+    }
+
+    public static final String concat(String [] s, String glue) {
+        if (s == null) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < s.length; ++i) {
+            if (i > 0 && glue != null) {
+                sb.append(glue);
+            }
+            sb.append(s[i]);
+        }
+        return sb.toString();
+    }
+
+    public static final String [] splitAfter(String [] src, int N) {
+        if (src == null) {
+            return null;
+        }
+
+        ArrayList<String> list = new ArrayList<String>(src.length);
+        for (int i = 0; i < src.length; ++i) {
+            String s = src[i];
+            int R;
+            if (s == null || (R = s.length()) == 0) {
+                list.add(s);
+            }
+            else {
+                while (R > N) {
+                    list.add(s.substring(0, N));
+                    s = s.substring(N);
+                    R = s.length();
+                }
+                list.add(s);
+            }
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
+    public static final String [] splitQuoted(String s) {
+        return splitQuoted(s, '"');
+    }
+
+    public static final String[] fitArray(String [] src, String [] dst) {
+        if (src == null) {
+            return dst;
+        }
+        if (dst == null) {
+            return src;
+        }
+
+        if (src.length == dst.length) {
+            return src;
+        }
+
+        System.arraycopy(src, 0, dst, 0, Math.min(dst.length, src.length));
+
+        return dst;
+    }
+
+    public static final String [] splitQuoted(String s, char quoteChar) {
+        if (s == null) {
+            return null;
+        }
+        ArrayList<String> l = new ArrayList<String>();
+        int mode = 0, last_mode = 0;
+        StringBuilder sb = new StringBuilder();
+        for (int N = s.length(), i = 0; i < N; ++i) {
+            char c = s.charAt(i);
+            switch (mode) {
+                case 0: // unquoted mode
+                    if (c == quoteChar) {
+                        mode = 1; // to quoted mode
+                        if (sb.length() > 0) {
+                            l.add(sb.toString());
+                            sb.setLength(0);
+                        }
+                    }
+                    else if (c == '\\') {
+                        last_mode = 0;
+                        mode = 2; // escape mode
+                    }
+                    else if (!Character.isWhitespace(c)) {
+                        sb.append(c);
+                    }
+                    else if (sb.length() > 0) {
+                        l.add(sb.toString());
+                        sb.setLength(0);
+                    }
+                    break;
+                case 1: // quote mode
+                    if (c == '\\') {
+                        last_mode = 1;
+                        mode = 2; // escape mode
+                    }
+                    else if (c == quoteChar) { // leave quote mode
+                        l.add(sb.toString());
+                        sb.setLength(0);
+                        mode = 0; // to unquoted mode
+                    }
+                    else {
+                        sb.append(c);
+                    }
+                    break;
+                case 2: // escape mode
+                    sb.append(c);
+                    mode = last_mode;
+                    break;
+            }
+        }
+        if (sb.length() > 0) {
+            l.add(sb.toString());
+        }
+        return l.toArray(new String[l.size()]);
+    }
+
+    public static final String [] splitUnique(String s) {
+        return splitUnique(s, "[\\s,]+");
+    }
+
+    public static final String [] splitUnique(String s, String sep) {
+        return s != null ? unique(s.split(sep)) : null;
+    }
+
+    public static final String [] unique(String [] str) {
+        if (str == null || str.length == 1) {
+            return str;
+        }
+
+        Arrays.sort(str);
+
+        for (int i = 1; i < str.length; ++i)
+            if (str[i].equals(str[i-1])) {
+                ArrayList<String> list = new ArrayList<String>(str.length);
+
+                for (int j = 0; j < i; ++j) {
+                    list.add(str[j]);
+                }
+
+                String last = str[i];
+
+                for (++i; i < str.length; ++i)
+                    if (!last.equals(str[i])) {
+                        list.add(last = str[i]);
+                    }
+
+                return list.toArray(new String[list.size()]);
+            }
+
+        return str;
+    }
+
+    public static final String [] ensureEmptyExistence(String [] str) {
+        if (str == null) {
+            return null;
+        }
+
+        for (int i = 0; i < str.length; ++i)
+            if (str[i].length() == 0) {
+                if (i != 0) { // copy to front
+                    String t = str[0];
+                    str[0] = str[i];
+                    str[i] = t;
+                }
+                return str;
+            }
+
+        String [] n = new String[str.length+1];
+        n[0] = "";
+        System.arraycopy(str, 0, n, 1, str.length);
+        return n;
+    }
+
+    public static final String ensureWidthPadLeft(String s, int width, char pad) {
+        int N = s.length();
+        if (N >= width) {
+            return s;
+        }
+        StringBuilder sb = new StringBuilder(width);
+        for (; N < width; ++N) {
+            sb.append(pad);
+        }
+        sb.append(s);
+        return sb.toString();
+    }
+
+    public static final String [] splitWhiteSpaceWithNAsPad(
+        String s,
+        int    N,
+        String pad
+    ) {
+        if (s == null) {
+            return null;
+        }
+
+        boolean copyChars = true;
+        int     count     = 0; // number of WS
+
+        int S = s.length();
+
+        ArrayList<String> parts = new ArrayList<String>();
+
+        StringBuilder part = new StringBuilder(S);
+
+        for (int i = 0; i < S; ++i) {
+            char c = s.charAt(i);
+            if (copyChars) { // char mode
+                if (Character.isWhitespace(c)) {
+                    if (part.length() > 0) {
+                        parts.add(part.toString());
+                        part.setLength(0);
+                    }
+                    count     = 1;
+                    copyChars = false; // to WS mode
+                }
+                else {
+                    part.append(c);
+                }
+            }
+            else { // counting WS
+                if (Character.isWhitespace(c)) {
+                    ++count;
+                }
+                else {
+                    while (count >= N) {// enough to insert pad?
+                        parts.add(pad);
+                        count -= N;
+                    }
+                    part.append(c);
+                    count     = 0;
+                    copyChars = true; // back to char mode
+                }
+            }
+        } // for all chars
+
+        if (copyChars) {
+            if (part.length() > 0) {
+                parts.add(part.toString());
+            }
+        }
+        else {
+            while (count >= N) { // enough to insert pad?
+                parts.add(pad);
+                count -= N;
+            }
+        }
+
+        return parts.toArray(new String[parts.size()]);
+    }
+
+    public static final String encodeURL(String url) {
+        try {
+            return url != null
+                   ? URLEncoder.encode(url, "UTF-8")
+                   : "";
+        }
+        catch (UnsupportedEncodingException usee) {
+            throw new RuntimeException(usee.getLocalizedMessage());
+        }
+    }
+
+    public static final String decodeURL(String url) {
+        try {
+            return url != null
+                   ? URLDecoder.decode(url, "UTF-8")
+                   : "";
+        }
+        catch (UnsupportedEncodingException usee) {
+            throw new RuntimeException(usee.getLocalizedMessage());
+        }
+    }
+
+    public static final boolean isEmpty(String s) {
+        return s == null || s.length() == 0;
+    }
+
+    public static final String empty(String s) {
+        return s == null ? "" : s;
+    }
+
+
+    public static final String trim(String s) {
+        return s != null ? s.trim() : null;
+    }
+
+    public static final String uniqueWhitespaces(String s) {
+        if (s == null) {
+            return null;
+        }
+
+        boolean wasWS = false;
+        StringBuilder sb = new StringBuilder();
+
+        for (int N = s.length(), i = 0; i < N; ++i) {
+            char c = s.charAt(i);
+            if (Character.isWhitespace(c)) {
+                if (!wasWS) {
+                    sb.append(c);
+                    wasWS = true;
+                }
+            }
+            else {
+                sb.append(c);
+                wasWS = false;
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public static final String replaceNewlines(String s) {
+        return s == null
+               ? null
+               : s.replace('\r', ' ').replace('\n', ' ');
+    }
+
+    /*
+    public static final String quoteReplacement(String s) {
+
+        if (s == null || (s.indexOf('\\') == -1 && s.indexOf('$') == -1))
+            return s;
+
+        StringBuilder sb = new StringBuilder();
+
+        for (int N = s.length(), i = 0; i < N; ++i) {
+            char c = s.charAt(i);
+            if (c == '\\' || c == '$') sb.append('\\');
+            sb.append(c);
+        }
+
+        return sb.toString();
+    }
+    */
+
+    public static final String quoteReplacement(String s) {
+
+        if (s == null) {
+            return null;
+        }
+
+        for (int N = s.length(), i = 0; i < N; ++i) { // plain check loop
+            char c = s.charAt(i);
+            if (c == '$' || c == '\\') { // first special -> StringBuilder
+                StringBuilder sb = new StringBuilder(s.substring(0, i))
+                .append('\\')
+                .append(c);
+                for (++i; i < N; ++i) { // build StringBuilder with rest
+                    if ((c = s.charAt(i)) == '$' || c == '\\') {
+                        sb.append('\\');
+                    }
+                    sb.append(c);
+                }
+                return sb.toString();
+            }
+        }
+
+        return s;
+    }
+
+    public static final String repeat(String what, int times) {
+        return repeat(what, times, new StringBuilder()).toString();
+    }
+
+    public static final StringBuilder repeat(String what, int times, StringBuilder sb) {
+        while (times-- > 0) {
+            sb.append(what);
+        }
+        return sb;
+    }
+
+    /**
+     * Gibt den Dateinamen in S ohne Dateiendung zurück.
+     */
+    public static final String cutExtension(String s) {
+        if (s == null) {
+            return null;
+        }
+        int dot = s.lastIndexOf('.');
+        return dot >= 0
+               ? s.substring(0, dot)
+               : s;
+    }
+
+    public static final String extension(String s) {
+        if (s == null) {
+            return null;
+        }
+        int dot = s.lastIndexOf('.');
+        return dot >= 0
+               ? s.substring(dot+1)
+               : s;
+    }
+
+    public static final String [] splitExtension(String x) {
+        if (x == null) {
+            return null;
+        }
+        int i = x.lastIndexOf('.');
+        return i < 0
+               ? new String[] { x, null }
+               : new String[] { x.substring(0, Math.max(0, i)), x.substring(i+1).toLowerCase() };
+    }
+
+    public static String entityEncode(String s) {
+        if (s == null || s.length() == 0) {
+            return s;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i=0, N =s.length(); i < N; i++) {
+            char c = s.charAt(i);
+            switch (c) {
+                case '<':
+                    sb.append("&lt;");
+                    break;
+                case '>':
+                    sb.append("&gt;");
+                    break;
+                case '&':
+                    sb.append("&amp;");
+                    break;
+                default:
+                    sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String entityDecode(String s) {
+        if (s == null || s.length() == 0) {
+            return s;
+        }
+
+        boolean amp = false;
+        StringBuilder sb = new StringBuilder();
+        StringBuilder ampbuf = new StringBuilder();
+        for (int i=0, N =s.length(); i < N; i++) {
+            char c = s.charAt(i);
+            if (amp) {
+                if (c == ';') {
+                    amp = false;
+                    String str = ampbuf.toString();
+                    ampbuf.setLength(0);
+                    if (str.equals("lt")) {
+                        sb.append('<');
+                    }
+                    else if (str.equals("gt")) {
+                        sb.append('>');
+                    }
+                    else if (str.equals("amp")) {
+                        sb.append('&');
+                    }
+                    else {
+                        sb.append('&').append(str).append(';');
+                    }
+                }
+                else {
+                    ampbuf.append(c);
+                }
+            }
+            else if (c=='&') {
+                amp = true;
+            }
+            else {
+                sb.append(c);
+            }
+
+        }
+        return sb.toString();
+    }
+
+    public static final String quote(String s) {
+        return quote(s, '"');
+    }
+
+    public static final String quote(String s, char quoteChar) {
+        if (s == null) {
+            return null;
+        }
+
+        int N = s.length();
+
+        if (N == 0)
+            return new StringBuilder(2)
+                   .append(quoteChar)
+                   .append(quoteChar)
+                   .toString();
+
+        StringBuilder sb = null;
+
+        int i = 0;
+
+        for (; i < N; ++i) {
+            char c = s.charAt(i);
+
+            if (Character.isWhitespace(c)) {
+                sb = new StringBuilder()
+                .append(quoteChar)
+                .append(s.substring(0, i+1));
+                break;
+            }
+            else if (c == quoteChar) {
+                sb = new StringBuilder()
+                .append(quoteChar)
+                .append(s.substring(0, i))
+                .append('\\')
+                .append(quoteChar);
+                break;
+            }
+        }
+
+        if (sb == null) {
+            return s;
+        }
+
+        for (++i; i < N; ++i) {
+            char c = s.charAt(i);
+            if (c == quoteChar || c == '\\') {
+                sb.append('\\');
+            }
+
+            sb.append(c);
+        }
+
+        return sb.append(quoteChar).toString();
+    }
+
+    /*
+    public static String sprintf(String format, Object... args) {
+        return sprintf(null, format, args);
+    }
+    */
+
+    public static String sprintf(Locale locale, String format, Object ... args) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        pw.printf(locale, format, args);
+        pw.flush();
+        return sw.toString();
+    }
+
+
+    public static void testQuote() {
+        System.err.println("testing quote:");
+
+        String cases []  = {
+            "",          "''",
+            "test",      "test",
+            "test test", "'test test'",
+            "  test",    "'  test'",
+            "test   ",   "'test   '",
+            " test ",    "' test '",
+            "'test",     "'\\'test'",
+            "'",         "'\\''",
+            " ' ' ",     "' \\' \\' '",
+            "te'st",     "'te\\'st'"
+        };
+
+        int failed = 0;
+
+        for (int i = 0; i < cases.length; i += 2) {
+            String in  = cases[i];
+            String out = cases[i+1];
+
+            String res = quote(in, '\'');
+            if (!res.equals(out)) {
+                ++failed;
+                System.err.println(
+                    "quote failed on: >" + in +
+                    "< result: >" + res +
+                    "< expected: >" + out + "<");
+            }
+        }
+
+        int T = cases.length/2;
+
+        System.err.println("tests total: "  + T);
+        System.err.println("tests failed: " + failed);
+        System.err.println("tests passed: " + (T - failed));
+    }
+
+    public static void testQuoteReplacement() {
+        System.err.println("testing quoteReplacement:");
+
+        String cases []  = {
+            "",          "",
+            "test",      "test",
+            "$",         "\\$",
+            "\\",        "\\\\",
+            "\\$",       "\\\\\\$",
+            "test\\$",   "test\\\\\\$",
+            "\\test",    "\\\\test",
+            "test$",     "test\\$",
+            "test$test", "test\\$test",
+            "$test$",    "\\$test\\$"
+        };
+
+        int failed = 0;
+
+        for (int i = 0; i < cases.length; i += 2) {
+            String in  = cases[i];
+            String out = cases[i+1];
+
+            String res = quoteReplacement(in);
+            if (!res.equals(out)) {
+                ++failed;
+                System.err.println(
+                    "quoteReplacement failed on: '" + in +
+                    "' result: '" + res +
+                    "' expected: '" + out + "'");
+            }
+        }
+
+        int T = cases.length/2;
+
+        System.err.println("tests total: "  + T);
+        System.err.println("tests failed: " + failed);
+        System.err.println("tests passed: " + (T - failed));
+    }
+
+    public static void testStringArray2D() {
+        int total = 0;
+        int fail = 0;
+        int passed = 0;
+
+        System.err.println("testing StringArray2D:");
+
+        double[][] testarray = {{1.0, 2.0, 3.0},
+            {1.1, 2.1, 3.1},
+            {100.2, 200.2}
+        };
+        String str = double2DArrayToString(testarray);
+
+        total += 1;
+        if (str.equals("1.0;2.0;3.0:1.1;2.1;3.1:100.2;200.2")) {
+            passed +=1;
+        }
+        else {
+            fail +=1;
+            System.err.println("Der Ergebnis-String ist nicht richtig:");
+            System.err.println(str);
+        }
+
+
+
+        double[][] testarray2 = stringToDouble2DArray(str);
+        boolean failed = false;
+
+        total +=1;
+        for (int i=0; i < testarray.length; i++)
+            for (int j=0; j < testarray[i].length; j++)
+                if (testarray[i][j] != testarray2[i][j]) {
+                    System.err.println("Test scheitert bei i=" +i +" j=" +j);
+                    System.err.println("alter Wert=" + testarray[i][j] +" neuer Wert=" +testarray2[i][j]);
+                    failed = true;
+                }
+        if (failed) {
+            fail +=1;
+        }
+        else {
+            passed +=1;
+        }
+        System.err.println("tests total: "+ total);
+        System.err.println("tests failed: "+ fail);
+        System.err.println("tests passed: "+ passed);
+    }
+
+    public static void main(String [] args) {
+
+        testQuoteReplacement();
+        testQuote();
+        testStringArray2D();
+    }
+}
+// end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/src/test/java/de/intevation/flys/AppTest.java	Fri Sep 28 12:14:15 2012 +0200
@@ -0,0 +1,38 @@
+package de.intevation.flys;
+
+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