diff artifact-database/src/main/java/org/dive4elements/artifactdatabase/ArtifactDatabaseImpl.java @ 473:d0ac790a6c89 dive4elements-move

Moved directories to org.dive4elements
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 10:57:18 +0200
parents artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java@8d8aed23c323
children 415df0fc4fa1
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/org/dive4elements/artifactdatabase/ArtifactDatabaseImpl.java	Thu Apr 25 10:57:18 2013 +0200
@@ -0,0 +1,1976 @@
+/*
+ * 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.XMLUtils;
+import de.intevation.artifacts.common.utils.StringUtils;
+
+import de.intevation.artifactdatabase.Backend.PersistentArtifact;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactCollection;
+import de.intevation.artifacts.ArtifactCollectionFactory;
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.ArtifactSerializer;
+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;
+import de.intevation.artifacts.UserFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import java.util.Arrays;
+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;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * The core implementation of artifact database. This layer exposes
+ * the needed methods to the artifact runtime system which e.g. may
+ * expose them via REST. The concrete persistent representation of the
+ * artifacts is handled by the {@link Backend backend}.
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
+ */
+public class ArtifactDatabaseImpl
+implements   ArtifactDatabase,
+             DatabaseCleaner.LockedIdsProvider,
+             Backend.FactoryLookup
+{
+    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";
+
+    /** Message that is returned if an operation failed.*/
+    public static final String OPERATION_FAILURE =
+        "FAILURE";
+
+    /**
+     * Error message issued if a requested artifact factory
+     * is not registered to this database.
+     */
+    public static final String NO_SUCH_FACTORY =
+        "No such factory";
+
+    /**
+     * Error message issued if a requested artifact is not found
+     * in this database.
+     */
+    public static final String NO_SUCH_ARTIFACT =
+        "No such artifact";
+
+    /**
+     * Error message issued if a requested artifact is not found
+     * in this database.
+     */
+    public static final String NO_SUCH_COLLECTION =
+        "No such collection";
+
+    /**
+     * Error message issued if the creation of an artifact failed.
+     */
+    public static final String CREATION_FAILED =
+        "Creation of artifact failed";
+
+    /**
+     * Error message if an severe internal error occurred.
+     */
+    public static final String INTERNAL_ERROR =
+        "Creation of artifact failed";
+
+    /**
+     * Error message issued if a requested service is not
+     * offered by this database.
+     */
+    public static final String NO_SUCH_SERVICE =
+        "No such service";
+
+    /**
+     * Default digest hash to be used while im-/exporting artifacts.
+     */
+    public static final String DIGEST_ALGORITHM =
+        "SHA-1";
+
+    /**
+     * XPath to get the checksum from an XML representation of
+     * an exported artifact.
+     */
+    public static final String XPATH_IMPORT_CHECKSUM =
+        "/art:action/art:data/@checksum";
+
+    /**
+     * XPath to get the name of the factory which should be
+     * used to revive an antrifact that is going to be imported.
+     */
+    public static final String XPATH_IMPORT_FACTORY =
+        "/art:action/art:data/@factory";
+
+    /**
+     * XPath to get the base64 encoded data of an artifact
+     * that is going to be imported.
+     */
+    public static final String XPATH_IMPORT_DATA =
+        "/art:action/art:data/text()";
+
+    /**
+     * Error message issued if the checksum of an
+     * artifact to be imported has an invalid syntax.
+     */
+    public static final String INVALID_CHECKSUM =
+        "Invalid checksum";
+
+    /**
+     * Error message issued the checksum validation
+     * of an artifact to be imported fails.
+     */
+    public static final String CHECKSUM_MISMATCH =
+        "Mismatching checksum";
+
+    /**
+     * Error message issued if an artifact to be imported
+     * does not have any data.
+     */
+    public static final String NO_DATA =
+        "No data";
+
+    /**
+     * Error message issued if the deserialization of
+     * an artifact to be imported fails.
+     */
+    public static final String INVALID_ARTIFACT =
+        "Invalid artifact";
+
+
+    // User constants
+
+    /**
+     * Error message issued if the creation of a user failed.
+     */
+    public static final String USER_CREATION_FAILED =
+        "Creation of user failed.";
+
+    /** XPath to figure out the name of a new user.*/
+    public static final String XPATH_USERNAME =
+        "/art:action/art:user/@name";
+
+    /** XPath to figure out the role of a new user.*/
+    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";
+
+    /** Error message if no username is given for user creation.*/
+    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
+
+    /**
+     * Error message issued if the creation of a collection failed.
+     */
+    public static final String COLLECTION_CREATION_FAILED =
+        "Creation of collection failed";
+
+    /**
+     * XPath to figure out the name of a collection described in the incoming
+     * document.
+     */
+    public static final String XPATH_COLLECTION_NAME =
+        "/art:action/art:type/art:collection/@name";
+
+    /**
+     * XPath to figure out the attributes for a collection.
+     */
+    public static final String XPATH_COLLECTION_ATTRIBUTE =
+        "/art:action/art:type/art:collection/art:attribute";
+
+    /**
+     * XPath to figure out the attributes for an artifact that is put into a
+     * collection.
+     */
+    public static final String XPATH_COLLECTION_ITEM_ATTRIBUTE =
+        "/art:action/art:type/art:artifact/art:attribute";
+
+    /**
+     * XPath to figure out the time to live value for setting a new TTL.
+     */
+    public static final String XPATH_COLLECTION_TTL =
+        "/art:action/art:type/art:ttl/@value";
+
+
+    /**
+     * This inner class allows the deferral of writing the output
+     * of the artifact's out() call.
+     */
+    public class DeferredOutputImpl
+    implements   DeferredOutput
+    {
+        /**
+         * The persistence wrapper around a living artifact.
+         */
+        protected PersistentArtifact artifact;
+        /**
+         * The output type.
+         */
+        protected String type;
+        /**
+         * The input document for the artifact's out() call.
+         */
+        protected Document           format;
+        /**
+         * The meta information of the artifact's out() call.
+         */
+        protected CallMeta           callMeta;
+
+        /**
+         * Default constructor.
+         */
+        public DeferredOutputImpl() {
+        }
+
+        /**
+         * Constructor to create a deferred execution unit for
+         * the artifact's out() call given an artifact, an input document
+         * an the meta information.
+         * @param artifact The persistence wrapper around a living artifact.
+         * @param format   The input document for the artifact's out() call.
+         * @param callMeta The meta information of the artifact's out() call.
+         */
+        public DeferredOutputImpl(
+            PersistentArtifact artifact,
+            String             type,
+            Document           format,
+            CallMeta           callMeta
+        ) {
+            this.artifact = artifact;
+            this.type     = type;
+            this.format   = format;
+            this.callMeta = callMeta;
+        }
+
+        public void write(OutputStream output) throws IOException {
+
+            ArtifactCallContext cc = new ArtifactCallContext(
+                ArtifactDatabaseImpl.this,
+                CallContext.TOUCH,
+                callMeta,
+                artifact);
+
+            try {
+                artifact.getArtifact().out(type, format, output, cc);
+            }
+            finally {
+                cc.postCall();
+            }
+        }
+    } // class DeferredOutputImpl
+
+
+    /**
+     * This inner class allows the deferral of writing the output
+     * of the artifact's out() call.
+     */
+    public class DeferredCollectionOutputImpl
+    implements   DeferredOutput
+    {
+        /**
+         * The persistence wrapper around a living collection.
+         */
+        protected ArtifactCollection collection;
+        /**
+         * The output type.
+         */
+        protected String type;
+        /**
+         * The input document for the collection's out() call.
+         */
+        protected Document format;
+        /**
+         * The meta information of the collection's out() call.
+         */
+        protected CallMeta callMeta;
+
+        /**
+         * Default constructor.
+         */
+        public DeferredCollectionOutputImpl() {
+        }
+
+        /**
+         * Constructor to create a deferred execution unit for
+         * the collection's out() call given a collection, an input document
+         * an the meta information.
+         * @param collection The collection.
+         * @param format   The input document for the collection's out() call.
+         * @param callMeta The meta information of the collection's out() call.
+         */
+        public DeferredCollectionOutputImpl(
+            ArtifactCollection collection,
+            String             type,
+            Document           format,
+            CallMeta           callMeta
+        ) {
+            this.collection = collection;
+            this.type       = type;
+            this.format     = format;
+            this.callMeta   = callMeta;
+        }
+
+        public void write(OutputStream output) throws IOException {
+
+            CollectionCallContext cc = new CollectionCallContext(
+                ArtifactDatabaseImpl.this,
+                CallContext.TOUCH,
+                callMeta,
+                collection);
+
+            try {
+                collection.out(type, format, output, cc);
+            }
+            finally {
+                cc.postCall();
+            }
+        }
+    } // class DeferredCollectionOutputImpl
+
+    /**
+     * List of name/description pairs needed for
+     * {@link #artifactFactoryNamesAndDescriptions() }.
+     */
+    protected String [][] factoryNamesAndDescription;
+    /**
+     * Map to access artifact factories by there name.
+     */
+    protected HashMap     name2factory;
+
+    /**
+     * List of name/description pairs needed for
+     * {@link #serviceNamesAndDescriptions() }.
+     */
+    protected String [][] serviceNamesAndDescription;
+    /**
+     * Map to access services by there name.
+     */
+    protected HashMap     name2service;
+
+    /**
+     * The factory that is used to create new artifact collections.
+     */
+    protected ArtifactCollectionFactory collectionFactory;
+
+    /**
+     * The factory that is used to create and list users.
+     */
+    protected UserFactory userFactory;
+
+    /**
+     * Reference to the storage backend.
+     */
+    protected Backend     backend;
+    /**
+     * Reference of the global context of the artifact runtime system.
+     */
+    protected GlobalContext context;
+
+    /**
+     * The signing secret to be used for ex-/importing artifacts.
+     */
+    protected byte []     exportSecret;
+
+    /**
+     * A set of ids of artifact which currently running in background.
+     * This artifacts should not be removed from the database by the
+     * database cleaner.
+     */
+    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() {
+    }
+
+    /**
+     * Constructor to create a artifact database with the given
+     * bootstrap parameters like artifact- and service factories et. al.
+     * Created this way the artifact database has no backend.
+     * @param bootstrap The parameters to start this artifact database.
+     */
+    public ArtifactDatabaseImpl(FactoryBootstrap bootstrap) {
+        this(bootstrap, null);
+    }
+
+    /**
+     * Constructor to create a artifact database with the a given
+     * backend and
+     * bootstrap parameters like artifact- and service factories et. al.
+     * @param bootstrap The parameters to start this artifact database.
+     * @param backend   The storage backend.
+     */
+    public ArtifactDatabaseImpl(FactoryBootstrap bootstrap, Backend backend) {
+
+        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.put(GLOBAL_CONTEXT_KEY, this);
+
+        exportSecret = bootstrap.getExportSecret();
+
+        wireWithBackend(backend, bootstrap);
+    }
+
+    public CallContext.Listener getCallContextListener() {
+        return callContextListener;
+    }
+
+    public void setCallContextListener(
+        CallContext.Listener callContextListener
+    ) {
+        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.
+     *
+     * @param bootstrap The bootstrap parameters.
+     */
+    protected void setupArtifactCollectionFactory(FactoryBootstrap bootstrap) {
+        collectionFactory = bootstrap.getArtifactCollectionFactory();
+    }
+
+    /**
+     * Used to extract the artifact factories from the bootstrap
+     * parameters and building the internal lookup tables.
+     * @param bootstrap The bootstrap parameters.
+     */
+    protected void setupArtifactFactories(FactoryBootstrap bootstrap) {
+        name2factory  = new HashMap();
+
+        ArtifactFactory [] factories = bootstrap.getArtifactFactories();
+        factoryNamesAndDescription = new String[factories.length][];
+
+        for (int i = 0; i < factories.length; ++i) {
+
+            ArtifactFactory factory = factories[i];
+
+            String name        = factory.getName();
+            String description = factory.getDescription();
+
+            factoryNamesAndDescription[i] =
+                new String [] { name, description };
+
+            name2factory.put(name, factory);
+        }
+    }
+
+    /**
+     * Used to extract the callContextListener from the bootstrap.
+     *
+     * @param bootstrap The bootstrap parameters.
+     */
+    protected void setupCallContextListener(FactoryBootstrap bootstrap) {
+        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.
+     */
+    protected void setupUserFactory(FactoryBootstrap bootstrap) {
+        userFactory = bootstrap.getUserFactory();
+    }
+
+    /**
+     * Used to extract the service factories from the bootstrap
+     * parameters, setting up the services and building the internal
+     * lookup tables.
+     * @param bootstrap The bootstrap parameters.
+     */
+    protected void setupServices(FactoryBootstrap bootstrap) {
+
+        name2service  = new HashMap();
+
+        ServiceFactory [] serviceFactories =
+            bootstrap.getServiceFactories();
+
+        serviceNamesAndDescription =
+            new String[serviceFactories.length][];
+
+        for (int i = 0; i < serviceFactories.length; ++i) {
+            ServiceFactory factory = serviceFactories[i];
+
+            String name        = factory.getName();
+            String description = factory.getDescription();
+
+            serviceNamesAndDescription[i] =
+                new String [] { name, description };
+
+            name2service.put(
+                name,
+                factory.createService(bootstrap.getContext()));
+        }
+
+    }
+
+    /**
+     * Wires a storage backend to this artifact database and
+     * establishes a callback to be able to revive artifacts
+     * via the serializers of this artifact factories.
+     * @param backend The backend to be wired with this artifact database.
+     */
+    public void wireWithBackend(Backend backend, FactoryBootstrap bootstrap) {
+        logger.debug("wireWithBackend");
+        if (backend != null) {
+            this.backend = backend;
+            backend.setFactoryLookup(this);
+            setupBackendListeners(bootstrap);
+        }
+    }
+
+    /**
+     * Called after an backgrounded artifact signals its
+     * will to be written back to the backend.
+     * @param artifact The persistence wrapper around
+     * the backgrounded artifact.
+     * @param action The action to be performed.
+     */
+    protected void fromBackground(PersistentArtifact artifact, int action) {
+        logger.warn("BACKGROUND processing is not fully implemented, yet!");
+        switch (action) {
+            case CallContext.NOTHING:
+                break;
+            case CallContext.TOUCH:
+                artifact.touch();
+                break;
+            case CallContext.STORE:
+                artifact.store();
+                break;
+            default:
+                logger.warn("operation not allowed in fromBackground");
+        }
+        removeIdFromBackground(artifact.getId());
+        removeBackgroundMessages(artifact.getArtifact().identifier());
+    }
+
+    /**
+     * Removes an artifact's database id from the set of backgrounded
+     * artifacts. The database cleaner is now able to remove it safely
+     * from the database again.
+     * @param id The database id of the artifact.
+     */
+    protected void removeIdFromBackground(int id) {
+        synchronized (backgroundIds) {
+            backgroundIds.remove(id);
+        }
+    }
+
+
+    /**
+     * 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
+     * artifact to be removed from the database by the database cleaner.
+     * @param id The database id of the artifact to be protected
+     * from being removed from the database.
+     */
+    protected void addIdToBackground(int id) {
+        synchronized (backgroundIds) {
+            backgroundIds.add(Integer.valueOf(id));
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    public ArtifactFactory getInternalArtifactFactory(String factoryName) {
+        return getArtifactFactory(factoryName);
+    }
+
+    public ArtifactFactory getArtifactFactory(String factoryName) {
+        return (ArtifactFactory)name2factory.get(factoryName);
+    }
+
+    public UserFactory getUserFactory() {
+        return userFactory;
+    }
+
+    public ArtifactCollectionFactory getArtifactCollectionFactory() {
+        return collectionFactory;
+    }
+
+    public Document createArtifactWithFactory(
+        String   factoryName,
+        CallMeta callMeta,
+        Document data
+    )
+    throws ArtifactDatabaseException
+    {
+        logger.debug("ArtifactDatabaseImpl.createArtifactWithFactory "
+             + factoryName);
+        ArtifactFactory factory = getArtifactFactory(factoryName);
+
+        if (factory == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        Artifact artifact = factory.createArtifact(
+            backend.newIdentifier(),
+            context,
+            callMeta,
+            data);
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(CREATION_FAILED);
+        }
+
+        PersistentArtifact persistentArtifact;
+
+        try {
+            persistentArtifact = backend.storeInitially(
+                artifact,
+                factory,
+                factory.timeToLiveUntouched(artifact, context));
+        }
+        catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new ArtifactDatabaseException(CREATION_FAILED);
+        }
+
+        ArtifactCallContext cc = new ArtifactCallContext(
+            ArtifactDatabaseImpl.this,
+            CallContext.NOTHING,
+            callMeta,
+            persistentArtifact);
+
+        try {
+            return artifact.describe(null, cc);
+        }
+        finally {
+            cc.postCall();
+        }
+    }
+
+
+    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,
+        CallMeta callMeta
+    )
+    throws ArtifactDatabaseException
+    {
+        // TODO: Handle background tasks
+        PersistentArtifact artifact = backend.getArtifact(identifier);
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT);
+        }
+
+        ArtifactCallContext cc = new ArtifactCallContext(
+            ArtifactDatabaseImpl.this,
+            CallContext.TOUCH,
+            callMeta,
+            artifact);
+
+        try {
+            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();
+        }
+    }
+
+    public Document advance(
+        String   identifier,
+        Document target,
+        CallMeta callMeta
+    )
+    throws ArtifactDatabaseException
+    {
+        // TODO: Handle background tasks
+        PersistentArtifact artifact = backend.getArtifact(identifier);
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT);
+        }
+
+        ArtifactCallContext cc = new ArtifactCallContext(
+            ArtifactDatabaseImpl.this,
+            CallContext.STORE,
+            callMeta,
+            artifact);
+
+        try {
+            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();
+        }
+    }
+
+    public Document feed(String identifier, Document data, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        // TODO: Handle background tasks
+        PersistentArtifact artifact = backend.getArtifact(identifier);
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT);
+        }
+
+        ArtifactCallContext cc = new ArtifactCallContext(
+            ArtifactDatabaseImpl.this,
+            CallContext.STORE,
+            callMeta,
+            artifact);
+
+        try {
+            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();
+        }
+    }
+
+    public DeferredOutput out(
+        String   identifier,
+        Document format,
+        CallMeta callMeta)
+    throws ArtifactDatabaseException
+    {
+        return out(identifier, null, format, callMeta);
+    }
+
+    public DeferredOutput out(
+        String   identifier,
+        String   type,
+        Document format,
+        CallMeta callMeta
+    )
+    throws ArtifactDatabaseException
+    {
+        // TODO: Handle background tasks
+        PersistentArtifact artifact = backend.getArtifact(identifier);
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT);
+        }
+
+        return new DeferredOutputImpl(artifact, type, format, callMeta);
+    }
+
+    public Document exportArtifact(String artifact, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        final String [] factoryName = new String[1];
+
+        byte [] bytes = (byte [])backend.loadArtifact(
+            artifact,
+            new Backend.ArtifactLoader() {
+                public Object load(
+                    ArtifactFactory factory,
+                    Long            ttl,
+                    byte []         bytes,
+                    int             id
+                ) {
+                    factoryName[0] = factory.getName();
+
+                    ArtifactSerializer serializer = factory.getSerializer();
+
+                    Artifact artifact = serializer.fromBytes(bytes);
+                    artifact.cleanup(context);
+
+                    return serializer.toBytes(artifact);
+                }
+            });
+
+        if (bytes == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT);
+        }
+
+        return createExportDocument(
+            factoryName[0],
+            bytes,
+            exportSecret);
+    }
+
+    /**
+     * Creates an exteral XML representation of an artifact.
+     * @param factoryName The name of the factory which is responsible
+     * for the serialized artifact.
+     * @param artifact The byte data of the artifact itself.
+     * @param secret   The signing secret.
+     * @return An XML document containing the external representation
+     * of the artifact.
+     */
+    protected static Document createExportDocument(
+        String  factoryName,
+        byte [] artifact,
+        byte [] secret
+    ) {
+        Document document = XMLUtils.newDocument();
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance(DIGEST_ALGORITHM);
+        }
+        catch (NoSuchAlgorithmException nsae) {
+            logger.error(nsae.getLocalizedMessage(), nsae);
+            return document;
+        }
+
+        md.update(artifact);
+        md.update(secret);
+
+        String checksum = Hex.encodeHexString(md.digest());
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            document,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("action");
+        document.appendChild(root);
+
+        Element type = ec.create("type");
+        ec.addAttr(type, "name", "export", true);
+        root.appendChild(type);
+
+        Element data = ec.create("data");
+        ec.addAttr(data, "checksum", checksum, true);
+        ec.addAttr(data, "factory",  factoryName, true);
+        data.setTextContent(Base64.encodeBase64String(artifact));
+
+        root.appendChild(data);
+
+        return document;
+    }
+
+    public Document importArtifact(Document input, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        String factoryName = XMLUtils.xpathString(
+            input,
+            XPATH_IMPORT_FACTORY,
+            ArtifactNamespaceContext.INSTANCE);
+
+        ArtifactFactory factory;
+
+        if (factoryName == null
+        || (factoryName = factoryName.trim()).length() == 0
+        || (factory = getArtifactFactory(factoryName)) == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        String checksumString = XMLUtils.xpathString(
+            input,
+            XPATH_IMPORT_CHECKSUM,
+            ArtifactNamespaceContext.INSTANCE);
+
+        byte [] checksum;
+
+        if (checksumString == null
+        || (checksumString = checksumString.trim()).length() == 0
+        || (checksum = StringUtils.decodeHex(checksumString)) == null
+        ) {
+            throw new ArtifactDatabaseException(INVALID_CHECKSUM);
+        }
+
+        checksumString = null;
+
+        String dataString = XMLUtils.xpathString(
+            input,
+            XPATH_IMPORT_DATA,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (dataString == null
+        || (dataString = dataString.trim()).length() == 0) {
+            throw new ArtifactDatabaseException(NO_DATA);
+        }
+
+        byte [] data = Base64.decodeBase64(dataString);
+
+        dataString = null;
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance(DIGEST_ALGORITHM);
+        }
+        catch (NoSuchAlgorithmException nsae) {
+            logger.error(nsae.getLocalizedMessage(), nsae);
+            return XMLUtils.newDocument();
+        }
+
+        md.update(data);
+        md.update(exportSecret);
+
+        byte [] digest = md.digest();
+
+        if (!Arrays.equals(checksum, digest)) {
+            throw new ArtifactDatabaseException(CHECKSUM_MISMATCH);
+        }
+
+        ArtifactSerializer serializer = factory.getSerializer();
+
+        Artifact artifact = serializer.fromBytes(data); data = null;
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(INVALID_ARTIFACT);
+        }
+
+        artifact.setIdentifier(backend.newIdentifier());
+        PersistentArtifact persistentArtifact;
+
+        try {
+            persistentArtifact = backend.storeOrReplace(
+                artifact,
+                factory,
+                factory.timeToLiveUntouched(artifact, context));
+        }
+        catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new ArtifactDatabaseException(CREATION_FAILED);
+        }
+
+        ArtifactCallContext cc = new ArtifactCallContext(
+            ArtifactDatabaseImpl.this,
+            CallContext.NOTHING,
+            callMeta,
+            persistentArtifact);
+
+        try {
+            return artifact.describe(input, cc);
+        }
+        finally {
+            cc.postCall();
+        }
+    }
+
+    public String [][] serviceNamesAndDescriptions() {
+        return serviceNamesAndDescription;
+    }
+
+    public Service.Output process(
+        String   serviceName,
+        Document input,
+        CallMeta callMeta
+    )
+    throws ArtifactDatabaseException
+    {
+        Service service = (Service)name2service.get(serviceName);
+
+        if (service == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_SERVICE);
+        }
+
+        return service.process(input, context, callMeta);
+    }
+
+    // User API
+
+    /** Returns user(s) elements. */
+    public Document listUsers(CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        UserFactory factory = getUserFactory();
+
+        if (factory == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        User [] users = backend.getUsers(factory, context);
+
+        if (users != null) {
+            logger.debug(users.length + " users found in the backend.");
+        }
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("users");
+        result.appendChild(root);
+
+        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));
+            }
+        }
+
+        result.appendChild(ue);
+
+        return result;
+    }
+
+    public Document createUser(Document data, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        UserFactory factory = getUserFactory();
+
+        if (factory == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        String name = XMLUtils.xpathString(
+            data, XPATH_USERNAME, ArtifactNamespaceContext.INSTANCE);
+
+        if (name == null || name.length() == 0) {
+            logger.warn("User without username not accepted!");
+            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,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        Document role = XMLUtils.newDocument();
+
+        if (tmp != null) {
+            Node    clone = role.importNode(tmp, true);
+            role.appendChild(clone);
+        }
+
+        User newUser = null;
+
+        try {
+            newUser = backend.createUser(name, account, role, userFactory, context);
+        }
+        catch (Exception e) {
+            logger.error(e.getMessage(), e);
+            throw new ArtifactDatabaseException(USER_CREATION_FAILED);
+        }
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+
+        if (newUser != null) {
+            root.setTextContent(OPERATION_SUCCESSFUL);
+        }
+        else {
+            root.setTextContent(OPERATION_FAILURE);
+        }
+
+        result.appendChild(root);
+
+        return result;
+    }
+
+    public Document deleteUser(String userId, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        logger.debug("Delete user: " + userId);
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        boolean success = backend.deleteUser(userId);
+
+        root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE);
+
+        return result;
+    }
+
+
+    // 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
+    {
+        ArtifactCollectionFactory acf = getArtifactCollectionFactory();
+        UserFactory               uf  = getUserFactory();
+
+        if (acf == null || uf == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        logger.debug("Fetch the list of collection for user: " + userId);
+
+        ArtifactCollection [] ac = backend.listCollections(
+            userId,
+            null, // XXX: fetch from REST
+            acf, uf,
+            context);
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("artifact-collections");
+        result.appendChild(root);
+
+        if (ac == null || ac.length == 0) {
+            logger.debug("No collections for the user existing.");
+
+            return result;
+        }
+
+        logger.debug("Found " + ac.length + " collections of the user.");
+
+        for (ArtifactCollection c: ac) {
+            Element collection = ec.create("artifact-collection");
+            ec.addAttr(collection, "name", c.getName(), true);
+            ec.addAttr(collection, "uuid", c.identifier(), true);
+            ec.addAttr(collection, "ttl",  String.valueOf(c.getTTL()), true);
+
+            Date creationTime = c.getCreationTime();
+            String creation   = creationTime != null
+                ? Long.toString(creationTime.getTime())
+                : "";
+
+            ec.addAttr(collection, "creation", creation,  true);
+
+            root.appendChild(collection);
+        }
+
+        return result;
+    }
+
+    public Document createCollection(String ownerId, Document data,
+        CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        ArtifactCollectionFactory acf = getArtifactCollectionFactory();
+
+        if (acf == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        String name = XMLUtils.xpathString(
+            data, XPATH_COLLECTION_NAME, ArtifactNamespaceContext.INSTANCE);
+
+        logger.debug("Create new collection with name: " + name);
+
+        Document attr = null;
+
+        Node attrNode = (Node) XMLUtils.xpath(
+            data,
+            XPATH_COLLECTION_ATTRIBUTE,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (attrNode != null) {
+            attr = XMLUtils.newDocument();
+            attr.appendChild(attr.importNode(attrNode, true));
+        }
+
+        ArtifactCollection ac = backend.createCollection(
+            ownerId, name, acf, attr, context);
+
+        if (ac == null) {
+            throw new ArtifactDatabaseException(COLLECTION_CREATION_FAILED);
+        }
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        Element acElement = ec.create("artifact-collection");
+        ec.addAttr(acElement, "uuid", ac.identifier(), true);
+        ec.addAttr(acElement, "ttl", String.valueOf(ac.getTTL()), true);
+
+        root.appendChild(acElement);
+
+        return result;
+    }
+
+    public Document deleteCollection(String collectionId, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        logger.debug("Delete collection: " + collectionId);
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        boolean success = backend.deleteCollection(collectionId);
+
+        root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE);
+
+        return result;
+    }
+
+    public Document describeCollection(String collectionId, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        logger.debug("Describe collection: " + collectionId);
+        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);
+        }
+
+        CollectionCallContext cc = new CollectionCallContext(
+            ArtifactDatabaseImpl.this,
+            CallContext.NOTHING,
+            callMeta,
+            c);
+
+        try {
+            return c.describe(cc);
+        }
+        finally {
+            cc.postCall();
+        }
+    }
+
+
+    public Document getCollectionAttribute(String collectionId, CallMeta meta)
+    throws ArtifactDatabaseException
+    {
+        logger.debug("Fetch collection attribute for: " + collectionId);
+
+        return backend.getCollectionAttribute(collectionId);
+    }
+
+
+    public Document setCollectionAttribute(
+        String   collectionId,
+        CallMeta meta,
+        Document attribute)
+    throws ArtifactDatabaseException
+    {
+        logger.debug("Set new attribute for the collection: " + collectionId);
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        boolean success = backend.setCollectionAttribute(
+            collectionId, attribute);
+
+        root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE);
+
+        return result;
+    }
+
+    public Document getCollectionItemAttribute(String collectionId, String artifactId,
+        CallMeta callMeta) throws ArtifactDatabaseException
+    {
+        logger.debug("Fetch the attribute for the artifact: " + artifactId);
+
+        return backend.getCollectionItemAttribute(collectionId, artifactId);
+    }
+
+    public Document setCollectionItemAttribute(String collectionId, String artifactId,
+        Document source, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        logger.debug("Set the attribute for the artifact: " + artifactId);
+
+        Document attribute = null;
+
+        Node attr = (Node) XMLUtils.xpath(
+            source,
+            XPATH_COLLECTION_ITEM_ATTRIBUTE,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (attr != null) {
+            attribute = XMLUtils.newDocument();
+            attribute.appendChild(attribute.importNode(attr, true));
+        }
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        boolean success = backend.setCollectionItemAttribute(
+            collectionId, artifactId, attribute);
+
+        root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE);
+
+        return result;
+    }
+
+    public Document addCollectionArtifact(
+        String   collectionId,
+        String   artifactId,
+        Document input,
+        CallMeta callMeta)
+    throws ArtifactDatabaseException
+    {
+        logger.debug(
+            "Add artifact '" + artifactId + "' collection '" +collectionId+"'");
+
+        Document attr = XMLUtils.newDocument();
+
+        Node attrNode = (Node) XMLUtils.xpath(
+            input,
+            XPATH_COLLECTION_ITEM_ATTRIBUTE,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (attrNode != null) {
+            attr.appendChild(attr.importNode(attrNode, true));
+        }
+
+        boolean success = backend.addCollectionArtifact(
+            collectionId,
+            artifactId,
+            attr);
+
+        if (!success) {
+            Document result = XMLUtils.newDocument();
+
+            XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+                result,
+                ArtifactNamespaceContext.NAMESPACE_URI,
+                ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+            Element root = ec.create("result");
+            result.appendChild(root);
+
+            root.setTextContent(OPERATION_FAILURE);
+
+            return result;
+        }
+
+        return describeCollection(collectionId, callMeta);
+    }
+
+    public Document removeCollectionArtifact(String collectionId, String artifactId,
+        CallMeta callMeta) throws ArtifactDatabaseException
+    {
+        logger.debug(
+            "Remove artifact '" + artifactId + "' from collection '" +
+            collectionId + "'");
+
+        Document attr = XMLUtils.newDocument();
+
+        boolean success = backend.removeCollectionArtifact(
+            collectionId,
+            artifactId);
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE);
+
+        return result;
+    }
+
+    public Document listCollectionArtifacts(String collectionId,
+        CallMeta callMeta) throws ArtifactDatabaseException
+    {
+        CollectionItem[] items = backend.listCollectionArtifacts(collectionId);
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        Element ac   = ec.create("artifact-collection");
+        ec.addAttr(ac, "uuid", collectionId, true);
+
+        for (CollectionItem item: items) {
+            Element i    = ec.create("collection-item");
+            Element attr = ec.create("attribute");
+            ec.addAttr(i, "uuid", item.getArtifactIdentifier(), true);
+
+            Document attribute = item.getAttribute();
+            if (attribute != null) {
+                Node firstChild = attribute.getFirstChild();
+                attr.appendChild(result.importNode(firstChild, true));
+            }
+            else {
+                logger.debug("No attributes for the collection item!");
+            }
+
+            i.appendChild(attr);
+            ac.appendChild(i);
+        }
+
+        root.appendChild(ac);
+        result.appendChild(root);
+
+        return result;
+    }
+
+    public Document setCollectionTTL(String uuid, Document doc, CallMeta meta)
+    throws ArtifactDatabaseException
+    {
+        Document result            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        String tmp = XMLUtils.xpathString(
+            doc, XPATH_COLLECTION_TTL, ArtifactNamespaceContext.INSTANCE);
+
+        logger.info("Set TTL of artifact collection '" + uuid + "' to: " + tmp);
+
+        if (tmp == null || tmp.length() == 0) {
+            logger.warn("No ttl for this collection specified.");
+            root.setTextContent(OPERATION_FAILURE);
+
+            return result;
+        }
+
+        Long ttl = null;
+        if ((tmp = tmp.toUpperCase()).equals("INF")) {
+            ttl = null;
+        }
+        else if (tmp.equals("DEFAULT")) {
+            ArtifactCollectionFactory acf = getArtifactCollectionFactory();
+            ttl = acf.timeToLiveUntouched(null, context);
+        }
+        else {
+            try {
+                ttl = Long.valueOf(tmp);
+
+                if (ttl < 0) {
+                    throw new NumberFormatException("Negative value.");
+                }
+            }
+            catch (NumberFormatException nfe) {
+                logger.error("Could not determine TTL", nfe);
+                root.setTextContent(OPERATION_FAILURE);
+                return result;
+            }
+        }
+
+        boolean success = backend.setCollectionTTL(uuid, ttl);
+        root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE);
+
+        return result;
+    }
+
+
+    public Document setCollectionName(String uuid, Document doc, CallMeta meta)
+    throws ArtifactDatabaseException
+    {
+        Document result            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        result.appendChild(root);
+
+        String name = XMLUtils.xpathString(
+            doc, XPATH_COLLECTION_NAME, ArtifactNamespaceContext.INSTANCE);
+
+        logger.info("Set name of collection '" + uuid + "' to: " + name);
+
+        if (name == null || name.length() == 0) {
+            logger.warn("The new name is emtpy. No new name set!");
+            root.setTextContent(OPERATION_FAILURE);
+            return result;
+        }
+
+        boolean success = backend.setCollectionName(uuid, name);
+        root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE);
+
+        return result;
+    }
+
+
+    public DeferredOutput outCollection(
+        String   collectionId,
+        Document format,
+        CallMeta callMeta)
+    throws ArtifactDatabaseException
+    {
+        return outCollection(collectionId, null, format, callMeta);
+    }
+
+    public DeferredOutput outCollection(
+        String   collectionId,
+        String   type,
+        Document format,
+        CallMeta callMeta)
+    throws ArtifactDatabaseException
+    {
+        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);
+        }
+
+        return new DeferredCollectionOutputImpl(c, type, format, callMeta);
+    }
+
+    protected void initCallContext(CallContext cc) {
+        logger.debug("initCallContext");
+        if (callContextListener != null) {
+            callContextListener.init(cc);
+        }
+    }
+
+    protected void closeCallContext(CallContext cc) {
+        logger.debug("closeCallContext");
+        if (callContextListener != null) {
+            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 :

http://dive4elements.wald.intevation.org