changeset 427:2c2981e53d4e

dummy merge for repo head
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:15:11 +0200
parents 7536a3288fc6 (diff) 98f6e6ae9498 (current diff)
children d01e9f6a3dc1
files
diffstat 93 files changed, 6503 insertions(+), 886 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Jun 28 07:51:17 2011 +0000
+++ b/ChangeLog	Fri Sep 28 12:15:11 2012 +0200
@@ -1,3 +1,1077 @@
+2012-09-26	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java:
+	  New method addOutput to allow subclasses to add Output object manually.
+
+2012-09-17  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.9.1
+
+2012-09-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts/pom.xml, pom.xml: source 1.5 -> 1.6
+	  (@Override annotations for interface implementations is 1.6).
+
+2012-09-07	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Fix queries for finding a user.
+
+2012-09-07  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.9
+
+2012-08-30	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultSection.java:
+	  Use LinkedHashMap to keep insertion order of attributes.
+
+2012-08-27	Christian Lins	<christian.lins@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifacts/src/main/java/de/intevation/artifacts/CallContext.java:
+	  Fix for NPE on empty user database.
+
+2012-08-24	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/rest/FindUserResource.java:
+	  Add REST service to be able to find a user by its account name.
+
+2012-08-24	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java:
+	  Implement findUser method. The findUser method in ArtifactDatabase
+	  converts a User object into its XML representation.
+
+2012-08-24	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java,
+	  artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Implement method to find/get a user by its account name.
+
+2012-08-23	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Also add the account information when listing users.
+
+2012-08-23	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Add account information to createUser
+
+2012-08-23	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Read the account information from the database.
+
+2012-08-23	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/LazyBackendUser.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultUserFactory.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultUser.java:
+	  Extend user class implementations to handle account information.
+
+2012-08-23	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/UserFactory.java,
+	  artifacts/src/main/java/de/intevation/artifacts/User.java:
+	  Extend user interfaces to handle account information.
+
+2012-08-23	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Update sql user queries to add the account column.
+
+2012-08-23	Björn Ricks	<bjoern.ricks@intevation.de>
+
+	* artifact-database/doc/schema-pg.sql,
+	  artifact-database/doc/schema-h2.sql:
+	  Add a account column to the users table.
+
+2012-07-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/FacetActivity.java:
+	  Refactored registry to use a Chain-of-responsibility pattern. This allows
+	  de-centralized facet activity treatment like in the facet producing states.
+
+2012-07-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/FacetActivity.java:
+	  Made Registry.getInstance() access static.
+
+2012-07-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/FacetActivity.java:
+	  New. Implementors of this interface can decide if a facet should be initially
+	  active or not. Contains a registry singleton.
+
+2012-07-27  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged trunk as '2.8.1'
+
+2012-07-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/StringUtils.java:
+	  Added concat() to join two String arrays.
+
+2012-07-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollection.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollectionFactory.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/transition/TransitionEngine.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/ProtocolUtils.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java:
+	  Doc fixes.
+
+2012-07-16  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged trunk as '2.8'
+
+2012-07-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/StringUtils.java:
+	  Added concat() to join two String arrays.
+
+2012-07-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollection.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollectionFactory.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/transition/TransitionEngine.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/ProtocolUtils.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java:
+	  Doc fixes.
+
+2012-07-16  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged trunk as '2.8'
+
+2012-07-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultSettings.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultSection.java,
+	  artifacts/src/main/java/de/intevation/artifacts/DataProvider.java,
+	  artifacts/src/main/java/de/intevation/artifacts/ArtifactFactory.java:
+	  Removed same package imports.
+
+2012-07-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/StringUtils.java:
+	  Added contains(String needle, String [] haystack) method.
+
+2012-07-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/.settings/org.eclipse.jdt.core.prefs: Removed from
+	  version control (Eclipse configurations should not be in SVN!).
+
+2012-02-26	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/rest/UserResource.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java:
+	  Cosmetics, doc.
+
+2012-06-23	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/**/*.java:
+	  Removed trailing whitespace.
+
+2012-06-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java:
+	  Added some more debug output during the process of removing Artifacts
+	  and Collections with the DatabaseCleaner.
+
+2012-06-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Remove extra <map></map> from the XML generated by JSON lists containing only 
+	  objects.
+
+2012-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged trunk as '2.7'
+
+2012-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/DateUtils.java:
+	  New utility class that provides functions that helps working with dates.
+
+2012-05-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/model/KVP.java:
+	  New class to store generic keys and values.
+
+2012-05-07  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Added method to create a document from string.
+
+2012-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/State.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java:
+	  Defined and implemented a method getHelpText().
+
+2012-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XSLTransformer.java:
+	  Widen parameters to store more than strings.
+
+2012-04-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/JSON.java:
+	  Added toJSONString() method.
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Fixed building XML documents from JSON strings.
+	
+2012-04-25  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/FileTools.java:
+	  Added method to extract zip archives to a specified directory.
+
+2012-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XSLTransformer.java:
+	  New. Added new XSLT processor similiar to old GNV one.
+
+2012-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/JSON.java:
+	  New. JSON parser from Artefact Server NG (which relies on JSON more heavily).
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Added method to convert JSON to XML. This is needed by the client which
+	  internally uses JSON to talk to the server which services only understands
+	  XML atm.
+	
+2012-04-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Modified services so that they are now able to return more than just
+	XML documents. Needed if you want to return e.g. a PNG image from a service.
+
+	* artifacts/src/main/java/de/intevation/artifacts/Service.java:
+	  A service now returns a tuple (data, MIME type).
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Adjusted calls.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java:
+	  Returns a DefaultService.Output by default.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/XMLService.java: New.
+	  Convenience sub class if DefaultService to produce XML documents. Old
+	  Services should derive from this class now to main compatibility.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ServiceResource.java:
+	  Adjusted to handle the new Output tuples.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ByteArrayRepresentation.java:
+	  New. Wraps a byte array as a Restlet representation.
+
+2012-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultOutput.java:
+	  Avoid adding duplicated facets into the output in addFacet().
+
+2012-02-10	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Facet.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/ArtifactAndFacet.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java:
+	  Pass context when asking facet for keys to write on 'blackboard'.
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/ArtifactAndFacet.java:
+	  Added a method setFacetDescription() which sets an alternative
+	  description for the facet. getFacetDescription() will now return the
+	  Facet's descritpion itself if no alternative description has been set;
+	  otherwise the alternative description.
+
+012-02-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/StringUtils.java(join):
+	  Added static method to join strings with a separator.
+
+012-01-30	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/ArtifactAndFacet.java:
+	  Added Override annotations.
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/StateEngine.java:
+	  Added a method that returns a StateData objects of a specific Artifact
+	  based on the Artifact's StateData's name.
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java:
+	  Bugfix: search for StateData objects by name only the State has
+	  StateData objects set.
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java:
+	  Added methods to extract the default value for a min and max item.
+
+2012-01-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java:
+	  Added methods to retrieve the min and max values of a data item.
+
+2012-01-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/FileTools.java:
+	  Replaced legacy java.util.Stack with java.util.Deque.
+
+2012-01-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifact.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollectionFactory.java,
+	  artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java,
+	  artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Cosmetics.
+
+2012-01-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java:
+	  Instance vars customValues and dataProviders are now of type Map 
+	  and not HashMap.
+
+2012-01-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/State.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java:
+	  Added the owner Artifact as further parameter to initialize().
+
+2012-01-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/FileTools.java:
+	  Added new functions to copy files (copyFile()) and to copy directories
+	  (copyDirectory()).
+
+2012-01-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/State.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java:
+	  Added an initialize() method which might be used to initialize the State
+	  based on model Artifact.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java:
+	  (getDataProvider): Never return null.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Output.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultOutput.java:
+	  Added a new method setFacets(List<Facet>) to replace an existing list of
+	  Facets.
+
+2011-12-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Attribute.java:
+	  Added a setValue(Object) method and modified the signature of toXML()
+	  which now returns a Node.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultAttribute.java:
+	  New. A default implementation of Attribute.
+
+2011-12-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Settings.java:
+	  Added a removeSection(Section) method.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Section.java:
+	  Improved the interface to allow section having subsections. Therefore,
+	  addSubsection(Section), getSubsectionCount() and getSubsection(int) have
+	  been added. In addition, a getId() method has been added which is used
+	  in toXML() to create a new DOM Node. The Node name is the result of
+	  getId().
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultSettings.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultSection.java:
+	  Default implementations for Settings and Section.
+
+2011-12-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Settings.java:
+	  An interface that describes a flat API for specifying settings for
+	  something. A Settings object can store one or more Section instances and
+	  defines a toXML() operation that should append a XML representation of
+	  itself to a given parent Node.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Section.java:
+	  This interface is used to describe an API for storing and retrieving
+	  Attribute objects. Just as the Settings interface, it defines a toXML()
+	  operation that should append a XML representation of itself to a given
+	  parent Node.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Attribute.java:
+	  The interface for concrete attributes in a Section instance. An Attribute
+	  is the placed on the lowest level of the Settings hierachy and should be
+	  used to save concrete key value pairs. Even the Attribute defines the
+	  toXML() operation described above.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Output.java:
+	  Added a getSettings() and setSettings(Settings) operation.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultOutput.java:
+	  A DefaultOutput is now able to store a Settings instance. It implements
+	  getSettings() and setSettings(Settings) defined in the Output interface.
+
+2011-12-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Facet.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java:
+	  (getDataProviderKeys): Added parameter artifact.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/ArtifactAndFacet.java:
+	  Pass artifact to changed getDataProviderKeys of Facet.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java:
+	  (getDataProvider): Never return null, empty list instead.
+
+2011-11-30	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/ArtifactAndFacet.java
+	  (getFacetDescription): New, access facets description.
+
+2011-11-30	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added convenienve bundle of artifact and facet that implements
+	DataProvider. This will help keeping things together in the upcoming
+	inter-facet pre-rendering communication ('blackboard') phase.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/ArtifactAndFacet.java:
+	  New class, keeping together an artifact and a facet (thus, should
+	  only have a short live span). Only implementation of the new
+	  DataProvider interface.
+
+2011-11-30	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Extended CallContext to also act as a blackboard/service broker.
+
+	* artifacts/src/main/java/de/intevation/artifacts/CallContext.java
+	  (getDataProvider,registerDataProvider): New functions to register/
+	  consume data providers.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java
+	  (getDataProvider,registerDataProvider): Implemented.
+
+2011-11-30	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added DataProvider interface that defines api for inter-facet
+	communication.
+
+	* artifacts/src/main/java/de/intevation/artifacts/DataProvider.java:
+	  New interface to prepare inter-facet pre-rendering communication
+	  phase ('blackboard').
+
+2011-11-30	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Prepare inter-facet pre-rendering communication ('blackboard') phase.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Facet.java
+	  (getDataProviderKeys,provideBlackboardData): Extended interface to
+	  allow easy integration of blackboard mechanism (inter-facet
+	  pre-rendering communication).
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java
+	  (getDataProviderKeys,provideBlackboardData): Trivial implementation.
+	  The DefaultFacet will not talk to other facets during inter-facet
+	  pre-rendering ("blackboard") phase. Cosmetics, docs.
+
+2011-11-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Changed Ingo's last commit to a more conservative notation (no auto-boxing).
+	  Strangely the flys-client does not compile for me with the auto-boxed
+	  syntax with OpenJDK!
+
+2011-11-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Added a method that allows to specify if the document needs to be
+	  namespace aware or not while parsing from InputStream.
+
+2011-11-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java:
+	  Enabled "post-describe" hooks for the Artifact's describe operation.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Let StateEngine compute a compatibiliy matrix based on given State
+	IDs.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/StateEngine.java
+	  (getCompatibleFacets): New. Compute output/facet compatibility
+	  matrix based on given states.
+
+2011-10-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties:
+	  Bugfix: added missing bracket to an sql statement.
+
+2011-10-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java(getMasterArtifact):
+	  Limit result set to one row.
+
+2011-10-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix accidentally corruptd key to sql statement.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Fix key.
+
+2011-10-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add backend and db functionality query artifact which is belongs to
+	a collection the longest (not neccessary the longest artifact itself).
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java
+	  (getCollectionsMasterArtifact): New. Access master artifact for
+	   given collection (masterartifact defined as artifact which belongs
+	   to the collection the longest).
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java
+	  (getCollectionsMasterArtifact): New (extended interface).
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java
+	 (getMasterArtifact): New, get UUID of oldest artifact which belongs to
+	 collection the longest.
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Added SQL query to get list of artifacts in collection sorted by the
+	  entry date.
+
+2011-10-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/StringUtils.java(toUpperCase):
+	  Added static method to convert an array of string to their uppercase counterparts.
+	  
+2011-10-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java:
+	(newRemoveArtifactDocument): New, create a "remove artifact" document.
+	Cosmetics, docs.
+
+2011-10-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Fix SQL typo.
+
+2011-10-10	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollection.java:
+	  Cosmetics, (shortened jdoc comments).
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Cosmetics, (shortened jdoc comments). Also, removed
+	  toString(Element), instead fixed toString(Node) - Node is
+	  superinterface of Element.
+
+2011-10-07	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Fixed flys/issue255
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/db/DBConnection.java(getDataSource()):
+	  Serialized the loading of database drivers.
+
+2011-09-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix debug helper toString(Node).
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  (toString(Node)): Fix issue with node from "wrong" document.
+
+2011-09-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added XMLUtils functions helping to debug parts of xml documents.
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  (toString(Node), toString(Element): New functions returning
+	  xml/string representations of parts of documents.
+
+2011-09-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Removed useless CollectionCallContext creation (was never used).
+
+2011-09-19  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 1.4
+
+	* Changes: Prepared changes for the release.
+
+2011-09-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Artifacts and Collections that started a background process might add
+	  Messages to a message board now. They (currently just implemented for
+	  the Artifact) can add new messages and receive a list of messages via
+	  the CallContext objects. If an Artifact or Collection instance is
+	  removed from background, all its messages are removed as well.
+
+	* artifacts/src/main/java/de/intevation/artifacts/Message.java: New. The
+	  message interface. Currently, there is just a single getText() method
+	  defined.
+
+	* artifacts/src/main/java/de/intevation/artifacts/CallContext.java: Got
+	  two new methods to add new messages and retrieve a list of messages to
+	  the background messages.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/CollectionCallContext.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactCallContext.java:
+	  Implement the new methods to add/get messages defined in CallContext.
+	  The CollectionCallContext just defines the two methods without real
+	  implementation (stub).
+
+2011-09-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/CallContext.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/CollectionCallContext.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactCallContext.java:
+	  The CallContext got a new method isInBackground() that determines, if
+	  the Artifact or Collection (currently not implemented) has started a
+	  background thread and is locked.
+
+2011-08-25  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/FileTools.java:
+	  New. A utility class for working with Files.
+
+2011-08-25	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java
+	  (outCollection): Called context listener too early before deferred out. This
+	  led to broken DB sessions.
+
+2011-08-25  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/State.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java:
+	  Enhanced the State interface with a endOfLife() method; the default
+	  implementation in AbstractState is empty.
+
+2011-08-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Minor cosmetics.
+
+	* artifacts/src/main/java/de/intevation/artifacts/Artifact.java:
+	  Corrected signature in link in comment, whitespace.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ArtifactResource.java:
+	  Punctuation, whitespace in comment.
+
+2011-08-24	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java:
+	  Write filters to create document if they are given.
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/CreationFilter.java:
+	  New. Model for the creation filter.
+
+2011-08-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java:
+	  Renamed an attribute in the document to create new Artifacts.
+
+2011-08-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/MapXPathVariableResolver.java:
+	  New. An XPathVariableResolver instance that stores its variables in a
+	  Map.
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java:
+	  Added support for variables in xpath expressions. There is a new xpath()
+	  method that takes a Map<String,String> that stores the required
+	  variables.
+
+2011-08-22	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Facet.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java:
+	  Added a deepCopy() method to make them cloneable (Not using java.lang.Cloneable for this).
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/data/DefaultStateData.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/data/StateData.java:
+	  Implement the changed interfaces.
+
+2011-08-22	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/pom.xml: Bumped PostgreSQL driver from 
+	  8.3-603.jdbc4 up to 8.4-702.jdbc4 (same as flys-backend).
+
+2011-08-10	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Extract the collection and artifact creation times when doing the initial
+	  scan, too.
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Adjusted the SQL statements.
+
+2011-08-08	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties:
+	  Fixed H2 SQL to set TTL of collection.
+
+2011-08-02	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Swapped SQL parameter indices when fetching collection name.
+
+2011-08-02	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java:
+	  Change the ArtifactLoadedCallback interface to take the name of the
+	  collection, too.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Fetches the collection name, too.
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Adjusted SQL statements.
+
+2011-08-02	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultUser.java:
+	  Added constructor only with the identifier of the owner.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Added dummy user to fresh created collection to have it accessible in the
+	  listeners.
+	
+2011-08-02	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java:
+	  Generate kill events if and only if there were killed collections or artifacts.
+
+2011-08-02	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Return uuid in statements used by database cleaner, too.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/BackendListener.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultBackendListener.java:
+	  Two new methods to reports a list of external killed collections and artifacts.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Broadcast the lists of externally killed collections and artifacts to the listeners.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java:
+	  Fire lists of deleted collections and artifacts to backend.
+
+2011-08-01	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Reorganized code a bit.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java: Added some
+	  debug output. Handle forgotten store/create event source.
+
+2011-08-01	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Fixed NPE.
+
+2011-08-01	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java:
+	  Made BackListeners loadable at boot time. To be configured with XPATH
+	  '/artifact-database/backend-listeners/listener'.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/BackendListener.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultBackendListener.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Wired listeners to backend.
+
+2011-08-01	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/BackendListener.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultBackendListener.java:
+	  Completed interface and the trival implementation.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Call the listeners for the new defined events.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/BackendListener.java:
+	  New. Interface to listener for backend events. TODO: Implement more
+	  events.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultBackendListener.java:
+	  New. Trivial implementation of BackendListener.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Added list of backend listeners. TODO: Add them at boot time.
+
+2011-07-31	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/pom.xml: Bumped Apache DBCP up to 1.4
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/db/DBConnection.java:
+	  Use real pooling to void races. Maybe it needs more configuration options!?
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java: Removed
+	  superfluous imports.
+
+2011-07-31	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Fixed broken SQL statement.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/db/DBConnection.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java: Added
+	  debug output.
+
+2011-07-31	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	Make artifact server bootable again.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java:
+	  Fixed NPE.
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/db/SQL.java: Fixed NPE.
+	  Added some debug output.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java: Fixed
+	  SQL key name. That was broken for long but not recognized.
+	
+2011-07-28	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/LRUCache.java:
+	  New. Simple LRU cache based on java.util.LinkedHashMap.
+
+2011-07-28	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	Refactorized the usage of dialect independent SQL to be reusable.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/db/SQL.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/db/DBConnection.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/db/SQLExecutor.java:
+	  New. Generalized versions to make code reusable for datacage.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DBConnection.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/SQL.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/SQLExecutor.java:
+	  Deleted.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DBConfig.java:
+	  New. Centralizes the SQL database configuration of the backend.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/App.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/h2/CollectionAccessUpdateTrigger.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java:
+	  A lot of adjustment to make the new infrastructure work. Needs heavy testing!
+
+2011-07-27	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/LifetimeListener.java:
+	  New. Interface instances of are called when system is up and is going down.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java:
+	  Load lifetime listeners from configuration. XPATH
+	  /artifact-database/lifetime-listeners/listeners/text()
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Call the listeners after start up and before shutdown.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/App.java:
+	  Trigger the start of the artifact database explicitly when the boot process
+	  is finished.
+
+2011-07-27	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java:
+	  Added a method loadAllArtifacts() to load all artifacts which are in
+	  a collection and have an owner. The loaded artifacts are passed one by
+	  one to an instance of the interface ArtifactLoadedCallback.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Adjusted to implement the interface.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Load the artifacts from the SQL backend.
+
+	* artifact-database/src/main/resources/sql/org-h2-driver.properties,
+	  artifact-database/src/main/resources/sql/org-postgresql-driver.properties:
+	  Added statements to load all the artifacts.
+
+2011-07-26	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/pom.xml: Bumped H2 version up to latest stable 1.3.158
+
+2011-07-22	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java,
+	  artifacts-common/src/main/java/de/intevation/artifacts/common/utils/StringUtils.java:
+	  Moved StringUtils to common package.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java:
+	  Adjusted imports.
+
+	* artifacts-common/pom.xml, artifact-database/pom.xml: Moved dependency to Apache codec
+	  to other package.
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactNamespaceContext.java:
+	  Added Override annotations (forgot to commit last time).
+
+2011-07-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java:
+	  New CREATE documents for Artifact creation might contain db-ids now.
+
+2011-07-21	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/Service.java,
+	  artifacts/src/main/java/de/intevation/artifacts/ServiceFactory.java:
+	  Services now take GlobalContext on setup and process.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultServiceFactory.java:
+	  Adusted and added Override annotations.
+
+2011-07-21	Sascha L. Teichmann	<teichmann@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactNamespaceContext.java:
+	  Made it more compatible with mixed namespaces and mixtures of namespaces
+	  and no namespaces.
+
+	* artifacts/pom.xml: Set Java compatibility to 1.5. Why isn't this inherited
+	  from main pom.xml? The artifact-database module uses 1.5 features but does
+	  not need any extra configuration.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/Artifact.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifact.java:
+	  Added a parameter of type CallMeta to the signature of setup(). The
+	  CallMeta object is required for i18n initial things.
+
+	  !! NOTE: This modification breaks the current API !!
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactFactory.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java:
+	  Call Artifact.setup() with an instance of CallMeta.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  The createArtifact() method got a CallMeta instance which is necessary
+	  to call Artifact.setup().
+
+2011-07-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Output.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultOutput.java:
+	  Added a method to add a list of Facets.
+
+2011-07-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Facet.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java:
+	  A facet can write its internal representation to XML using a public
+	  method toXML(Document).
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ProtocolUtils.java:
+	  Facets are appended using its toXML() method. Concrete facets can now
+	  change its xml representation.
+
+2011-07-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/Hook.java: The Hook's
+	  execute() method is called with a Document now.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  The hooks that are called after feed() and advance() are called with the
+	  documents which are returned by those operations.
+
+2011-07-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/GlobalContext.java: New.
+	  An interface for global context objects. It defines two basic operations
+	  to put objects into the context and to retrieve objects from context.
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java:
+	  Added a method that returns an instance of an Artifact with one
+	  limitation: the internal state of this artifact is not persisted to
+	  database!
+
+	* artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java:
+	  Added a method that creates a document used to create new Artifacts with
+	  the UUID of another one.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContextFactory.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java,
+	  artifacts/src/main/java/de/intevation/artifacts/ArtifactFactory.java,
+	  artifacts/src/main/java/de/intevation/artifacts/ArtifactContextFactory.java:
+	  Adapted method signatures to apply the new interface for global context
+	  objects.
+
+2011-07-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifacts/src/main/java/de/intevation/artifacts/Hook.java: New. A hook
+	  can be used to execute at a specific point in time. E.g. after an
+	  Artifact was fed or after an Artifact has advanced.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Call hooks after Artifact's feed() and advance() operations.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java:
+	  Load hooks from configuration that match the XPath "/artifact-database/hooks/hook".
+
+2011-07-13	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Fix for flys/issue20
+
+	* artifact-database/pom.xml: Bumped version of SLF4J up to 1.6.1
+
+2011-07-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/Output.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultOutput.java:
+	  An Output can have a type (string) declaration now that should make it
+	  easier to distinguish different Output types.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java:
+	  Read the attribute "type" from OutputMode's configuration section.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ProtocolUtils.java:
+	  Write the member variable "type" to an OutputMode.
+
 2011-06-28  Ingo Weinzierl <ingo@intevation.de>
 
 	Tagged RELEASE 1.3
--- a/Changes	Tue Jun 28 07:51:17 2011 +0000
+++ b/Changes	Fri Sep 28 12:15:11 2012 +0200
@@ -1,3 +1,80 @@
+2011-09-19     RELEASE 1.4
+
+    !! Release 1.4 is no longer API compatible with old versions of this module !!
+
+    NEW:
+
+        * Modified the signature of Artifact.setup() -> Artifact.setup(CallMeta).
+          The CallMeta is required for I18N support while setting up an
+          Artifact. !! THIS MODIFICATION BREAKS THE CURRENT API !!
+
+        * Modified the signature of Service.setup() -> Service.setup(GlobalContext).
+
+        * Introduced a LifetimeListener interface: applications can register
+          instances of this interface to execute tasks after the system goes and
+          and before the system goes down.
+
+        * Introduced a BackendListener interface: applications can register
+          instances of this interface to execute tasks after an event has been
+          fired. See the JavaDoc of BackendListener for detailed descriptions of
+          the events.
+
+        * Introduced "Hooks": Hooks can be used to execute tasks at a specific
+          point in time. In our cases, Hooks can be executed before/after feed()
+          and advance() operations.
+
+        * Introduced a GlobalContext interface for the global context object:
+          this interface describes two method to put new objects into this
+          context and a method to retrieve objects from this context.
+
+        * Introduced a CreationFilter: this filter might be used to create
+          Artifacts with restricted Outputs/Facets.
+
+        * Introduced a Message interface: Messages might be used by background
+          threads to provide information about the process (e.g. status reports).
+
+        * Improved ArtifactDatabaseImpl to support background messages.
+
+        * Improved the CallContext interface: added a method isInBackground()
+          that determines if the current Artifact has started a background
+          thread which is still processing.
+
+        * Improved the CallContext interface: added methods to add/retrieve
+          background messages.
+
+        * Improved the State interface: added a endOfLife() method that should
+          be called by owner Artifacts.
+
+        * Improved the interface of Facets: added a deepCopy() method to be able
+          to clone Facets.
+
+        * Improved the interface of Facets: added a toXML() method.
+
+        * Improved the ArtifactDatabase: added a method loadAllArtifacts(ArtifactLoadedCallback)
+          to load all Artifacts of an ArtifactCollection.
+
+        * Improved XMLUtils: added support for variables in XPath expression.
+
+        * Added a FileTools helper class that implements some convinience
+          functions to work with files.
+
+        * Some little improvements in ClientProtocolUtils.
+
+        * Bumped SLF4J up to 2.0.7.
+
+        * Bumped H2 up to 1.3.158.
+
+        * Bumped Apache DBCP up to 1.4.
+
+        * Bumped PostgreSQL driver up to 8.4-702.jdbc4.
+
+
+    FIXED:
+
+        * flys/issue20 (Versions-Clash bei slf4j verhindert Start des Artefakt-Servers.)
+
+
+
 2011-06-27     RELEASE 1.3
 
     NEW:
--- a/artifact-database/.settings/org.eclipse.jdt.core.prefs	Tue Jun 28 07:51:17 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-#Tue Sep 08 09:33:09 CEST 2009
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.source=1.5
-org.eclipse.jdt.core.compiler.compliance=1.5
--- a/artifact-database/doc/schema-h2.sql	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/doc/schema-h2.sql	Fri Sep 28 12:15:11 2012 +0200
@@ -23,7 +23,8 @@
 CREATE TABLE users (
     id   INT PRIMARY KEY NOT NULL,
     gid  UUID            NOT NULL UNIQUE,
-    name VARCHAR(256)    NOT NULL UNIQUE,
+    name VARCHAR(256)    NOT NULL,
+    account VARCHAR(256) NOT NULL UNIQUE,
     role BINARY
 );
 
--- a/artifact-database/doc/schema-pg.sql	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/doc/schema-pg.sql	Fri Sep 28 12:15:11 2012 +0200
@@ -23,7 +23,8 @@
 CREATE TABLE users (
     id   int PRIMARY KEY NOT NULL,
     gid  uuid            NOT NULL UNIQUE,
-    name VARCHAR(256)    NOT NULL UNIQUE,
+    name VARCHAR(256)    NOT NULL,
+    account VARCHAR(256) NOT NULL UNIQUE,
     role bytea
 );
 
--- a/artifact-database/pom.xml	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/pom.xml	Fri Sep 28 12:15:11 2012 +0200
@@ -72,22 +72,17 @@
     <dependency>
       <groupId>com.h2database</groupId>
       <artifactId>h2</artifactId>
-      <version>1.3.152</version>
+      <version>1.3.158</version>
     </dependency>
     <dependency>
       <groupId>postgresql</groupId>
       <artifactId>postgresql</artifactId>
-      <version>8.3-603.jdbc4</version>
-    </dependency>
-    <dependency>
-      <groupId>commons-codec</groupId>
-      <artifactId>commons-codec</artifactId>
-      <version>1.4</version>
+      <version>8.4-702.jdbc4</version>
     </dependency>
     <dependency>
       <groupId>commons-dbcp</groupId>
       <artifactId>commons-dbcp</artifactId>
-      <version>1.2.2</version>
+      <version>1.4</version>
     </dependency>
     <dependency>
       <groupId>log4j</groupId>
@@ -97,12 +92,12 @@
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>jul-to-slf4j</artifactId>
-      <version>1.5.11</version>
+      <version>1.6.1</version>
     </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-log4j12</artifactId>
-      <version>1.5.11</version>
+      <version>1.6.1</version>
     </dependency>
     <dependency>
         <groupId>org.mortbay.jetty</groupId>
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/AbstractCallContext.java	Fri Sep 28 12:15:11 2012 +0200
@@ -7,11 +7,17 @@
  */
 package de.intevation.artifactdatabase;
 
+import org.apache.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import java.util.HashMap;
 
 import de.intevation.artifacts.ArtifactDatabase;
 import de.intevation.artifacts.CallContext;
 import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.DataProvider;
 
 
 /**
@@ -22,6 +28,8 @@
  */
 public abstract class AbstractCallContext implements CallContext {
 
+    Logger logger = Logger.getLogger(AbstractCallContext.class);
+
     /**
      * The ArtifactDatabase instance.
      */
@@ -40,15 +48,20 @@
     /**
      * Map to act like a clipboard when nesting calls like a proxy artifact.
      */
-    protected HashMap customValues;
+    protected Map customValues;
+
+    /**
+     * Map to act like a clipboard when nesting calls like a proxy artifact.
+     */
+    protected Map<Object, List<DataProvider>> dataProviders;
 
 
     /**
      * The default constructor of this abstract CallContext.
      *
+     * @param artifactDatabase The artifact database.
      * @param action The action.
      * @param callMeta The CallMeta object.
-     * @param context The global context.
      */
     public AbstractCallContext(
         ArtifactDatabaseImpl artifactDatabase,
@@ -101,5 +114,41 @@
         }
         return customValues.put(key, value);
     }
+
+    /**
+     * Get list of DataProviders that registered for given key.
+     * @return list (empty list if none found, never null).
+     */
+    public List<DataProvider> getDataProvider(Object key) {
+        if (dataProviders != null) {
+            List<DataProvider> list = dataProviders.get(key);
+            return list != null
+                ? list
+                : java.util.Collections.<DataProvider>emptyList();
+        }
+        return java.util.Collections.<DataProvider>emptyList();
+    }
+
+
+    /**
+     * Let a DataProvider register itself with given key.
+     * Multiple DataProvider can register under the same key.
+     */
+    public Object registerDataProvider(Object key, DataProvider value) {
+        List<DataProvider> providers = null;
+        if (dataProviders == null) {
+            dataProviders = new HashMap();
+            providers = new ArrayList<DataProvider>();
+            providers.add(value);
+            return dataProviders.put(key, providers);
+        }
+        providers = dataProviders.get(key);
+
+        if (providers == null) {
+            providers = new ArrayList<DataProvider>();
+        }
+        providers.add(value);
+        return dataProviders.put(key, providers);
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/App.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/App.java	Fri Sep 28 12:15:11 2012 +0200
@@ -70,7 +70,7 @@
             bootstrap, backend);
 
         DatabaseCleaner cleaner = new DatabaseCleaner(
-            bootstrap.getContext(), backend);
+            bootstrap.getContext(), backend, backend.getConfig());
 
         HTTPServer httpServer = bootstrap.getHTTPServer();
 
@@ -82,6 +82,8 @@
 
         cleaner.start();
 
+        db.start();
+
         httpServer.startAsServer(db);
     }
 }
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactCallContext.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactCallContext.java	Fri Sep 28 12:15:11 2012 +0200
@@ -7,9 +7,12 @@
  */
 package de.intevation.artifactdatabase;
 
+import java.util.LinkedList;
+
 import org.apache.log4j.Logger;
 
 import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.Message;
 
 import de.intevation.artifactdatabase.Backend.PersistentArtifact;
 
@@ -73,6 +76,22 @@
     }
 
 
+    public boolean isInBackground() {
+        return database.getLockedIds().contains(artifact.getId());
+    }
+
+
+    public void addBackgroundMessage(Message msg) {
+        database.addBackgroundMessage(artifact.getArtifact().identifier(), msg);
+    }
+
+
+    public LinkedList<Message> getBackgroundMessages() {
+        return database.getBackgroundMessages(
+            artifact.getArtifact().identifier());
+    }
+
+
     public Long getTimeToLive() {
         return artifact.getTTL();
     }
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java	Fri Sep 28 12:15:11 2012 +0200
@@ -9,6 +9,7 @@
 package de.intevation.artifactdatabase;
 
 import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.StringUtils;
 
 import de.intevation.artifactdatabase.Backend.PersistentArtifact;
 
@@ -23,6 +24,9 @@
 import de.intevation.artifacts.CallContext;
 import de.intevation.artifacts.CallMeta;
 import de.intevation.artifacts.CollectionItem;
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.Hook;
+import de.intevation.artifacts.Message;
 import de.intevation.artifacts.Service;
 import de.intevation.artifacts.ServiceFactory;
 import de.intevation.artifacts.User;
@@ -38,6 +42,9 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.xml.xpath.XPathConstants;
@@ -66,6 +73,10 @@
     private static Logger logger =
         Logger.getLogger(ArtifactDatabaseImpl.class);
 
+    /** The key under which the artifact database is stored in the global
+     * context.*/
+    public static final String GLOBAL_CONTEXT_KEY = "global.artifact.database";
+
     /** Message that is returned if an operation was successful.*/
     public static final String OPERATION_SUCCESSFUL =
         "SUCCESS";
@@ -186,6 +197,14 @@
     public static final String XPATH_USERROLE =
         "/art:action/art:user/art:role";
 
+    /** XPath to figure out the account of a new user.*/
+    public static final String XPATH_USERACCOUNT =
+        "/art:action/art:user/art:account/@name";
+
+    /** XPath to figure out the account of when searching for a user .*/
+    public static final String XPATH_USERACCOUNT_FIND =
+        "/art:action/art:account/@name";
+
     /** Error message if a specified user does not exist.*/
     public static final String NO_SUCH_USER =
         "No such user";
@@ -194,6 +213,10 @@
     public static final String NO_USERNAME =
         "Invalid username";
 
+    /** Error message if no user account is given for user creation.*/
+    public static final String NO_USERACCOUNT =
+        "Invalid user account name";
+
     // Collection constants
 
     /**
@@ -401,7 +424,7 @@
     /**
      * Reference of the global context of the artifact runtime system.
      */
-    protected Object      context;
+    protected GlobalContext context;
 
     /**
      * The signing secret to be used for ex-/importing artifacts.
@@ -413,11 +436,35 @@
      * This artifacts should not be removed from the database by the
      * database cleaner.
      */
-    protected HashSet<Integer>  backgroundIds;
+    protected HashSet<Integer> backgroundIds;
+
+    /**
+     * A list of background messages for Artifacts and Collections.
+     */
+    protected Map<String, LinkedList<Message>> backgroundMsgs;
+
 
     protected CallContext.Listener callContextListener;
 
     /**
+     * Hooks that are executed after an artifact has been fed.
+     */
+    protected List<Hook> postFeedHooks;
+
+    /**
+     * Hooks that are executed after an artifact has advanced.
+     */
+    protected List<Hook> postAdvanceHooks;
+
+    /**
+     * Hooks that are executed after an artifact's describe() operation was
+     * called.
+     */
+    protected List<Hook> postDescribeHooks;
+
+    protected List<LifetimeListener> lifetimeListeners;
+
+    /**
      * Default constructor.
      */
     public ArtifactDatabaseImpl() {
@@ -442,18 +489,25 @@
      */
     public ArtifactDatabaseImpl(FactoryBootstrap bootstrap, Backend backend) {
 
-        backgroundIds = new HashSet<Integer>();
+        logger.debug("new ArtifactDatabaseImpl");
+
+        backgroundIds  = new HashSet<Integer>();
+        backgroundMsgs = new HashMap<String, LinkedList<Message>>();
 
         setupArtifactCollectionFactory(bootstrap);
         setupArtifactFactories(bootstrap);
         setupServices(bootstrap);
         setupUserFactory(bootstrap);
         setupCallContextListener(bootstrap);
+        setupHooks(bootstrap);
+        setupLifetimeListeners(bootstrap);
 
-        context      = bootstrap.getContext();
+        context = bootstrap.getContext();
+        context.put(GLOBAL_CONTEXT_KEY, this);
+
         exportSecret = bootstrap.getExportSecret();
 
-        wireWithBackend(backend);
+        wireWithBackend(backend, bootstrap);
     }
 
     public CallContext.Listener getCallContextListener() {
@@ -466,6 +520,19 @@
         this.callContextListener = callContextListener;
     }
 
+
+    public void setPostFeedHook(List<Hook> postFeedHooks) {
+        this.postFeedHooks = postFeedHooks;
+    }
+
+    public void setPostAdvanceHook(List<Hook> postAdvanceHooks) {
+        this.postAdvanceHooks = postAdvanceHooks;
+    }
+
+    public void setPostDescribeHook(List<Hook> postDescribeHooks) {
+        this.postDescribeHooks = postDescribeHooks;
+    }
+
     /**
      * Used to extract the artifact collection factory from bootstrap.
      *
@@ -509,6 +576,28 @@
         setCallContextListener(bootstrap.getCallContextListener());
     }
 
+
+    protected void setupHooks(FactoryBootstrap bootstrap) {
+        setPostFeedHook(bootstrap.getPostFeedHooks());
+        setPostAdvanceHook(bootstrap.getPostAdvanceHooks());
+        setPostDescribeHook(bootstrap.getPostDescribeHooks());
+    }
+
+    protected void setupBackendListeners(FactoryBootstrap bootstrap) {
+        logger.debug("setupBackendListeners");
+        List<BackendListener> bls = bootstrap.getBackendListeners();
+        if (bls != null && !bls.isEmpty()) {
+            for (BackendListener listener: bls) {
+                listener.setup(context);
+            }
+            backend.addAllListeners(bls);
+        }
+    }
+
+    protected void setupLifetimeListeners(FactoryBootstrap bootstrap) {
+        this.lifetimeListeners = bootstrap.getLifetimeListeners();
+    }
+
     /**
      * Used to extract the user factory from the bootstrap.
      */
@@ -554,10 +643,12 @@
      * via the serializers of this artifact factories.
      * @param backend The backend to be wired with this artifact database.
      */
-    public void wireWithBackend(Backend backend) {
+    public void wireWithBackend(Backend backend, FactoryBootstrap bootstrap) {
+        logger.debug("wireWithBackend");
         if (backend != null) {
             this.backend = backend;
             backend.setFactoryLookup(this);
+            setupBackendListeners(bootstrap);
         }
     }
 
@@ -583,6 +674,7 @@
                 logger.warn("operation not allowed in fromBackground");
         }
         removeIdFromBackground(artifact.getId());
+        removeBackgroundMessages(artifact.getArtifact().identifier());
     }
 
     /**
@@ -597,6 +689,21 @@
         }
     }
 
+
+    /**
+     * Removes all messages that have been added to the <i>backgroundMsgs</i>
+     * list.
+     *
+     * @param uuid The UUID of an artifact or collection.
+     */
+    protected void removeBackgroundMessages(String uuid) {
+        logger.debug("Remove background messages for: " + uuid);
+
+        synchronized (backgroundMsgs) {
+            backgroundMsgs.remove(uuid);
+        }
+    }
+
     /**
      * Adds an artifact's database id to the set of artifacts
      * running in backgroound. To be in this set prevents the
@@ -610,12 +717,52 @@
         }
     }
 
+    /**
+     * Adds a <i>Message</i> to the background messages list of the Artifact or
+     * Collection.
+     *
+     * @param uuid The UUID of the Artifact or Collection.
+     * @param msg The message that should be added to the background messages
+     * list.
+     */
+    public void addBackgroundMessage(String uuid, Message msg) {
+        logger.debug("Add new background messsage for: " + uuid);
+
+        synchronized (backgroundMsgs) {
+            LinkedList<Message> messages = backgroundMsgs.get(uuid);
+
+            if (messages == null) {
+                messages = new LinkedList<Message>();
+                backgroundMsgs.put(uuid, messages);
+            }
+
+            messages.addLast(msg);
+        }
+    }
+
     public Set<Integer> getLockedIds() {
         synchronized (backgroundIds) {
             return new HashSet<Integer>(backgroundIds);
         }
     }
 
+    /**
+     * Returns the background <i>Message</i>s for a specific Artifact or
+     * Collection.
+     *
+     * @param uuid The Artifact's or Collection's UUID.
+     *
+     * @return a <i>List</i> of <i>Message</i>s or null if no messages are
+     * existing.
+     */
+    public LinkedList<Message> getBackgroundMessages(String uuid) {
+        logger.debug("Retrieve background message for: " + uuid);
+
+        synchronized (backgroundMsgs) {
+            return backgroundMsgs.get(uuid);
+        }
+    }
+
     public String [][] artifactFactoryNamesAndDescriptions() {
         return factoryNamesAndDescription;
     }
@@ -643,6 +790,8 @@
     )
     throws ArtifactDatabaseException
     {
+        logger.debug("ArtifactDatabaseImpl.createArtifactWithFactory "
+             + factoryName);
         ArtifactFactory factory = getArtifactFactory(factoryName);
 
         if (factory == null) {
@@ -652,6 +801,7 @@
         Artifact artifact = factory.createArtifact(
             backend.newIdentifier(),
             context,
+            callMeta,
             data);
 
         if (artifact == null) {
@@ -685,6 +835,20 @@
         }
     }
 
+
+    public Artifact getRawArtifact(String identifier)
+    throws ArtifactDatabaseException
+    {
+        PersistentArtifact artifact = backend.getArtifact(identifier);
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT);
+        }
+
+        return artifact.getArtifact();
+    }
+
+
     public Document describe(
         String   identifier,
         Document data,
@@ -706,7 +870,16 @@
             artifact);
 
         try {
-            return artifact.getArtifact().describe(data, cc);
+            Artifact art = artifact.getArtifact();
+            Document res = art.describe(data, cc);
+
+            if (postDescribeHooks != null) {
+                for (Hook hook: postDescribeHooks) {
+                    hook.execute(art, cc, res);
+                }
+            }
+
+            return res;
         }
         finally {
             cc.postCall();
@@ -734,7 +907,16 @@
             artifact);
 
         try {
-            return artifact.getArtifact().advance(target, cc);
+            Artifact art = artifact.getArtifact();
+            Document res = art.advance(target, cc);
+
+            if (postAdvanceHooks != null) {
+                for (Hook hook: postAdvanceHooks) {
+                    hook.execute(art, cc, res);
+                }
+            }
+
+            return res;
         }
         finally {
             cc.postCall();
@@ -758,7 +940,16 @@
             artifact);
 
         try {
-            return artifact.getArtifact().feed(data, cc);
+            Artifact art = artifact.getArtifact();
+            Document res = art.feed(data, cc);
+
+            if (postFeedHooks != null) {
+                for (Hook hook: postFeedHooks) {
+                    hook.execute(art, cc, res);
+                }
+            }
+
+            return res;
         }
         finally {
             cc.postCall();
@@ -983,7 +1174,7 @@
         return serviceNamesAndDescription;
     }
 
-    public Document process(
+    public Service.Output process(
         String   serviceName,
         Document input,
         CallMeta callMeta
@@ -1001,6 +1192,7 @@
 
     // User API
 
+    /** Returns user(s) elements. */
     public Document listUsers(CallMeta callMeta)
         throws ArtifactDatabaseException
     {
@@ -1026,20 +1218,74 @@
         Element root = ec.create("users");
         result.appendChild(root);
 
-        for (User user: users) {
-            Element ue = ec.create("user");
+        if(users != null) {
+            for (User user: users) {
+                Element ue = ec.create("user");
+                ec.addAttr(ue, "uuid", user.identifier(), true);
+                ec.addAttr(ue, "name", user.getName(), true);
+                Element ua = ec.create("account");
+                ec.addAttr(ua, "name", user.getAccount(), true);
+                ue.appendChild(ua);
+
+                Document role = user.getRole();
+
+                if (role != null) {
+                    ue.appendChild(result.importNode(role.getFirstChild(), true));
+                }
+
+                root.appendChild(ue);
+            }
+        }
+
+        return result;
+    }
+
+    /** Search for a user. */
+    public Document findUser(Document data, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        UserFactory factory = getUserFactory();
+
+        if (factory == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        String account = XMLUtils.xpathString(
+            data, XPATH_USERACCOUNT_FIND, ArtifactNamespaceContext.INSTANCE);
+
+        if (account == null || account.length() == 0) {
+            logger.warn("Can't find user without account!");
+            throw new ArtifactDatabaseException(NO_USERACCOUNT);
+        }
+
+        User user = backend.findUser(account, factory, context);
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element ue = ec.create("user");
+
+        if (user != null) {
+            logger.debug(user + " user found in the backend.");
+
             ec.addAttr(ue, "uuid", user.identifier(), true);
             ec.addAttr(ue, "name", user.getName(), true);
+            Element ua = ec.create("account");
+            ec.addAttr(ua, "name", user.getAccount(), true);
+            ue.appendChild(ua);
 
             Document role = user.getRole();
 
             if (role != null) {
                 ue.appendChild(result.importNode(role.getFirstChild(), true));
             }
+        }
 
-            root.appendChild(ue);
-
-        }
+        result.appendChild(ue);
 
         return result;
     }
@@ -1061,6 +1307,14 @@
             throw new ArtifactDatabaseException(NO_USERNAME);
         }
 
+        String account = XMLUtils.xpathString(
+            data, XPATH_USERACCOUNT, ArtifactNamespaceContext.INSTANCE);
+
+        if (account == null || account.length() == 0) {
+            logger.warn("User without account not accepted!");
+            throw new ArtifactDatabaseException(NO_USERACCOUNT);
+        }
+
         Node tmp = (Node) XMLUtils.xpath(
             data,
             XPATH_USERROLE,
@@ -1077,7 +1331,7 @@
         User newUser = null;
 
         try {
-            newUser = backend.createUser(name, role, userFactory, context);
+            newUser = backend.createUser(name, account, role, userFactory, context);
         }
         catch (Exception e) {
             logger.error(e.getMessage(), e);
@@ -1130,6 +1384,64 @@
 
     // Collection API
 
+    public Document getCollectionsMasterArtifact(
+        String collectionId,
+        CallMeta meta)
+        throws ArtifactDatabaseException
+    {
+        Document result = XMLUtils.newDocument();
+        String masterUUID = backend.getMasterArtifact(collectionId);
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        ArtifactCollectionFactory acf = getArtifactCollectionFactory();
+
+        if (acf == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        UserFactory uf = getUserFactory();
+        if (uf == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        ArtifactCollection c = backend.getCollection(
+            collectionId, acf, uf, context);
+
+        if (c == null) {
+            logger.warn("No collection found with identifier: " + collectionId);
+            throw new ArtifactDatabaseException(NO_SUCH_COLLECTION);
+        }
+
+        Element root = ec.create("artifact-collection");
+        ec.addAttr(root, "name", c.getName(), true);
+        ec.addAttr(root, "uuid", c.identifier(), true);
+        ec.addAttr(root, "ttl",  String.valueOf(c.getTTL()), true);
+
+        Date creationTime = c.getCreationTime();
+        String creation   = creationTime != null
+            ? Long.toString(creationTime.getTime())
+            : "";
+
+        ec.addAttr(root, "creation", creation,  true);
+        result.appendChild(root);
+
+        if (masterUUID == null || masterUUID.length() == 0) {
+            logger.debug("No master for the collection existing.");
+            return result;
+        }
+
+        Element master = ec.create("artifact");
+        ec.addAttr(master, "uuid", masterUUID, true);
+
+        root.appendChild(master);
+
+        return result;
+    }
+
     public Document listCollections(String userId, CallMeta callMeta)
         throws ArtifactDatabaseException
     {
@@ -1612,18 +1924,7 @@
             throw new ArtifactDatabaseException(NO_SUCH_COLLECTION);
         }
 
-        CollectionCallContext cc = new CollectionCallContext(
-            ArtifactDatabaseImpl.this,
-            CallContext.NOTHING,
-            callMeta,
-            c);
-
-        try {
-            return new DeferredCollectionOutputImpl(c, type, format, callMeta);
-        }
-        finally {
-            cc.postCall();
-        }
+        return new DeferredCollectionOutputImpl(c, type, format, callMeta);
     }
 
     protected void initCallContext(CallContext cc) {
@@ -1639,5 +1940,37 @@
             callContextListener.close(cc);
         }
     }
+
+    @Override
+    public void loadAllArtifacts(ArtifactLoadedCallback callback)
+        throws ArtifactDatabaseException
+    {
+        logger.debug("loadAllArtifacts");
+        boolean success = backend.loadAllArtifacts(callback);
+        if (!success) {
+            throw new ArtifactDatabaseException(INTERNAL_ERROR);
+        }
+    }
+
+    public void start() {
+        if (lifetimeListeners == null || lifetimeListeners.isEmpty()) {
+            return;
+        }
+
+        for (LifetimeListener ltl: lifetimeListeners) {
+            ltl.systemUp(context);
+        }
+
+        logger.debug("all lifetime listeners started");
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                for (LifetimeListener ltl: lifetimeListeners) {
+                    ltl.systemDown(context);
+                }
+            }
+        });
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java	Fri Sep 28 12:15:11 2012 +0200
@@ -10,22 +10,31 @@
 import de.intevation.artifacts.Artifact;
 import de.intevation.artifacts.ArtifactCollection;
 import de.intevation.artifacts.ArtifactCollectionFactory;
+import de.intevation.artifacts.ArtifactDatabase.ArtifactLoadedCallback;
 import de.intevation.artifacts.ArtifactFactory;
 import de.intevation.artifacts.ArtifactSerializer;
 import de.intevation.artifacts.CollectionItem;
 import de.intevation.artifacts.User;
 import de.intevation.artifacts.UserFactory;
 
+import de.intevation.artifacts.common.utils.StringUtils;
 import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.LRUCache;
+
+import de.intevation.artifactdatabase.db.SQLExecutor;
+import de.intevation.artifactdatabase.db.SQL;
 
 import java.sql.SQLException;
 import java.sql.Timestamp;
 import java.sql.Types;
 
+import java.util.List;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 
+import java.util.concurrent.CopyOnWriteArrayList;
+
 import org.apache.log4j.Logger;
 
 import org.w3c.dom.Document;
@@ -44,169 +53,96 @@
     /**
      * The SQL statement to create new artifact id inside the database.
      */
-    public static final String SQL_NEXT_ID =
-        SQL.get("artifacts.id.nextval");
+    public String SQL_NEXT_ID;
 
     /**
      * The SQL statement to insert an artifact into the database.
      */
-    public static final String SQL_INSERT =
-        SQL.get("artifacts.insert");
+    public String SQL_INSERT;
 
     /**
      * The SQL statement to update some columns of an existing
      * artifact in the database.
      */
-    public static final String SQL_UPDATE =
-        SQL.get("artifacts.update");
+    public String SQL_UPDATE;
 
     /**
      * The SQL statement to touch the access time of an
      * artifact inside the database.
      */
-    public static final String SQL_TOUCH =
-        SQL.get("artifacts.touch");
+    public String SQL_TOUCH;
 
     /**
      * The SQL statement to load an artifact by a given
      * identifier from the database.
      */
-    public static final String SQL_LOAD_BY_GID =
-        SQL.get("artifacts.select.gid");
+    public String SQL_LOAD_BY_GID;
 
     /**
      * The SQL statement to get the database id of an artifact
      * identified by the identifier.
      */
-    public static final String SQL_GET_ID =
-        SQL.get("artifacts.get.id");
+    public String SQL_GET_ID;
 
     /**
      * The SQL statement to replace the content of an
      * existing artifact inside the database.
      */
-    public static final String SQL_REPLACE =
-        SQL.get("artifacts.replace");
+    public String SQL_REPLACE;
 
     // USER SQL
 
-    public static final String SQL_USERS_NEXT_ID =
-        SQL.get("users.id.nextval");
-
-    public static final String SQL_USERS_INSERT =
-        SQL.get("users.insert");
-
-    public static final String SQL_USERS_SELECT_ID_BY_GID =
-        SQL.get("users.select.id.by.gid");
-
-    public static final String SQL_USERS_SELECT_GID =
-        SQL.get("users.select.gid");
-
-    public static final String SQL_USERS_DELETE_ID =
-        SQL.get("users.delete.id");
-
-    public static final String SQL_USERS_DELETE_COLLECTIONS =
-        SQL.get("users.delete.collections");
-
-    public static final String SQL_USERS_SELECT_ALL =
-        SQL.get("users.select.all");
-
-    public static final String SQL_USERS_COLLECTIONS =
-        SQL.get("users.collections");
-
-    public static final String SQL_USERS_COLLECTION_IDS =
-        SQL.get("users.collection.ids");
-
-    public static final String SQL_USERS_DELETE_ALL_COLLECTIONS =
-        SQL.get("users.delete.all.collections");
-
-    public static final String SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY =
-        SQL.get("artifacts.in.one.collection.only");
-
-    public static final String SQL_OUTDATE_ARTIFACTS_COLLECTION =
-        SQL.get("outdate.artifacts.collection");
-
-    public static final String SQL_UPDATE_COLLECTION_TTL =
-        SQL.get("collections.update.ttl");
-
-    public static final String SQL_UPDATE_COLLECTION_NAME =
-        SQL.get("collections.update.name");
-
-    public static final String SQL_OUTDATE_ARTIFACTS_USER =
-        SQL.get("outdate.artifacts.user");
-
-    public static final String SQL_DELETE_USER_COLLECTION_ITEMS =
-        SQL.get("delete.user.collection.items");
-
-    public static final String SQL_COLLECTIONS_NEXT_ID =
-        SQL.get("collections.id.nextval");
-
-    public static final String SQL_COLLECTIONS_INSERT =
-        SQL.get("collections.insert");
-
-    public static final String SQL_COLLECTIONS_SELECT_USER =
-        SQL.get("collections.select.user");
-
-    public static final String SQL_COLLECTIONS_SELECT_ALL =
-        SQL.get("collections.select.all");
-
-    public static final String SQL_COLLECTIONS_SELECT_GID =
-        SQL.get("collections.select.by.gid");
-
-    public static final String SQL_COLLECTIONS_CREATION_TIME =
-        SQL.get("collection.creation.time");
-
-    public static final String SQL_COLLECTIONS_ID_BY_GID =
-        SQL.get("collections.id.by.gid");
-
-    public static final String SQL_DELETE_COLLECTION_ITEMS =
-        SQL.get("delete.collection.items");
-
-    public static final String SQL_DELETE_COLLECTION =
-        SQL.get("delete.collection");
-
-    public static final String SQL_COLLECTION_CHECK_ARTIFACT =
-        SQL.get("collection.check.artifact");
-
-    public static final String SQL_COLLECTION_ITEMS_ID_NEXTVAL =
-        SQL.get("collection.items.id.nextval");
-
-    public static final String SQL_COLLECTION_ITEMS_INSERT =
-        SQL.get("collection.items.insert");
-
-    public static final String SQL_COLLECTION_GET_ATTRIBUTE =
-        SQL.get("collection.get.attribute");
-
-    public static final String SQL_COLLECTION_SET_ATTRIBUTE =
-        SQL.get("collection.set.attribute");
-
-    public static final String SQL_COLLECTION_ITEM_GET_ATTRIBUTE =
-        SQL.get("collection.item.get.attribute");
-
-    public static final String SQL_COLLECTION_ITEM_SET_ATTRIBUTE =
-        SQL.get("collection.item.set.attribute");
-
-    public static final String SQL_COLLECTIONS_TOUCH_BY_GID =
-        SQL.get("collections.touch.by.gid");
-
-    public static final String SQL_COLLECTION_ITEM_ID_CID_AID =
-        SQL.get("collection.item.id.cid.aid");
-
-    public static final String SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT =
-        SQL.get("collection.item.outdate.artifact");
-
-    public static final String SQL_COLLECTION_ITEM_DELETE =
-        SQL.get("collection.item.delete");
-
-    public static final String SQL_COLLECTIONS_TOUCH_BY_ID =
-        SQL.get("collections.touch.by.id");
-
-    public static final String SQL_COLLECTION_ITEMS_LIST_GID =
-        SQL.get("collection.items.list.gid");
+    public String SQL_USERS_NEXT_ID;
+    public String SQL_USERS_INSERT;
+    public String SQL_USERS_SELECT_ID_BY_GID;
+    public String SQL_USERS_SELECT_GID;
+    public String SQL_USERS_SELECT_ACCOUNT;
+    public String SQL_USERS_DELETE_ID;
+    public String SQL_USERS_DELETE_COLLECTIONS;
+    public String SQL_USERS_SELECT_ALL;
+    public String SQL_USERS_COLLECTIONS;
+    public String SQL_USERS_COLLECTION_IDS;
+    public String SQL_USERS_DELETE_ALL_COLLECTIONS;
+    public String SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY;
+    public String SQL_OUTDATE_ARTIFACTS_COLLECTION;
+    public String SQL_UPDATE_COLLECTION_TTL;
+    public String SQL_UPDATE_COLLECTION_NAME;
+    public String SQL_OUTDATE_ARTIFACTS_USER;
+    public String SQL_DELETE_USER_COLLECTION_ITEMS;
+    public String SQL_COLLECTIONS_NEXT_ID;
+    public String SQL_COLLECTIONS_INSERT;
+    public String SQL_COLLECTIONS_SELECT_USER;
+    public String SQL_COLLECTIONS_SELECT_ALL;
+    public String SQL_COLLECTIONS_SELECT_GID;
+    public String SQL_COLLECTIONS_CREATION_TIME;
+    public String SQL_COLLECTIONS_ID_BY_GID;
+    public String SQL_COLLECTIONS_OLDEST_ARTIFACT;
+    public String SQL_DELETE_COLLECTION_ITEMS;
+    public String SQL_DELETE_COLLECTION;
+    public String SQL_COLLECTION_CHECK_ARTIFACT;
+    public String SQL_COLLECTION_ITEMS_ID_NEXTVAL;
+    public String SQL_COLLECTION_ITEMS_INSERT;
+    public String SQL_COLLECTION_GET_ATTRIBUTE;
+    public String SQL_COLLECTION_SET_ATTRIBUTE;
+    public String SQL_COLLECTION_ITEM_GET_ATTRIBUTE;
+    public String SQL_COLLECTION_ITEM_SET_ATTRIBUTE;
+    public String SQL_COLLECTIONS_TOUCH_BY_GID;
+    public String SQL_COLLECTION_ITEM_ID_CID_AID;
+    public String SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT;
+    public String SQL_COLLECTION_ITEM_DELETE;
+    public String SQL_COLLECTIONS_TOUCH_BY_ID;
+    public String SQL_COLLECTION_ITEMS_LIST_GID;
+    public String SQL_ALL_ARTIFACTS;
 
     /** The singleton.*/
     protected static Backend instance;
 
+    protected SQLExecutor sqlExecutor;
+
+    protected List<BackendListener> listeners;
+
+    protected DBConfig config;
+
     /**
      * The database cleaner. Reference is stored here because
      * the cleaner is woken up if the backend finds an outdated
@@ -327,6 +263,14 @@
      * Default constructor
      */
     public Backend() {
+        listeners = new CopyOnWriteArrayList<BackendListener>();
+    }
+
+    public Backend(DBConfig config) {
+        this();
+        this.config = config;
+        sqlExecutor = new SQLExecutor(config.getDBConnection());
+        setupSQL(config.getSQL());
     }
 
     /**
@@ -334,10 +278,14 @@
      * @param cleaner The clean which periodically removes outdated
      * artifacts from the database.
      */
-    public Backend(DatabaseCleaner cleaner) {
+    public Backend(DBConfig config, DatabaseCleaner cleaner) {
+        this(config);
         this.cleaner = cleaner;
     }
 
+    public DBConfig getConfig() {
+        return config;
+    }
 
     /**
      * Returns the singleton of this Backend.
@@ -346,12 +294,81 @@
      */
     public static synchronized Backend getInstance() {
         if (instance == null) {
-            instance = new Backend();
+            instance = new Backend(DBConfig.getInstance());
         }
 
         return instance;
     }
 
+    protected void setupSQL(SQL sql) {
+        SQL_NEXT_ID = sql.get("artifacts.id.nextval");
+        SQL_INSERT = sql.get("artifacts.insert");
+        SQL_UPDATE = sql.get("artifacts.update");
+        SQL_TOUCH = sql.get("artifacts.touch");
+        SQL_LOAD_BY_GID = sql.get("artifacts.select.gid");
+        SQL_GET_ID = sql.get("artifacts.get.id");
+        SQL_REPLACE = sql.get("artifacts.replace");
+        SQL_USERS_NEXT_ID = sql.get("users.id.nextval");
+        SQL_USERS_INSERT = sql.get("users.insert");
+        SQL_USERS_SELECT_ID_BY_GID = sql.get("users.select.id.by.gid");
+        SQL_USERS_SELECT_GID = sql.get("users.select.gid");
+        SQL_USERS_SELECT_ACCOUNT = sql.get("users.select.account");
+        SQL_USERS_DELETE_ID = sql.get("users.delete.id");
+        SQL_USERS_DELETE_COLLECTIONS = sql.get("users.delete.collections");
+        SQL_USERS_SELECT_ALL = sql.get("users.select.all");
+        SQL_USERS_COLLECTIONS = sql.get("users.collections");
+        SQL_USERS_COLLECTION_IDS = sql.get("users.collection.ids");
+        SQL_USERS_DELETE_ALL_COLLECTIONS =
+            sql.get("users.delete.collections");
+        SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY =
+            sql.get("artifacts.in.one.collection.only");
+        SQL_OUTDATE_ARTIFACTS_COLLECTION =
+            sql.get("outdate.artifacts.collection");
+        SQL_UPDATE_COLLECTION_TTL = sql.get("collections.update.ttl");
+        SQL_UPDATE_COLLECTION_NAME = sql.get("collections.update.name");
+        SQL_OUTDATE_ARTIFACTS_USER = sql.get("outdate.artifacts.user");
+        SQL_DELETE_USER_COLLECTION_ITEMS =
+            sql.get("delete.user.collection.items");
+        SQL_COLLECTIONS_NEXT_ID = sql.get("collections.id.nextval");
+        SQL_COLLECTIONS_INSERT = sql.get("collections.insert");
+        SQL_COLLECTIONS_SELECT_USER = sql.get("collections.select.user");
+        SQL_COLLECTIONS_SELECT_ALL = sql.get("collections.select.all");
+        SQL_COLLECTIONS_SELECT_GID = sql.get("collections.select.by.gid");
+        SQL_COLLECTIONS_CREATION_TIME = sql.get("collection.creation.time");
+        SQL_COLLECTIONS_OLDEST_ARTIFACT = sql.get("collections.artifacts.oldest");
+        SQL_COLLECTIONS_ID_BY_GID = sql.get("collections.id.by.gid");
+        SQL_DELETE_COLLECTION_ITEMS = sql.get("delete.collection.items");
+        SQL_DELETE_COLLECTION = sql.get("delete.collection");
+        SQL_COLLECTION_CHECK_ARTIFACT = sql.get("collection.check.artifact");
+        SQL_COLLECTION_ITEMS_ID_NEXTVAL =
+            sql.get("collection.items.id.nextval");
+        SQL_COLLECTION_ITEMS_INSERT = sql.get("collection.items.insert");
+        SQL_COLLECTION_GET_ATTRIBUTE = sql.get("collection.get.attribute");
+        SQL_COLLECTION_SET_ATTRIBUTE = sql.get("collection.set.attribute");
+        SQL_COLLECTION_ITEM_GET_ATTRIBUTE =
+            sql.get("collection.item.get.attribute");
+        SQL_COLLECTION_ITEM_SET_ATTRIBUTE =
+            sql.get("collection.item.set.attribute");
+        SQL_COLLECTIONS_TOUCH_BY_GID = sql.get("collections.touch.by.gid");
+        SQL_COLLECTION_ITEM_ID_CID_AID = sql.get("collection.item.id.cid.aid");
+        SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT =
+            sql.get("collection.item.outdate.artifact");
+        SQL_COLLECTION_ITEM_DELETE = sql.get("collection.item.delete");
+        SQL_COLLECTIONS_TOUCH_BY_ID = sql.get("collections.touch.by.id");
+        SQL_COLLECTION_ITEMS_LIST_GID = sql.get("collection.items.list.gid");
+        SQL_ALL_ARTIFACTS = sql.get("all.artifacts");
+    }
+
+    public void addListener(BackendListener listener) {
+        listeners.add(listener);
+        logger.debug("# listeners: " + listeners.size());
+    }
+
+    public void addAllListeners(List<BackendListener> others) {
+        listeners.addAll(others);
+        logger.debug("# listeners: " + listeners.size());
+    }
+
     /**
      * Sets the factory lookup mechanism to decouple ArtifactDatabase
      * and Backend.
@@ -497,9 +514,14 @@
             return null;
         }
 
+        if (factoryLookup == null) {
+            logger.error("factory lookup == null");
+            return false;
+        }
+
         final Object [] loaded = new Object[1];
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_LOAD_BY_GID);
                 stmnt.setString(1, identifer);
@@ -516,11 +538,6 @@
 
                 String factoryName = result.getString(3);
 
-                if (factoryLookup == null) {
-                    logger.error("factory lookup == null");
-                    return false;
-                }
-
                 ArtifactFactory factory = factoryLookup
                     .getArtifactFactory(factoryName);
 
@@ -592,9 +609,10 @@
             throw new RuntimeException("No valid UUID");
         }
 
-        final int [] id = new int[1];
+        final int     [] id     = new int[1];
+        final boolean [] stored = new boolean[1];
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
 
                 prepareStatement(SQL_GET_ID);
@@ -607,7 +625,7 @@
 
                 reset();
 
-                if (ID != null) { // already in database
+                if (stored[0] = ID != null) { // already in database
                     prepareStatement(SQL_REPLACE);
 
                     if (ttl == null) {
@@ -663,6 +681,13 @@
             throw new RuntimeException("failed insert artifact into database");
         }
 
+        if (stored[0]) {
+            fireStoredArtifact(artifact);
+        }
+        else {
+            fireCreatedArtifact(artifact);
+        }
+
         return id[0];
     }
 
@@ -680,7 +705,7 @@
     ) {
         final int [] id = new int[1];
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_NEXT_ID);
                 result = stmnt.executeQuery();
@@ -722,16 +747,24 @@
             throw new RuntimeException("failed insert artifact into database");
         }
 
+        fireCreatedArtifact(artifact);
+
         return id[0];
     }
 
+    protected void fireCreatedArtifact(Artifact artifact) {
+        for (BackendListener listener: listeners) {
+            listener.createdArtifact(artifact, this);
+        }
+    }
+
     /**
      * Touches the access timestamp of a given artifact to prevent
      * that it will be removed from the database by the database cleaner.
      * @param artifact The persistent wrapper around the living artifact.
      */
     public void touch(final PersistentArtifact artifact) {
-        new SQLExecutor() {
+        sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_TOUCH);
                 stmnt.setInt(1, artifact.getId());
@@ -748,7 +781,7 @@
      * artifact.
      */
     public void store(final PersistentArtifact artifact) {
-        new SQLExecutor() {
+        boolean success = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_UPDATE);
                 stmnt.setInt(2, artifact.getId());
@@ -763,10 +796,22 @@
                 return true;
             }
         }.runWrite();
+
+        if (success) {
+            fireStoredArtifact(artifact.getArtifact());
+        }
     }
 
+    protected void fireStoredArtifact(Artifact artifact) {
+        for (BackendListener listener: listeners) {
+            listener.storedArtifact(artifact, this);
+        }
+    }
+
+
     public User createUser(
         final String      name,
+        final String      account,
         final Document    role,
         final UserFactory factory,
         final Object      context
@@ -775,7 +820,7 @@
 
         final byte [] roleData = XMLUtils.toByteArray(role, true);
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
 
                 prepareStatement(SQL_USERS_NEXT_ID);
@@ -796,24 +841,38 @@
                 stmnt.setInt(1, id);
                 stmnt.setString(2, identifier);
                 stmnt.setString(3, name);
+                stmnt.setString(4, account);
 
                 if (roleData == null) {
-                    stmnt.setNull(4, Types.BIGINT);
+                    stmnt.setNull(5, Types.BIGINT);
                 }
                 else {
-                    stmnt.setBytes(4, roleData);
+                    stmnt.setBytes(5, roleData);
                 }
 
                 stmnt.execute();
                 conn.commit();
 
                 user[0] = factory.createUser(
-                    identifier, name, role, context);
+                    identifier, name, account, role, context);
                 return true;
             }
         };
 
-        return exec.runWrite() ? user[0] : null;
+        boolean success = exec.runWrite();
+
+        if (success) {
+            fireCreatedUser(user[0]);
+            return user[0];
+        }
+
+        return null;
+    }
+
+    protected void fireCreatedUser(User user) {
+        for (BackendListener listener: listeners) {
+            listener.createdUser(user, this);
+        }
     }
 
     public boolean deleteUser(final String identifier) {
@@ -822,7 +881,7 @@
             return false;
         }
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_USERS_SELECT_ID_BY_GID);
 
@@ -873,7 +932,19 @@
             }
         };
 
-        return exec.runWrite();
+        boolean success = exec.runWrite();
+
+        if (success) {
+            fireDeletedUser(identifier);
+        }
+
+        return success;
+    }
+
+    protected void fireDeletedUser(String identifier) {
+        for (BackendListener listener: listeners) {
+            listener.deletedUser(identifier, this);
+        }
     }
 
     public User getUser(
@@ -888,7 +959,7 @@
 
         final User [] user = new User[1];
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_USERS_SELECT_GID);
                 stmnt.setString(1, identifier);
@@ -898,12 +969,50 @@
                 }
                 // omit id
                 String  name     = result.getString(2);
-                byte [] roleData = result.getBytes(3);
+                String account   = result.getString(3);
+                byte [] roleData = result.getBytes(4);
 
                 Document role = XMLUtils.fromByteArray(roleData, true);
 
                 user[0] = factory.createUser(
-                    identifier, name, role, context);
+                    identifier, name, account, role, context);
+                return true;
+            }
+        };
+
+        return exec.runRead() ? user[0] : null;
+    }
+
+    /**
+     * Find/Get user by account
+     */
+    public User findUser(
+        final String      account,
+        final UserFactory factory,
+        final Object      context
+    ) {
+
+        final User [] user = new User[1];
+        logger.debug("Tying to find user by account " + account);
+
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_USERS_SELECT_ACCOUNT);
+                stmnt.setString(1, account);
+                result = stmnt.executeQuery();
+                if (!result.next()) { // no such user
+                    logger.debug("No user found.");
+                    return false;
+                }
+                String  identifier = result.getString(1);
+                String  name     = result.getString(2);
+                String account   = result.getString(3);
+                byte [] roleData = result.getBytes(4);
+
+                Document role = XMLUtils.fromByteArray(roleData, true);
+
+                user[0] = factory.createUser(
+                    identifier, name, account, role, context);
                 return true;
             }
         };
@@ -917,7 +1026,7 @@
     ) {
         final ArrayList<User> users = new ArrayList<User>();
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_USERS_SELECT_ALL);
                 result = stmnt.executeQuery();
@@ -926,11 +1035,12 @@
                     // omit id
                     String  identifier = result.getString(2);
                     String  name       = result.getString(3);
-                    byte [] roleData   = result.getBytes(4);
+                    String  account    = result.getString(4);
+                    byte [] roleData   = result.getBytes(5);
 
                     Document role = XMLUtils.fromByteArray(roleData, true);
                     User user = factory.createUser(
-                        identifier, name, role, context);
+                        identifier, name, account, role, context);
                     users.add(user);
                 }
                 return true;
@@ -963,7 +1073,7 @@
 
         final byte [] data = XMLUtils.toByteArray(attribute, true);
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 // fetch owner id
                 prepareStatement(SQL_USERS_SELECT_ID_BY_GID);
@@ -1038,11 +1148,28 @@
                 collection[0] = factory.createCollection(
                     identifier, name, creationTime, ttl, attribute, context);
 
+                if (collection[0] != null) {
+                    // XXX: Little hack to make the listeners happy
+                    collection[0].setUser(new DefaultUser(ownerIdentifier));
+                }
+
                 return true;
             }
         };
 
-        return exec.runWrite() ? collection[0]: null;
+        boolean success = exec.runWrite();
+
+        if (success) {
+            fireCreatedCollection(collection[0]);
+            return collection[0];
+        }
+        return null;
+    }
+
+    protected void fireCreatedCollection(ArtifactCollection collection) {
+        for (BackendListener listener: listeners) {
+            listener.createdCollection(collection, this);
+        }
     }
 
     public ArtifactCollection getCollection(
@@ -1058,7 +1185,7 @@
 
         final ArtifactCollection[] ac = new ArtifactCollection[1];
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
 
                 prepareStatement(SQL_COLLECTIONS_SELECT_GID);
@@ -1119,7 +1246,7 @@
         final ArrayList<ArtifactCollection> collections =
             new ArrayList<ArtifactCollection>();
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
 
             public boolean doIt() throws SQLException {
 
@@ -1176,12 +1303,41 @@
     }
 
 
+    public String getMasterArtifact(final String collectionId) {
+        if (!isValidIdentifier(collectionId)) {
+            logger.debug("Invalid collection id: '" + collectionId + "'");
+            return null;
+        }
+        final String [] uuid = new String[1];
+
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            public boolean doIt() throws SQLException {
+                // Fetch masters (oldest artifact) id.
+                prepareStatement(SQL_COLLECTIONS_OLDEST_ARTIFACT);
+                stmnt.setString(1, collectionId);
+                stmnt.setMaxRows(1); //
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    logger.debug("No such collection: " + collectionId);
+                    return false;
+                }
+                uuid[0] = result.getString(1);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("getMasterArtifact result.getString " +
+                        uuid[0]);
+                }
+                return true;
+            }
+        };
+        return exec.runRead() ? uuid[0] : null;
+    }
+
     public boolean deleteCollection(final String collectionId) {
         if (!isValidIdentifier(collectionId)) {
             logger.debug("Invalid collection id: '" + collectionId + "'");
             return false;
         }
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 // fetch collection id
                 prepareStatement(SQL_COLLECTIONS_ID_BY_GID);
@@ -1195,6 +1351,8 @@
                 reset();
 
                 // outdate artifacts that are only in this collection
+                logger.info("Outdate Artifacts that belong to collection: " + id);
+
                 prepareStatement(SQL_OUTDATE_ARTIFACTS_COLLECTION);
                 stmnt.setInt(1, id);
                 stmnt.setInt(2, id);
@@ -1215,7 +1373,19 @@
                 return true;
             }
         };
-        return exec.runWrite();
+        boolean success = exec.runWrite();
+
+        if (success) {
+            fireDeletedCollection(collectionId);
+        }
+
+        return success;
+    }
+
+    protected void fireDeletedCollection(String identifier) {
+        for (BackendListener listener: listeners) {
+            listener.deletedCollection(identifier, this);
+        }
     }
 
     public Document getCollectionAttribute(final String collectionId) {
@@ -1225,7 +1395,7 @@
 
         final byte[][] data = new byte[1][1];
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_COLLECTION_GET_ATTRIBUTE);
                 stmnt.setString(1, collectionId);
@@ -1256,7 +1426,7 @@
 
         final byte [] data = XMLUtils.toByteArray(attribute, true);
 
-        return new SQLExecutor() {
+        boolean success = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
 
                 // set the column in collection items
@@ -1280,6 +1450,21 @@
                 return true;
             }
         }.runWrite();
+
+        if (success) {
+            fireChangedCollectionAttribute(collectionId, attribute);
+        }
+
+        return success;
+    }
+
+    protected void fireChangedCollectionAttribute(
+        String   collectionId,
+        Document document
+    ) {
+        for (BackendListener listener: listeners) {
+            listener.changedCollectionAttribute(collectionId, document, this);
+        }
     }
 
     public Document getCollectionItemAttribute(
@@ -1297,7 +1482,7 @@
 
         final byte [][] data = new byte[1][1];
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_COLLECTION_ITEM_GET_ATTRIBUTE);
                 stmnt.setString(1, collectionId);
@@ -1333,7 +1518,7 @@
 
         final byte [] data = XMLUtils.toByteArray(attribute, true);
 
-        return new SQLExecutor() {
+        boolean success = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
 
                 // set the column in collection items
@@ -1358,6 +1543,24 @@
                 return true;
             }
         }.runWrite();
+
+        if (success) {
+            fireChangedCollectionItemAttribute(
+                collectionId, artifactId, attribute);
+        }
+
+        return success;
+    }
+
+    protected void fireChangedCollectionItemAttribute(
+        String collectionId,
+        String artifactId,
+        Document document
+    ) {
+        for (BackendListener listener: listeners) {
+            listener.changedCollectionItemAttribute(
+                collectionId, artifactId, document, this);
+        }
     }
 
     public boolean addCollectionArtifact(
@@ -1377,7 +1580,7 @@
 
         final byte [] data = XMLUtils.toByteArray(attribute, true);
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 // fetch artifact id
                 prepareStatement(SQL_GET_ID);
@@ -1439,7 +1642,23 @@
                 return true;
             }
         };
-        return exec.runWrite();
+        boolean success = exec.runWrite();
+
+        if (success) {
+            fireAddedArtifactToCollection(artifactId, collectionId);
+        }
+
+        return success;
+    }
+
+    protected void fireAddedArtifactToCollection(
+        String artifactId,
+        String collectionId
+    ) {
+        for (BackendListener listener: listeners) {
+            listener.addedArtifactToCollection(
+                artifactId, collectionId, this);
+        }
     }
 
     public boolean removeCollectionArtifact(
@@ -1450,7 +1669,8 @@
             logger.debug("Invalid collection id: '" + collectionId + "'");
             return false;
         }
-        return new SQLExecutor() {
+
+        boolean success = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
 
                 // fetch id, collection id and artitfact id
@@ -1490,6 +1710,22 @@
                 return true;
             }
         }.runWrite();
+
+        if (success) {
+            fireRemovedArtifactFromCollection(artifactId, collectionId);
+        }
+
+        return success;
+    }
+
+    protected void fireRemovedArtifactFromCollection(
+        String artifactId,
+        String collectionId
+    ) {
+        for (BackendListener listener: listeners) {
+            listener.removedArtifactFromCollection(
+                artifactId, collectionId, this);
+        }
     }
 
     public CollectionItem [] listCollectionArtifacts(
@@ -1503,7 +1739,7 @@
         final ArrayList<CollectionItem> collectionItems =
             new ArrayList<CollectionItem>();
 
-        SQLExecutor exec = new SQLExecutor() {
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_COLLECTION_ITEMS_LIST_GID);
                 stmnt.setString(1, collectionId);
@@ -1531,7 +1767,7 @@
             return false;
         }
 
-        return new SQLExecutor() {
+        return sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_UPDATE_COLLECTION_TTL);
                 if (ttl == null) {
@@ -1556,7 +1792,7 @@
             return false;
         }
 
-        return new SQLExecutor() {
+        boolean success = sqlExecutor.new Instance() {
             public boolean doIt() throws SQLException {
                 prepareStatement(SQL_UPDATE_COLLECTION_NAME);
                 stmnt.setString(1, name);
@@ -1567,6 +1803,106 @@
                 return true;
             }
         }.runWrite();
+
+        if (success) {
+            fireSetCollectionName(uuid, name);
+        }
+
+        return success;
+    }
+
+    protected void fireSetCollectionName(String identifier, String name) {
+        for (BackendListener listener: listeners) {
+            listener.setCollectionName(identifier, name);
+        }
+    }
+
+    public boolean loadAllArtifacts(final ArtifactLoadedCallback alc) {
+
+        logger.debug("loadAllArtifacts");
+
+        if (factoryLookup == null) {
+            logger.error("factory lookup == null");
+            return false;
+        }
+
+        boolean success = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                // a little cache to avoid too much deserializations.
+                LRUCache<String, Artifact> alreadyLoaded =
+                    new LRUCache<String, Artifact>(200);
+
+                prepareStatement(SQL_ALL_ARTIFACTS);
+                result = stmnt.executeQuery();
+                while (result.next()) {
+                    String userId         = result.getString("u_gid");
+                    String collectionId   = result.getString("c_gid");
+                    String collectionName = result.getString("c_name");
+                    String artifactId     = result.getString("a_gid");
+                    String factoryName    = result.getString("factory");
+                    Date collectionCreated =
+                        new Date(result.getTimestamp("c_creation").getTime());
+                    Date artifactCreated =
+                        new Date(result.getTimestamp("a_creation").getTime());
+
+                    Artifact artifact = alreadyLoaded.get(artifactId);
+
+                    if (artifact != null) {
+                        alc.artifactLoaded(
+                            userId,
+                            collectionId, collectionName,
+                            collectionCreated,
+                            artifactId, artifactCreated, artifact);
+                        continue;
+                    }
+
+                    ArtifactFactory factory = factoryLookup
+                        .getArtifactFactory(factoryName);
+
+                    if (factory == null) {
+                        logger.error("factory '" + factoryName + "' not found");
+                        continue;
+                    }
+
+                    byte [] bytes = result.getBytes("data");
+
+                    artifact = factory.getSerializer().fromBytes(bytes);
+
+                    if (artifact != null) {
+                        alc.artifactLoaded(
+                            userId,
+                            collectionId, collectionName, collectionCreated,
+                            artifactId, artifactCreated, artifact);
+                    }
+
+                    alreadyLoaded.put(artifactId, artifact);
+                }
+                return true;
+            }
+        }.runRead();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("loadAllArtifacts success: " + success);
+        }
+
+        return success;
+    }
+
+    @Override
+    public void killedArtifacts(List<String> identifiers) {
+        logger.debug("killedArtifacts");
+        for (BackendListener listener: listeners) {
+            listener.killedArtifacts(identifiers, this);
+        }
+    }
+
+    @Override
+    public void killedCollections(List<String> identifiers) {
+        logger.debug("killedCollections");
+        for (BackendListener listener: listeners) {
+            listener.killedCollections(identifiers, this);
+        }
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/BackendListener.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,61 @@
+package de.intevation.artifactdatabase;
+
+import java.util.List;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactCollection;
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.User;
+
+import org.w3c.dom.Document;
+
+public interface BackendListener
+{
+    void setup(GlobalContext globalContext);
+
+    void createdArtifact(Artifact artifact, Backend backend);
+
+    void storedArtifact(Artifact artifact, Backend backend);
+
+    void createdUser(User user, Backend backend);
+
+    void deletedUser(String identifier, Backend backend);
+
+    void createdCollection(ArtifactCollection collection, Backend backend);
+
+    void deletedCollection(String identifier, Backend backend);
+
+    void changedCollectionAttribute(
+        String   identifier,
+        Document document,
+        Backend  backend);
+
+    void changedCollectionItemAttribute(
+        String   collectionId,
+        String   artifactId,
+        Document document,
+        Backend  backend);
+
+    void addedArtifactToCollection(
+        String  artifactId,
+        String  collectionId,
+        Backend backend);
+
+    void removedArtifactFromCollection(
+        String  artifactId,
+        String  collectionId,
+        Backend backend);
+
+    void setCollectionName(
+        String collectionId,
+        String name);
+
+    void killedCollections(
+        List<String> identifiers,
+        Backend      backend);
+
+    void killedArtifacts(
+        List<String> identifiers,
+        Backend      backend);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/CollectionCallContext.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/CollectionCallContext.java	Fri Sep 28 12:15:11 2012 +0200
@@ -7,10 +7,13 @@
  */
 package de.intevation.artifactdatabase;
 
+import java.util.LinkedList;
+
 import org.apache.log4j.Logger;
 
 import de.intevation.artifacts.ArtifactCollection;
 import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.Message;
 
 
 /**
@@ -51,6 +54,23 @@
     }
 
 
+    public boolean isInBackground() {
+        log.debug("CollectionCallContext.isInBackground - NOT IMPLEMENTED");
+        return false;
+    }
+
+
+    public void addBackgroundMessage(Message msg) {
+        log.debug("CollectionCallContext.addBackgroundMessage NOT IMPLEMENTED");
+    }
+
+
+    public LinkedList<Message> getBackgroundMessages() {
+        log.debug("CollectionCallContext.addBackgroundMessage NOT IMPLEMENTED");
+        return null;
+    }
+
+
     public Long getTimeToLive() {
         log.debug("CollectionCallContext.getTimeToLive - NOT IMPLEMENTED");
         return null;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DBConfig.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,83 @@
+package de.intevation.artifactdatabase;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.artifactdatabase.db.SQL;
+import de.intevation.artifactdatabase.db.DBConnection;
+
+public class DBConfig
+{
+    /**
+     * XPath to access the database driver within the global configuration.
+     */
+    public static final String DB_DRIVER =
+        "/artifact-database/database/driver/text()";
+    /**
+     * XPath to access the database URL within the global configuration.
+     */
+    public static final String DB_URL =
+        "/artifact-database/database/url/text()";
+    /**
+     * XPath to access the database use within the global configuration.
+     */
+    public static final String DB_USER =
+        "/artifact-database/database/user/text()";
+    /**
+     * XPath to access the database password within the global configuration.
+     */
+    public static final String DB_PASSWORD =
+        "/artifact-database/database/password/text()";
+
+    private static DBConfig instance;
+
+    private DBConnection dbConnection;
+    private SQL          sql;
+
+    private DBConfig() {
+    }
+
+    private DBConfig(DBConnection dbConnection, SQL sql) {
+        this.dbConnection = dbConnection;
+        this.sql          = sql;
+    }
+
+    public static synchronized DBConfig getInstance() {
+        if (instance == null) {
+            instance = createInstance();
+        }
+        return instance;
+    }
+
+    public SQL getSQL() {
+        return sql;
+    }
+
+    public DBConnection getDBConnection() {
+        return dbConnection;
+    }
+
+    private static DBConfig createInstance() {
+
+        String driver = Config.getStringXPath(
+            DB_DRIVER, DBConnection.DEFAULT_DRIVER);
+
+        String url = Config.getStringXPath(
+            DB_URL, DBConnection.DEFAULT_URL);
+
+        url = Config.replaceConfigDir(url);
+
+        String user = Config.getStringXPath(
+            DB_USER, DBConnection.DEFAULT_USER);
+
+        String password = Config.getStringXPath(
+            DB_PASSWORD, DBConnection.DEFAULT_PASSWORD);
+
+        DBConnection dbConnection = new DBConnection(
+            driver, url, user, password);
+
+        SQL sql = new SQL(driver);
+
+        return new DBConfig(dbConnection, sql);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DBConnection.java	Tue Jun 28 07:51:17 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * Copyright (c) 2010 by Intevation GmbH
- *
- * This program is free software under the LGPL (>=v2.1)
- * Read the file LGPL.txt coming with the software for details
- * or visit http://www.gnu.org/licenses/ if it does not exist.
- */
-
-package de.intevation.artifactdatabase;
-
-import de.intevation.artifacts.common.utils.Config;
-
-import java.io.File;
-
-import java.sql.SQLException;
-
-import javax.sql.DataSource;
-
-import org.apache.commons.dbcp.BasicDataSource;
-
-import org.apache.log4j.Logger;
-
-/**
- * This class encapsulate the creation and pooling of database connections used
- * by the artifact database. The credential to open the database connections
- * are taken from the global configuratiion.
- * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
- */
-public class DBConnection
-{
-    private static Logger logger = Logger.getLogger(DBConnection.class);
-
-    /**
-     * XPath to access the database driver within the global configuration.
-     */
-    public static final String DB_DRIVER =
-        "/artifact-database/database/driver/text()";
-    /**
-     * XPath to access the database URL within the global configuration.
-     */
-    public static final String DB_URL =
-        "/artifact-database/database/url/text()";
-    /**
-     * XPath to access the database use within the global configuration.
-     */
-    public static final String DB_USER =
-        "/artifact-database/database/user/text()";
-    /**
-     * XPath to access the database password within the global configuration.
-     */
-    public static final String DB_PASSWORD =
-        "/artifact-database/database/password/text()";
-
-    /**
-     * The default database driver: H2
-     */
-    public static final String DEFAULT_DRIVER =
-        "org.h2.Driver";
-
-    /**
-     * The default database name: artifacts.db
-     */
-    public static final String DEFAULT_DATABASE_FILE =
-        "artifacts.db";
-
-    /**
-     * The default database URL: This is created once by #getDefaultURL()
-     */
-    public static final String DEFAULT_URL = getDefaultURL();
-
-    /**
-     * The default database user: ""
-     */
-    public static final String DEFAULT_USER     = "";
-
-    /**
-     * The default database password: ""
-     */
-    public static final String DEFAULT_PASSWORD = "";
-
-    private DBConnection() {
-    }
-
-    /**
-     * Constructs the default databse URL. It concats the
-     * config directory and the #DEFAULT_DATABASE_FILE
-     * to the string with is needed to access H2 databases.
-     * @return The default URL.
-     */
-    public static final String getDefaultURL() {
-        File configDir = Config.getConfigDirectory();
-        File databaseFile = new File(configDir, DEFAULT_DATABASE_FILE);
-        return "jdbc:h2:" + databaseFile;
-    }
-
-    private static BasicDataSource dataSource;
-
-    private static final void addShutdownHook() {
-        Runtime.getRuntime().addShutdownHook(new Thread() {
-            @Override
-            public void run() {
-                if (dataSource != null) {
-                    try {
-                        dataSource.close();
-                    }
-                    catch (SQLException sqle) {
-                    }
-                    dataSource = null;
-                }
-            }
-        });
-    }
-
-    /**
-     * Static method to fetch a database connection.
-     * @return a DataSource to access the database connection.
-     */
-    public static synchronized DataSource getDataSource() {
-        if (dataSource == null) {
-            dataSource = new BasicDataSource();
-
-            String driver = Config.getStringXPath(
-                DB_DRIVER, DEFAULT_DRIVER);
-
-            String url = Config.getStringXPath(
-                DB_URL, DEFAULT_URL);
-
-            url = Config.replaceConfigDir(url);
-
-            String user = Config.getStringXPath(
-                DB_USER, DEFAULT_USER);
-
-            String password = Config.getStringXPath(
-                DB_PASSWORD, DEFAULT_PASSWORD);
-
-            logger.info("database driver: " + driver);
-            logger.info("database url: " + url);
-
-            dataSource.setDriverClassName(driver);
-            dataSource.setUsername(user);
-            dataSource.setPassword(password);
-            dataSource.setUrl(url);
-            addShutdownHook();
-        }
-
-        return dataSource;
-    }
-}
-//  vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java	Fri Sep 28 12:15:11 2012 +0200
@@ -9,9 +9,13 @@
 package de.intevation.artifactdatabase;
 
 import de.intevation.artifacts.common.utils.Config;
+import de.intevation.artifacts.common.utils.StringUtils;
 
 import de.intevation.artifacts.Artifact;
 
+import de.intevation.artifactdatabase.db.SQL;
+import de.intevation.artifactdatabase.db.DBConnection;
+
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -57,6 +61,9 @@
          */
         Artifact reviveArtifact(String factoryName, byte [] bytes);
 
+        void killedArtifacts(List<String> identifiers);
+        void killedCollections(List<String> identifiers);
+
     } // interface ArtifactReviver
 
     public interface LockedIdsProvider {
@@ -77,23 +84,16 @@
     /**
      * The SQL statement to select the outdated artifacts.
      */
-    public static final String SQL_OUTDATED =
-        SQL.get("artifacts.outdated");
+    public String SQL_OUTDATED;
 
-    public static final String SQL_OUTDATED_COLLECTIONS =
-        SQL.get("collections.outdated");
-
-    public static final String SQL_DELETE_COLLECTION_ITEMS =
-        SQL.get("delete.collection.items");
-
-    public static final String SQL_DELETE_COLLECTION =
-        SQL.get("delete.collection");
+    public String SQL_OUTDATED_COLLECTIONS;
+    public String SQL_DELETE_COLLECTION_ITEMS;
+    public String SQL_DELETE_COLLECTION;
 
     /**
      * The SQL statement to delete some artifacts from the database.
      */
-    public static final String SQL_DELETE_ARTIFACT =
-        SQL.get("artifacts.delete");
+    public String SQL_DELETE_ARTIFACT;
 
     /**
      * XPath to figure out how long the cleaner should sleep between
@@ -135,6 +135,8 @@
      */
     protected ArtifactReviver reviver;
 
+    protected DBConnection dbConnection;
+
     /**
      * Default constructor.
      */
@@ -147,11 +149,21 @@
      * @param context The global context of the artifact database
      * @param reviver The reviver to awake artifact one last time.
      */
-    public DatabaseCleaner(Object context, ArtifactReviver reviver) {
+    public DatabaseCleaner(Object context, ArtifactReviver reviver, DBConfig config) {
         setDaemon(true);
         sleepTime = getSleepTime();
         this.context = context;
         this.reviver = reviver;
+        this.dbConnection = config.getDBConnection();
+        setupSQL(config.getSQL());
+    }
+
+    protected void setupSQL(SQL sql) {
+        SQL_OUTDATED                = sql.get("artifacts.outdated");
+        SQL_OUTDATED_COLLECTIONS    = sql.get("collections.outdated");
+        SQL_DELETE_COLLECTION_ITEMS = sql.get("delete.collection.items");
+        SQL_DELETE_COLLECTION       = sql.get("delete.collection");
+        SQL_DELETE_ARTIFACT         = sql.get("artifacts.delete");
     }
 
     /**
@@ -159,7 +171,6 @@
      * Living artifacts are artifacts which are currently active
      * inside the artifact database. Deleting them in this state
      * would create severe internal problems.
-     * @param filter
      */
     public void setLockedIdsProvider(LockedIdsProvider lockedIdsProvider) {
         this.lockedIdsProvider = lockedIdsProvider;
@@ -196,14 +207,30 @@
         return SLEEP_DEFAULT;
     }
 
-    private static final class IdData {
+    private static class IdIdentifier {
 
         int     id;
+        String  identifier;
+
+        private IdIdentifier(int id, String identifier) {
+            this.id         = id;
+            this.identifier = identifier;
+        }
+    } // class IdIdentifier
+
+    private static final class IdData
+    extends IdIdentifier
+    {
         byte [] data;
         String  factoryName;
 
-        public IdData(int id, String factoryName, byte [] data) {
-            this.id          = id;
+        public IdData(
+            int     id,
+            String  factoryName,
+            byte [] data,
+            String  identifier
+        ) {
+            super(id, identifier);
             this.factoryName = factoryName;
             this.data        = data;
         }
@@ -227,10 +254,7 @@
         PreparedStatement stmnt      = null;
         ResultSet         result     = null;
 
-        int removedCollections = 0;
-        int removedArtifacts   = 0;
-
-        DataSource dataSource = DBConnection.getDataSource();
+        DataSource dataSource = dbConnection.getDataSource();
 
         Set<Integer> lockedIds = lockedIdsProvider != null
             ? lockedIdsProvider.getLockedIds()
@@ -240,6 +264,9 @@
             ? "-666" // XXX: A bit hackish.
             : StringUtils.repeat('?', lockedIds.size(), ',');
 
+        List<String> deletedCollections = new ArrayList<String>();
+        List<String> deletedArtifacts   = new ArrayList<String>();
+
         try {
             connection = dataSource.getConnection();
             connection.setAutoCommit(false);
@@ -264,10 +291,12 @@
                 ++idx;
             }
 
-            ArrayList<Integer> cs = new ArrayList<Integer>();
+            ArrayList<IdIdentifier> cs = new ArrayList<IdIdentifier>();
             result = stmnt.executeQuery();
             while (result.next()) {
-                cs.add(result.getInt(1));
+                cs.add(new IdIdentifier(
+                    result.getInt(1),
+                    result.getString(2)));
             }
 
             result.close(); result = null;
@@ -276,8 +305,9 @@
             // delete collection items
             stmnt = connection.prepareStatement(SQL_DELETE_COLLECTION_ITEMS);
 
-            for (Integer id: cs) {
-                stmnt.setInt(1, id);
+            for (IdIdentifier id: cs) {
+                logger.debug("Mark collection for deletion: " + id.id);
+                stmnt.setInt(1, id.id);
                 stmnt.execute();
             }
 
@@ -286,15 +316,16 @@
             // delete collections
             stmnt = connection.prepareStatement(SQL_DELETE_COLLECTION);
 
-            for (Integer id: cs) {
-                stmnt.setInt(1, id);
+            for (IdIdentifier id: cs) {
+                stmnt.setInt(1, id.id);
                 stmnt.execute();
+                deletedCollections.add(id.identifier);
             }
 
             stmnt.close(); stmnt = null;
             connection.commit();
 
-            removedCollections = cs.size(); cs = null;
+            cs = null;
 
             // remove artifacts
             stmnt = connection.prepareStatement(SQL_DELETE_ARTIFACT);
@@ -308,7 +339,8 @@
                     ids.add(new IdData(
                         result.getInt(1),
                         result.getString(2),
-                        result.getBytes(3)));
+                        result.getBytes(3),
+                        result.getString(4)));
                 }
 
                 result.close(); result = null;
@@ -323,21 +355,27 @@
                         idData.factoryName, idData.data);
                     idData.data = null;
 
+                    logger.debug("Prepare Artifact (id="
+                        + idData.id + ") for deletion.");
+
                     stmnt.setInt(1, idData.id);
                     stmnt.execute();
                     connection.commit();
 
                     try {
                         if (artifact != null) {
+                            logger.debug("Call endOfLife for Artifact: "
+                                + artifact.identifier());
+
                             artifact.endOfLife(context);
                         }
                     }
                     catch (Exception e) {
-                        logger.error(e.getLocalizedMessage(), e);
+                        logger.error(e.getMessage(), e);
                     }
+
+                    deletedArtifacts.add(idData.identifier);
                 } // for all fetched data
-
-                removedArtifacts += ids.size();
             }
         }
         catch (SQLException sqle) {
@@ -362,8 +400,20 @@
             }
         }
 
-        logger.info("collections removed: " + removedCollections);
-        logger.info("artifacts removed: "   + removedArtifacts);
+        if (!deletedCollections.isEmpty()) {
+            reviver.killedCollections(deletedCollections);
+        }
+
+        if (!deletedArtifacts.isEmpty()) {
+            reviver.killedArtifacts(deletedArtifacts);
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(
+                "collections removed: " + deletedCollections.size());
+            logger.debug(
+                "artifacts removed: " + deletedArtifacts.size());
+        }
     }
 
     /**
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifact.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifact.java	Fri Sep 28 12:15:11 2012 +0200
@@ -13,6 +13,7 @@
 import de.intevation.artifacts.Artifact;
 import de.intevation.artifacts.ArtifactFactory;
 import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -35,6 +36,7 @@
      */
     protected String identifier;
 
+
     /**
      * Default constructor.
      */
@@ -57,6 +59,7 @@
         return this.identifier;
     }
 
+
     public String hash() {
         String hash = String.valueOf(hashCode());
         if (logger.isDebugEnabled()) {
@@ -112,8 +115,12 @@
         }
     }
 
-    public void setup(String identifier, ArtifactFactory factory,
-                      Object context, Document data) {
+    public void setup(String identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
         if (logger.isDebugEnabled()) {
             logger.debug("DefaultArtifact.setup: " + identifier);
         }
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollection.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollection.java	Fri Sep 28 12:15:11 2012 +0200
@@ -36,33 +36,23 @@
 public class DefaultArtifactCollection
 implements   ArtifactCollection
 {
-    /** The logger used in this class.*/
+    /** The logger used in this class. */
     private static Logger logger =
         Logger.getLogger(DefaultArtifactCollection.class);
 
-    /**
-     * The identifier of the collection.
-     */
+    /** The identifier of the collection. */
     protected String identifier;
 
-    /**
-     * The identifier of the collection.
-     */
+    /** The identifier of the collection. */
     protected String name;
 
-    /**
-     * The owner of this collection.
-     */
+    /** The owner of this collection. */
     protected User user;
 
-    /**
-     * The attribute of this collection.
-     */
+    /** The attribute of this collection. */
     protected Document attribute;
 
-    /**
-     * The artifacts stored in this collection.
-     */
+    /** The artifacts stored in this collection. */
     protected List<Artifact> artifacts;
 
     /**
@@ -151,7 +141,7 @@
 
     /**
      * Name of this collection.
-     * @return Returns the name of this collection
+     * @param name the name of this collection
      */
     public void setName(String name) {
         this.name = name;
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollectionFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactCollectionFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -103,8 +103,6 @@
 
     /**
      * Returns the time to live of the given artifact.
-     *
-     * @param artifact
      */
     public Long timeToLiveUntouched(
         ArtifactCollection collection,
@@ -134,7 +132,13 @@
             ArtifactCollection collection =
                 (ArtifactCollection) clazz.newInstance();
 
-            collection.setup(identifier, name, creationTime, ttl, this, context, data);
+            collection.setup(identifier,
+                name,
+                creationTime,
+                ttl,
+                this,
+                context,
+                data);
 
             return collection;
         }
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java	Fri Sep 28 12:15:11 2012 +0200
@@ -12,13 +12,15 @@
 
 import org.w3c.dom.Document;
 
+import de.intevation.artifacts.GlobalContext;
+
 /**
  * Default implementation of the context.
  * Besides of the configuration it hosts a map to store key/value pairs.
  *
  * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
-public class DefaultArtifactContext
+public class DefaultArtifactContext implements GlobalContext
 {
     /**
      * The global configuration document of the artifact database.
@@ -28,7 +30,7 @@
     /**
      * Custom key/value pairs to be used globally in the whole server.
      */
-    protected HashMap  map;
+    protected HashMap map;
 
     /**
      * Default constructor
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContextFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContextFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -9,6 +9,7 @@
 package de.intevation.artifactdatabase;
 
 import de.intevation.artifacts.ArtifactContextFactory;
+import de.intevation.artifacts.GlobalContext;
 
 import org.w3c.dom.Document;
 
@@ -27,7 +28,7 @@
     public DefaultArtifactContextFactory() {
     }
 
-    public Object createArtifactContext(Document config) {
+    public GlobalContext createArtifactContext(Document config) {
         return new DefaultArtifactContext(config);
     }
 }
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -13,6 +13,8 @@
 import de.intevation.artifacts.Artifact;
 import de.intevation.artifacts.ArtifactFactory;
 import de.intevation.artifacts.ArtifactSerializer;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
 
 import org.apache.log4j.Logger;
 
@@ -100,15 +102,16 @@
     }
 
     public Artifact createArtifact(
-        String   identifier,
-        Object   context,
-        Document data
+        String        identifier,
+        GlobalContext context,
+        CallMeta      callMeta,
+        Document      data
     ) {
         try {
             Artifact artifact =
                 (Artifact)artifactClass.newInstance();
 
-            artifact.setup(identifier, this, context, data);
+            artifact.setup(identifier, this, context, callMeta, data);
 
             return artifact;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultBackendListener.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,116 @@
+package de.intevation.artifactdatabase;
+
+import java.util.List;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactCollection;
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.User;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+public class DefaultBackendListener
+implements   BackendListener
+{
+    private static Logger log = Logger.getLogger(DefaultBackendListener.class);
+
+    public DefaultBackendListener() {
+    }
+
+    @Override
+    public void setup(GlobalContext globalContext) {
+        log.debug("setup");
+    }
+
+    @Override
+    public void createdArtifact(Artifact artifact, Backend backend) {
+        log.debug("createdArtifact");
+    }
+
+    @Override
+    public void storedArtifact(Artifact artifact, Backend backend) {
+        log.debug("storedArtifact");
+    }
+
+    @Override
+    public void createdUser(User user, Backend backend) {
+        log.debug("createdUser");
+    }
+
+    @Override
+    public void deletedUser(String identifier, Backend backend) {
+        log.debug("deletedUser");
+    }
+
+    @Override
+    public void createdCollection(
+        ArtifactCollection collection,
+        Backend            backend
+    ) {
+        log.debug("createdCollection");
+    }
+
+    @Override
+    public void deletedCollection(String identifier, Backend backend) {
+        log.debug("deletedCollection");
+    }
+
+    @Override
+    public void changedCollectionAttribute(
+        String   identifier,
+        Document document,
+        Backend  backend
+    ) {
+        log.debug("changedCollectionAttribute");
+    }
+
+    @Override
+    public void changedCollectionItemAttribute(
+        String   collectionId,
+        String   artifactId,
+        Document document,
+        Backend  backend
+    ) {
+        log.debug("changedCollectionItemAttribute");
+    }
+
+    @Override
+    public void addedArtifactToCollection(
+        String  artifactId,
+        String  collectionId,
+        Backend backend
+    ) {
+        log.debug("addedArtifactToCollection");
+    }
+
+    @Override
+    public void removedArtifactFromCollection(
+        String  artifactId,
+        String  collectionId,
+        Backend backend
+    ) {
+        log.debug("removedArtifactFromCollection");
+    }
+
+    @Override
+    public void setCollectionName(
+        String collectionId,
+        String name
+    ) {
+        log.debug("setCollectionName");
+    }
+
+    @Override
+    public void killedCollections(List<String> identifiers, Backend backend) {
+        log.debug("killedCollections");
+    }
+
+    @Override
+    public void killedArtifacts(List<String> identifiers, Backend backend) {
+        log.debug("killedArtifacts");
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java	Fri Sep 28 12:15:11 2012 +0200
@@ -8,10 +8,9 @@
 
 package de.intevation.artifactdatabase;
 
-import de.intevation.artifacts.common.utils.XMLUtils;
-
 import de.intevation.artifacts.CallMeta;
 import de.intevation.artifacts.Service;
+import de.intevation.artifacts.GlobalContext;
 import de.intevation.artifacts.ServiceFactory;
 
 import org.apache.log4j.Logger;
@@ -29,21 +28,43 @@
 {
     private static Logger logger = Logger.getLogger(DefaultService.class);
 
-    public Document process(
-        Document data,
-        Object   globalContext,
-        CallMeta callMeta
+    public static class Output implements Service.Output {
+
+        protected Object data;
+        protected String mimeType;
+
+        public Output() {
+        }
+
+        public Output(Object data, String mimeType) {
+            this.data     = data;
+            this.mimeType = mimeType;
+        }
+
+        @Override
+        public Object getData() {
+            return data;
+        }
+
+        @Override
+        public String getMIMEType() {
+            return mimeType;
+        }
+    } // class Output
+
+    @Override
+    public Service.Output process(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
     ) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("Service.process");
-        }
-        return XMLUtils.newDocument();
+        logger.debug("Service.process");
+        return new Output(new byte[0], "application/octet-stream");
     }
 
-    public void setup(ServiceFactory factory, Object globalContext) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("Service.setup");
-        }
+    @Override
+    public void setup(ServiceFactory factory, GlobalContext globalContext) {
+        logger.debug("Service.setup");
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultServiceFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultServiceFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -11,6 +11,7 @@
 import de.intevation.artifacts.common.utils.Config;
 
 import de.intevation.artifacts.Service;
+import de.intevation.artifacts.GlobalContext;
 import de.intevation.artifacts.ServiceFactory;
 
 import org.apache.log4j.Logger;
@@ -80,15 +81,18 @@
     public DefaultServiceFactory() {
     }
 
+    @Override
     public String getName() {
         return name;
     }
 
+    @Override
     public String getDescription() {
         return description;
     }
 
-    public Service createService(Object globalContext) {
+    @Override
+    public Service createService(GlobalContext globalContext) {
         try {
             Service service = (Service)serviceClass.newInstance();
 
@@ -109,6 +113,7 @@
         return null;
     }
 
+    @Override
     public void setup(Document config, Node factoryNode) {
 
         description = Config.getStringXPath(
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultUser.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultUser.java	Fri Sep 28 12:15:11 2012 +0200
@@ -25,6 +25,9 @@
     /** The name of the user.*/
     protected String name;
 
+    /** The account name of the user.*/
+    protected String account;
+
     /** The role of the user.*/
     protected Document role;
 
@@ -35,18 +38,24 @@
     public DefaultUser() {
     }
 
+    public DefaultUser(String identifier) {
+        this.identifier = identifier;
+    }
 
     /**
      * A constructor that creates a new user.
      *
      * @param identifier The uuid of the user.
      * @param name The name of the user.
+     * @param account The account name of the user.
      * @param role The role of the user.
      */
-    public DefaultUser(String identifier, String name, Document role) {
+    public DefaultUser(String identifier, String name, String account,
+                       Document role) {
         this.identifier = identifier;
         this.name       = name;
         this.role       = role;
+        this.account    = account;
     }
 
 
@@ -55,6 +64,7 @@
      *
      * @return the identifier of this user.
      */
+    @Override
     public String identifier() {
         return identifier;
     }
@@ -65,6 +75,7 @@
      *
      * @return the name of the user.
      */
+    @Override
     public String getName() {
         return name;
     }
@@ -75,6 +86,7 @@
      *
      * @param name The name for this user.
      */
+    @Override
     public void setName(String name) {
         this.name = name;
     }
@@ -85,6 +97,7 @@
      *
      * @param identifier The new identifier.
      */
+    @Override
     public void setIdentifier(String identifier) {
         this.identifier = identifier;
     }
@@ -95,6 +108,7 @@
      *
      * @param role The new role of the user.
      */
+    @Override
     public void setRole(Document role) {
         this.role = role;
     }
@@ -105,8 +119,19 @@
      *
      * @return the role of the user.
      */
+    @Override
     public Document getRole() {
         return role;
     }
+
+    /**
+     * Returns the account of the user.
+     *
+     * @return the account name of the user.
+     */
+    @Override
+    public String getAccount() {
+        return account;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultUserFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultUserFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -47,17 +47,19 @@
      *
      * @param identifier The identifier for the new user.
      * @param name The name for the new user.
+     * @param account The name of the new users account.
      * @param role The role for the new user.
      * @param context The CallContext.
      */
     public User createUser(
         String   identifier,
         String   name,
+        String   account,
         Document role,
         Object   context)
     {
         logger.debug("DefaultUserFactory.createUser: " + name);
-        return new DefaultUser(identifier, name, role);
+        return new DefaultUser(identifier, name, account, role);
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java	Fri Sep 28 12:15:11 2012 +0200
@@ -14,13 +14,17 @@
 import de.intevation.artifacts.ArtifactContextFactory;
 import de.intevation.artifacts.ArtifactFactory;
 import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.Hook;
 import de.intevation.artifacts.ServiceFactory;
 import de.intevation.artifacts.UserFactory;
 
+import de.intevation.artifacts.common.utils.StringUtils;
 
 import de.intevation.artifactdatabase.rest.HTTPServer;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.log4j.Logger;
 
@@ -104,6 +108,11 @@
     public static final String CALLCONTEXT_LISTENER =
         "/artifact-database/callcontext-listener";
 
+    /**
+     * XPath that points to configuration nodes for hooks.
+     */
+    public static final String HOOKS =
+        "/artifact-database/hooks/hook";
 
     public static final String HTTP_SERVER =
         "/artifact-database/rest-server/http-server/text()";
@@ -111,6 +120,12 @@
     public static final String DEFAULT_HTTP_SERVER =
         "de.intevation.artifactdatabase.rest.Standalone";
 
+    public static final String LIFETIME_LISTENERS =
+        "/artifact-database/lifetime-listeners/listener";
+
+    public static final String BACKEND_LISTENERS =
+        "/artifact-database/backend-listeners/listener";
+
     /**
      * Default export signing secret.
      * <strong>PLEASE CHANGE THE SECRET VIA THE XPATH EXPORT_SECRET
@@ -122,7 +137,7 @@
     /**
      * Reference to the global context build by the global context factory.
      */
-    protected Object context;
+    protected GlobalContext context;
 
     /**
      * List of the artifact factories to be exposed by the
@@ -151,6 +166,16 @@
      */
     protected CallContext.Listener callContextListener;
 
+    protected List<Hook> postFeedHooks;
+
+    protected List<Hook> postAdvanceHooks;
+
+    protected List<Hook> postDescribeHooks;
+
+    protected List<LifetimeListener> lifetimeListeners;
+
+    protected List<BackendListener> backendListeners;
+
     /**
      * byte array holding the export signing secret.
      */
@@ -428,6 +453,161 @@
         }
     }
 
+    protected void loadLifetimeListeners() {
+        logger.info("loading lifetime listeners");
+
+        NodeList nodes = Config.getNodeSetXPath(LIFETIME_LISTENERS);
+
+        if (nodes == null) {
+            logger.debug("no lifetime listeners configure");
+            return;
+        }
+
+        List<LifetimeListener> ltls = new ArrayList<LifetimeListener>();
+
+        for (int i = 0, N = nodes.getLength(); i < N; ++i) {
+            Node node = nodes.item(i);
+            String className = node.getTextContent();
+            if (className == null
+            || (className = className.trim()).length() == 0) {
+                continue;
+            }
+            try {
+                Class clazz = Class.forName(className);
+                LifetimeListener listener =
+                    (LifetimeListener)clazz.newInstance();
+
+                listener.setup(Config.getConfig());
+
+                ltls.add(listener);
+            }
+            catch (ClassNotFoundException cnfe) {
+                logger.error(cnfe.getLocalizedMessage(), cnfe);
+            }
+            catch (InstantiationException ie) {
+                logger.error(ie.getLocalizedMessage(), ie);
+            }
+            catch (ClassCastException cce) {
+                logger.error(cce.getLocalizedMessage(), cce);
+            }
+            catch (IllegalAccessException iae) {
+                logger.error(iae.getLocalizedMessage(), iae);
+            }
+        }
+
+        lifetimeListeners = ltls;
+    }
+
+    protected void loadBackendListeners() {
+        logger.info("loading backend listeners");
+
+        NodeList nodes = Config.getNodeSetXPath(BACKEND_LISTENERS);
+
+        if (nodes == null) {
+            logger.debug("no backend listeners configure");
+            return;
+        }
+
+        List<BackendListener> bls = new ArrayList<BackendListener>();
+
+        for (int i = 0, N = nodes.getLength(); i < N; ++i) {
+            Node node = nodes.item(i);
+            String className = node.getTextContent();
+            if (className == null
+            || (className = className.trim()).length() == 0) {
+                continue;
+            }
+            try {
+                Class clazz = Class.forName(className);
+                BackendListener listener =
+                    (BackendListener)clazz.newInstance();
+
+                bls.add(listener);
+            }
+            catch (ClassNotFoundException cnfe) {
+                logger.error(cnfe.getLocalizedMessage(), cnfe);
+            }
+            catch (InstantiationException ie) {
+                logger.error(ie.getLocalizedMessage(), ie);
+            }
+            catch (ClassCastException cce) {
+                logger.error(cce.getLocalizedMessage(), cce);
+            }
+            catch (IllegalAccessException iae) {
+                logger.error(iae.getLocalizedMessage(), iae);
+            }
+        }
+
+        backendListeners = bls;
+    }
+
+    protected void loadHooks() {
+        logger.info("loading hooks");
+
+        postFeedHooks     = new ArrayList<Hook>();
+        postAdvanceHooks  = new ArrayList<Hook>();
+        postDescribeHooks = new ArrayList<Hook>();
+
+        NodeList nodes = Config.getNodeSetXPath(HOOKS);
+
+        for (int i = 0, len = nodes.getLength(); i < len; i++) {
+            Node   cfg     = nodes.item(i);
+            String applies = Config.getStringXPath(cfg, "@applies");
+
+            if (applies == null || applies.length() == 0) {
+                continue;
+            }
+
+            Hook     hook  = loadHook(cfg);
+            String[] apply = applies.split(",");
+
+            for (String a: apply) {
+                a = a.trim().toLowerCase();
+
+                if (a.equals("post-feed")) {
+                    postFeedHooks.add(hook);
+                }
+                else if (a.equals("post-advance")) {
+                    postAdvanceHooks.add(hook);
+                }
+                else if (a.equals("post-describe")) {
+                    postDescribeHooks.add(hook);
+                }
+            }
+        }
+    }
+
+    protected Hook loadHook(Node hookCfg) {
+        if (hookCfg == null) {
+            return null;
+        }
+
+        Hook hook = null;
+
+        String className = Config.getStringXPath(hookCfg, "@class");
+
+        try {
+            Class clazz = Class.forName(className);
+            hook        = (Hook) clazz.newInstance();
+
+            hook.setup(hookCfg);
+        }
+        catch (ClassNotFoundException cnfe) {
+            logger.error(cnfe.getLocalizedMessage(), cnfe);
+        }
+        catch (InstantiationException ie) {
+            logger.error(ie.getLocalizedMessage(), ie);
+        }
+        catch (ClassCastException cce) {
+            logger.error(cce.getLocalizedMessage(), cce);
+        }
+        catch (IllegalAccessException iae) {
+            logger.error(iae.getLocalizedMessage(), iae);
+        }
+
+        return hook;
+    }
+
     /**
      * Fetches the export signing secret from the global configuration.
      * If none is found if defaults to the DEFAULT_EXORT_SECRET which
@@ -456,6 +636,9 @@
         loadUserFactory();
         loadCallContextListener();
         loadHTTPServer();
+        loadHooks();
+        loadLifetimeListeners();
+        loadBackendListeners();
     }
 
     /**
@@ -496,7 +679,7 @@
      * Returns the global context created by the global context factory.
      * @return The global context.
      */
-    public Object getContext() {
+    public GlobalContext getContext() {
         return context;
     }
 
@@ -518,8 +701,28 @@
         return callContextListener;
     }
 
+    public List<Hook> getPostFeedHooks() {
+        return postFeedHooks;
+    }
+
+    public List<Hook> getPostAdvanceHooks() {
+        return postAdvanceHooks;
+    }
+
+    public List<Hook> getPostDescribeHooks() {
+        return postDescribeHooks;
+    }
+
     public HTTPServer getHTTPServer() {
         return httpServer;
     }
+
+    public List<LifetimeListener> getLifetimeListeners() {
+        return lifetimeListeners;
+    }
+
+    public List<BackendListener> getBackendListeners() {
+        return backendListeners;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/LazyBackendUser.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/LazyBackendUser.java	Fri Sep 28 12:15:11 2012 +0200
@@ -43,28 +43,39 @@
         return user;
     }
 
+    @Override
     public String identifier() {
         return getUser().identifier();
     }
 
+    @Override
     public String getName() {
         return getUser().getName();
     }
 
+    @Override
     public void setName(String name) {
         getUser().setName(name);
     }
 
+    @Override
     public void setIdentifier(String identifier) {
         getUser().setIdentifier(identifier);
     }
 
+    @Override
     public Document getRole() {
         return getUser().getRole();
     }
 
+    @Override
     public void setRole(Document document) {
         getUser().setRole(document);
     }
+
+    @Override
+    public String getAccount() {
+        return getUser().getAccount();
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/LifetimeListener.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,15 @@
+package de.intevation.artifactdatabase;
+
+import de.intevation.artifacts.GlobalContext;
+
+import org.w3c.dom.Document;
+
+public interface LifetimeListener
+{
+    void setup(Document document);
+
+    void systemUp(GlobalContext globalContext);
+
+    void systemDown(GlobalContext globalContext);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/ProtocolUtils.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/ProtocolUtils.java	Fri Sep 28 12:15:11 2012 +0200
@@ -9,9 +9,14 @@
 
 import java.util.List;
 
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
 
 import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
 
 import de.intevation.artifactdatabase.state.Facet;
 import de.intevation.artifactdatabase.state.Output;
@@ -152,24 +157,33 @@
      * This method appends a node for each Output in the <i>outputs</i> list to
      * <i>out</i>. Note: an output node includes its provided facets!
      *
-     * @param creator The ElementCreator used to create new elements.
+     * @param doc The document to which to add new elements.
      * @param out The parent node for new elements.
      * @param outputs The list of reachable outputs.
      */
     public static void appendOutputModes(
-        XMLUtils.ElementCreator creator,
-        Element                 out,
-        List<Output>            outputs)
+        Document     doc,
+        Element      out,
+        List<Output> outputs)
     {
+        ElementCreator creator = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
         for (Output o: outputs) {
             Element newOut = createArtNode(
                 creator,
                 "output",
-                new String[] {"name", "description", "mime-type"},
-                new String[] {o.getName(),o.getDescription(),o.getMimeType()});
+                new String[] {"name", "description", "mime-type", "type"},
+                new String[] {
+                    o.getName(),
+                    o.getDescription(),
+                    o.getMimeType(),
+                    o.getType() });
 
             Element facets = createArtNode(creator, "facets", null, null);
-            appendFacets(creator, facets, o.getFacets());
+            appendFacets(doc, facets, o.getFacets());
 
             newOut.appendChild(facets);
             out.appendChild(newOut);
@@ -181,31 +195,30 @@
      * This method appends a node for each Facet in the <i>facets</i> list to
      * <i>facet</i>.
      *
-     * @param creator The ElementCreator used to create new elements.
+     * @param doc The document to wich to add new elements.
      * @param facet The root node for new elements.
      * @param facets The list of facets.
      */
     public static void appendFacets(
-        XMLUtils.ElementCreator creator,
-        Element                 facet,
-        List<Facet>             facets)
+        Document    doc,
+        Element     facet,
+        List<Facet> facets)
     {
         if (facets == null || facets.size() == 0) {
             return;
         }
 
+        ElementCreator creator = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
         for (Facet f: facets) {
-            Element newFacet = createArtNode(
-                creator,
-                "facet",
-                new String[] { "index", "name", "description" },
-                new String[] {
-                    String.valueOf(f.getIndex()),
-                    f.getName(),
-                    f.getDescription()
-                });
+            Node node = f.toXML(doc);
 
-            facet.appendChild(newFacet);
+            if (node != null) {
+                facet.appendChild(node);
+            }
         }
     }
 }
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/SQL.java	Tue Jun 28 07:51:17 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- * Copyright (c) 2010 by Intevation GmbH
- *
- * This program is free software under the LGPL (>=v2.1)
- * Read the file LGPL.txt coming with the software for details
- * or visit http://www.gnu.org/licenses/ if it does not exist.
- */
-
-package de.intevation.artifactdatabase;
-
-import de.intevation.artifacts.common.utils.Config;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import java.util.Properties;
-
-import org.apache.log4j.Logger;
-
-/**
- * Singleton to provide SQL statement strings as key/value pairs.
- * This mechanism is used to encapsulate database specific SQL
- * dialects.
- *
- * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
- */
-public final class SQL
-{
-    private static Logger logger = Logger.getLogger(SQL.class);
-
-    private SQL() {
-    }
-
-    private static Properties statements;
-
-    /**
-     * Returns key/value pairs of SQL statements for the used database
-     * backend.
-     * The concrete set of SQL statements is determined by the
-     * used JDBC database driver which is configured in conf.xml.
-     * The class name of the driver is transformed by replacing
-     * all '.' with '_' and lower case the resulting string.
-     * The transformed string is used to load a properties file
-     * in '/sql/' which should contain the statements.
-     * Example:<br>
-     * <code>org.postgresql.Driver</code> results in loading of
-     * <code>/sql/org-postgresql-driver.properties</code>.
-     * @return The key/value pairs of SQL statements.
-     */
-    public static final synchronized Properties getStatements() {
-        if (statements == null) {
-            statements = loadStatements();
-        }
-        return statements;
-    }
-
-    private static final Properties loadStatements() {
-        String driver = Config.getStringXPath(
-            DBConnection.DB_DRIVER, DBConnection.DEFAULT_DRIVER);
-
-        Properties properties = new Properties();
-
-        InputStream in = null;
-        try {
-            String res = "/sql/" + driver.replace('.', '-').toLowerCase()
-                + ".properties";
-            in = SQL.class.getResourceAsStream(res);
-
-            if (in == null) {
-                logger.warn("No SQL file for driver '" + driver + "' found.");
-                res = "/sql/"
-                    + DBConnection.DEFAULT_DRIVER.replace('.', '-')
-                        .toLowerCase()
-                    + ".properties";
-                if ((in = SQL.class.getResourceAsStream(res)) == null) {
-                    logger.error("No SQL file found");
-                }
-            }
-
-            properties.load(in);
-        }
-        catch (IOException ioe) {
-            logger.error(ioe.getLocalizedMessage(), ioe);
-        }
-        finally {
-            if (in != null) {
-                try { in.close(); } catch (IOException ioe) {}
-            }
-        }
-
-        return properties;
-    }
-
-    /**
-     * Returns a particular SQL statement for a given key.
-     * @param key The key of the statement.
-     * @return The corresponing SQL statement or null if none
-     * is found.
-     */
-    public static final String get(String key) {
-        return getStatements().getProperty(key);
-    }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/SQLExecutor.java	Tue Jun 28 07:51:17 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- * Copyright (c) 2011 by Intevation GmbH
- *
- * This program is free software under the LGPL (>=v2.1)
- * Read the file LGPL.txt coming with the software for details
- * or visit http://www.gnu.org/licenses/ if it does not exist.
- */
-package de.intevation.artifactdatabase;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import javax.sql.DataSource;
-
-import org.apache.log4j.Logger;
-
-public class SQLExecutor {
-
-    private static Logger logger = Logger.getLogger(SQLExecutor.class);
-
-    public Connection        conn;
-    public PreparedStatement stmnt;
-    public ResultSet         result;
-
-    public SQLExecutor() {
-    }
-
-    public void reset() throws SQLException {
-        if (result != null) {
-            result.close();
-            result = null;
-        }
-        if (stmnt != null) {
-            result = null;
-            stmnt.close();
-        }
-    }
-
-    public PreparedStatement prepareStatement(String query)
-    throws SQLException {
-        return stmnt = conn.prepareStatement(query);
-    }
-
-    public void close() {
-        if (result != null) {
-            try { result.close(); }
-            catch (SQLException sqle) {}
-        }
-        if (stmnt != null) {
-            try { stmnt.close(); }
-            catch (SQLException sqle) {}
-        }
-        if (conn != null) {
-            try { conn.close(); }
-            catch (SQLException sqle) {}
-        }
-    }
-
-    public boolean runWrite() {
-        DataSource dataSource = DBConnection.getDataSource();
-        try {
-            conn = dataSource.getConnection();
-            try {
-                conn.setAutoCommit(false);
-                return doIt();
-            }
-            catch (SQLException sqle) {
-                conn.rollback();
-                throw sqle;
-            }
-        }
-        catch (SQLException sqle) {
-            logger.error(sqle.getLocalizedMessage(), sqle);
-        }
-        finally {
-            close();
-        }
-        return false;
-    }
-
-    public boolean runRead() {
-        DataSource dataSource = DBConnection.getDataSource();
-        try {
-            conn = dataSource.getConnection();
-            return doIt();
-        }
-        catch (SQLException sqle) {
-            logger.error(sqle.getLocalizedMessage(), sqle);
-        }
-        finally {
-            close();
-        }
-        return false;
-    }
-
-    public boolean doIt() throws SQLException {
-        return true;
-    }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java	Tue Jun 28 07:51:17 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) 2010 by Intevation GmbH
- *
- * This program is free software under the LGPL (>=v2.1)
- * Read the file LGPL.txt coming with the software for details
- * or visit http://www.gnu.org/licenses/ if it does not exist.
- */
-
-package de.intevation.artifactdatabase;
-
-import java.io.UnsupportedEncodingException;
-
-import java.util.UUID;
-
-import org.apache.commons.codec.DecoderException;
-
-import org.apache.commons.codec.binary.Hex;
-
-import org.apache.log4j.Logger;
-
-/**
- * Commonly used string functions.
- *
- * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
- */
-public final class StringUtils
-{
-    private static Logger logger = Logger.getLogger(StringUtils.class);
-
-    /**
-     * Generated a random UUIDv4 in form of a string.
-     * @return the UUID
-     */
-    public static final String newUUID() {
-        return UUID.randomUUID().toString();
-    }
-
-    /**
-     * Checks if a given string is a valid UUID.
-     * @param uuid The string to test.
-     * @return true if the string is a valid UUID else false.
-     */
-    public static final boolean checkUUID(String uuid) {
-        try {
-            UUID.fromString(uuid);
-        }
-        catch (IllegalArgumentException iae) {
-            logger.warn(iae.getLocalizedMessage());
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Returns the UTF-8 byte array representation of a given string.
-     * @param s The string to be transformed.
-     * @return The byte array representation.
-     */
-    public static final byte [] getUTF8Bytes(String s) {
-        try {
-            return s.getBytes("UTF-8");
-        }
-        catch (UnsupportedEncodingException usee) {
-            logger.error(usee.getLocalizedMessage(), usee);
-            return s.getBytes();
-        }
-    }
-
-    /**
-     * Tries to convert a Base64 encoded string into the
-     * corresponing byte array.
-     * @param s The Base64 encoded string
-     * @return The byte array representation or null if
-     * an decoding error occurs.
-     */
-    public static final byte [] decodeHex(String s) {
-        try {
-            return Hex.decodeHex(s.toCharArray());
-        }
-        catch (DecoderException de) {
-            return null;
-        }
-    }
-
-    public static final String repeat(String s, int count, String sep) {
-        if (count <= 0) {
-            return "";
-        }
-        StringBuilder sb = new StringBuilder(s);
-        for (--count; count > 0; --count) {
-            sb.append(sep).append(s);
-        }
-        return sb.toString();
-    }
-
-    public static final String repeat(char c, int count, char sep) {
-        if (count <= 0) {
-            return "";
-        }
-        StringBuilder sb = new StringBuilder(2*count-1).append(c);
-        for (--count; count > 0; --count) {
-            sb.append(sep).append(c);
-        }
-        return sb.toString();
-    }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/XMLService.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2010 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+
+package de.intevation.artifactdatabase;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.Service;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+/**
+ * Trivial implementation of an artifact database service. Useful to
+ * be subclassed.
+ *
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
+ */
+public class XMLService
+extends      DefaultService
+{
+    private static Logger logger = Logger.getLogger(XMLService.class);
+
+    @Override
+    public Service.Output process(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        return new Output(
+            processXML(data, globalContext, callMeta),
+            "application/xml");
+    }
+
+    public Document processXML(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        return XMLUtils.newDocument();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/data/DefaultStateData.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/data/DefaultStateData.java	Fri Sep 28 12:15:11 2012 +0200
@@ -25,6 +25,8 @@
     /** The value. */
     protected Object value;
 
+    public DefaultStateData() {
+    }
 
     /**
      * The default constructor. It creates empty StateData objects with no
@@ -40,6 +42,13 @@
         this.type        = type;
     }
 
+    public void set(StateData other) {
+        name        = other.getName();
+        description = other.getDescription();
+        type        = other.getType();
+        value       = other.getValue();
+    }
+
 
     /**
      * A constructor that takes the name of the data, its value and the
@@ -111,5 +120,12 @@
     public void setValue(Object value) {
         this.value = value;
     }
+
+    @Override
+    public StateData deepCopy() {
+        DefaultStateData copy = new DefaultStateData();
+        copy.set(this);
+        return copy;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/data/StateData.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/data/StateData.java	Fri Sep 28 12:15:11 2012 +0200
@@ -53,5 +53,7 @@
      * @param value The new value for this data object.
      */
     public void setValue(Object value);
+
+    public StateData deepCopy();
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/db/DBConnection.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,121 @@
+package de.intevation.artifactdatabase.db;
+
+import javax.sql.DataSource;
+
+import java.io.File;
+
+import org.apache.commons.pool.ObjectPool;
+
+import org.apache.commons.pool.impl.GenericObjectPool;
+
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import org.apache.log4j.Logger;
+
+public class DBConnection
+{
+    private static Logger log = Logger.getLogger(DBConnection.class);
+
+    public static final String DEFAULT_DRIVER        = "org.h2.Driver";
+    public static final String DEFAULT_USER          = "";
+    public static final String DEFAULT_PASSWORD      = "";
+    public static final String DEFAULT_DATABASE_FILE = "artifacts.db";
+    public static final String DEFAULT_URL           = getDefaultURL();
+
+    public static final String getDefaultURL() {
+        File configDir = Config.getConfigDirectory();
+        File databaseFile = new File(configDir, DEFAULT_DATABASE_FILE);
+        return "jdbc:h2:" + databaseFile;
+    }
+
+    protected DataSource dataSource;
+
+    protected String driver;
+    protected String url;
+    protected String user;
+    protected String password;
+
+    public DBConnection() {
+    }
+
+    public DBConnection(
+        String driver,
+        String url,
+        String user,
+        String password
+    ) {
+        this.driver   = driver;
+        this.url      = url;
+        this.user     = user;
+        this.password = password;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getDriver() {
+        return driver;
+    }
+
+    public void setDriver(String driver) {
+        this.driver = driver;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public synchronized DataSource getDataSource() {
+        if (dataSource == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("create new datasource:");
+                log.debug(" driver: " + driver);
+                log.debug(" url   : " + url);
+                log.debug(" user  : " + user);
+            }
+
+            try {
+                synchronized (DBConnection.class) {
+                    Class.forName(driver);
+                }
+            }
+            catch (ClassNotFoundException cnfe) {
+                log.error("cannot load driver", cnfe);
+                return null;
+            }
+
+            DriverManagerConnectionFactory dmcf =
+                new DriverManagerConnectionFactory(url, user, password);
+
+            ObjectPool cp = new GenericObjectPool();
+
+            PoolableConnectionFactory pcf = new PoolableConnectionFactory(
+                dmcf, cp, null, null, false, false);
+
+            dataSource = new PoolingDataSource(cp);
+        }
+        return dataSource;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/db/SQL.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,114 @@
+package de.intevation.artifactdatabase.db;
+
+import java.util.Properties;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.log4j.Logger;
+
+public class SQL {
+
+    private static Logger logger = Logger.getLogger(SQL.class);
+
+    protected Properties statements;
+
+    public SQL() {
+    }
+
+    public SQL(String driver) {
+        this(SQL.class, driver);
+    }
+
+    public SQL(Class clazz, String driver) {
+        this(clazz, "/sql", driver);
+    }
+
+    public SQL(Class clazz, String resourcePath, String driver) {
+        statements = loadStatements(clazz, resourcePath, driver);
+    }
+
+    public static final String driverToProperties(String driver) {
+        return driver.replace('.', '-').toLowerCase() + ".properties";
+    }
+
+    /**
+     * Returns key/value pairs of SQL statements for the used database
+     * backend.
+     * The concrete set of SQL statements is determined by the
+     * used JDBC database driver which is configured in conf.xml.
+     * The class name of the driver is transformed by replacing
+     * all '.' with '_' and lower case the resulting string.
+     * The transformed string is used to load a properties file
+     * in '/sql/' which should contain the statements.
+     * Example:<br>
+     * <code>org.postgresql.Driver</code> results in loading of
+     * <code>/sql/org-postgresql-driver.properties</code>.
+     * @return The key/value pairs of SQL statements.
+     */
+    protected Properties loadStatements(
+        Class  clazz,
+        String resourcePath,
+        String driver
+    ) {
+        logger.debug("loadStatements");
+
+        Properties properties = new Properties();
+
+        String resDriver = driverToProperties(driver);
+
+        InputStream in = null;
+        try {
+            String res = resourcePath + "/" + resDriver;
+
+            in = clazz.getResourceAsStream(res);
+
+            if (in == null) {
+                logger.warn("No SQL file for driver '" + driver + "' found.");
+                resDriver = driverToProperties(DBConnection.DEFAULT_DRIVER);
+                res = resourcePath + "/" + resDriver;
+
+                in = clazz.getResourceAsStream(res);
+                if (in == null) {
+                    logger.error("No SQL file for driver '" +
+                        DBConnection.DEFAULT_DRIVER + "' found.");
+                }
+            }
+            else {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("found resource: " + res);
+                }
+            }
+
+            if (in != null) {
+                properties.load(in);
+            }
+        }
+        catch (IOException ioe) {
+            logger.error(ioe);
+        }
+
+        return properties;
+    }
+
+    public String get(String key) {
+        boolean debug = logger.isDebugEnabled();
+        if (debug) {
+            logger.debug("looking for SQL " + key);
+            logger.debug("statements != null: " + (statements != null));
+        }
+
+        String sql = statements.getProperty(key);
+
+        if (sql == null) {
+            logger.error("cannot find SQL for key '" + key + "'");
+        }
+
+        if (debug) {
+            logger.debug("-> '" + sql + "'");
+        }
+
+        return sql;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/db/SQLExecutor.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,111 @@
+package de.intevation.artifactdatabase.db;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.apache.log4j.Logger;
+
+public class SQLExecutor
+{
+    private static Logger logger = Logger.getLogger(SQLExecutor.class);
+
+    public class Instance {
+
+        public Connection        conn;
+        public PreparedStatement stmnt;
+        public ResultSet         result;
+
+        public Instance() {
+        }
+
+        public void reset() throws SQLException {
+            if (result != null) {
+                result.close();
+                result = null;
+            }
+            if (stmnt != null) {
+                result = null;
+                stmnt.close();
+            }
+        }
+
+        public PreparedStatement prepareStatement(String query)
+        throws SQLException {
+            return stmnt = conn.prepareStatement(query);
+        }
+
+        public void close() {
+            if (result != null) {
+                try { result.close(); }
+                catch (SQLException sqle) {}
+            }
+            if (stmnt != null) {
+                try { stmnt.close(); }
+                catch (SQLException sqle) {}
+            }
+            if (conn != null) {
+                try { conn.close(); }
+                catch (SQLException sqle) {}
+            }
+        }
+
+        public boolean runWrite() {
+            DataSource dataSource = dbConnection.getDataSource();
+            try {
+                conn = dataSource.getConnection();
+                try {
+                    conn.setAutoCommit(false);
+                    return doIt();
+                }
+                catch (SQLException sqle) {
+                    conn.rollback();
+                    throw sqle;
+                }
+            }
+            catch (SQLException sqle) {
+                logger.error(sqle.getLocalizedMessage(), sqle);
+            }
+            finally {
+                close();
+            }
+            return false;
+        }
+
+        public boolean runRead() {
+            DataSource dataSource = dbConnection.getDataSource();
+            try {
+                conn = dataSource.getConnection();
+                return doIt();
+            }
+            catch (SQLException sqle) {
+                logger.error(sqle.getLocalizedMessage(), sqle);
+            }
+            finally {
+                close();
+            }
+            return false;
+        }
+
+        public boolean doIt() throws SQLException {
+            return true;
+        }
+    } // class Instance
+
+    protected DBConnection dbConnection;
+
+    public SQLExecutor() {
+    }
+
+    public SQLExecutor(DBConnection dbConnection) {
+        this.dbConnection = dbConnection;
+    }
+
+    public DBConnection getDBConnection() {
+        return dbConnection;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/h2/CollectionAccessUpdateTrigger.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/h2/CollectionAccessUpdateTrigger.java	Fri Sep 28 12:15:11 2012 +0200
@@ -6,7 +6,9 @@
 import java.sql.SQLException;
 import java.sql.PreparedStatement;
 
-import de.intevation.artifactdatabase.SQL;
+import de.intevation.artifactdatabase.DBConfig;
+
+import de.intevation.artifactdatabase.db.SQL;
 
 import org.apache.log4j.Logger;
 
@@ -16,8 +18,7 @@
     private static Logger logger =
         Logger.getLogger(CollectionAccessUpdateTrigger.class);
 
-    public static final String COLLECTIONS_TOUCH_TRIGGER_FUNCTION =
-        SQL.get("collections.touch.trigger.function");
+    public String COLLECTIONS_TOUCH_TRIGGER_FUNCTION;
 
     public void init(
         Connection conn,
@@ -29,6 +30,12 @@
     )
     throws SQLException {
         logger.debug("CollectionAccessUpdateTrigger.init");
+        setupSQL(DBConfig.getInstance().getSQL());
+    }
+
+    protected void setupSQL(SQL sql) {
+        COLLECTIONS_TOUCH_TRIGGER_FUNCTION =
+            sql.get("collections.touch.trigger.function");
     }
 
     public void fire(
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ArtifactResource.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ArtifactResource.java	Fri Sep 28 12:15:11 2012 +0200
@@ -121,7 +121,8 @@
     /**
      * Method to figure out which POST action (feed or advance) was
      * triggered and perform this operation on the artifact specified
-     * by 'identifier' and found in the artifact database 'db'
+     * by 'identifier' and found in the artifact database 'db'.
+     *
      * @param identifier The identifier of the artifact.
      * @param action The action to be performed.
      * @param source The input document to further parameterize the
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ByteArrayRepresentation.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,65 @@
+package de.intevation.artifactdatabase.rest;
+
+import org.restlet.representation.Representation;
+
+import java.io.Reader;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.Writer;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.ByteArrayInputStream;
+
+import java.nio.ByteBuffer;
+
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+import org.restlet.data.MediaType;
+
+public class ByteArrayRepresentation
+extends      Representation
+{
+    protected byte [] data;
+
+    public ByteArrayRepresentation(MediaType mediaType, byte [] data) {
+        super(mediaType);
+        this.data = data;
+    }
+
+    @Override
+    public long getSize() {
+        return data.length;
+    }
+
+    @Override
+    public ReadableByteChannel getChannel() throws IOException {
+        return null;
+    }
+
+    @Override
+    public Reader getReader() throws IOException {
+        return new InputStreamReader(getStream());
+    }
+
+    @Override
+    public InputStream getStream() throws IOException {
+        return new ByteArrayInputStream(data);
+    }
+
+    @Override
+    public void write(Writer writer) throws IOException {
+        writer.append(ByteBuffer.wrap(data).asCharBuffer());
+    }
+
+    @Override
+    public void write(WritableByteChannel writableChannel) throws IOException {
+        writableChannel.write(ByteBuffer.wrap(data));
+    }
+
+    @Override
+    public void write(OutputStream outputStream) throws IOException {
+        outputStream.write(data);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/FindUserResource.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2011 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+package de.intevation.artifactdatabase.rest;
+
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.data.MediaType;
+import org.restlet.data.Status;
+import org.restlet.ext.xml.DomRepresentation;
+import org.restlet.representation.EmptyRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.resource.ResourceException;
+import org.restlet.Response;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+
+/**
+ * A Rest resource that finds the user provided by the artifact database.
+ *
+ */
+public class FindUserResource
+extends      BaseResource
+{
+    /** The logger that is used in this class.*/
+    private static Logger logger = Logger.getLogger(FindUserResource.class);
+
+    /** server URL where to reach the resource.*/
+    public static final String PATH = "/find-user";
+
+
+    @Override
+    protected Representation innerPost(Representation requestRepr)
+    throws    ResourceException
+    {
+        Document input = null;
+
+        try {
+            DomRepresentation in = new DomRepresentation(requestRepr);
+            in.setNamespaceAware(true);
+            input = in.getDocument();
+        }
+        catch (IOException ioe) {
+            logger.error(ioe.getLocalizedMessage(), ioe);
+
+            Response response = getResponse();
+            response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, ioe);
+            return new EmptyRepresentation();
+        }
+
+        ArtifactDatabase db = getArtifactDatabase();
+
+        try {
+            logger.info(PATH);
+
+            return new DomRepresentation(
+                MediaType.APPLICATION_XML,
+                db.findUser(input, getCallMeta()));
+        }
+        catch (ArtifactDatabaseException adbe) {
+            logger.warn(adbe.getLocalizedMessage(), adbe);
+
+            Response response = getResponse();
+            response.setStatus(
+                Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY, adbe.getMessage());
+            return new EmptyRepresentation();
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java	Fri Sep 28 12:15:11 2012 +0200
@@ -81,6 +81,7 @@
         router.attach(CreateUserResource.PATH,  CreateUserResource.class);
         router.attach(ListUsersResource.PATH,   ListUsersResource.class);
         router.attach(UserResource.PATH,        UserResource.class);
+        router.attach(FindUserResource.PATH,    FindUserResource.class);
         router.attach(
             CreateCollectionResource.PATH, CreateCollectionResource.class);
         router.attach(
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ServiceResource.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ServiceResource.java	Fri Sep 28 12:15:11 2012 +0200
@@ -27,6 +27,8 @@
 
 import org.w3c.dom.Document;
 
+import de.intevation.artifacts.Service;
+
 /**
  * Resource to process incoming XML documents with a given service.
  *
@@ -72,8 +74,7 @@
             .getAttributes().get("database");
 
         try {
-            return new DomRepresentation(
-                MediaType.APPLICATION_XML,
+            return guessRepresentation(
                 db.process(service, inputDocument, getCallMeta()));
         }
         catch (ArtifactDatabaseException adbe) {
@@ -84,5 +85,21 @@
             return new EmptyRepresentation();
         }
     }
+
+    protected static Representation guessRepresentation(Service.Output output) {
+
+        MediaType mediaType = new MediaType(output.getMIMEType());
+        Object    data      = output.getData();
+
+        if (data instanceof Document) {
+            return new DomRepresentation(mediaType, (Document)data);
+        }
+
+        if (data instanceof byte []) {
+            return new ByteArrayRepresentation(mediaType, (byte [])data);
+        }
+
+        return new EmptyRepresentation();
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/UserResource.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/UserResource.java	Fri Sep 28 12:15:11 2012 +0200
@@ -35,10 +35,10 @@
 public class UserResource
 extends      BaseResource
 {
-    /** The logger that is used in this class.*/
+    /** The logger that is used in this class. */
     private static Logger logger = Logger.getLogger(UserResource.class);
 
-    /** server URL where to reach the resource.*/
+    /** server URL where to reach the resource. */
     public static final String PATH = "/user/{uuid}";
 
     /**
@@ -47,13 +47,13 @@
      */
     public static final String XPATH_ACTION = "/art:action/art:type/@name";
 
-    /** Error message if no action was given.*/
+    /** Error message if no action was given. */
     public static final String NO_ACTION_MSG = "no action given";
 
-    /** Error message if a unknown action was given.*/
+    /** Error message if a unknown action was given. */
     public static final String NO_SUCH_ACTION_MSG = "no such action";
 
-    /** Action name for deleting users.*/
+    /** Action name for deleting users. */
     public static final String ACTION_DELETE = "delete";
 
 
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/AbstractState.java	Fri Sep 28 12:15:11 2012 +0200
@@ -24,6 +24,7 @@
 import de.intevation.artifacts.Artifact;
 import de.intevation.artifacts.ArtifactNamespaceContext;
 import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
 
 import de.intevation.artifacts.common.utils.XMLUtils;
 
@@ -46,15 +47,18 @@
      * the configuration. */
     public static final String XPATH_DESCRIPTION = "@description";
 
-    /** The XPath to the output nodes of the state configuration.*/
+    /** The XPath that points to the help text.*/
+    public static final String XPATH_HELP_TEXT = "@helpText";
+
+    /** The XPath to the output nodes of the state configuration. */
     public static final String XPATH_OUTPUT_MODES = "outputmodes/outputmode";
 
     /** The XPath to the list of facets relative to the output mode it belongs
-     * to.*/
+     * to. */
     public static final String XPATH_FACETS = "facets/facet";
 
 
-    /** The logger that is used in this class.*/
+    /** The logger that is used in this class. */
     private static Logger logger = Logger.getLogger(AbstractState.class);
 
 
@@ -64,10 +68,13 @@
     /** The description of the state. */
     protected String description;
 
+    /** The help text for this state.*/
+    protected String helpText;
+
     /** The data provided by this state. */
     protected Map<String, StateData> data;
 
-    /** A list of output modes which are available for this state.*/
+    /** A list of output modes which are available for this state. */
     protected List<Output> outputs;
 
 
@@ -87,7 +94,12 @@
 
         this.id          = id;
         this.description = description;
+    }
 
+
+    public AbstractState(String id, String description, String helpText) {
+        this(id, description);
+        this.helpText = helpText;
     }
 
 
@@ -132,6 +144,26 @@
 
 
     /**
+     * Returns the help text of this state.
+     *
+     * @return the help text.
+     */
+    public String getHelpText() {
+        return helpText;
+    }
+
+
+    /**
+     * Set the help text for this state.
+     *
+     * @param helpText The help text.
+     */
+    public void setHelpText(String helpText) {
+        this.helpText = helpText;
+    }
+
+
+    /**
      * Returns the data of the state.
      *
      * @return the data of the state.
@@ -149,7 +181,11 @@
      * @return a data object of the state or null if no such data object exists.
      */
     public StateData getData(String name) {
-        return data.get(name);
+        if (data != null) {
+            return data.get(name);
+        }
+
+        return null;
     }
 
 
@@ -158,7 +194,7 @@
      * with the key <i>name</i>, this object is overwritten by the new value.
      *
      * @param name The name of the data object.
-     * @param StateData The data object.
+     * @param data The data object.
      */
     public void addData(String name, StateData data) {
         if (this.data == null) {
@@ -193,11 +229,32 @@
         description = (String) XMLUtils.xpath(
             config, XPATH_DESCRIPTION, XPathConstants.STRING);
 
+        helpText = (String) XMLUtils.xpath(
+            config, XPATH_HELP_TEXT, XPathConstants.STRING);
+
         setupOutputs(config);
     }
 
 
     /**
+     * This default implementation does nothing at all.
+     *
+     * @param orig
+     * @param owner
+     * @param context
+     * @param callMeta
+     */
+    public void initialize(
+        Artifact orig,
+        Artifact owner,
+        Object   context,
+        CallMeta callMeta
+    ) {
+        // do nothing.
+    }
+
+
+    /**
      * This method tries reading the available output nodes configured in the
      * state configuration and adds possible Outputs to the outputs list.
      *
@@ -217,10 +274,18 @@
         int size = outs.getLength();
 
         for (int i = 0; i < size; i++) {
-            outputs.add(buildOutput(outs.item(i)));
+            addOutput(buildOutput(outs.item(i)));
         }
     }
 
+    /**
+     * This methods allows subclasses to manually add outputs
+     *
+     * @param out The output to add
+     */
+    protected void addOutput(Output out) {
+        outputs.add(out);
+    }
 
     /**
      * A helper method that creates an Output object based on the <i>out</i>
@@ -240,6 +305,9 @@
         String mimetype = XMLUtils.xpathString(
             out, "@mime-type", ArtifactNamespaceContext.INSTANCE);
 
+        String type = XMLUtils.xpathString(
+            out, "@type", ArtifactNamespaceContext.INSTANCE);
+
         if (name == null) {
             return null;
         }
@@ -251,7 +319,7 @@
             ArtifactNamespaceContext.INSTANCE);
 
         if (facets == null || facets.getLength() == 0) {
-            return new DefaultOutput(name, desc, mimetype);
+            return new DefaultOutput(name, desc, mimetype, type);
         }
 
         int num = facets.getLength();
@@ -266,7 +334,7 @@
             }
         }
 
-        return new DefaultOutput(name, desc, mimetype, facetList);
+        return new DefaultOutput(name, desc, mimetype, facetList, type);
     }
 
 
@@ -306,5 +374,11 @@
         CallContext context,
         String      uuid
     );
+
+
+    @Override
+    public void endOfLife(Artifact artifact, Object context) {
+        // nothing to do here
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/ArtifactAndFacet.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,97 @@
+package de.intevation.artifactdatabase.state;
+
+import java.util.List;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.DataProvider;
+
+
+/**
+ * A bundle of a "native" Facet and its Artifact.
+ */
+public class ArtifactAndFacet implements DataProvider {
+    /** The Artifact. */
+    protected Artifact artifact;
+
+    /** The (native) facet. */
+    protected Facet    facet;
+
+    /** An alternative facet description that might be set from outside. */
+    protected String facetDescription;
+
+
+    /** Trivial constructor. */
+    public ArtifactAndFacet(
+        Artifact a,
+        Facet f
+    ) {
+        this.artifact   = a;
+        this.facet      = f;
+    }
+
+
+    /** Get data (to plot). */
+    public Object getData(CallContext context) {
+        return facet.getData(artifact, context);
+    }
+
+
+    /** Get data (for other facet). */
+    @Override
+    public Object provideData(Object key, Object param, CallContext context) {
+        return facet.provideBlackboardData(artifact, key, param, context);
+    }
+
+
+    /** (Maybe) Register on blackboard (depending on facet). */
+    @Override
+    public void register(CallContext context) {
+        List keys = facet.getDataProviderKeys(this.artifact, context);
+        if (keys == null) {
+            return;
+        }
+        for (Object key: keys) {
+            context.registerDataProvider(key, this);
+        }
+    }
+
+
+    /** Access the artifact. */
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+
+    /** Access the (native) facet. */
+    public Facet getFacet() {
+        return facet;
+    }
+
+
+    /** Shortcut to facets name. */
+    public String getFacetName() {
+        return facet.getName();
+    }
+
+
+    /**
+     * Returns the description for a facet. The return value depends on the
+     * internal <i>facetDescription</i> instance variable. If this has been set
+     * by setFacetDescription, this value is returned, otherwise the return
+     * value of facet.getDescription().
+     */
+    public String getFacetDescription() {
+        if (facetDescription == null) {
+            return facet.getDescription();
+        }
+
+        return facetDescription;
+    }
+
+
+    public void setFacetDescription(String facetDescription) {
+        this.facetDescription = facetDescription;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/Attribute.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,43 @@
+package de.intevation.artifactdatabase.state;
+
+import java.io.Serializable;
+
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Attribute extends Serializable {
+
+    /**
+     * Returns the name of this Attribute.
+     *
+     * @return the name of this Attribute.
+     */
+    String getName();
+
+    /**
+     * Returns the value of this Attribute.
+     *
+     * @return the value of this Attribute.
+     */
+    Object getValue();
+
+    /**
+     * Sets the value of this Attribute.
+     *
+     * @param value The new value.
+     */
+    void setValue(Object value);
+
+    /**
+     * Transforms this Attribute into XML.
+     *
+     * @param parent The parent node.
+     *
+     * @return the Node that represents this Attribute.
+     */
+    Node toXML(Node parent);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultAttribute.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,54 @@
+package de.intevation.artifactdatabase.state;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultAttribute implements Attribute {
+
+    protected String name;
+
+    protected Object value;
+
+
+    public DefaultAttribute(String name, Object value) {
+        this.name  = name;
+        this.value = value;
+    }
+
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+
+    @Override
+    public Object getValue() {
+        return value;
+    }
+
+
+    @Override
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+
+    @Override
+    public Node toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+        Element   attr = owner.createElement(getName());
+
+        parent.appendChild(attr);
+
+        attr.setTextContent(String.valueOf(getValue()));
+
+        return attr;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultFacet.java	Fri Sep 28 12:15:11 2012 +0200
@@ -1,8 +1,17 @@
 package de.intevation.artifactdatabase.state;
 
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
 import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactNamespaceContext;
 import de.intevation.artifacts.CallContext;
 
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
 
 /**
  * The default implementation of a Facet.
@@ -11,16 +20,21 @@
  */
 public class DefaultFacet implements Facet {
 
-    /** The index of this facet.*/
+    /** The index of this facet. */
     protected int index;
 
-    /** The name of this facet.*/
+    /** The name of this facet. */
     protected String name;
 
-    /** The description of this facet.*/
+    /** The description of this facet. */
     protected String description;
 
 
+    /** Trivial, empty constructor. */
+    public DefaultFacet() {
+    }
+
+
     /**
      * The default constructor to create new Facet objects.
      *
@@ -46,25 +60,85 @@
     }
 
 
+    /** Get index. */
     public int getIndex() {
         return index;
     }
 
 
+    /** Returns the name ('type'). */
     public String getName() {
         return name;
     }
 
 
+    /** Returns the description (e.g. displayed in gui). */
     public String getDescription() {
         return description;
     }
 
 
+    /**
+     * @return null
+     */
     public Object getData(Artifact artifact, CallContext context) {
         return null;
     }
 
+
+    /**
+     * (Do not) provide data.
+     * Override to allow other facets to access your data.
+     * @return always null.
+     */
+    public Object provideBlackboardData(
+        Artifact artifact,
+        Object key,
+        Object param,
+        CallContext context
+    ) {
+        return null;
+    }
+
+
+    /*
+     * Return list of keys (objects) for which this facet can provide data
+     * ("external parameterization"), for other facets, via blackboard.
+     * These are the keys that are independent from the current call (thus
+     * 'static').
+     * @param artifact that this facet belongs to.
+     */
+    public List getStaticDataProviderKeys(Artifact artifact) {
+        return null;
+    }
+
+    /**
+     * Return list of keys (objects) for which this facet can provide data
+     * ("external parameterization"), for other facets, via blackboard.
+     * @param artifact that this facet belongs to.
+     */
+    public List getDataProviderKeys(Artifact artifact, CallContext context) {
+        return getStaticDataProviderKeys(artifact);
+    }
+
+
+    /** Create a xml represantation. */
+    public Node toXML(Document doc) {
+        ElementCreator ec = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element facet = ec.create("facet");
+        ec.addAttr(facet, "description", description, true);
+        ec.addAttr(facet, "name", name, true);
+        ec.addAttr(facet, "index", String.valueOf(index), true);
+
+        return facet;
+    }
+
+
+    /** Create a string representation. */
     public String toString() {
         return new StringBuilder("name = '")
             .append(name).append("', index = ")
@@ -72,5 +146,23 @@
             .append(description).append("'")
             .toString();
     }
+
+
+    /**
+     * Copies name, index and description of other facet.
+     */
+    public void set(Facet other) {
+        index       = other.getIndex();
+        name        = other.getName();
+        description = other.getDescription();
+    }
+
+
+    /** Create a deep copy of this facet. */
+    public Facet deepCopy() {
+        DefaultFacet copy = new DefaultFacet();
+        copy.set(this);
+        return copy;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultOutput.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultOutput.java	Fri Sep 28 12:15:11 2012 +0200
@@ -16,8 +16,12 @@
 
     protected String mimeType;
 
+    protected String type;
+
     protected List<Facet> facets;
 
+    protected Settings settings;
+
 
     /**
      * The default constructor that instantiates a new DefaultOutput object.
@@ -30,10 +34,37 @@
         this.name        = name;
         this.description = description;
         this.mimeType    = mimeType;
+        this.type        = "";
         this.facets      = new ArrayList<Facet>();
     }
 
 
+    public DefaultOutput(
+        String      name,
+        String      description,
+        String      mimeType,
+        String      type)
+    {
+        this(name, description, mimeType);
+
+        this.facets = new ArrayList<Facet>();
+        this.type   = type;
+    }
+
+
+    public DefaultOutput(
+        String      name,
+        String      description,
+        String      mimeType,
+        List<Facet> facets)
+    {
+        this(name, description, mimeType);
+
+        this.type   = "";
+        this.facets = facets;
+    }
+
+
     /**
      * This constructor builds a new Output object that contains facets as well.
      *
@@ -46,11 +77,12 @@
         String      name,
         String      description,
         String      mimeType,
-        List<Facet> facets)
+        List<Facet> facets,
+        String      type)
     {
-        this(name, description, mimeType);
+        this(name, description, mimeType, facets);
 
-        this.facets = facets;
+        this.type = type;
     }
 
 
@@ -84,6 +116,11 @@
     }
 
 
+    public String getType() {
+        return type;
+    }
+
+
     /**
      * Returns the list of facets supported by this output.
      *
@@ -95,7 +132,32 @@
 
 
     public void addFacet(Facet facet) {
-        facets.add(facet);
+        if (facet != null && !facets.contains(facet)) {
+            facets.add(facet);
+        }
+    }
+
+
+    public void addFacets(List<Facet> facets) {
+        this.facets.addAll(facets);
+    }
+
+
+    @Override
+    public void setFacets(List<Facet> facets) {
+        this.facets = facets;
+    }
+
+
+    @Override
+    public void setSettings(Settings settings) {
+        this.settings = settings;
+    }
+
+
+    @Override
+    public Settings getSettings() {
+        return settings;
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultSection.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,112 @@
+package de.intevation.artifactdatabase.state;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * Attributes keep the order in which they were inserted.
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultSection implements Section {
+
+    protected String id;
+
+    protected List<Section> subsections;
+
+    /** Attribute-map. */
+    protected Map<String, Attribute> attributes;
+
+
+    /**
+     * Creates a new DefaultSection instance. <b>Note, that the <i>id</i> is used
+     * as Node name of the new Element that is created in toXML().</b>
+     */
+    public DefaultSection(String id) {
+        this.id          = id;
+        // Use LinkedHashMap to keep insertion order.
+        this.attributes  = new LinkedHashMap<String, Attribute>();
+        this.subsections = new ArrayList<Section>();
+    }
+
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+
+    @Override
+    public void addSubsection(Section subsection) {
+        if (subsection != null) {
+            subsections.add(subsection);
+        }
+    }
+
+
+    @Override
+    public int getSubsectionCount() {
+        return subsections.size();
+    }
+
+
+    @Override
+    public Section getSubsection(int pos) {
+        if (pos >= 0 && pos < getSubsectionCount()) {
+            return subsections.get(pos);
+        }
+
+        return null;
+    }
+
+
+    /** Adding attribute to end of list. */
+    @Override
+    public void addAttribute(String key, Attribute attribute) {
+        if (key != null && key.length() > 0 && attribute != null) {
+            attributes.put(key, attribute);
+        }
+    }
+
+
+    @Override
+    public Attribute getAttribute(String key) {
+        if (key == null || key.length() == 0) {
+            return null;
+        }
+
+        return attributes.get(key);
+    }
+
+
+    @Override
+    public Set<String> getKeys() {
+        return attributes.keySet();
+    }
+
+
+    @Override
+    public void toXML(Node parent) {
+        Document owner     = parent.getOwnerDocument();
+        Element  sectionEl = owner.createElement(getId());
+
+        parent.appendChild(sectionEl);
+
+        for (String key: getKeys()) {
+            Attribute attr = getAttribute(key);
+            attr.toXML(sectionEl);
+        }
+
+        for (int i = 0, n = getSubsectionCount(); i < n; i++) {
+            Section subsection = getSubsection(i);
+            subsection.toXML(sectionEl);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/DefaultSettings.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,62 @@
+package de.intevation.artifactdatabase.state;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultSettings implements Settings {
+
+    protected List<Section> sections;
+
+    public DefaultSettings() {
+        sections = new ArrayList<Section>();
+    }
+
+    @Override
+    public void addSection(Section section) {
+        if (section != null) {
+            sections.add(section);
+        }
+    }
+
+    @Override
+    public int getSectionCount() {
+        return sections.size();
+    }
+
+    @Override
+    public Section getSection(int pos) {
+        if (pos >= 0 && pos < getSectionCount()) {
+            return sections.get(pos);
+        }
+
+        return null;
+    }
+
+    @Override
+    public void removeSection(Section section) {
+        if (section != null) {
+            sections.remove(section);
+        }
+    }
+
+    @Override
+    public void toXML(Node parent) {
+        Document owner    = parent.getOwnerDocument();
+        Element  settings = owner.createElement("settings");
+
+        parent.appendChild(settings);
+
+        for (Section section: sections) {
+            section.toXML(settings);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/state/Facet.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/Facet.java	Fri Sep 28 12:15:11 2012 +0200
@@ -1,7 +1,12 @@
 package de.intevation.artifactdatabase.state;
 
+import java.util.List;
+
 import java.io.Serializable;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
 import de.intevation.artifacts.Artifact;
 import de.intevation.artifacts.CallContext;
 
@@ -43,5 +48,43 @@
      * @return the data.
      */
     Object getData(Artifact artifact, CallContext context);
+
+
+    /**
+     * Get keys for which this Facet can provide data (for other facets, not
+     * for plot).
+     * @param artifact Artifact that this facet belongs to.
+     * @return list of keys
+     */
+    List getDataProviderKeys(Artifact artifact, CallContext context);
+
+
+    /**
+     * Provide data to other facet.
+     *
+     * @param art  The artifact that this facet belongs to.
+     * @param key  the key of the requested service.
+     * @param prm  optional parameters.
+     * @param ctxt the callcontext.
+     *
+     * @return the data
+     */
+    Object provideBlackboardData(
+        Artifact art,
+        Object key,
+        Object prm,
+        CallContext ctxt);
+
+
+    /**
+     * Write the internal representation of a facet to a node.
+     *
+     * @param doc A Document.
+     *
+     * @return the representation as Node.
+     */
+    Node toXML(Document doc);
+
+    Facet deepCopy();
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/FacetActivity.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,80 @@
+package de.intevation.artifactdatabase.state;
+
+import de.intevation.artifacts.Artifact;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public interface FacetActivity
+{
+    public static final FacetActivity ACTIVE = new FacetActivity() {
+        @Override
+        public Boolean isInitialActive(
+            Artifact artifact,
+            Facet    facet,
+            String   output
+        ) {
+            return Boolean.TRUE;
+        }
+    };
+
+    public static final FacetActivity INACTIVE = new FacetActivity() {
+        @Override
+        public Boolean isInitialActive(
+            Artifact artifact,
+            Facet    facet,
+            String   output
+        ) {
+            return Boolean.FALSE;
+        }
+    };
+
+    Boolean isInitialActive(Artifact artifact, Facet facet, String output);
+
+    public static final class Registry {
+
+        private static final Registry INSTANCE = new Registry();
+
+        private Map<String, List<FacetActivity>> activities;
+
+        private Registry() {
+            activities = new HashMap<String, List<FacetActivity>>();
+        }
+
+        public static Registry getInstance() {
+            return INSTANCE;
+        }
+
+        public synchronized boolean isInitialActive(
+            String   key,
+            Artifact artifact,
+            Facet    facet,
+            String   output
+        ) {
+            List<FacetActivity> activityList = activities.get(key);
+            if (activityList == null) {
+                return true;
+            }
+            for (FacetActivity activity: activityList) {
+                Boolean isActive =
+                    activity.isInitialActive(artifact, facet, output);
+                if (isActive != null) {
+                    return isActive;
+                }
+            }
+            return true;
+        }
+
+        public synchronized void register(String key, FacetActivity activity) {
+            List<FacetActivity> activityList = activities.get(key);
+            if (activityList == null) {
+                activityList = new ArrayList<FacetActivity>(3);
+                activities.put(key, activityList);
+            }
+            activityList.add(activity);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/state/Output.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/Output.java	Fri Sep 28 12:15:11 2012 +0200
@@ -29,6 +29,14 @@
      */
     public String getMimeType();
 
+
+    /**
+     * Returns the type of this output.
+     *
+     * @return the type.
+     */
+    public String getType();
+
     /**
      * Retrieve the facets of this output.
      *
@@ -42,5 +50,31 @@
      * @param facet The new facet.
      */
     public void addFacet(Facet facet);
+
+    /**
+     * Add a list of facet to this output.
+     *
+     * @param facets A list of facets.
+     */
+    public void addFacets(List<Facet> facets);
+
+    /**
+     * Replaces the old list of facets with a new one.
+     *
+     * @param facets A list of new facets.
+     */
+    public void setFacets(List<Facet> facets);
+
+    /**
+     * Returns a Settings object for this Output.
+     */
+    public Settings getSettings();
+
+    /**
+     * Sets the Settings for this Output.
+     *
+     * @param settings the Settings for this Output.
+     */
+    public void setSettings(Settings settings);
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/Section.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,76 @@
+package de.intevation.artifactdatabase.state;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Section extends Serializable {
+
+    /**
+     * Returns an ID for this Section.
+     *
+     * @return an ID for this Section.
+     */
+    String getId();
+
+    /**
+     * Adds a new subsection to this Section object.
+     *
+     * @param subsection the new Section.
+     */
+    void addSubsection(Section subsection);
+
+    /**
+     * Returns the number of subsections in this Section.
+     *
+     * @return the number of subsections.
+     */
+    int getSubsectionCount();
+
+    /**
+     * Returns a subsection at position <i>pos</i>.
+     *
+     * @param pos The position of the target subsection.
+     *
+     * @return the subsection at position <i>pos</i>.
+     */
+    Section getSubsection(int pos);
+
+    /**
+     * Adds a new Attribute to this Section.
+     *
+     * @param key The key that is used to store/retrieve the Attribute.
+     * @param attribute The new Attribute.
+     */
+    void addAttribute(String key, Attribute attribute);
+
+    /**
+     * Returns an Attribute for the specified <i>key</i>.
+     *
+     * @param key The key that is used to retrieve the target Attribute.
+     *
+     * @return the Attribute specified by <i>key</i>.
+     */
+    Attribute getAttribute(String key);
+
+    /**
+     * Returns all keys of all Attributes currently stored in this Section.
+     *
+     * @return all keys of all Attributes.
+     */
+    Set<String> getKeys();
+
+    /**
+     * Transforms this Section into XML using Attribute.toXML() for each
+     * Attribute and Section.toXML() for each subsection stored in this Section.
+     *
+     * @param parent The parent node.
+     */
+    void toXML(Node parent);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/Settings.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,53 @@
+package de.intevation.artifactdatabase.state;
+
+import java.io.Serializable;
+
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Settings extends Serializable {
+
+    /**
+     * Adds a new Section to this Settings object.
+     *
+     * @param section the new Section.
+     */
+    void addSection(Section section);
+
+    /**
+     * Returns the number of Sections in this Settings object.
+     *
+     * @return the number of sections.
+     */
+    int getSectionCount();
+
+    /**
+     * Returns the section at position <i>pos</i>.
+     *
+     * @param pos the position of the target Section.
+     *
+     * @return the Section at position <i>pos</i> or null if no Section is
+     * existing at <i>pos</i>.
+     */
+    Section getSection(int pos);
+
+    /**
+     * Removes a Section if it is existing in this Settings.
+     *
+     * @param section The section that should be removed.
+     */
+    void removeSection(Section section);
+
+    /**
+     * Transforms this Settings object into a XML representation. Therefore,
+     * each Section object's <i>toXML</i> method is called to append its XML
+     * representation to the final document.
+     *
+     * @param parent The parent node.
+     */
+    void toXML(Node parent);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/state/State.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/State.java	Fri Sep 28 12:15:11 2012 +0200
@@ -17,6 +17,7 @@
 
 import de.intevation.artifacts.Artifact;
 import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
 
 import de.intevation.artifactdatabase.data.StateData;
 
@@ -46,6 +47,14 @@
 
 
     /**
+     * Returns the help text configured for the state.
+     *
+     * @return the help text configured for the state.
+     */
+    public String getHelpText();
+
+
+    /**
      * Returns the data provided by this state.
      *
      * @return the data stored in this state.
@@ -91,6 +100,21 @@
 
 
     /**
+     * Initializes the internal state of this State based on an other State.
+     *
+     * @param orig The owner Artifact or the original State.
+     * @param owner The owner Artifact of this State.
+     * @param context The context object.
+     * @param callMeta The CallMeta of the current call.
+     */
+    public void initialize(
+        Artifact orig,
+        Artifact owner,
+        Object   context,
+        CallMeta callMeta);
+
+
+    /**
      * This method is called when an artifacts retrieves a describe request. It
      * creates the user interface description of the current state.
      *
@@ -107,5 +131,16 @@
         CallContext context,
         String      uuid
     );
+
+
+    /**
+     * This method should be called by an Artifact that removes this State
+     * (current State and previous States). E.g. this might be interesting to
+     * remove generated files or stuff like that.
+     *
+     * @param artifact A parent Artifact.
+     * @param context The CallContext.
+     */
+    public void endOfLife(Artifact artifact, Object context);
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/state/StateEngine.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/state/StateEngine.java	Fri Sep 28 12:15:11 2012 +0200
@@ -1,13 +1,18 @@
 package de.intevation.artifactdatabase.state;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.log4j.Logger;
 
+import de.intevation.artifactdatabase.data.StateData;
+
+
 /**
- * The StateEngine stores all states for each Artifact.
+ * The StateEngine stores all states and associated information about
+ * outputs and facets for each Artifact.
  *
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
  */
@@ -62,6 +67,30 @@
     }
 
 
+    public StateData getStateData(String artifact, String dataName) {
+        List<State> artifactStates = getStates(artifact);
+
+        if (artifactStates == null || artifactStates.size() == 0) {
+            logger.warn("No States for Artifact '" + artifact + "' existing.");
+            return null;
+        }
+
+        for (State state: artifactStates) {
+            StateData sd = state.getData(dataName);
+
+            if (sd != null) {
+                return sd;
+            }
+        }
+
+        logger.warn(
+            "No StateData for Artifact '" + artifact +
+            "' with name '" + dataName + "' existing.");
+
+        return null;
+    }
+
+
     /**
      * Add new states for a specific artifact.
      *
@@ -93,7 +122,7 @@
     /**
      * Returns the state list of an artifact specified by its name.
      *
-     * @param artifact The name of the artifact.
+     * @param artifact The name of the artifact (e.g. "winfo").
      *
      * @return the list of states of this artifact or <i>null</i> if no states
      * are existing for this <i>artifact</i>.
@@ -101,5 +130,40 @@
     public List<State> getStates(String artifact) {
         return states.get(artifact);
     }
+
+
+    /**
+     * Return mapping of output to facets for an artifact in its states.
+     */
+    public Map<String, List<String>> getCompatibleFacets(List<String> aStates) {
+        Map<String, List<String>> compatibilityMatrix =
+            new HashMap<String, List<String>>();
+
+        // For all states that the artifact had seen, add outputs facets.
+        for (String stateId: aStates) {
+
+            State state = allStates.get(stateId);
+            if (state == null) {
+                continue;
+            }
+
+            for (Output output: state.getOutputs()) {
+                List<Facet> outFacets = output.getFacets();
+
+                List<String> oldFacets = compatibilityMatrix.get(output.getName());
+
+                if (oldFacets == null) {
+                    oldFacets = new ArrayList<String>();
+                }
+
+                for (Facet facet: outFacets) {
+                    oldFacets.add(facet.getName());
+                }
+
+                compatibilityMatrix.put(output.getName(), oldFacets);
+            }
+        }
+        return compatibilityMatrix;
+    }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+// vim:set ts=4 sw=4 et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/transition/TransitionEngine.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/transition/TransitionEngine.java	Fri Sep 28 12:15:11 2012 +0200
@@ -44,7 +44,7 @@
      * Add new transitions for a specific artifact.
      *
      * @param stateId the name of the Artifact.
-     * @param transitions the list of transition of the artifact.
+     * @param transition the list of transition of the artifact.
      *
      * @return true, if the transitions were added, otherwise false.
      */
--- a/artifact-database/src/main/resources/sql/org-h2-driver.properties	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/resources/sql/org-h2-driver.properties	Fri Sep 28 12:15:11 2012 +0200
@@ -9,7 +9,7 @@
 
 artifacts.touch=UPDATE artifacts SET last_access = CURRENT_TIMESTAMP WHERE id = ?
 
-artifacts.outdated=SELECT id, factory, data FROM artifacts WHERE ttl IS NOT NULL \
+artifacts.outdated=SELECT id, factory, data, gid FROM artifacts WHERE ttl IS NOT NULL \
     AND DATEDIFF('MILLISECOND', last_access, CURRENT_TIMESTAMP) > ttl \
     AND id NOT IN (SELECT DISTINCT artifact_id FROM collection_items) \
     AND id NOT IN ($LOCKED_IDS$) \
@@ -33,16 +33,17 @@
 
 users.id.nextval=SELECT NEXTVAL('USERS_ID_SEQ')
 
-users.insert=INSERT INTO users (id, gid, name, role) VALUES (?, ?, ?, ?)
+users.insert=INSERT INTO users (id, gid, name, account, role) VALUES (?, ?, ?, ?, ?)
 
 users.select.id.by.gid=SELECT id FROM users WHERE gid = ?
-users.select.gid=SELECT id, name, role FROM users WHERE gid = ?
+users.select.gid=SELECT id, name, account, role FROM users WHERE gid = ?
+users.select.account=SELECT gid, name, account, role FROM users WHERE account = ?
 
 users.delete.id=DELETE FROM users WHERE id = ?
 
 users.delete.collections=DELETE FROM collections where owner_id = ?
 
-users.select.all=SELECT id, gid, name, role FROM users
+users.select.all=SELECT id, gid, name, account, role FROM users
 
 collection.check.artifact=SELECT id FROM collection_items \
     WHERE artifact_id = ? AND collection_id = ?
@@ -77,7 +78,7 @@
     UPDATE artifacts \
     SET last_access = DATEADD('MILLISECOND', -2, CURRENT_TIMESTAMP), ttl = 1 \
     WHERE id = ? AND \
-    NOT EXSITS \
+    NOT EXISTS \
     (SELECT id FROM collection_items WHERE collection_id <> ? AND artifact_id = ?)
 
 collection.item.delete=DELETE FROM collection_items WHERE id = ?
@@ -91,14 +92,14 @@
 # COLLECTIONS
 
 collections.outdated= \
-    SELECT c.id FROM collections c \
+    SELECT c.id, c.gid FROM collections c \
         INNER JOIN collection_items ci ON c.id = ci.collection_id \
         INNER JOIN artifacts        a  ON ci.artifact_id = a.id \
         WHERE c.ttl IS NOT NULL \
             AND DATEDIFF('MILLISECOND', c.last_access, CURRENT_TIMESTAMP) > c.ttl \
             AND a.id NOT IN ($LOCKED_IDS$)
 
-collections.update.ttl=UPDATE collections SET ttl = ? WHERE id = ?
+collections.update.ttl=UPDATE collections SET ttl = ? WHERE gid = ?
 
 collections.update.name=UPDATE collections SET name = ? WHERE gid = ?
 
@@ -137,6 +138,11 @@
 
 collection.creation.time=SELECT creation from collections WHERE id = ?
 
+collections.artifacts.oldest=SELECT a.gid, ci.artifact_id  \
+    FROM artifacts AS a, collection_items AS ci, collections AS c \
+    WHERE ci.collection_id = c.id AND c.gid = ?::uuid AND ci.artifact_id = a.id \
+    ORDER BY ci.creation
+
 collections.select.user= \
     SELECT c.gid, c.name, c.creation, u.gid, c.ttl FROM \
     collections c LEFT OUTER JOIN users u ON c.owner_id = u.id \
@@ -163,7 +169,7 @@
 outdate.artifacts.collection=UPDATE artifacts \
     SET last_access = DATEADD('MILLISECOND', -2, CURRENT_TIMESTAMP), ttl = 1 \
     WHERE id IN \
-    SELECT artifact_id FROM collection_items \
+    (SELECT artifact_id FROM collection_items \
         WHERE collection_id = ? AND \
         artifact_id NOT IN (SELECT DISTINCT artifact_id FROM collection_items WHERE collection_id <> ?))
 
@@ -181,3 +187,12 @@
 
 collection.set.attribute= \
     UPDATE collections SET attribute = ? WHERE gid = ?
+
+all.artifacts = \
+    SELECT u.gid AS u_gid, c.gid AS c_gid, c.name AS c_name, c.creation as c_creation, \
+           a.gid AS a_gid, a.factory AS factory, a.creation AS a_creation, a.data AS data \
+    FROM \
+        users u INNER JOIN collections c       ON u.id = c.owner_id \
+                INNER JOIN collection_items ci ON c.id = ci.collection_id \
+                INNER JOIN artifacts a         ON a.id = ci.artifact_id \
+        ORDER BY u_gid, c_gid
--- a/artifact-database/src/main/resources/sql/org-postgresql-driver.properties	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifact-database/src/main/resources/sql/org-postgresql-driver.properties	Fri Sep 28 12:15:11 2012 +0200
@@ -9,7 +9,7 @@
 
 artifacts.touch=UPDATE artifacts SET last_access = CURRENT_TIMESTAMP WHERE id = ?
 
-artifacts.outdated=SELECT id, factory, data FROM artifacts WHERE ttl IS NOT NULL \
+artifacts.outdated=SELECT id, factory, data, gid FROM artifacts WHERE ttl IS NOT NULL \
     AND CURRENT_TIMESTAMP - last_access > (ttl || ' milliseconds')::interval \
     AND id NOT IN (SELECT DISTINCT artifact_id FROM collection_items) \
     AND id NOT IN ($LOCKED_IDS$) \
@@ -33,16 +33,17 @@
 
 users.id.nextval=SELECT NEXTVAL('USERS_ID_SEQ')
 
-users.insert=INSERT INTO users (id, gid, name, role) VALUES (?, ?::uuid, ?, ?)
+users.insert=INSERT INTO users (id, gid, name, account, role) VALUES (?, ?::uuid, ?, ?, ?)
 
 users.select.id.by.gid=SELECT id FROM users WHERE gid = ?::uuid
-users.select.gid=SELECT id, name, role FROM users WHERE gid = ?::uuid
+users.select.gid=SELECT id, name, account, role FROM users WHERE gid = ?::uuid
+users.select.account=SELECT gid, name, account, role FROM users WHERE account = ?
 
 users.delete.id=DELETE FROM users WHERE id = ?
 
 users.delete.collections=DELETE FROM collections where owner_id = ?
 
-users.select.all=SELECT id, gid, name, role FROM users
+users.select.all=SELECT id, gid, name, account, role FROM users
 
 collection.check.artifact=SELECT id FROM collection_items \
     WHERE artifact_id = ? AND collection_id = ?
@@ -77,7 +78,7 @@
     UPDATE artifacts \
     SET last_access = CURRENT_TIMESTAMP - '2 milliseconds'::interval, ttl = 1 \
     WHERE id = ? AND \
-    NOT EXSITS \
+    NOT EXISTS \
     (SELECT id FROM collection_items WHERE collection_id <> ? AND artifact_id = ?)
 
 collection.item.delete=DELETE FROM collection_items WHERE id = ?
@@ -91,7 +92,7 @@
 # COLLECTIONS
 
 collections.outdated= \
-    SELECT c.id FROM collections c \
+    SELECT c.id, c.gid FROM collections c \
         INNER JOIN collection_items ci ON c.id = ci.collection_id \
         INNER JOIN artifacts        a  ON ci.artifact_id = a.id \
         WHERE c.ttl IS NOT NULL \
@@ -128,6 +129,11 @@
 
 collection.creation.time=SELECT creation from collections WHERE id = ?
 
+collections.artifacts.oldest=SELECT a.gid, ci.artifact_id  \
+    FROM artifacts AS a, collection_items AS ci, collections AS c \
+    WHERE ci.collection_id = c.id AND c.gid = ?::uuid AND ci.artifact_id = a.id \
+    ORDER BY ci.creation
+
 collections.select.user= \
     SELECT c.gid, c.name, c.creation, u.gid, c.ttl FROM \
     collections c LEFT OUTER JOIN users u ON c.owner_id = u.id \
@@ -170,3 +176,12 @@
 
 collection.set.attribute= \
     UPDATE collections SET attribute = ? WHERE gid = ?::uuid
+
+all.artifacts = \
+    SELECT u.gid AS u_gid, c.gid AS c_gid, c.name AS c_name, c.creation as c_creation, \
+           a.gid AS a_gid, a.factory AS factory, a.creation AS a_creation, a.data AS data \
+    FROM \
+        users u INNER JOIN collections c       ON u.id = c.owner_id \
+                INNER JOIN collection_items ci ON c.id = ci.collection_id \
+                INNER JOIN artifacts a         ON a.id = ci.artifact_id \
+        ORDER BY u_gid, c_gid
--- a/artifacts-common/pom.xml	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts-common/pom.xml	Fri Sep 28 12:15:11 2012 +0200
@@ -26,6 +26,11 @@
       <artifactId>log4j</artifactId>
       <version>1.2.14</version>
     </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.4</version>
+    </dependency>
   </dependencies>
 
   <build>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/model/KVP.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+package de.intevation.artifacts.common.model;
+
+
+public class KVP<K, V> {
+
+    private K key;
+    private V value;
+
+
+    public KVP(K key, V value) {
+        this.key   = key;
+        this.value = value;
+    }
+
+
+    public K getKey() {
+        return key;
+    }
+
+
+    public V getValue() {
+        return value;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/ClientProtocolUtils.java	Fri Sep 28 12:15:11 2012 +0200
@@ -25,36 +25,47 @@
  */
 public class ClientProtocolUtils {
 
-    /** The XPath to the current state in the DESCRIBE document.*/
+    /** The XPath to the current state in the DESCRIBE document. */
     public static final String XPATH_CURRENT_STATE = "/art:result/art:state";
 
-    /** The XPath to the static UI part in the DESCRIBE document.*/
+    /** The XPath to the static UI part in the DESCRIBE document. */
     public static final String XPATH_STATIC  = "/art:result/art:ui/art:static";
 
-    /** The XPath to the dynamic UI part in the DESCRIBE document.*/
+    /** The XPath to the dynamic UI part in the DESCRIBE document. */
     public static final String XPATH_DYNAMIC = "/art:result/art:ui/art:dynamic";
 
-    /** The XPath to the reachable states part in the DESCRIBE document.*/
+    /** The XPath to the reachable states part in the DESCRIBE document. */
     public static final String XPATH_STATES  =
         "/art:result/art:reachable-states";
 
-    /** The XPath to the output modes in the DESCRIBE document.*/
+    /** The XPath to the output modes in the DESCRIBE document. */
     public static final String XPATH_OUTPUT_MODES  =
         "/art:result/art:outputmodes/art:output";
 
-
     /** The XPath to the select node relative to the dynamic UI node in the
-     * DESCRIBE document.*/
+     * DESCRIBE document. */
     public static final String XPATH_DATA_SELECT = "art:select";
 
     /** The XPath to the choices nodes relative to the select node in the
-     * DESCRIBE document.*/
+     * DESCRIBE document. */
     public static final String XPATH_DATA_ITEMS = "art:choices/art:item";
 
-    /** The XPath to a label in the artifact's DESCRIBE document.*/
+    /** The XPath that points to the min value of a range.*/
+    public static final String XPATH_MIN_NODE = "art:min/@art:value";
+
+    /** The XPath that points to the max value of a range.*/
+    public static final String XPATH_MAX_NODE = "art:max/@art:value";
+
+    /** The XPath that points to the default min value of a range.*/
+    public static final String XPATH_DEF_MIN = "art:min/@art:default";
+
+    /** The XPath that points to the default max value of a range.*/
+    public static final String XPATH_DEF_MAX = "art:max/@art:default";
+
+    /** The XPath to a label in the artifact's DESCRIBE document. */
     public static final String XPATH_LABEL = "art:label/text()";
 
-    /** The XPath to a value in the artifact's DESCRIBE document.*/
+    /** The XPath to a value in the artifact's DESCRIBE document. */
     public static final String XPATH_VALUE = "art:value/text()";
 
 
@@ -71,6 +82,38 @@
      * @return the CREATE document.
      */
     public static Document newCreateDocument(String factory) {
+        return newCreateDocument(factory, null);
+    }
+
+
+    /**
+     * This method creates a new CREATE document.
+     *
+     * @return the CREATE document.
+     */
+    public static Document newCreateDocument(String factory, String uuid) {
+        return newCreateDocument(factory, uuid, null);
+    }
+
+    public static Document newCreateDocument(
+        String  factory,
+        String  uuid,
+        String  ids
+    ) {
+        return newCreateDocument(factory, uuid, ids, null);
+    }
+
+    /**
+     * This method creates a new CREATE document.
+     *
+     * @return the CREATE document.
+     */
+    public static Document newCreateDocument(
+        String         factory,
+        String         uuid,
+        String         ids,
+        CreationFilter filter
+    ) {
         Document doc = XMLUtils.newDocument();
 
         XMLUtils.ElementCreator cr = new XMLUtils.ElementCreator(
@@ -88,6 +131,22 @@
         action.appendChild(type);
         action.appendChild(fac);
 
+        if (uuid != null) {
+            Element templ = cr.create("template");
+            templ.setAttribute("uuid", uuid);
+            action.appendChild(templ);
+        }
+
+        if (ids != null) {
+            Element id = cr.create("ids");
+            id.setAttribute("value", ids);
+            action.appendChild(id);
+        }
+
+        if (filter != null) {
+            action.appendChild(filter.toXML(cr));
+        }
+
         doc.appendChild(action);
 
         return doc;
@@ -277,7 +336,7 @@
      * collection in the artifact server.
      *
      * @param artId The identifier of the artifact that should be added.
-     * @param attr A document that contains attributes for the attribute's
+     * @param attr A document that contains attributes for the artifact's
      * life in the collection.
      *
      * @return the document to add an artifact into a collection.
@@ -313,6 +372,38 @@
 
 
     /**
+     * Create a new Document that is used to remove an artifact from a
+     * collection in the artifact server.
+     *
+     * @param artId The identifier of the artifact that should be added.
+     *
+     * @return the document to add an artifact into a collection.
+     */
+    public static Document newRemoveArtifactDocument(String artId) {
+        Document doc = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator cr = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element action    = cr.create("action");
+        Element type      = cr.create("type");
+        Element artifact  = cr.create("artifact");
+
+        cr.addAttr(artifact, "uuid", artId);
+        cr.addAttr(type, "name", "removeartifact");
+
+        action.appendChild(type);
+        type.appendChild(artifact);
+
+        doc.appendChild(action);
+
+        return doc;
+    }
+
+
+    /**
      * This method creates a new Document that is used to trigger the DESCRIBE
      * operation of a collection in the artifact server.
      *
@@ -729,5 +820,41 @@
             XPathConstants.NODESET,
             ArtifactNamespaceContext.INSTANCE);
     }
+
+
+    public static String getMinNode(Node parent) {
+        return (String) XMLUtils.xpath(
+            parent,
+            XPATH_MIN_NODE,
+            XPathConstants.STRING,
+            ArtifactNamespaceContext.INSTANCE);
+    }
+
+
+    public static String getMaxNode(Node parent) {
+        return (String) XMLUtils.xpath(
+            parent,
+            XPATH_MAX_NODE,
+            XPathConstants.STRING,
+            ArtifactNamespaceContext.INSTANCE);
+    }
+
+
+    public static String getDefMin(Node parent) {
+        return (String) XMLUtils.xpath(
+            parent,
+            XPATH_DEF_MIN,
+            XPathConstants.STRING,
+            ArtifactNamespaceContext.INSTANCE);
+    }
+
+
+    public static String getDefMax(Node parent) {
+        return (String) XMLUtils.xpath(
+            parent,
+            XPATH_DEF_MAX,
+            XPathConstants.STRING,
+            ArtifactNamespaceContext.INSTANCE);
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/CreationFilter.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,61 @@
+package de.intevation.artifacts.common.utils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+import org.w3c.dom.Element;
+
+public class CreationFilter
+{
+    public static class Facet {
+
+        protected String name;
+        protected String index;
+
+        public Facet() {
+        }
+
+        public Facet(String name, String index) {
+            this.name  = name;
+            this.index = index;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getIndex() {
+            return index;
+        }
+    }
+
+    protected Map<String, List<Facet>> outs;
+
+    public CreationFilter() {
+        outs = new HashMap<String, List<Facet>>();
+    }
+
+    public void add(String out, List<Facet> facets) {
+        outs.put(out, facets);
+    }
+
+    public Element toXML(XMLUtils.ElementCreator ec) {
+        Element filter = ec.create("filter");
+
+        for (Map.Entry<String, List<Facet>> entry: outs.entrySet()) {
+            Element out = ec.create("out");
+            out.setAttribute("name", entry.getKey());
+            for (Facet facet: entry.getValue()) {
+                Element f = ec.create("facet");
+                f.setAttribute("name", facet.getName());
+                f.setAttribute("index", facet.getIndex());
+                out.appendChild(f);
+            }
+            filter.appendChild(out);
+        }
+
+        return filter;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/DateUtils.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,30 @@
+package de.intevation.artifacts.common.utils;
+
+import java.util.Calendar;
+import java.util.Date;
+
+
+public class DateUtils {
+
+    private DateUtils() {
+    }
+
+
+    /**
+     * This function extracts the year as int value from <i>date</i>.
+     *
+     * @param date The source date.
+     *
+     * @return the year as integer or -1 if date is empty.
+     */
+    public static int getYearFromDate(Date date) {
+        if (date == null) {
+            return -1;
+        }
+
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+
+        return cal.get(Calendar.YEAR);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/FileTools.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) 2010, 2011 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+package de.intevation.artifacts.common.utils;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.BufferedOutputStream;
+import java.nio.channels.FileChannel;
+
+import java.util.Deque;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.log4j.Logger;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class FileTools
+{
+    private static Logger log = Logger.getLogger(FileTools.class);
+
+    public static final String DIGEST =
+        System.getProperty("artifacts.common.file.cmp.digest", "MD5");
+
+    private FileTools() {
+    }
+
+
+    public static File getDirectory(String path, String name) {
+        if (path == null || name == null) {
+            return null;
+        }
+
+        File dir = new File(path, name);
+
+        if (!dir.exists()) {
+            log.debug(
+                "Directory '" + dir.getAbsolutePath() + "' doesn't " +
+                "exist. Try to create it.");
+
+            return dir.mkdir() ? dir : null;
+        }
+        else {
+            return dir.isDirectory() ? dir : null;
+        }
+    }
+
+    public static File repair(File file) {
+        file = file.getAbsoluteFile();
+        if (file.exists()) {
+            return file;
+        }
+        Deque<String> parts = new ArrayDeque<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;
+    }
+
+    public static class HashedFile
+    implements Comparable<HashedFile>
+    {
+        protected File    file;
+        protected long    length;
+        protected byte [] hash;
+
+        public HashedFile(File file) {
+            this.file = file;
+            length = file.length();
+        }
+
+        public File getFile() {
+            return file;
+        }
+
+        protected byte [] getHash() {
+            if (hash == null) {
+                InputStream in = null;
+
+                try {
+                    in = new FileInputStream(file);
+
+                    MessageDigest digest = MessageDigest.getInstance(DIGEST);
+
+                    byte [] buf = new byte[40*1024];
+                    int r;
+
+                    while ((r = in.read(buf)) >= 0) {
+                        digest.update(buf, 0, r);
+                    }
+
+                    hash = digest.digest();
+                }
+                catch (IOException ioe) {
+                    log.error(ioe);
+                    hash = new byte[0];
+                }
+                catch (NoSuchAlgorithmException nsae) {
+                    log.error(nsae);
+                    hash = new byte[0];
+                }
+                finally {
+                    if (in != null) {
+                        try {
+                            in.close();
+                        }
+                        catch (IOException ioe) {
+                            log.error(ioe);
+                        }
+                    }
+                }
+            }
+            return hash;
+        }
+
+        @Override
+        public int compareTo(HashedFile other) {
+            if (length < other.length) return -1;
+            if (length > other.length) return +1;
+            return compare(getHash(), other.getHash());
+        }
+
+        private static int compare(byte [] a, byte [] b) {
+            if (a.length < b.length) return -1;
+            if (a.length > b.length) return +1;
+            for (int i = 0; i < a.length; ++i) {
+                int x = a[i] & 0xff;
+                int y = b[i] & 0xff;
+                if (x < y) return -1;
+                if (x > y) return +1;
+            }
+            return 0;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            return other instanceof HashedFile
+                && ((HashedFile)other).compareTo(this) == 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return (int)(length ^ (length >>> 32));
+        }
+    } // class HashedFile
+
+    public static List<File> uniqueFiles(List<File> files) {
+
+        Set<HashedFile> set = new HashSet<HashedFile>();
+
+        for (File file: files) {
+            if (!set.add(new HashedFile(file))) {
+                log.warn("file '" + file + "' is a duplicate.");
+            }
+        }
+
+        ArrayList<File> out = new ArrayList<File>(set.size());
+        for (HashedFile hf: set) {
+            out.add(hf.file);
+        }
+
+        return out;
+    }
+
+    public interface FileVisitor {
+        boolean visit(File file);
+    } // Visitor
+
+    public static void walkTree(File root, FileVisitor visitor) {
+
+        Deque<File> stack = new ArrayDeque<File>();
+
+        stack.push(root);
+
+        while (!stack.isEmpty()) {
+            File current = stack.pop();
+            if (!visitor.visit(current)) break;
+            if (current.isDirectory()) {
+                File [] subs = current.listFiles();
+                if (subs != null) {
+                    for (File f: subs) {
+                        stack.push(f);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Deletes everything in a directory.
+     *
+     * @param dir The directory.
+     */
+    public final static void deleteContent(File dir) {
+        if (dir == null || !dir.isDirectory()) {
+            return;
+        }
+
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file: files) {
+                deleteRecursive(file);
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * Delete <i>file</i> and everything in <i>file</i> if it is a directory.
+     *
+     * @param file The file or directory.
+     * @return true, if deletion was successful - otherwise false.
+     */
+    public final static boolean deleteRecursive(File file) {
+
+        if (file == null) {
+            return false;
+        }
+
+        if (file.isDirectory()) {
+            File [] files = file.listFiles();
+            if (files != null) {
+                for (File sub: files) {
+                    if (!deleteRecursive(sub)) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return file.delete();
+    }
+
+    /**
+     * Put the given file or directory into a zip archive.
+     *
+     * @param file The file or directory.
+     * @param outputStream The stream to write the archive to.
+     * @throws IOException if an error occured while zip creation or writing to
+     * output stream.
+     */
+    public static void createZipArchive(
+        File         file,
+        OutputStream outputStream
+    )
+    throws IOException
+    {
+        ZipOutputStream out = new ZipOutputStream(outputStream);
+
+        if (file.isFile()) {
+            copyFileToZip("", file, out);
+        }
+        else if (file.isDirectory()) {
+
+            Deque<PrefixDir> stack = new ArrayDeque<PrefixDir>();
+            stack.push(new PrefixDir(file.getName() + "/", file));
+
+            while (!stack.isEmpty()) {
+                PrefixDir pd = stack.pop();
+
+                ZipEntry dirEntry = new ZipEntry(pd.prefix);
+                out.putNextEntry(dirEntry);
+                out.closeEntry();
+
+                File [] files = pd.dir.listFiles();
+                if (files != null) {
+                    for (File sub: files) {
+                        if (sub.isDirectory()) {
+                            stack.push(new PrefixDir(
+                                pd.prefix + sub.getName() + "/",
+                                sub));
+                        }
+                        else if (sub.isFile()) {
+                            copyFileToZip(pd.prefix, sub, out);
+                        }
+                    }
+                }
+            }
+        }
+
+        out.finish();
+    }
+
+
+    public static void extractArchive(File archive, File destDir)
+    throws IOException {
+        if (!destDir.exists()) {
+            destDir.mkdir();
+        }
+
+        ZipFile zipFile = new ZipFile(archive);
+        Enumeration entries = zipFile.entries();
+
+        byte[] buffer = new byte[16384];
+        int len;
+        while (entries.hasMoreElements()) {
+            ZipEntry entry = (ZipEntry) entries.nextElement();
+
+            String entryFileName = entry.getName();
+
+            File dir = dir = buildDirectoryHierarchyFor(entryFileName, destDir);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+
+            if (!entry.isDirectory()) {
+                BufferedOutputStream bos = new BufferedOutputStream(
+                        new FileOutputStream(new File(destDir, entryFileName)));
+
+                BufferedInputStream bis = new BufferedInputStream(zipFile
+                        .getInputStream(entry));
+
+                while ((len = bis.read(buffer)) > 0) {
+                    bos.write(buffer, 0, len);
+                }
+
+                bos.flush();
+                bos.close();
+                bis.close();
+            }
+        }
+        zipFile.close();
+    }
+
+    private static File buildDirectoryHierarchyFor(
+        String entryName,
+        File destDir)
+    {
+        int lastIndex = entryName.lastIndexOf('/');
+        String entryFileName = entryName.substring(lastIndex + 1);
+        String internalPathToEntry = entryName.substring(0, lastIndex + 1);
+        return new File(destDir, internalPathToEntry);
+    }
+
+    /**
+     * A class representing a directory with a prefix.
+     */
+    private static final class PrefixDir {
+
+        String prefix;
+        File   dir;
+
+        public PrefixDir(String prefix, File dir) {
+            this.prefix = prefix;
+            this.dir    = dir;
+        }
+
+    } // class PrefixDir
+
+    /**
+     * Write a file to zip archive.
+     *
+     * @param prefix A prefix.
+     * @param file The file.
+     * @param out The output stream.
+     * @throws IOException if an error occured while writing to zip output
+     * stream.
+     */
+    private static void copyFileToZip(
+        String          prefix,
+        File            file,
+        ZipOutputStream out
+    )
+    throws IOException
+    {
+        String   entryName = prefix + file.getName();
+        ZipEntry entry     = new ZipEntry(entryName);
+        out.putNextEntry(entry);
+        InputStream in = null;
+        try {
+            in =
+                new BufferedInputStream(
+                new FileInputStream(file), 20*1024);
+
+            byte [] buf = new byte[2048];
+
+            int r;
+            while ((r = in.read(buf)) > 0) {
+                out.write(buf, 0, r);
+            }
+        }
+        finally {
+            if (in != null) {
+                try { in.close(); }
+                catch (IOException ioe) {}
+            }
+        }
+        out.closeEntry();
+    }
+
+
+    /**
+     * Copies a <i>src</i> file to <i>target</i>.
+     *
+     * @param src A file (not a directory) that should be copied.
+     * @param target The destination. This might be a file or a directory.
+     *
+     * @return true, if <i>src</i> has been successfully copied; otherwise
+     * false.
+     */
+    public static boolean copyFile(File src, File target)
+    throws IOException
+    {
+        if (src == null || !src.exists()) {
+            log.warn("Source file does not exist!");
+            return false;
+        }
+
+        if (!src.canRead()) {
+            log.warn("Cannot read Source file!");
+            return false;
+        }
+
+        if (src.isDirectory()) {
+            log.warn("Source is a directory!");
+            return false;
+        }
+
+        if (target.isDirectory()) {
+            target = new File(target, src.getName());
+        }
+
+        FileInputStream  in  = null;
+        FileOutputStream out = null;
+
+        try {
+            in  = new FileInputStream(src);
+            out = new FileOutputStream(target);
+
+            FileChannel inChannel  = in.getChannel();
+            FileChannel outChannel = out.getChannel();
+
+            inChannel.transferTo(0l, inChannel.size(), outChannel);
+
+            return true;
+        }
+        catch (IOException ioe) {
+            log.warn(ioe, ioe);
+        }
+        finally {
+            if (in != null) {
+                try {
+                    in.close();
+                }
+                catch (IOException ioe) { /* do nothing here */ }
+            }
+
+            if (out != null) {
+                try {
+                    out.close();
+                }
+                catch (IOException ioe) { /* do nothing here */ }
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Copies a directory <i>source</i> to a destination path <i>dest</i>.
+     *
+     * @param source A directory that should be copied.
+     * @param dest A destination directory which is created if it is not
+     * existing yet.
+     *
+     * @return true, if the directory has been successfully copied; otherwise
+     * false.
+     */
+    public static boolean copyDirectory(final File source, final File dest) {
+        if (source == null || !source.exists()) {
+            log.warn("Source directory does not exist!");
+            return false;
+        }
+
+        if (!source.isDirectory()) {
+            log.warn("Source is not a directory!");
+            return false;
+        }
+
+        if (dest == null) {
+            log.warn("Destination directory is null!");
+            return false;
+        }
+
+        if (!dest.exists()) {
+            if (!dest.mkdir()) {
+                log.warn("Cannot create destination directory!");
+                return false;
+            }
+        }
+
+        File[] children = source.listFiles();
+        int    failed   = 0;
+
+        if (children != null && children.length > 0) {
+            for (File child: children) {
+                if (child.isFile()) {
+                    try {
+                        if (!copyFile(child, dest)) {
+                            failed++;
+                        }
+                    }
+                    catch (IOException ioe) {
+                        log.warn(ioe, ioe);
+                        failed++;
+                    }
+                }
+                else if (child.isDirectory()) {
+                    copyDirectory(child, new File(dest, child.getName()));
+                }
+            }
+        }
+
+        log.debug("Failed to copy " + failed + " files.");
+
+        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/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/JSON.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,445 @@
+package de.intevation.artifacts.common.utils;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import java.io.IOException;
+import java.io.PushbackInputStream;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+
+public final class JSON
+{
+    private JSON() {
+    }
+
+    private static final boolean isDigit(int c) {
+        return c >= '0' && c <= '9';
+    }
+
+    public static final boolean isWhitespace(int c) {
+        return c == ' '  || c == '\n' || c == '\r'
+            || c == '\t' || c == '\f';
+    }
+
+    private static final void match(int c, int x) throws IOException {
+        if (c != x) {
+            throw new IOException(
+                "Expecting '" + (char)c + "' found '" + (char)x + "'");
+        }
+    }
+
+    private static final int eof(InputStream in)
+    throws IOException
+    {
+        int c = in.read();
+        if (c == -1) {
+            throw new IOException("EOF unexpected.");
+        }
+        return c;
+    }
+
+    private static final int whitespace(InputStream in)
+    throws IOException
+    {
+        int c;
+        while (isWhitespace(c = eof(in)));
+        return c;
+    }
+
+    private static final int parseHex(String hex) throws IOException {
+        try {
+            return Integer.parseInt(hex, 16);
+        }
+        catch (NumberFormatException nfe) {
+            throw new IOException("'" + hex + "' is not a hex string.");
+        }
+    }
+
+    public static final String jsonString(String string) {
+        StringBuilder sb = new StringBuilder(string.length()+2);
+
+        sb.append('"');
+
+        for (int i = 0, N = string.length(); i < N; ++i) {
+            char c = string.charAt(i);
+            switch (c) {
+                case '"':  sb.append("\\\""); break;
+                case '\t': sb.append("\\t"); break;
+                case '\r': sb.append("\\r"); break;
+                case '\n': sb.append("\\n"); break;
+                case '\b': sb.append("\\b"); break;
+                case '\f': sb.append("\\f"); break;
+                default:
+                    if (c >= 128) {
+                        sb.append("\\u");
+                        String hex = Integer.toHexString((int)c);
+                        for (int j = 4-hex.length(); j > 0; --j) {
+                            sb.append('0');
+                        }
+                        sb.append(hex);
+                    }
+                    else {
+                        sb.append(c);
+                    }
+            }
+        }
+
+        sb.append('"');
+
+        return sb.toString();
+    }
+
+    public static String toJSONString(Map<String, Object> map) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        write(pw, map);
+        pw.flush();
+        return sw.toString();
+    }
+
+
+    public static void write(PrintWriter out, Map<String, Object> map) {
+        writeObject(out, map);
+    }
+
+    private static void writeValue(PrintWriter out, Object value) {
+        if (value instanceof Map) {
+            writeObject(out, (Map)value);
+        }
+        else if (value instanceof List) {
+            writeList(out, (List)value);
+        }
+        else if (value instanceof Number) {
+            out.print(value);
+        }
+        else if (value instanceof Boolean) {
+            out.print(((Boolean)value) ? "true" : "false");
+        }
+        else if (value == null) {
+            out.print("null");
+        }
+        else {
+            out.print(jsonString(value.toString()));
+        }
+    }
+
+    private static void writeObject(PrintWriter out, Map map) {
+
+        out.print('{');
+        Iterator iter = map.entrySet().iterator();
+        while (iter.hasNext()) {
+            Map.Entry entry = (Map.Entry)iter.next();
+            out.print(jsonString(entry.getKey().toString()));
+            out.print(':');
+            writeValue(out, entry.getValue());
+            if (iter.hasNext()) {
+                out.print(',');
+            }
+        }
+        out.print('}');
+    }
+
+    private static void writeList(PrintWriter out, List list) {
+        out.print('[');
+        Iterator iter = list.iterator();
+        while (iter.hasNext()) {
+            writeValue(out, iter.next());
+            if (iter.hasNext()) {
+                out.print(',');
+            }
+        }
+        out.print(']');
+    }
+
+    public static Map<String, Object> parse(String in)
+    throws IOException
+    {
+        return parse(asInputStream(in));
+    }
+
+    private static InputStream asInputStream(String in) {
+        byte [] bytes;
+        try {
+            bytes = in.getBytes(Charset.forName("US-ASCII"));
+        }
+        catch (UnsupportedCharsetException uce) {
+            // Should not happen.
+            bytes = in.getBytes();
+        }
+        return new ByteArrayInputStream(bytes);
+    }
+
+    public static Map<String, Object> parse(InputStream in)
+    throws IOException
+    {
+        return parseObject(new PushbackInputStream(in, 1));
+    }
+
+    public static Map<String, Object> parse(PushbackInputStream in)
+    throws IOException
+    {
+        return parseObject(in);
+    }
+
+    private static final String parseString(
+        PushbackInputStream in
+    )
+    throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+
+        int mode = 0;
+
+        char [] hex = new char[4];
+
+        match('"', eof(in));
+
+        OUT: for (int c = eof(in);; c = eof(in)) {
+
+            switch (mode) {
+                case 0:
+                    if (c == '"') {
+                        break OUT;
+                    }
+                    if (c == '\\') {
+                        mode = 1;
+                    }
+                    else {
+                        sb.append((char)c);
+                    }
+                    break;
+                case 1:
+                    switch (c) {
+                        case 'u':
+                            mode = 2;
+                            continue;
+                        case 'b':
+                            sb.append('\b');
+                            break;
+                        case 'f':
+                            sb.append('\f');
+                            break;
+                        case 'n':
+                            sb.append('\n');
+                            break;
+                        case 'r':
+                            sb.append('\r');
+                            break;
+                        case 't':
+                            sb.append('\t');
+                            break;
+                        default:
+                            sb.append((char)c);
+                    }
+                    mode = 0;
+                    break;
+                case 2:
+                    hex[0] = (char)c;
+                    mode = 3;
+                    break;
+                case 3:
+                    hex[1] = (char)c;
+                    mode = 4;
+                    break;
+                case 4:
+                    hex[2] = (char)c;
+                    mode = 5;
+                    break;
+                case 5:
+                    hex[3] = (char)c;
+                    sb.append((char)parseHex(new String(hex)));
+                    mode = 0;
+                    break;
+            }
+        }
+        return sb.toString();
+    }
+
+    private static final Boolean parseTrue(InputStream in)
+    throws IOException
+    {
+        match('t', eof(in));
+        match('r', eof(in));
+        match('u', eof(in));
+        match('e', eof(in));
+        return Boolean.TRUE;
+    }
+
+    private static final Boolean parseFalse(InputStream in)
+    throws IOException
+    {
+        match('f', eof(in));
+        match('a', eof(in));
+        match('l', eof(in));
+        match('s', eof(in));
+        match('e', eof(in));
+        return Boolean.FALSE;
+    }
+
+    private static final Object parseNull(InputStream in)
+    throws IOException
+    {
+        match('n', eof(in));
+        match('u', eof(in));
+        match('l', eof(in));
+        match('l', eof(in));
+        return null;
+    }
+
+    private static final Number parseNumber(PushbackInputStream in)
+    throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+
+        boolean isInteger = true;
+
+        int c;
+        OUT: for (;;) {
+            switch (c = eof(in)) {
+                case '0': case '1': case '2': case '3': case '4':
+                case '5': case '6': case '7': case '8': case '9':
+                case '-': case '+':
+                    sb.append((char)c);
+                    break;
+                case '.': case 'e': case 'E':
+                    isInteger = false;
+                    sb.append((char)c);
+                    break;
+                default:
+                    in.unread(c);
+                    break OUT;
+            }
+        }
+
+        try {
+            if (isInteger) {
+                return sb.length() > 9
+                    ? (Number)Long   .valueOf(sb.toString())
+                    : (Number)Integer.valueOf(sb.toString());
+            }
+            return (Number)Double.valueOf(sb.toString());
+        }
+        catch (NumberFormatException nfe) {
+            throw new IOException("Not a number '" + sb + "'");
+        }
+    }
+
+    private static List<Object> parseList(PushbackInputStream in)
+    throws IOException
+    {
+        List<Object> list = new ArrayList<Object>();
+        match('[', whitespace(in));
+        int c = whitespace(in);
+        if (c == ']') {
+            return list;
+        }
+
+        for (;; c = whitespace(in)) {
+            Object value;
+            in.unread(c);
+            switch (c) {
+                case '{':
+                    value = parseObject(in);
+                    break;
+                case '[':
+                    value = parseList(in);
+                    break;
+                case '"':
+                    value = parseString(in);
+                    break;
+                case 't':
+                    value = parseTrue(in);
+                    break;
+                case 'f':
+                    value = parseFalse(in);
+                    break;
+                case 'n':
+                    value = parseNull(in);
+                    break;
+                default:
+                    value = parseNumber(in);
+            }
+            list.add(value);
+
+            if ((c = whitespace(in)) == ']') break;
+            match(',', c);
+        }
+        return list;
+    }
+
+    private static void parsePair(
+        PushbackInputStream in,
+        Map<String, Object> pairs
+    )
+    throws IOException
+    {
+        in.unread(whitespace(in));
+        String string = parseString(in);
+        match(':', whitespace(in));
+
+        Object value;
+
+        int c = whitespace(in);
+        in.unread(c);
+        switch (c) {
+            case '{':
+                value = parseObject(in);
+                break;
+            case '[':
+                value = parseList(in);
+                break;
+            case '"':
+                value = parseString(in);
+                break;
+            case 't':
+                value = parseTrue(in);
+                break;
+            case 'f':
+                value = parseFalse(in);
+                break;
+            case 'n':
+                value = parseNull(in);
+                break;
+            default:
+                value = parseNumber(in);
+        }
+        pairs.put(string, value);
+    }
+
+    private static Map<String, Object> parseObject(PushbackInputStream in)
+    throws IOException
+    {
+        Map<String, Object> pairs = new LinkedHashMap<String, Object>();
+
+        int c = whitespace(in);
+        match('{', c);
+
+        if ((c = whitespace(in)) == '}') {
+            return pairs;
+        }
+
+        in.unread(c);
+
+        for (;;) {
+            parsePair(in, pairs);
+
+            if ((c = whitespace(in)) == '}') {
+                break;
+            }
+
+            if (c == '}') break;
+            match(',', c);
+        }
+
+        return pairs;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/LRUCache.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,26 @@
+package de.intevation.artifacts.common.utils;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+public class LRUCache<K, V>
+extends      LinkedHashMap<K, V>
+{
+    public static final int DEFAULT_MAX_CAPACITY = 25;
+
+    private int maxCapacity;
+
+    public LRUCache() {
+        this(DEFAULT_MAX_CAPACITY);
+    }
+
+    public LRUCache(int maxCapacity) {
+        this.maxCapacity = maxCapacity;
+    }
+
+    @Override
+    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+        return size() > maxCapacity;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/MapXPathVariableResolver.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+package de.intevation.artifacts.common.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathVariableResolver;
+
+
+/**
+ *  @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MapXPathVariableResolver implements XPathVariableResolver {
+
+    protected Map<String, String> variables;
+
+
+    public MapXPathVariableResolver() {
+        this.variables = new HashMap<String, String>();
+    }
+
+
+    public MapXPathVariableResolver(Map<String, String> variables) {
+        this.variables = variables;
+    }
+
+
+    public void addVariable(String name, String value) {
+        if (name != null && value != null) {
+            variables.put(name, value);
+        }
+    }
+
+
+    @Override
+    public Object resolveVariable(QName variableName) {
+        String key = variableName.getLocalPart();
+        return variables.get(key);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/StringUtils.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2010 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+
+package de.intevation.artifacts.common.utils;
+
+import java.io.UnsupportedEncodingException;
+
+import java.util.UUID;
+
+import org.apache.commons.codec.DecoderException;
+
+import org.apache.commons.codec.binary.Hex;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Commonly used string functions.
+ *
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
+ */
+public final class StringUtils
+{
+    private static Logger logger = Logger.getLogger(StringUtils.class);
+
+    /**
+     * Generated a random UUIDv4 in form of a string.
+     * @return the UUID
+     */
+    public static final String newUUID() {
+        return UUID.randomUUID().toString();
+    }
+
+    /**
+     * Checks if a given string is a valid UUID.
+     * @param uuid The string to test.
+     * @return true if the string is a valid UUID else false.
+     */
+    public static final boolean checkUUID(String uuid) {
+        try {
+            UUID.fromString(uuid);
+        }
+        catch (IllegalArgumentException iae) {
+            logger.warn(iae.getLocalizedMessage());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the UTF-8 byte array representation of a given string.
+     * @param s The string to be transformed.
+     * @return The byte array representation.
+     */
+    public static final byte [] getUTF8Bytes(String s) {
+        try {
+            return s.getBytes("UTF-8");
+        }
+        catch (UnsupportedEncodingException usee) {
+            logger.error(usee.getLocalizedMessage(), usee);
+            return s.getBytes();
+        }
+    }
+
+    /**
+     * Tries to convert a Base64 encoded string into the
+     * corresponing byte array.
+     * @param s The Base64 encoded string
+     * @return The byte array representation or null if
+     * an decoding error occurs.
+     */
+    public static final byte [] decodeHex(String s) {
+        try {
+            return Hex.decodeHex(s.toCharArray());
+        }
+        catch (DecoderException de) {
+            return null;
+        }
+    }
+
+    public static final String repeat(String s, int count, String sep) {
+        if (count <= 0) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder(s);
+        for (--count; count > 0; --count) {
+            sb.append(sep).append(s);
+        }
+        return sb.toString();
+    }
+
+    public static final String repeat(char c, int count, char sep) {
+        if (count <= 0) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder(2*count-1).append(c);
+        for (--count; count > 0; --count) {
+            sb.append(sep).append(c);
+        }
+        return sb.toString();
+    }
+
+    public static final String [] toUpperCase(String [] s) {
+        if (s == null) {
+            return null;
+        }
+        String [] d = new String[s.length];
+        for (int i = 0; i < s.length; ++i) {
+            if (s[i] != null) {
+                d[i] = s[i].toUpperCase();
+            }
+        }
+        return d;
+    }
+
+    public static String join(String sep, String [] strings) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < strings.length; ++i) {
+            if (i > 0) sb.append(sep);
+            sb.append(strings[i]);
+        }
+        return sb.toString();
+    }
+
+    public static final String [] join(String [] a, String [] b) {
+        if (a == null && b == null) return null;
+        if (a == null) return b;
+        if (b == null) return a;
+        String [] dst = new String[a.length + b.length];
+        System.arraycopy(a, 0, dst, 0, a.length);
+        System.arraycopy(b, 0, dst, a.length, b.length);
+        return dst;
+    }
+
+    public static final boolean contains(String needle, String [] haystack) {
+        for (String stray: haystack) {
+            if (needle.equals(stray)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java	Fri Sep 28 12:15:11 2012 +0200
@@ -8,6 +8,10 @@
 
 package de.intevation.artifacts.common.utils;
 
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.LinkedHashMap;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
@@ -41,12 +45,14 @@
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
 import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathVariableResolver;
 
 import org.apache.log4j.Logger;
 
 import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 
 import org.xml.sax.SAXException;
 
@@ -57,15 +63,13 @@
  */
 public final class XMLUtils
 {
-    /**
-     * W3C URL of XForms
-     */
+    /** W3C URL of XForms. */
     public static final String XFORM_URL    = "http://www.w3.org/2002/xforms";
-    /**
-     * W3C prefix of XForms
-     */
+
+    /** W3C prefix of XForms. */
     public static final String XFORM_PREFIX = "xform";
 
+    /** Logger for this class. */
     private static Logger logger = Logger.getLogger(XMLUtils.class);
 
     private XMLUtils() {
@@ -77,17 +81,13 @@
      */
     public static class ElementCreator
     {
-        /**
-         * owner document of the elements to be created
-         */
+        /** Owner document of the elements to be created. */
         protected Document document;
-        /**
-         * namespace to be used
-         */
+
+        /** Namespace to be used. */
         protected String   ns;
-        /**
-         * prefix to be used
-         */
+
+        /** Prefix to be used. */
         protected String   prefix;
 
         /**
@@ -152,7 +152,6 @@
             else {
                 element.setAttribute(name, value);
             }
-
         }
     } // class ElementCreator
 
@@ -174,6 +173,20 @@
         return null;
     }
 
+
+    /**
+     * Create xml/string representation of element (nested in otherwise empty
+     * document).
+     * @param element element to inspect in string.
+     * @return string with xml representation of element.
+     */
+    public final static String toString(Node node) {
+        Document doc = newDocument();
+        doc.appendChild(doc.importNode(node,true));
+        return toString(doc);
+    }
+
+
     /**
      * Loads a XML document namespace aware from a file
      * @param file The file to load.
@@ -198,9 +211,31 @@
         return null;
     }
 
+    /**
+     * Parses a String to a xml document.
+     *
+     * @param string The xml string
+     * @return the XML document or null if something went wrong.
+     */
+    public static final Document parseDocument(String string) {
+        InputStream inputStream = new ByteArrayInputStream(string.getBytes());
+        return parseDocument(inputStream);
+    }
+
+
     public static final Document parseDocument(InputStream inputStream) {
+        return parseDocument(inputStream, Boolean.TRUE);
+    }
+
+    public static final Document parseDocument(
+        InputStream inputStream,
+        Boolean     namespaceAware
+    ) {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        factory.setNamespaceAware(true);
+
+        if (namespaceAware != null) {
+            factory.setNamespaceAware(namespaceAware.booleanValue());
+        }
 
         try {
             return factory.newDocumentBuilder().parse(inputStream);
@@ -222,7 +257,7 @@
      * @return the new XPath.
      */
     public static final XPath newXPath() {
-        return newXPath(null);
+        return newXPath(null, null);
     }
 
     /**
@@ -231,12 +266,19 @@
      * if none should be used.
      * @return The new XPath
      */
-    public static final XPath newXPath(NamespaceContext namespaceContext) {
+    public static final XPath newXPath(
+        NamespaceContext      namespaceContext,
+        XPathVariableResolver resolver)
+    {
         XPathFactory factory = XPathFactory.newInstance();
         XPath        xpath   = factory.newXPath();
         if (namespaceContext != null) {
             xpath.setNamespaceContext(namespaceContext);
         }
+
+        if (resolver != null) {
+            xpath.setXPathVariableResolver(resolver);
+        }
         return xpath;
     }
 
@@ -294,12 +336,26 @@
         QName            returnType,
         NamespaceContext namespaceContext
     ) {
+        return xpath(root, query, returnType, namespaceContext, null);
+    }
+
+    public static final Object xpath(
+        Object           root,
+        String           query,
+        QName            returnType,
+        NamespaceContext namespaceContext,
+        Map<String, String> variables)
+    {
         if (root == null) {
             return null;
         }
 
+        XPathVariableResolver resolver = variables != null
+            ? new MapXPathVariableResolver(variables)
+            : null;
+
         try {
-            XPath xpath = newXPath(namespaceContext);
+            XPath xpath = newXPath(namespaceContext, resolver);
             if (xpath != null) {
                 return xpath.evaluate(query, root, returnType);
             }
@@ -422,5 +478,140 @@
         }
         return null;
     }
+
+    private static class BuildResult {
+        List<Node>          children;
+        Map<String, String> attributes;
+        BuildResult() {
+            children   = new ArrayList<Node>();
+            attributes = new LinkedHashMap<String, String>();
+        }
+
+        void setAttributes(Element element) {
+            for (Map.Entry<String, String> entry: attributes.entrySet()) {
+                element.setAttribute(entry.getKey(), entry.getValue());
+            }
+        }
+
+        void finish(Element element) {
+            setAttributes(element);
+            for (Node child: children) {
+                element.appendChild(child);
+            }
+        }
+
+        void add(Node node) {
+            children.add(node);
+        }
+
+        void add(String key, Object value) {
+            attributes.put(key, value != null ? value.toString() : "null");
+        }
+
+        int numChildren() {
+            return children.size();
+        }
+
+        Node firstChild() {
+            return children.get(0);
+        }
+    } // class BuildResult
+
+    private static BuildResult recursiveBuild(
+        List     list,
+        Document document
+    ) {
+        BuildResult result = new BuildResult();
+        for (Object entry: list) {
+            if (entry instanceof Map) {
+                BuildResult subResult = recursiveBuild(
+                    (Map<String, Object>)entry, document);
+                if (subResult.numChildren() == 1) {
+                    result.add(subResult.firstChild());
+                }
+                else {
+                    Element element = document.createElement("map");
+                    subResult.finish(element);
+                    result.add(element);
+                }
+            }
+            else if (entry instanceof List) {
+                Element element = document.createElement("list");
+                BuildResult subResult = recursiveBuild((List)entry, document);
+                subResult.finish(element);
+                result.add(element);
+            }
+            else {
+                Element element = document.createElement("entry");
+                element.setAttribute(
+                    "value",
+                    entry != null ? entry.toString() : "null");
+            }
+        }
+        return result;
+    }
+
+    private static BuildResult recursiveBuild(
+        Map<String, Object> map,
+        Document            document
+    ) {
+        BuildResult result = new BuildResult();
+
+        List<Node> nodes = new ArrayList<Node>();
+        for (Map.Entry<String, Object> entry: map.entrySet()) {
+            Object value = entry.getValue();
+            if (value instanceof Map) {
+                Element element = document.createElement(entry.getKey());
+                BuildResult subResult = recursiveBuild(
+                    (Map<String, Object>)value, document);
+                subResult.finish(element);
+                result.add(element);
+            }
+            else if (value instanceof List) {
+                Element element = document.createElement(entry.getKey());
+                BuildResult subResult = recursiveBuild((List)value, document);
+                subResult.finish(element);
+                result.add(element);
+            }
+            else {
+                result.add(entry.getKey(), value);
+            }
+        }
+        return result;
+    }
+
+    public static Document jsonToXML(String input) {
+        Document document = newDocument();
+
+        if (document == null) {
+            return null;
+        }
+
+        Map<String, Object> map;
+        try {
+            map = JSON.parse(input);
+        }
+        catch (IOException ioe) {
+            logger.error(ioe);
+            return null;
+        }
+
+        BuildResult roots = recursiveBuild(map, document);
+
+        int N = roots.children.size();
+
+        if (N == 1) {
+            document.appendChild(roots.children.get(0));
+        }
+        else if (N > 1) {
+            Node root = document.createElement("root");
+            for (int i = 0; i < N; ++i) {
+                root.appendChild(roots.children.get(i));
+            }
+            document.appendChild(root);
+        }
+
+        return document;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XSLTransformer.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,73 @@
+package de.intevation.artifacts.common.utils;
+
+import java.io.InputStream;
+import java.io.StringWriter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+
+import javax.xml.transform.dom.DOMSource;
+
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Node;
+
+public class XSLTransformer {
+
+    private static Logger log = Logger.getLogger(XSLTransformer.class);
+
+    protected Map<String, Object> parameters;
+
+    public XSLTransformer() {
+    }
+
+    public String transform(Node source, InputStream transform) {
+
+        try {
+            Source templateSource = new StreamSource(transform);
+            TransformerFactory xformFactory =
+                TransformerFactory.newInstance();
+            Transformer transformer =
+                xformFactory.newTransformer(templateSource);
+
+            if (parameters != null) {
+                for (Map.Entry<String, Object> entry: parameters.entrySet()) {
+                    transformer.setParameter(entry.getKey(), entry.getValue());
+                }
+            }
+
+            StringWriter result = new StringWriter();
+
+            DOMSource    src = new DOMSource(source);
+            StreamResult dst = new StreamResult(result);
+            transformer.transform(src, dst);
+
+            return result.toString();
+        }
+        catch (TransformerConfigurationException tce) {
+            log.error(tce, tce);
+        }
+        catch (TransformerException te) {
+            log.error(te, te);
+        }
+
+        return null;
+    }
+
+    public void addParameter(String key, Object value) {
+        if (parameters == null) {
+            parameters = new HashMap<String, Object>();
+        }
+        parameters.put(key, value);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/pom.xml	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/pom.xml	Fri Sep 28 12:15:11 2012 +0200
@@ -18,6 +18,15 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.0.2</version>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+        </configuration>
+      </plugin>
+      <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>buildnumber-maven-plugin</artifactId>
         <executions>
--- a/artifacts/src/main/java/de/intevation/artifacts/Artifact.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/Artifact.java	Fri Sep 28 12:15:11 2012 +0200
@@ -34,7 +34,7 @@
  *
  * There are two more methods involved with the life cycle of the are:
  * <ol>
- *   <li>{@link #setup(String, ArtifactFactory, Object, Document) setup()}:
+ *   <li>{@link #setup(String, ArtifactFactory, Object, CallMeta, Document) setup()}:
  *   Called after created by the factory.</li>
  *   <li>{@link #endOfLife(Object) endOfLife()}: Called when the artifact
  *                                               is going to be removed from
@@ -121,6 +121,7 @@
     /**
      * When created by a factory this method is called to
      * initialize the artifact.
+     *
      * @param identifier The identifier from artifact database
      * @param factory    The factory which created this artifact.
      * @param context    The global context of the runtime system.
@@ -131,6 +132,7 @@
         String          identifier,
         ArtifactFactory factory,
         Object          context,
+        CallMeta        callMeta,
         Document        data);
 
     /**
--- a/artifacts/src/main/java/de/intevation/artifacts/ArtifactContextFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/ArtifactContextFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -24,6 +24,6 @@
      *   {@link de.intevation.artifacts.ArtifactFactory#createArtifact(String, Object, Document) createArtifact()}
      *   {@link de.intevation.artifacts.Artifact Artifact}
      */
-    Object createArtifactContext(Document config);
+    GlobalContext createArtifactContext(Document config);
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java	Fri Sep 28 12:15:11 2012 +0200
@@ -13,6 +13,8 @@
 
 import org.w3c.dom.Document;
 
+import java.util.Date;
+
 /**
  * Interface of an artifact managing database.
  *
@@ -73,6 +75,13 @@
     ) throws ArtifactDatabaseException;
 
     /**
+     * Used to retrieve an artifact.<b>NOTE: artifact modifications are not
+     * persisted to database!</b>
+     */
+    Artifact getRawArtifact(String identifier)
+    throws ArtifactDatabaseException;
+
+    /**
      * Returns the describe document of artifact identified
      * with the string 'artifact'.
      * @param artifact The identifier of the artifact.
@@ -198,7 +207,7 @@
      * @throws ArtifactDatabaseException Thrown if someting went wrong during
      * the service processing.
      */
-    Document process(String service, Document input, CallMeta callMeta)
+    Service.Output process(String service, Document input, CallMeta callMeta)
         throws ArtifactDatabaseException;
 
     // User API
@@ -206,6 +215,9 @@
     Document listUsers(CallMeta callMeta)
         throws ArtifactDatabaseException;
 
+    Document findUser(Document data, CallMeta callMeta)
+        throws ArtifactDatabaseException;
+
     Document createUser(Document data, CallMeta callMeta)
         throws ArtifactDatabaseException;
 
@@ -214,6 +226,9 @@
 
     // Collection API
 
+    Document getCollectionsMasterArtifact(String collectionId, CallMeta meta)
+        throws ArtifactDatabaseException;
+
     Document listCollections(String userId, CallMeta callMeta)
         throws ArtifactDatabaseException;
 
@@ -265,5 +280,19 @@
 
     Document setCollectionName(String collectionId, Document doc, CallMeta meta)
     throws ArtifactDatabaseException;
+
+    public interface ArtifactLoadedCallback {
+        void artifactLoaded(
+            String   userId,
+            String   collectionId,
+            String   collectionName,
+            Date     collectionCreated,
+            String   artifactId,
+            Date     artifactCreated,
+            Artifact artifact);
+    };
+
+    public void loadAllArtifacts(ArtifactLoadedCallback callback)
+        throws ArtifactDatabaseException;
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/de/intevation/artifacts/ArtifactFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/ArtifactFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -13,6 +13,7 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 
+
 /**
  * Interface of an artifact producing factory.
  *
@@ -40,7 +41,11 @@
      * @param data  the data containing more details for the setup of an Artifact.
      * @return a new {@linkplain de.intevation.artifacts.Artifact Artifact}
      */
-    Artifact createArtifact(String identifier, Object context, Document data);
+    Artifact createArtifact(
+        String        identifier,
+        GlobalContext context,
+        CallMeta      callMeta,
+        Document      data);
 
     /**
      * Setup the factory with a given configuration
--- a/artifacts/src/main/java/de/intevation/artifacts/ArtifactNamespaceContext.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/ArtifactNamespaceContext.java	Fri Sep 28 12:15:11 2012 +0200
@@ -9,6 +9,9 @@
 package de.intevation.artifacts;
 
 import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
 
 import javax.xml.XMLConstants;
 
@@ -39,10 +42,25 @@
     public static final ArtifactNamespaceContext INSTANCE =
         new ArtifactNamespaceContext();
 
+    protected Map<String, String> map;
+
     /**
      * The default constructor.
      */
     public ArtifactNamespaceContext() {
+        map = new HashMap<String, String>();
+        map.put(
+            XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
+        map.put(
+            XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.DEFAULT_NS_PREFIX);
+        map.put(
+            XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
+        map.put(
+            NAMESPACE_PREFIX, NAMESPACE_URI);
+    }
+
+    public void add(String prefix, String uri) {
+        map.put(prefix, uri);
     }
 
     /**
@@ -50,41 +68,51 @@
      * @param prefix The prefix
      * @return The corresponing URI
      */
+    @Override
     public String getNamespaceURI(String prefix) {
 
         if (prefix == null) {
-            throw new NullPointerException("Null prefix");
+            throw new IllegalArgumentException("Null prefix");
         }
 
-        if (NAMESPACE_PREFIX.equals(prefix)) {
-            return NAMESPACE_URI;
-        }
+        String namespace = map.get(prefix);
 
-        if ("xml".equals(prefix)) {
-            return XMLConstants.XML_NS_URI;
-        }
-
-        return XMLConstants.NULL_NS_URI;
+        return namespace != null ? namespace : XMLConstants.NULL_NS_URI;
     }
 
     /**
      * @see javax.xml.namespace.NamespaceContext#getPrefix(String)
      * @param uri The URI
-     * @return nothing.
-     * @throws java.lang.UnsupportedOperationException
      */
+    @Override
     public String getPrefix(String uri) {
-        throw new UnsupportedOperationException();
+
+        if (uri == null) {
+            throw new IllegalArgumentException("Null uri");
+        }
+
+        for (Map.Entry<String, String> entry: map.entrySet()) {
+            if (entry.getValue().equals(uri)) {
+                return entry.getKey();
+            }
+        }
+
+        return XMLConstants.DEFAULT_NS_PREFIX;
     }
 
     /**
      * @see javax.xml.namespace.NamespaceContext#getPrefixes(java.lang.String)
      * @param uri The URI
-     * @return nothing
-     * @throws java.lang.UnsupportedOperationException
      */
+    @Override
     public Iterator getPrefixes(String uri) {
-        throw new UnsupportedOperationException();
+        ArrayList<String> results = new ArrayList<String>();
+        for (Map.Entry<String, String> entry: map.entrySet()) {
+            if (entry.getValue().equals(uri)) {
+                results.add(entry.getKey());
+            }
+        }
+        return results.iterator();
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/de/intevation/artifacts/CallContext.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/CallContext.java	Fri Sep 28 12:15:11 2012 +0200
@@ -8,6 +8,9 @@
 
 package de.intevation.artifacts;
 
+import java.util.LinkedList;
+import java.util.List;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 
@@ -65,6 +68,26 @@
     void afterBackground(int action);
 
     /**
+     * Returns true, if the object forked a background thread and has not
+     * finished it yet.
+     */
+    boolean isInBackground();
+
+    /**
+     * Adds a background message for the current Artifact or Collection.
+     *
+     * @param msg The message.
+     */
+    void addBackgroundMessage(Message msg);
+
+    /**
+     * Returns the background messages of the current Artifact or Collection.
+     *
+     * @return the list of background messages.
+     */
+    LinkedList<Message> getBackgroundMessages();
+
+    /**
      * Access to the global context of the runtime system.
      * @return The global context.
      */
@@ -87,7 +110,7 @@
      * Each call context has a clipboard.
      * getContextValue is used to fetch data from this board.
      * @param key Key of the requested item.
-     * @return The value stored for the secified value, null if
+     * @return The value stored for the specified value, null if
      *         no item with this key exists.
      */
     Object getContextValue(Object key);
@@ -106,5 +129,17 @@
      * @return The time to live of the current artifact.
      */
     Long getTimeToLive();
+
+    /**
+     * Get a list of DataProvider that get provide 'key' type of data to
+     * other facets.
+     */
+    public List<DataProvider> getDataProvider(Object key);
+
+    /**
+     * Register a DataProvider that can provide 'key' type of data to
+     * other facets.
+     */
+    public Object registerDataProvider(Object key, DataProvider provider);
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/DataProvider.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,21 @@
+package de.intevation.artifacts;
+
+/**
+ * DataProviders register on a Blackboard with a key (basically shouting
+ * "I can or know X!").
+ *
+ * Consumers look at the blackboard and then consume data from these
+ * DataProvider, passing them (optional) parameterization and the blackboard
+ * itself.
+ *
+ * Through the blackboard-passing-when-consuming, also recursive patterns can
+ * be modelled (but take care, there is no in-built cycle detection).
+ */
+public interface DataProvider {
+    /** Register this DataProvider on a blackboard under a key. */
+    public void register(CallContext blackboard);
+
+    /** Provide data, given parameterization and a "blackboard". */
+    public Object provideData(Object key, Object param, CallContext context);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/GlobalContext.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,23 @@
+package de.intevation.artifacts;
+
+public interface GlobalContext {
+
+    /**
+     * Fetch a custom value from the global key/value map using
+     * a given key.
+     * @param key The key.
+     * @return The stored value or null if no value was found under
+     * this key.
+     */
+    Object get(Object key);
+
+    /**
+     * Store a custom key/value pair in the global map.
+     * @param key The key to store
+     * @param value The value to store
+     * @return The old value registered under the key or null
+     * if none wa there before.
+     */
+    Object put(Object key, Object 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/artifacts/src/main/java/de/intevation/artifacts/Hook.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,13 @@
+package de.intevation.artifacts;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+
+public interface Hook {
+
+    void setup(Node config);
+
+    void execute(Artifact artifact, CallContext context, Document document);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/Message.java	Fri Sep 28 12:15:11 2012 +0200
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+package de.intevation.artifacts;
+
+import java.io.Serializable;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Message extends Serializable {
+
+    String getText();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/de/intevation/artifacts/Service.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/Service.java	Fri Sep 28 12:15:11 2012 +0200
@@ -20,14 +20,20 @@
 public interface Service
 extends          Serializable
 {
+    interface Output {
+        Object getData();
+
+        String getMIMEType();
+    } // interface Output
+
     /**
      * Processes some input XML document
      * @param data The input data
      * @param globalContext The global context of the artifact database.
      * @param callMeta The call meta contex, e.g. preferred languages.
-     * @return The result output XML document.
+     * @return The result.
      */
-    Document process(Document data, Object globalContext, CallMeta callMeta);
+    Output process(Document data, GlobalContext globalContext, CallMeta callMeta);
 
     /**
      * Setup the concrete processing service. This is done at startup time
@@ -35,6 +41,6 @@
      * @param factory The service factory which created this service.
      * @param globalContext The global context of the artifact database.
      */
-    void setup(ServiceFactory factory, Object globalContext);
+    void setup(ServiceFactory factory, GlobalContext globalContext);
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/de/intevation/artifacts/ServiceFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/ServiceFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -39,7 +39,7 @@
      * @param globalContext The global context of the artifact database.
      * @return The created service.
      */
-    Service createService(Object globalContext);
+    Service createService(GlobalContext globalContext);
 
     /**
      * Configures this factory. This is called before
--- a/artifacts/src/main/java/de/intevation/artifacts/User.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/User.java	Fri Sep 28 12:15:11 2012 +0200
@@ -25,5 +25,7 @@
     void setRole(Document role);
 
     Document getRole();
+
+    String getAccount();
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/de/intevation/artifacts/UserFactory.java	Tue Jun 28 07:51:17 2011 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/UserFactory.java	Fri Sep 28 12:15:11 2012 +0200
@@ -14,6 +14,7 @@
 {
     void setup(Document config, Node factoryNode);
 
-    User createUser(String identifier, String name, Document role, Object context);
+    User createUser(String identifier, String name, String account,
+                    Document role, Object context);
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/pom.xml	Tue Jun 28 07:51:17 2011 +0000
+++ b/pom.xml	Fri Sep 28 12:15:11 2012 +0200
@@ -27,8 +27,8 @@
         <artifactId>maven-compiler-plugin</artifactId>
         <version>2.0.2</version>
         <configuration>
-          <source>1.5</source>
-          <target>1.5</target>
+          <source>1.6</source>
+          <target>1.6</target>
         </configuration>
       </plugin>
       <plugin>

http://dive4elements.wald.intevation.org