ingo@100: /*
sascha@182:  * Copyright (c) 2010, 2011 by Intevation GmbH
ingo@100:  *
ingo@100:  * This program is free software under the LGPL (>=v2.1)
ingo@100:  * Read the file LGPL.txt coming with the software for details
ingo@100:  * or visit http://www.gnu.org/licenses/ if it does not exist.
ingo@100:  */
sascha@13: package de.intevation.artifactdatabase;
sascha@13: 
ingo@158: import de.intevation.artifacts.Artifact;
sascha@157: import de.intevation.artifacts.ArtifactCollection;
ingo@158: import de.intevation.artifacts.ArtifactCollectionFactory;
sascha@41: import de.intevation.artifacts.ArtifactFactory;
sascha@41: import de.intevation.artifacts.ArtifactSerializer;
ingo@186: import de.intevation.artifacts.CollectionItem;
sascha@133: import de.intevation.artifacts.User;
sascha@157: import de.intevation.artifacts.UserFactory;
sascha@13: 
sascha@138: import de.intevation.artifacts.common.utils.XMLUtils;
sascha@138: 
sascha@93: import java.sql.SQLException;
sascha@170: import java.sql.Timestamp;
sascha@93: import java.sql.Types;
sascha@93: 
sascha@148: import java.util.ArrayList;
sascha@170: import java.util.Date;
sascha@235: import java.util.HashMap;
sascha@148: 
sascha@17: import org.apache.log4j.Logger;
sascha@17: 
sascha@133: import org.w3c.dom.Document;
sascha@133: 
sascha@13: /**
sascha@90:  * The backend implements the low level layer used to store artifacts
sascha@90:  * in a SQL database.
sascha@90:  *
ingo@79:  * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
sascha@13:  */
sascha@13: public class Backend
sascha@41: implements   DatabaseCleaner.ArtifactReviver
sascha@13: {
sascha@17:     private static Logger logger = Logger.getLogger(Backend.class);
sascha@17: 
sascha@90:     /**
sascha@90:      * The SQL statement to create new artifact id inside the database.
sascha@90:      */
sascha@14:     public static final String SQL_NEXT_ID =
sascha@14:         SQL.get("artifacts.id.nextval");
sascha@14: 
sascha@90:     /**
sascha@90:      * The SQL statement to insert an artifact into the database.
sascha@90:      */
sascha@14:     public static final String SQL_INSERT =
sascha@14:         SQL.get("artifacts.insert");
sascha@14: 
sascha@90:     /**
sascha@90:      * The SQL statement to update some columns of an existing
sascha@90:      * artifact in the database.
sascha@90:      */
sascha@14:     public static final String SQL_UPDATE =
sascha@14:         SQL.get("artifacts.update");
sascha@14: 
sascha@90:     /**
sascha@90:      * The SQL statement to touch the access time of an
sascha@90:      * artifact inside the database.
sascha@90:      */
sascha@14:     public static final String SQL_TOUCH =
sascha@14:         SQL.get("artifacts.touch");
sascha@14: 
sascha@90:     /**
sascha@90:      * The SQL statement to load an artifact by a given
sascha@90:      * identifier from the database.
sascha@90:      */
sascha@15:     public static final String SQL_LOAD_BY_GID =
sascha@15:         SQL.get("artifacts.select.gid");
sascha@15: 
sascha@90:     /**
sascha@90:      * The SQL statement to get the database id of an artifact
sascha@90:      * identified by the identifier.
sascha@90:      */
ingo@80:     public static final String SQL_GET_ID =
ingo@80:         SQL.get("artifacts.get.id");
ingo@80: 
sascha@90:     /**
sascha@90:      * The SQL statement to replace the content of an
sascha@90:      * existing artifact inside the database.
sascha@90:      */
ingo@80:     public static final String SQL_REPLACE =
ingo@80:         SQL.get("artifacts.replace");
ingo@80: 
sascha@133:     // USER SQL
sascha@133: 
sascha@133:     public static final String SQL_USERS_NEXT_ID =
sascha@133:         SQL.get("users.id.nextval");
sascha@133: 
sascha@133:     public static final String SQL_USERS_INSERT =
sascha@133:         SQL.get("users.insert");
sascha@133: 
sascha@144:     public static final String SQL_USERS_SELECT_ID_BY_GID =
sascha@144:         SQL.get("users.select.id.by.gid");
sascha@144: 
sascha@133:     public static final String SQL_USERS_SELECT_GID =
sascha@133:         SQL.get("users.select.gid");
sascha@133: 
sascha@133:     public static final String SQL_USERS_DELETE_ID =
sascha@133:         SQL.get("users.delete.id");
sascha@133: 
sascha@133:     public static final String SQL_USERS_DELETE_COLLECTIONS =
sascha@133:         SQL.get("users.delete.collections");
sascha@133: 
sascha@148:     public static final String SQL_USERS_SELECT_ALL =
sascha@133:         SQL.get("users.select.all");
sascha@133: 
sascha@144:     public static final String SQL_USERS_COLLECTIONS =
sascha@144:         SQL.get("users.collections");
sascha@144: 
sascha@144:     public static final String SQL_USERS_COLLECTION_IDS =
sascha@144:         SQL.get("users.collection.ids");
sascha@144: 
sascha@144:     public static final String SQL_USERS_DELETE_ALL_COLLECTIONS =
sascha@144:         SQL.get("users.delete.all.collections");
sascha@144: 
sascha@144:     public static final String SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY =
sascha@144:         SQL.get("artifacts.in.one.collection.only");
sascha@144: 
sascha@144:     public static final String SQL_OUTDATE_ARTIFACTS_COLLECTION =
sascha@144:         SQL.get("outdate.artifacts.collection");
sascha@144: 
ingo@273:     public static final String SQL_UPDATE_COLLECTION_TTL =
ingo@273:         SQL.get("collections.update.ttl");
ingo@273: 
ingo@275:     public static final String SQL_UPDATE_COLLECTION_NAME =
ingo@275:         SQL.get("collections.update.name");
ingo@275: 
sascha@144:     public static final String SQL_OUTDATE_ARTIFACTS_USER =
sascha@144:         SQL.get("outdate.artifacts.user");
sascha@144: 
sascha@144:     public static final String SQL_DELETE_USER_COLLECTION_ITEMS =
sascha@144:         SQL.get("delete.user.collection.items");
sascha@144: 
sascha@159:     public static final String SQL_COLLECTIONS_NEXT_ID =
sascha@159:         SQL.get("collections.id.nextval");
sascha@159: 
sascha@159:     public static final String SQL_COLLECTIONS_INSERT =
sascha@159:         SQL.get("collections.insert");
sascha@159: 
sascha@167:     public static final String SQL_COLLECTIONS_SELECT_USER =
sascha@167:         SQL.get("collections.select.user");
sascha@167: 
sascha@167:     public static final String SQL_COLLECTIONS_SELECT_ALL =
sascha@167:         SQL.get("collections.select.all");
sascha@167: 
ingo@217:     public static final String SQL_COLLECTIONS_SELECT_GID =
ingo@217:         SQL.get("collections.select.by.gid");
ingo@217: 
sascha@170:     public static final String SQL_COLLECTIONS_CREATION_TIME =
sascha@170:         SQL.get("collection.creation.time");
sascha@170: 
sascha@175:     public static final String SQL_COLLECTIONS_ID_BY_GID =
ingo@187:         SQL.get("collections.id.by.gid");
sascha@175: 
sascha@175:     public static final String SQL_DELETE_COLLECTION_ITEMS =
sascha@175:         SQL.get("delete.collection.items");
sascha@175: 
sascha@175:     public static final String SQL_DELETE_COLLECTION =
sascha@175:         SQL.get("delete.collection");
sascha@175: 
sascha@176:     public static final String SQL_COLLECTION_CHECK_ARTIFACT =
sascha@176:         SQL.get("collection.check.artifact");
sascha@176: 
sascha@176:     public static final String SQL_COLLECTION_ITEMS_ID_NEXTVAL =
ingo@187:         SQL.get("collection.items.id.nextval");
sascha@176: 
sascha@176:     public static final String SQL_COLLECTION_ITEMS_INSERT =
sascha@176:         SQL.get("collection.items.insert");
sascha@176: 
ingo@253:     public static final String SQL_COLLECTION_GET_ATTRIBUTE =
ingo@253:         SQL.get("collection.get.attribute");
ingo@253: 
ingo@253:     public static final String SQL_COLLECTION_SET_ATTRIBUTE =
ingo@253:         SQL.get("collection.set.attribute");
ingo@253: 
sascha@178:     public static final String SQL_COLLECTION_ITEM_GET_ATTRIBUTE =
sascha@178:         SQL.get("collection.item.get.attribute");
sascha@178: 
sascha@179:     public static final String SQL_COLLECTION_ITEM_SET_ATTRIBUTE =
sascha@179:         SQL.get("collection.item.set.attribute");
sascha@179: 
sascha@179:     public static final String SQL_COLLECTIONS_TOUCH_BY_GID =
sascha@180:         SQL.get("collections.touch.by.gid");
sascha@180: 
sascha@180:     public static final String SQL_COLLECTION_ITEM_ID_CID_AID =
sascha@180:         SQL.get("collection.item.id.cid.aid");
sascha@180: 
sascha@180:     public static final String SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT =
sascha@180:         SQL.get("collection.item.outdate.artifact");
sascha@180: 
sascha@180:     public static final String SQL_COLLECTION_ITEM_DELETE =
sascha@180:         SQL.get("collection.item.delete");
sascha@180: 
sascha@180:     public static final String SQL_COLLECTIONS_TOUCH_BY_ID =
sascha@180:         SQL.get("collections.touch.by.id");
sascha@179: 
sascha@184:     public static final String SQL_COLLECTION_ITEMS_LIST_GID =
sascha@184:         SQL.get("collection.items.list.gid");
sascha@184: 
ingo@128:     /** The singleton.*/
ingo@128:     protected static Backend instance;
ingo@128: 
sascha@90:     /**
sascha@90:      * The database cleaner. Reference is stored here because
sascha@90:      * the cleaner is woken up if the backend finds an outdated
sascha@90:      * artifact. This artifact should be removed as soon as
sascha@90:      * possible.
sascha@90:      */
sascha@30:     protected DatabaseCleaner cleaner;
sascha@30: 
sascha@90:     /**
sascha@90:      * To revive an artifact from the bytes coming from the database
sascha@90:      * we need the artifact factory which references the artifact
sascha@90:      * serializer which is able to do the reviving job.
sascha@90:      */
sascha@41:     protected FactoryLookup   factoryLookup;
sascha@41: 
sascha@90:     /**
sascha@90:      * Little helper interface to decouple the ArtifactDatabase
sascha@90:      * from the Backend. A ArtifactDatabase should depend on a
sascha@90:      * Backend but a Backend not from an ArtifactDatabase.
sascha@90:      */
sascha@41:     public interface FactoryLookup {
sascha@41: 
sascha@90:         /**
sascha@90:          * Returns an ArtifactFactory which is bound to a given name.
sascha@90:          * @param factoryName The name of the artifact factory.
sascha@90:          * @return The ArtifactFactory bound to the factory name or
sascha@90:          * null if not matching factory is found.
sascha@90:          */
sascha@41:         ArtifactFactory getArtifactFactory(String factoryName);
sascha@41: 
sascha@41:     } // interface FactoryLookup
sascha@41: 
sascha@90:     /**
sascha@90:      * Inner class that brigdes between the persisten form of the
sascha@90:      * artifact and the living one inside the artifact database.
sascha@90:      * After the describe(), feed(), advance() and out() operations
sascha@90:      * of the artifact it must be possible to write to modified artifact
sascha@90:      * back into the database.
sascha@90:      */
sascha@32:     public final class PersistentArtifact
sascha@14:     {
sascha@230:         private int                id;
sascha@41:         private Artifact           artifact;
sascha@41:         private ArtifactSerializer serializer;
ingo@84:         private Long               ttl;
sascha@14: 
sascha@90:         /**
sascha@90:          * Cronstructor to create a persistent artifact.
sascha@90:          * @param artifact   The living artifact.
sascha@90:          * @param serializer The serializer to store the artifact
sascha@90:          * after the operations.
sascha@90:          * @param ttl The time to life of the artifact.
sascha@90:          * @param id The database id of the artifact.
sascha@90:          */
sascha@41:         public PersistentArtifact(
sascha@47:             Artifact           artifact,
sascha@41:             ArtifactSerializer serializer,
ingo@84:             Long               ttl,
sascha@41:             int                id
sascha@41:         ) {
sascha@230:             this.id         = id;
sascha@41:             this.artifact   = artifact;
sascha@41:             this.serializer = serializer;
ingo@84:             this.ttl        = ttl;
sascha@14:         }
sascha@14: 
sascha@230:         public int getId() {
sascha@230:             return id;
sascha@230:         }
sascha@230: 
sascha@90:         /**
sascha@90:          * Returns the wrapped living artifact.
sascha@90:          * @return the living artifact.
sascha@90:          */
sascha@32:         public Artifact getArtifact() {
sascha@32:             return artifact;
sascha@14:         }
sascha@14: 
sascha@90:         /**
sascha@90:          * Returns the serialized which is able to write a
sascha@90:          * modified artifact back into the database.
sascha@90:          * @return The serializer.
sascha@90:          */
sascha@41:         public ArtifactSerializer getSerializer() {
sascha@41:             return serializer;
sascha@41:         }
sascha@41: 
sascha@90:         /**
sascha@90:          * The time to life of the artifact.
sascha@90:          * @return The time to live.
sascha@90:          */
ingo@84:         public Long getTTL() {
ingo@84:             return ttl;
ingo@84:         }
ingo@84: 
sascha@90:         /**
sascha@90:          * Stores the living artifact back into the database.
sascha@90:          */
sascha@32:         public void store() {
sascha@38:             if (logger.isDebugEnabled()) {
sascha@38:                 logger.debug("storing artifact id = " + getId());
sascha@38:             }
sascha@32:             Backend.this.store(this);
sascha@14:         }
sascha@14: 
sascha@90:         /**
sascha@90:          * Only touches the access time of the artifact.
sascha@90:          */
sascha@32:         public void touch() {
sascha@38:             if (logger.isDebugEnabled()) {
sascha@38:                 logger.debug("touching artifact id = " + getId());
sascha@38:             }
sascha@32:             Backend.this.touch(this);
sascha@14:         }
sascha@32:     } // class ArtifactWithId
sascha@14: 
sascha@90:     /**
sascha@90:      * Default constructor
sascha@90:      */
sascha@13:     public Backend() {
sascha@13:     }
sascha@13: 
sascha@90:     /**
sascha@90:      * Constructor to create a backend with a link to the database cleaner.
sascha@90:      * @param cleaner The clean which periodically removes outdated
sascha@90:      * artifacts from the database.
sascha@90:      */
sascha@30:     public Backend(DatabaseCleaner cleaner) {
sascha@30:         this.cleaner = cleaner;
sascha@30:     }
sascha@30: 
ingo@128: 
ingo@128:     /**
ingo@128:      * Returns the singleton of this Backend.
ingo@128:      *
ingo@128:      * @return the backend.
ingo@128:      */
ingo@128:     public static synchronized Backend getInstance() {
ingo@128:         if (instance == null) {
ingo@128:             instance = new Backend();
ingo@128:         }
ingo@128: 
ingo@128:         return instance;
ingo@128:     }
ingo@128: 
sascha@90:     /**
sascha@90:      * Sets the factory lookup mechanism to decouple ArtifactDatabase
sascha@90:      * and Backend.
sascha@90:      * @param factoryLookup
sascha@90:      */
sascha@41:     public void setFactoryLookup(FactoryLookup factoryLookup) {
sascha@41:         this.factoryLookup = factoryLookup;
sascha@41:     }
sascha@41: 
sascha@90:     /**
sascha@90:      * Sets the database cleaner explicitly.
sascha@90:      * @param cleaner The database cleaner
sascha@90:      */
sascha@32:     public void setCleaner(DatabaseCleaner cleaner) {
sascha@32:         this.cleaner = cleaner;
sascha@32:     }
sascha@32: 
sascha@90:     /**
sascha@90:      * Returns a new unique identifier to external identify
sascha@90:      * the artifact across the system. This implementation
sascha@90:      * uses random UUIDs v4 to achieve this target.
sascha@90:      * @return the new identifier
sascha@90:      */
sascha@32:     public String newIdentifier() {
sascha@32:         // TODO: check database for collisions.
ingo@79:         return StringUtils.newUUID();
sascha@32:     }
sascha@32: 
sascha@177:     public boolean isValidIdentifier(String identifier) {
sascha@177:         return StringUtils.checkUUID(identifier);
sascha@177:     }
sascha@177: 
sascha@90:     /**
sascha@90:      * Stores a new artifact into the database.
sascha@90:      * @param artifact The artifact to be stored
sascha@90:      * @param factory  The factory which build the artifact
sascha@90:      * @param ttl      The initial time to life of the artifact.
sascha@90:      * @return         A persistent wrapper around the living
sascha@90:      * artifact to be able to write modification later.
sascha@90:      * @throws Exception Thrown if something went wrong with the
sascha@90:      * storage process.
sascha@90:      */
sascha@32:     public PersistentArtifact storeInitially(
sascha@41:         Artifact        artifact,
sascha@41:         ArtifactFactory factory,
sascha@41:         Long            ttl
sascha@47:     )
sascha@32:     throws Exception
sascha@32:     {
sascha@32:         return new PersistentArtifact(
sascha@32:             artifact,
sascha@41:             factory.getSerializer(),
ingo@84:             ttl,
sascha@41:             insertDatabase(artifact, factory, ttl));
sascha@32:     }
sascha@47: 
sascha@90:     /**
sascha@90:      * Stores an artifact into database if it does not exist there.
sascha@90:      * If it exists there it is only updated.
sascha@90:      * @param artifact The artifact to store/update.
sascha@90:      * @param factory The factory which created the artifact.
sascha@90:      * @param ttl The initial time to live of the artifact.
sascha@90:      * @return A persistent version of the artifact to be able
sascha@90:      * to store a modification later.
sascha@90:      * @throws Exception Thrown if something went wrong during
sascha@90:      * storing/updating.
sascha@90:      */
ingo@80:     public PersistentArtifact storeOrReplace(
ingo@80:         Artifact        artifact,
ingo@80:         ArtifactFactory factory,
ingo@80:         Long            ttl
ingo@80:     )
ingo@80:     throws Exception
ingo@80:     {
ingo@80:         return new PersistentArtifact(
ingo@80:             artifact,
ingo@80:             factory.getSerializer(),
ingo@84:             ttl,
ingo@80:             storeOrReplaceDatabase(artifact, factory, ttl));
ingo@80:     }
ingo@80: 
sascha@90:     /**
sascha@90:      * Implementors of this interface are able to process the raw
sascha@90:      * artifact data from the database for loading.
sascha@90:      */
ingo@79:     public interface ArtifactLoader {
ingo@79: 
sascha@90:         /**
sascha@90:          * Creates a custom object from the raw artifact database data.
sascha@90:          * @param factory The factory that created this artifact.
sascha@90:          * @param ttl The current time to life of the artifact.
sascha@90:          * @param bytes The raw artifact bytes from the database.
sascha@90:          * @param id The database id of the artifact.
sascha@90:          * @return The custom object created by the implementation.
sascha@90:          */
ingo@84:         Object load(ArtifactFactory factory, Long ttl, byte [] bytes, int id);
ingo@79: 
ingo@79:     } // interface ArtifactLoader
ingo@79: 
sascha@90:     /**
sascha@90:      * Fetches an artifact from the database identified by the
sascha@90:      * given identifier.
sascha@90:      * @param identifer The identifier of the artifact.
sascha@90:      * @return A persistent wrapper around the found artifact
sascha@90:      * to be able to write back a modifaction later or null
sascha@90:      * if no artifact is found for this identifier.
sascha@90:      */
sascha@32:     public PersistentArtifact getArtifact(String identifer) {
sascha@15: 
ingo@79:         return (PersistentArtifact)loadArtifact(
sascha@86:             identifer,
ingo@79:             new ArtifactLoader() {
ingo@79: 
ingo@79:                 public Object load(
ingo@79:                     ArtifactFactory factory,
ingo@84:                     Long            ttl,
ingo@79:                     byte []         bytes,
ingo@79:                     int             id
ingo@79:                 ) {
ingo@79:                     ArtifactSerializer serializer = factory.getSerializer();
ingo@79: 
ingo@79:                     Artifact artifact = serializer.fromBytes(bytes);
ingo@79: 
ingo@79:                     return artifact == null
ingo@79:                         ? null
ingo@84:                         : new PersistentArtifact(artifact, serializer, ttl, id);
ingo@79:                 }
ingo@79:             });
ingo@79:     }
ingo@79: 
sascha@90:     /**
sascha@90:      * More general loading mechanism for artifacts. The concrete
sascha@90:      * load processing is delegated to the given loader.
sascha@90:      * @param identifer The identifier of the artifact.
sascha@90:      * @param loader The loader which processes the raw database data.
sascha@90:      * @return The object created by the loader.
sascha@90:      */
sascha@174:     public Object loadArtifact(
sascha@174:         final String         identifer,
sascha@174:         final ArtifactLoader loader
sascha@174:     ) {
sascha@177:         if (!isValidIdentifier(identifer)) {
sascha@15:             return null;
sascha@15:         }
sascha@15: 
sascha@174:         final Object [] loaded = new Object[1];
sascha@15: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@174:                 prepareStatement(SQL_LOAD_BY_GID);
sascha@174:                 stmnt.setString(1, identifer);
sascha@41: 
sascha@174:                 result = stmnt.executeQuery();
sascha@174: 
sascha@174:                 if (!result.next()) {
sascha@174:                     return false;
sascha@174:                 }
sascha@174: 
sascha@174:                 int  id   = result.getInt(1);
sascha@242:                 long ttlX = result.getLong(2);
sascha@242:                 Long ttl  = result.wasNull() ? null : ttlX;
sascha@174: 
sascha@242:                 String factoryName = result.getString(3);
sascha@174: 
sascha@174:                 if (factoryLookup == null) {
sascha@174:                     logger.error("factory lookup == null");
sascha@174:                     return false;
sascha@174:                 }
sascha@174: 
sascha@174:                 ArtifactFactory factory = factoryLookup
sascha@174:                     .getArtifactFactory(factoryName);
sascha@174: 
sascha@174:                 if (factory == null) {
sascha@174:                     logger.error("factory '" + factoryName + "' not found");
sascha@174:                     return false;
sascha@174:                 }
sascha@174: 
sascha@242:                 byte [] bytes = result.getBytes(4);
sascha@174: 
sascha@174:                 loaded[0] = loader.load(factory, ttl, bytes, id);
sascha@174:                 return true;
sascha@15:             }
sascha@174:         };
sascha@174: 
sascha@174:         return exec.runRead() ? loaded[0] : null;
sascha@15:     }
sascha@15: 
sascha@90:     /**
sascha@90:      * Called if the load mechanism found an outdated artifact.
sascha@90:      * It  wakes up the database cleaner.
sascha@90:      * @param id The id of the outdated artifact.
sascha@90:      */
sascha@15:     protected void artifactOutdated(int id) {
ingo@79:         if (logger.isDebugEnabled()) {
ingo@79:             logger.info("artifactOutdated: id = " + id);
ingo@79:         }
sascha@30:         if (cleaner != null) {
sascha@30:             cleaner.wakeup();
sascha@30:         }
sascha@15:     }
sascha@15: 
sascha@41:     public Artifact reviveArtifact(String factoryName, byte [] bytes) {
sascha@41:         if (factoryLookup == null) {
sascha@41:             logger.error("reviveArtifact: factory lookup == null");
sascha@41:             return null;
sascha@41:         }
sascha@41:         ArtifactFactory factory = factoryLookup
sascha@41:             .getArtifactFactory(factoryName);
sascha@32: 
sascha@41:         if (factory == null) {
sascha@90:             logger.error(
sascha@90:                 "reviveArtifact: no factory '" + factoryName + "' found");
sascha@41:             return null;
sascha@41:         }
sascha@41: 
sascha@41:         ArtifactSerializer serializer = factory.getSerializer();
sascha@41: 
sascha@41:         return serializer.fromBytes(bytes);
sascha@41:     }
sascha@41: 
sascha@90:     /**
sascha@90:      * Internal method to store/replace an artifact inside the database.
sascha@90:      * If an artifact with the given identifier does not exists it is
sascha@90:      * created else only the content data is updated.
sascha@90:      * @param artifact The artifact to be store/update inside the database.
sascha@90:      * @param factory The factory that created the artifact.
sascha@90:      * @param ttl The initial time to life of the artifact.
sascha@90:      * @return The database id of the stored/updated artifact.
sascha@90:      */
ingo@80:     protected int storeOrReplaceDatabase(
sascha@174:         final Artifact        artifact,
sascha@174:         final ArtifactFactory factory,
sascha@174:         final Long            ttl
ingo@80:     ) {
sascha@174:         final String uuid = artifact.identifier();
ingo@80: 
sascha@177:         if (!isValidIdentifier(uuid)) {
ingo@80:             throw new RuntimeException("No valid UUID");
ingo@80:         }
ingo@80: 
sascha@174:         final int [] id = new int[1];
ingo@80: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
ingo@80: 
sascha@174:                 prepareStatement(SQL_GET_ID);
ingo@80:                 stmnt.setString(1, uuid);
ingo@80:                 result = stmnt.executeQuery();
ingo@80: 
ingo@80:                 Integer ID = result.next()
ingo@80:                     ? Integer.valueOf(result.getInt(1))
ingo@80:                     : null;
ingo@80: 
sascha@174:                 reset();
ingo@80: 
ingo@80:                 if (ID != null) { // already in database
sascha@174:                     prepareStatement(SQL_REPLACE);
ingo@80: 
ingo@80:                     if (ttl == null) {
ingo@80:                         stmnt.setNull(1, Types.BIGINT);
ingo@80:                     }
ingo@80:                     else {
ingo@80:                         stmnt.setLong(1, ttl.longValue());
ingo@80:                     }
ingo@80: 
ingo@80:                     stmnt.setString(2, factory.getName());
ingo@80:                     stmnt.setBytes(
ingo@80:                         3,
ingo@80:                         factory.getSerializer().toBytes(artifact));
sascha@174:                     id[0] = ID.intValue();
sascha@174:                     stmnt.setInt(4, id[0]);
sascha@174:                 }
sascha@174:                 else { // new artifact
sascha@174:                     prepareStatement(SQL_NEXT_ID);
sascha@174:                     result = stmnt.executeQuery();
ingo@80: 
sascha@174:                     if (!result.next()) {
sascha@174:                         logger.error("No id generated");
sascha@174:                         return false;
sascha@174:                     }
sascha@174: 
sascha@174:                     reset();
sascha@174: 
sascha@174:                     prepareStatement(SQL_INSERT);
sascha@174: 
sascha@174:                     id[0] = result.getInt(1);
sascha@174:                     stmnt.setInt(1, id[0]);
sascha@174:                     stmnt.setString(2, uuid);
sascha@174:                     if (ttl == null) {
sascha@174:                         stmnt.setNull(3, Types.BIGINT);
sascha@174:                     }
sascha@174:                     else {
sascha@174:                         stmnt.setLong(3, ttl.longValue());
sascha@174:                     }
sascha@174: 
sascha@174:                     stmnt.setString(4, factory.getName());
sascha@174: 
sascha@174:                     stmnt.setBytes(
sascha@174:                         5,
sascha@174:                         factory.getSerializer().toBytes(artifact));
ingo@80:                 }
sascha@174:                 stmnt.execute();
sascha@174:                 conn.commit();
sascha@174:                 return true;
sascha@174:             }
sascha@174:         };
ingo@80: 
sascha@174:         if (!exec.runWrite()) {
sascha@174:             throw new RuntimeException("failed insert artifact into database");
sascha@174:         }
sascha@174: 
sascha@174:         return id[0];
sascha@174:     }
sascha@174: 
sascha@174:     /**
sascha@174:      * Internal method to store an artifact inside the database.
sascha@174:      * @param artifact The artifact to be stored.
sascha@174:      * @param factory The factory which created the artifact.
sascha@174:      * @param ttl The initial time to live of the artifact.
sascha@174:      * @return The database id of the stored artifact.
sascha@174:      */
sascha@174:     protected int insertDatabase(
sascha@174:         final Artifact        artifact,
sascha@174:         final ArtifactFactory factory,
sascha@174:         final Long            ttl
sascha@174:     ) {
sascha@174:         final int [] id = new int[1];
sascha@174: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@174:                 prepareStatement(SQL_NEXT_ID);
ingo@80:                 result = stmnt.executeQuery();
ingo@80: 
ingo@80:                 if (!result.next()) {
sascha@174:                     logger.error("No id generated");
sascha@174:                     return false;
ingo@80:                 }
ingo@80: 
sascha@174:                 id[0] = result.getInt(1);
ingo@80: 
sascha@174:                 reset();
sascha@174:                 prepareStatement(SQL_INSERT);
ingo@80: 
sascha@174:                 String uuid = artifact.identifier();
sascha@174:                 stmnt.setInt(1, id[0]);
ingo@80:                 stmnt.setString(2, uuid);
ingo@80:                 if (ttl == null) {
ingo@80:                     stmnt.setNull(3, Types.BIGINT);
ingo@80:                 }
ingo@80:                 else {
ingo@80:                     stmnt.setLong(3, ttl.longValue());
ingo@80:                 }
ingo@80: 
ingo@80:                 stmnt.setString(4, factory.getName());
ingo@80: 
ingo@80:                 stmnt.setBytes(
ingo@80:                     5,
ingo@80:                     factory.getSerializer().toBytes(artifact));
ingo@80: 
ingo@80:                 stmnt.execute();
sascha@14: 
sascha@174:                 conn.commit();
sascha@174:                 return true;
sascha@174:             }
sascha@174:         };
sascha@14: 
sascha@174:         if (!exec.runWrite()) {
sascha@174:             throw new RuntimeException("failed insert artifact into database");
sascha@14:         }
sascha@174: 
sascha@174:         return id[0];
sascha@14:     }
sascha@14: 
sascha@90:     /**
sascha@90:      * Touches the access timestamp of a given artifact to prevent
sascha@90:      * that it will be removed from the database by the database cleaner.
sascha@90:      * @param artifact The persistent wrapper around the living artifact.
sascha@90:      */
sascha@174:     public void touch(final PersistentArtifact artifact) {
sascha@174:         new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@174:                 prepareStatement(SQL_TOUCH);
sascha@174:                 stmnt.setInt(1, artifact.getId());
sascha@174:                 stmnt.execute();
sascha@174:                 conn.commit();
sascha@174:                 return true;
sascha@14:             }
sascha@174:         }.runWrite();
sascha@14:     }
sascha@14: 
sascha@90:     /**
sascha@90:      * Writes modification of an artifact back to the database.
sascha@90:      * @param artifact The persistent wrapper around a living
sascha@90:      * artifact.
sascha@90:      */
sascha@174:     public void store(final PersistentArtifact artifact) {
sascha@174:         new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@174:                 prepareStatement(SQL_UPDATE);
sascha@174:                 stmnt.setInt(2, artifact.getId());
sascha@26: 
sascha@174:                 byte [] bytes = artifact
sascha@174:                     .getSerializer()
sascha@174:                     .toBytes(artifact.getArtifact());
sascha@174: 
sascha@174:                 stmnt.setBytes(1, bytes);
sascha@174:                 stmnt.execute();
sascha@174:                 conn.commit();
sascha@174:                 return true;
sascha@26:             }
sascha@174:         }.runWrite();
sascha@14:     }
sascha@133: 
sascha@133:     public User createUser(
sascha@233:         final String      name,
sascha@174:         final Document    role,
sascha@174:         final UserFactory factory,
sascha@174:         final Object      context
sascha@133:     ) {
sascha@174:         final User [] user = new User[1];
sascha@138: 
sascha@235:         final byte [] roleData = XMLUtils.toByteArray(role, true);
sascha@235: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@138: 
sascha@174:                 prepareStatement(SQL_USERS_NEXT_ID);
sascha@174:                 result = stmnt.executeQuery();
sascha@138: 
sascha@146:                 if (!result.next()) {
sascha@174:                     return false;
sascha@138:                 }
sascha@138: 
sascha@145:                 int id = result.getInt(1);
sascha@138: 
sascha@174:                 reset();
sascha@174: 
sascha@174:                 String identifier = newIdentifier();
sascha@174: 
sascha@174:                 prepareStatement(SQL_USERS_INSERT);
sascha@174: 
sascha@174:                 stmnt.setInt(1, id);
sascha@174:                 stmnt.setString(2, identifier);
sascha@174:                 stmnt.setString(3, name);
sascha@138: 
sascha@138:                 if (roleData == null) {
sascha@174:                     stmnt.setNull(4, Types.BIGINT);
sascha@138:                 }
sascha@138:                 else {
sascha@174:                     stmnt.setBytes(4, roleData);
sascha@138:                 }
sascha@138: 
sascha@174:                 stmnt.execute();
sascha@174:                 conn.commit();
sascha@138: 
sascha@174:                 user[0] = factory.createUser(
sascha@174:                     identifier, name, role, context);
sascha@174:                 return true;
sascha@138:             }
sascha@174:         };
sascha@174: 
sascha@174:         return exec.runWrite() ? user[0] : null;
sascha@133:     }
sascha@133: 
sascha@174:     public boolean deleteUser(final String identifier) {
sascha@144: 
sascha@177:         if (!isValidIdentifier(identifier)) {
sascha@154:             return false;
sascha@147:         }
sascha@147: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@174:                 prepareStatement(SQL_USERS_SELECT_ID_BY_GID);
sascha@144: 
sascha@144:                 stmnt.setString(1, identifier);
sascha@144:                 result = stmnt.executeQuery();
sascha@144: 
sascha@144:                 if (!result.next()) { // No such user
sascha@154:                     return false;
sascha@144:                 }
sascha@144: 
sascha@144:                 int id = result.getInt(1);
sascha@144: 
sascha@174:                 reset();
sascha@144: 
sascha@144:                 // outdate the artifacts exclusively used by the user
sascha@144: 
sascha@174:                 prepareStatement(SQL_OUTDATE_ARTIFACTS_USER);
sascha@144:                 stmnt.setInt(1, id);
sascha@144:                 stmnt.setInt(2, id);
sascha@144:                 stmnt.execute();
sascha@144: 
sascha@174:                 reset();
sascha@144: 
sascha@144:                 // delete the collection items of the user
sascha@144: 
sascha@174:                 prepareStatement(SQL_DELETE_USER_COLLECTION_ITEMS);
sascha@144:                 stmnt.setInt(1, id);
sascha@144:                 stmnt.execute();
sascha@233: 
sascha@174:                 reset();
sascha@144: 
sascha@144:                 // delete the collections of the user
sascha@144: 
sascha@174:                 prepareStatement(SQL_USERS_DELETE_COLLECTIONS);
sascha@144:                 stmnt.setInt(1, id);
sascha@144:                 stmnt.execute();
sascha@174: 
sascha@174:                 reset();
sascha@144: 
sascha@144:                 // delete the user
sascha@144: 
sascha@174:                 prepareStatement(SQL_USERS_DELETE_ID);
sascha@144:                 stmnt.setInt(1, id);
sascha@144:                 stmnt.execute();
sascha@144: 
sascha@144:                 conn.commit();
sascha@154:                 return true;
sascha@144:             }
sascha@174:         };
sascha@154: 
sascha@174:         return exec.runWrite();
sascha@133:     }
sascha@133: 
sascha@157:     public User getUser(
sascha@233:         final String      identifier,
sascha@174:         final UserFactory factory,
sascha@174:         final Object      context
sascha@157:     ) {
sascha@177:         if (!isValidIdentifier(identifier)) {
sascha@147:             logger.debug("Invalid UUID: '" + identifier + "'");
sascha@147:             return null;
sascha@147:         }
sascha@147: 
sascha@174:         final User [] user = new User[1];
sascha@147: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@174:                 prepareStatement(SQL_USERS_SELECT_GID);
sascha@174:                 stmnt.setString(1, identifier);
sascha@174:                 result = stmnt.executeQuery();
sascha@174:                 if (!result.next()) { // no such user
sascha@174:                     return false;
sascha@174:                 }
sascha@174:                 // omit id
sascha@174:                 String  name     = result.getString(2);
sascha@174:                 byte [] roleData = result.getBytes(3);
sascha@174: 
sascha@235:                 Document role = XMLUtils.fromByteArray(roleData, true);
sascha@174: 
sascha@174:                 user[0] = factory.createUser(
sascha@174:                     identifier, name, role, context);
sascha@174:                 return true;
sascha@147:             }
sascha@174:         };
sascha@147: 
sascha@174:         return exec.runRead() ? user[0] : null;
sascha@133:     }
sascha@133: 
sascha@174:     public User [] getUsers(
sascha@233:         final UserFactory factory,
sascha@174:         final Object      context
sascha@174:     ) {
sascha@174:         final ArrayList<User> users = new ArrayList<User>();
sascha@148: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@174:                 prepareStatement(SQL_USERS_SELECT_ALL);
sascha@174:                 result = stmnt.executeQuery();
sascha@148: 
sascha@174:                 while (result.next()) {
sascha@174:                     // omit id
sascha@174:                     String  identifier = result.getString(2);
sascha@174:                     String  name       = result.getString(3);
sascha@174:                     byte [] roleData   = result.getBytes(4);
sascha@174: 
sascha@235:                     Document role = XMLUtils.fromByteArray(roleData, true);
sascha@174:                     User user = factory.createUser(
sascha@174:                         identifier, name, role, context);
sascha@174:                     users.add(user);
sascha@174:                 }
sascha@174:                 return true;
sascha@148:             }
sascha@174:         };
sascha@148: 
sascha@174:         return exec.runRead()
sascha@174:             ? users.toArray(new User[users.size()])
sascha@174:             : null;
sascha@133:     }
sascha@156: 
sascha@159:     public ArtifactCollection createCollection(
sascha@233:         final String                    ownerIdentifier,
sascha@174:         final String                    name,
sascha@174:         final ArtifactCollectionFactory factory,
sascha@199:         final Document                  attribute,
sascha@174:         final Object                    context
sascha@159:     ) {
sascha@159:         if (name == null) {
sascha@159:             logger.debug("Name is null");
sascha@159:             return null;
sascha@159:         }
sascha@159: 
sascha@177:         if (!isValidIdentifier(ownerIdentifier)) {
sascha@159:             logger.debug("Invalid owner id: '" + ownerIdentifier + "'");
sascha@159:             return null;
sascha@159:         }
sascha@159: 
sascha@235:         final ArtifactCollection [] collection = new ArtifactCollection[1];
sascha@235: 
sascha@235:         final byte [] data = XMLUtils.toByteArray(attribute, true);
sascha@159: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@174:             public boolean doIt() throws SQLException {
sascha@159:                 // fetch owner id
sascha@174:                 prepareStatement(SQL_USERS_SELECT_ID_BY_GID);
sascha@159:                 stmnt.setString(1, ownerIdentifier);
sascha@159:                 result = stmnt.executeQuery();
sascha@159: 
sascha@159:                 if (!result.next()) { // no such user
sascha@174:                     return false;
sascha@159:                 }
sascha@159: 
sascha@159:                 int ownerId = result.getInt(1);
sascha@174:                 reset();
sascha@159: 
sascha@159:                 // fetch new collection seq number.
sascha@174:                 prepareStatement(SQL_COLLECTIONS_NEXT_ID);
sascha@159:                 result = stmnt.executeQuery();
sascha@159: 
sascha@159:                 if (!result.next()) { // no identifier generated
sascha@174:                     return false;
sascha@159:                 }
sascha@159: 
sascha@159:                 int id = result.getInt(1);
sascha@174:                 reset();
sascha@159: 
sascha@159:                 String identifier = newIdentifier();
sascha@159: 
sascha@174:                 prepareStatement(SQL_COLLECTIONS_INSERT);
sascha@159: 
sascha@159:                 stmnt.setInt(1, id);
sascha@159:                 stmnt.setString(2, identifier);
ingo@161:                 stmnt.setString(3, name);
ingo@161:                 stmnt.setInt(4, ownerId);
sascha@159: 
sascha@159:                 // XXX: A bit odd: we don't have a collection, yet.
sascha@159:                 Long ttl = factory.timeToLiveUntouched(null, context);
sascha@159: 
sascha@159:                 if (ttl == null) {
sascha@159:                     stmnt.setNull(5, Types.BIGINT);
sascha@159:                 }
sascha@159:                 else {
sascha@159:                     stmnt.setLong(5, ttl);
sascha@159:                 }
sascha@159: 
sascha@199:                 if (data == null) {
sascha@199:                     stmnt.setNull(6, Types.BINARY);
sascha@199:                 }
sascha@199:                 else {
sascha@199:                     stmnt.setBytes(6, data);
sascha@199:                 }
sascha@199: 
sascha@159:                 stmnt.execute();
sascha@159:                 conn.commit();
sascha@159: 
sascha@174:                 reset();
sascha@170: 
sascha@170:                 // fetch creation time from database
sascha@170:                 // done this way to use the time system
sascha@170:                 // of the database.
sascha@170: 
sascha@174:                 prepareStatement(SQL_COLLECTIONS_CREATION_TIME);
sascha@170:                 stmnt.setInt(1, id);
sascha@170: 
sascha@170:                 result = stmnt.executeQuery();
sascha@170: 
sascha@170:                 Date creationTime = null;
sascha@170: 
sascha@170:                 if (result.next()) {
sascha@170:                     Timestamp timestamp = result.getTimestamp(1);
sascha@170:                     creationTime = new Date(timestamp.getTime());
sascha@170:                 }
sascha@170: 
sascha@174:                 collection[0] = factory.createCollection(
ingo@281:                     identifier, name, creationTime, ttl, attribute, context);
sascha@174: 
sascha@174:                 return true;
sascha@159:             }
sascha@174:         };
sascha@174: 
sascha@174:         return exec.runWrite() ? collection[0]: null;
sascha@156:     }
sascha@156: 
ingo@217:     public ArtifactCollection getCollection(
ingo@217:         final String                    collectionId,
ingo@217:         final ArtifactCollectionFactory collectionFactory,
ingo@217:         final UserFactory               userFactory,
ingo@217:         final Object                    context
ingo@217:     ) {
ingo@217:         if (!isValidIdentifier(collectionId)) {
ingo@217:             logger.debug("collection id is not valid: " + collectionId);
ingo@217:             return null;
ingo@217:         }
ingo@217: 
ingo@217:         final ArtifactCollection[] ac = new ArtifactCollection[1];
ingo@217: 
ingo@217:         SQLExecutor exec = new SQLExecutor() {
ingo@217:             public boolean doIt() throws SQLException {
ingo@217: 
ingo@217:                 prepareStatement(SQL_COLLECTIONS_SELECT_GID);
ingo@217:                 stmnt.setString(1, collectionId);
ingo@217: 
ingo@217:                 result = stmnt.executeQuery();
ingo@217:                 if (!result.next()) {
ingo@217:                     logger.debug("No such collection");
ingo@217:                     return false;
ingo@217:                 }
ingo@217: 
sascha@235:                 String collectionName = result.getString(2);
sascha@235:                 String ownerId        = result.getString(3);
sascha@235:                 Date   creationTime   =
ingo@217:                     new Date(result.getTimestamp(4).getTime());
sascha@235:                 Date   lastAccess     =
ingo@217:                     new Date(result.getTimestamp(5).getTime());
sascha@235:                 Document attr         =
sascha@235:                     XMLUtils.fromByteArray(result.getBytes(6), true);
ingo@281:                 long ttl              = result.getLong(7);
ingo@217: 
ingo@217:                 ArtifactCollection collection =
ingo@217:                     collectionFactory.createCollection(
ingo@217:                         collectionId,
ingo@217:                         collectionName,
ingo@217:                         creationTime,
ingo@281:                         ttl,
ingo@217:                         attr,
ingo@217:                         context);
ingo@217: 
ingo@217:                 if (ownerId != null) {
ingo@217:                     collection.setUser(new LazyBackendUser(
ingo@217:                         ownerId, userFactory, Backend.this, context));
ingo@217:                 }
ingo@217: 
ingo@217:                 ac[0] = collection;
ingo@217: 
ingo@217:                 return true;
ingo@217:             }
ingo@217:         };
ingo@217: 
ingo@217:         return exec.runRead() ? ac[0] : null;
ingo@217:     }
ingo@217: 
ingo@164:     public ArtifactCollection [] listCollections(
sascha@174:         final String                    ownerIdentifier,
sascha@174:         final Document                  data,
sascha@174:         final ArtifactCollectionFactory collectionFactory,
sascha@174:         final UserFactory               userFactory,
sascha@174:         final Object                    context
sascha@167:     ) {
sascha@233:         if (ownerIdentifier != null
sascha@177:         && !isValidIdentifier(ownerIdentifier)) {
sascha@167:             logger.debug("Invalid owner id: '" + ownerIdentifier + "'");
sascha@167:             return null;
sascha@167:         }
sascha@167: 
sascha@174:         final ArrayList<ArtifactCollection> collections =
sascha@174:             new ArrayList<ArtifactCollection>();
sascha@167: 
sascha@174:         SQLExecutor exec = new SQLExecutor() {
sascha@167: 
sascha@174:             public boolean doIt() throws SQLException {
sascha@167: 
sascha@174:                 if (ownerIdentifier != null) {
sascha@174:                     prepareStatement(SQL_COLLECTIONS_SELECT_USER);
sascha@174:                     stmnt.setString(1, ownerIdentifier);
sascha@174:                 }
sascha@174:                 else {
sascha@174:                     prepareStatement(SQL_COLLECTIONS_SELECT_ALL);
sascha@167:                 }
sascha@167: 
sascha@174:                 result = stmnt.executeQuery();
sascha@167: 
sascha@235:                 HashMap<String, LazyBackendUser> users =
sascha@235:                     new HashMap<String, LazyBackendUser>();
sascha@235: 
sascha@174:                 while (result.next()) {
sascha@174:                     String collectionIdentifier = result.getString(1);
sascha@174:                     String collectionName       = result.getString(2);
sascha@174:                     Date   creationTime         =
sascha@174:                         new Date(result.getTimestamp(3).getTime());
sascha@174:                     String userIdentifier       = result.getString(4);
ingo@281:                     long   ttl                  = result.getLong(5);
sascha@174: 
sascha@174:                     ArtifactCollection collection =
sascha@174:                         collectionFactory.createCollection(
sascha@174:                             collectionIdentifier,
sascha@174:                             collectionName,
sascha@174:                             creationTime,
ingo@281:                             ttl,
sascha@174:                             data,
sascha@174:                             context);
sascha@174: 
sascha@174:                     if (userIdentifier != null) {
sascha@235:                         LazyBackendUser user = users.get(userIdentifier);
sascha@235:                         if (user == null) {
sascha@235:                             user = new LazyBackendUser(
sascha@235:                                 userIdentifier, userFactory,
sascha@235:                                 Backend.this, context);
sascha@235:                             users.put(userIdentifier, user);
sascha@235:                         }
sascha@235:                         collection.setUser(user);
sascha@174:                     }
sascha@174: 
sascha@174:                     collections.add(collection);
sascha@174:                 }
sascha@174:                 return true;
sascha@167:             }
sascha@174:         };
sascha@174: 
sascha@174:         return exec.runRead()
sascha@174:             ? collections.toArray(new ArtifactCollection[collections.size()])
sascha@174:             : null;
sascha@156:     }
sascha@156: 
sascha@174: 
sascha@175:     public boolean deleteCollection(final String collectionId) {
sascha@177:         if (!isValidIdentifier(collectionId)) {
sascha@175:             logger.debug("Invalid collection id: '" + collectionId + "'");
sascha@175:             return false;
sascha@175:         }
sascha@175:         SQLExecutor exec = new SQLExecutor() {
sascha@175:             public boolean doIt() throws SQLException {
sascha@175:                 // fetch collection id
sascha@175:                 prepareStatement(SQL_COLLECTIONS_ID_BY_GID);
sascha@175:                 stmnt.setString(1, collectionId);
sascha@175:                 result = stmnt.executeQuery();
sascha@175:                 if (!result.next()) {
sascha@175:                     logger.debug("No such collection: " + collectionId);
sascha@175:                     return false;
sascha@175:                 }
sascha@175:                 int id = result.getInt(1);
sascha@175:                 reset();
sascha@175: 
sascha@175:                 // outdate artifacts that are only in this collection
sascha@175:                 prepareStatement(SQL_OUTDATE_ARTIFACTS_COLLECTION);
sascha@175:                 stmnt.setInt(1, id);
sascha@175:                 stmnt.setInt(2, id);
sascha@175:                 stmnt.execute();
sascha@175:                 reset();
sascha@175: 
sascha@175:                 // delete the collection items
sascha@175:                 prepareStatement(SQL_DELETE_COLLECTION_ITEMS);
sascha@175:                 stmnt.setInt(1, id);
sascha@175:                 stmnt.execute();
sascha@175:                 reset();
sascha@175: 
sascha@175:                 // delete the collection
sascha@175:                 prepareStatement(SQL_DELETE_COLLECTION);
sascha@175:                 stmnt.setInt(1, id);
sascha@175:                 stmnt.execute();
sascha@175:                 conn.commit();
sascha@175:                 return true;
sascha@175:             }
sascha@175:         };
sascha@175:         return exec.runWrite();
sascha@156:     }
sascha@156: 
ingo@253:     public Document getCollectionAttribute(final String collectionId) {
ingo@253:         if (!isValidIdentifier(collectionId)) {
ingo@253:             logger.debug("collection id is not valid: " + collectionId);
ingo@253:         }
ingo@253: 
ingo@253:         final byte[][] data = new byte[1][1];
ingo@253: 
ingo@253:         SQLExecutor exec = new SQLExecutor() {
ingo@253:             public boolean doIt() throws SQLException {
ingo@253:                 prepareStatement(SQL_COLLECTION_GET_ATTRIBUTE);
ingo@253:                 stmnt.setString(1, collectionId);
ingo@253:                 result = stmnt.executeQuery();
ingo@253:                 if (!result.next()) {
ingo@253:                     logger.debug("No such collection.");
ingo@253:                     return false;
ingo@253:                 }
ingo@253: 
ingo@253:                 data[0] = result.getBytes(1);
ingo@253:                 return true;
ingo@253:             }
ingo@253:         };
ingo@253: 
ingo@253:         return exec.runRead()
ingo@253:             ? XMLUtils.fromByteArray(data[0], true)
ingo@253:             : null;
ingo@253:     }
ingo@253: 
ingo@253:     public boolean setCollectionAttribute(
ingo@253:         final String   collectionId,
ingo@253:         Document       attribute
ingo@253:     ) {
ingo@253:         if (!isValidIdentifier(collectionId)) {
ingo@253:             logger.debug("collection id is not valid: " + collectionId);
ingo@253:             return false;
ingo@253:         }
ingo@253: 
ingo@253:         final byte [] data = XMLUtils.toByteArray(attribute, true);
ingo@253: 
ingo@253:         return new SQLExecutor() {
ingo@253:             public boolean doIt() throws SQLException {
ingo@253: 
ingo@253:                 // set the column in collection items
ingo@253:                 prepareStatement(SQL_COLLECTION_SET_ATTRIBUTE);
ingo@253:                 if (data == null) {
ingo@253:                     stmnt.setNull(1, Types.BINARY);
ingo@253:                 }
ingo@253:                 else {
ingo@253:                     stmnt.setBytes(1, data);
ingo@253:                 }
ingo@253:                 stmnt.setString(2, collectionId);
ingo@253:                 stmnt.execute();
ingo@253:                 reset();
ingo@253: 
ingo@253:                 // touch the collection
ingo@253:                 prepareStatement(SQL_COLLECTIONS_TOUCH_BY_GID);
ingo@253:                 stmnt.setString(1, collectionId);
ingo@253:                 stmnt.execute();
ingo@253: 
ingo@253:                 conn.commit();
ingo@253:                 return true;
ingo@253:             }
ingo@253:         }.runWrite();
ingo@253:     }
ingo@253: 
ingo@252:     public Document getCollectionItemAttribute(
sascha@178:         final String collectionId,
sascha@178:         final String artifactId
sascha@156:     ) {
sascha@178:         if (!isValidIdentifier(collectionId)) {
sascha@178:             logger.debug("collection id is not valid: " + collectionId);
sascha@178:             return null;
sascha@178:         }
sascha@178:         if (!isValidIdentifier(artifactId)) {
sascha@178:             logger.debug("artifact id is not valid: " + artifactId);
sascha@179:             return null;
sascha@178:         }
sascha@178: 
sascha@235:         final byte [][] data = new byte[1][1];
sascha@178: 
sascha@178:         SQLExecutor exec = new SQLExecutor() {
sascha@178:             public boolean doIt() throws SQLException {
sascha@178:                 prepareStatement(SQL_COLLECTION_ITEM_GET_ATTRIBUTE);
sascha@178:                 stmnt.setString(1, collectionId);
sascha@178:                 stmnt.setString(2, artifactId);
sascha@178:                 result = stmnt.executeQuery();
sascha@178:                 if (!result.next()) {
sascha@178:                     logger.debug("No such collection item");
sascha@178:                     return false;
sascha@178:                 }
sascha@235:                 data[0] = result.getBytes(1);
sascha@178:                 return true;
sascha@178:             }
sascha@178:         };
sascha@178: 
sascha@235:         return exec.runRead()
sascha@235:             ? XMLUtils.fromByteArray(data[0], true)
sascha@235:             : null;
sascha@156:     }
sascha@156: 
ingo@252:     public boolean setCollectionItemAttribute(
sascha@233:         final String   collectionId,
sascha@179:         final String   artifactId,
sascha@179:         Document       attribute
sascha@156:     ) {
sascha@179:         if (!isValidIdentifier(collectionId)) {
sascha@179:             logger.debug("collection id is not valid: " + collectionId);
sascha@179:             return false;
sascha@179:         }
sascha@179:         if (!isValidIdentifier(artifactId)) {
sascha@179:             logger.debug("artifact id is not valid: " + artifactId);
sascha@179:             return false;
sascha@179:         }
sascha@179: 
sascha@235:         final byte [] data = XMLUtils.toByteArray(attribute, true);
sascha@179: 
sascha@179:         return new SQLExecutor() {
sascha@179:             public boolean doIt() throws SQLException {
sascha@179: 
sascha@179:                 // set the column in collection items
sascha@179:                 prepareStatement(SQL_COLLECTION_ITEM_SET_ATTRIBUTE);
sascha@179:                 if (data == null) {
sascha@179:                     stmnt.setNull(1, Types.BINARY);
sascha@179:                 }
sascha@179:                 else {
sascha@179:                     stmnt.setBytes(1, data);
sascha@179:                 }
sascha@179:                 stmnt.setString(2, collectionId);
sascha@179:                 stmnt.setString(3, artifactId);
sascha@179:                 stmnt.execute();
sascha@179:                 reset();
sascha@179: 
sascha@179:                 // touch the collection
sascha@179:                 prepareStatement(SQL_COLLECTIONS_TOUCH_BY_GID);
sascha@179:                 stmnt.setString(1, collectionId);
sascha@179:                 stmnt.execute();
sascha@179: 
sascha@179:                 conn.commit();
sascha@179:                 return true;
sascha@179:             }
sascha@179:         }.runWrite();
sascha@156:     }
sascha@156: 
sascha@156:     public boolean addCollectionArtifact(
sascha@176:         final String   collectionId,
sascha@176:         final String   artifactId,
sascha@176:         final Document attribute
sascha@156:     ) {
sascha@177:         if (!isValidIdentifier(collectionId)) {
sascha@176:             logger.debug("Invalid collection id: '" + collectionId + "'");
sascha@176:             return false;
sascha@176:         }
sascha@176: 
sascha@177:         if (!isValidIdentifier(artifactId)) {
sascha@176:             logger.debug("Invalid artifact id: '" + artifactId + "'");
sascha@176:             return false;
sascha@176:         }
sascha@176: 
sascha@235:         final byte [] data = XMLUtils.toByteArray(attribute, true);
sascha@235: 
sascha@176:         SQLExecutor exec = new SQLExecutor() {
sascha@176:             public boolean doIt() throws SQLException {
sascha@176:                 // fetch artifact id
sascha@176:                 prepareStatement(SQL_GET_ID);
sascha@176:                 stmnt.setString(1, artifactId);
sascha@176:                 result = stmnt.executeQuery();
sascha@176:                 if (!result.next()) {
sascha@176:                     logger.debug("No such artifact: " + artifactId);
sascha@176:                     return false;
sascha@176:                 }
sascha@176:                 int aid = result.getInt(1);
sascha@176:                 reset();
sascha@176: 
sascha@176:                 // fetch collection id
sascha@176:                 prepareStatement(SQL_COLLECTIONS_ID_BY_GID);
sascha@176:                 stmnt.setString(1, collectionId);
sascha@176:                 result = stmnt.executeQuery();
sascha@176:                 if (!result.next()) {
sascha@176:                     logger.debug("No such collection: " + collectionId);
sascha@176:                 }
sascha@176:                 int cid = result.getInt(1);
sascha@176:                 reset();
sascha@176: 
sascha@176:                 // check if artifact is already in collection
sascha@176:                 prepareStatement(SQL_COLLECTION_CHECK_ARTIFACT);
sascha@176:                 stmnt.setInt(1, aid);
sascha@176:                 stmnt.setInt(2, cid);
sascha@176:                 result = stmnt.executeQuery();
sascha@176:                 if (result.next()) {
sascha@176:                     logger.debug("artifact already in collection");
sascha@176:                     return false;
sascha@176:                 }
sascha@176:                 reset();
sascha@176: 
sascha@176:                 // fetch fresh id for new collection item
sascha@176:                 prepareStatement(SQL_COLLECTION_ITEMS_ID_NEXTVAL);
sascha@176:                 result = stmnt.executeQuery();
sascha@176:                 if (!result.next()) {
sascha@176:                     logger.debug("no collection item id generated");
sascha@176:                     return false;
sascha@176:                 }
sascha@176:                 int ci_id = result.getInt(1);
sascha@176:                 reset();
sascha@176: 
sascha@176:                 // insert new collection item
sascha@176:                 prepareStatement(SQL_COLLECTION_ITEMS_INSERT);
sascha@176:                 stmnt.setInt(1, ci_id);
sascha@176:                 stmnt.setInt(2, cid);
sascha@176:                 stmnt.setInt(3, aid);
sascha@176: 
sascha@176:                 if (data == null) {
sascha@176:                     stmnt.setNull(4, Types.BINARY);
sascha@176:                 }
sascha@176:                 else {
sascha@176:                     stmnt.setBytes(4, data);
sascha@176:                 }
sascha@176:                 stmnt.execute();
sascha@176:                 conn.commit();
sascha@176: 
sascha@176:                 return true;
sascha@176:             }
sascha@176:         };
sascha@176:         return exec.runWrite();
sascha@156:     }
sascha@156: 
sascha@156:     public boolean removeCollectionArtifact(
sascha@180:         final String collectionId,
sascha@180:         final String artifactId
sascha@156:     ) {
sascha@180:         if (!isValidIdentifier(collectionId)) {
sascha@180:             logger.debug("Invalid collection id: '" + collectionId + "'");
sascha@180:             return false;
sascha@180:         }
sascha@180:         return new SQLExecutor() {
sascha@180:             public boolean doIt() throws SQLException {
sascha@180: 
sascha@180:                 // fetch id, collection id and artitfact id
sascha@180:                 prepareStatement(SQL_COLLECTION_ITEM_ID_CID_AID);
sascha@180:                 stmnt.setString(1, collectionId);
sascha@180:                 stmnt.setString(2, artifactId);
sascha@180:                 result = stmnt.executeQuery();
sascha@180:                 if (!result.next()) {
sascha@180:                     logger.debug("No such collection item");
sascha@180:                     return false;
sascha@180:                 }
sascha@180:                 int  id = result.getInt(1);
sascha@180:                 int cid = result.getInt(2);
sascha@180:                 int aid = result.getInt(3);
sascha@180:                 reset();
sascha@180: 
sascha@180:                 // outdate artifact iff it is only in this collection
sascha@180:                 prepareStatement(SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT);
sascha@180:                 stmnt.setInt(1, aid);
sascha@180:                 stmnt.setInt(2, cid);
sascha@194:                 stmnt.setInt(3, aid);
sascha@180:                 stmnt.execute();
sascha@180:                 reset();
sascha@180: 
sascha@180:                 // delete collection item
sascha@180:                 prepareStatement(SQL_COLLECTION_ITEM_DELETE);
sascha@180:                 stmnt.setInt(1, id);
sascha@180:                 stmnt.execute();
sascha@180:                 reset();
sascha@180: 
sascha@180:                 // touch collection
sascha@180:                 prepareStatement(SQL_COLLECTIONS_TOUCH_BY_ID);
sascha@180:                 stmnt.setInt(1, cid);
sascha@180:                 stmnt.execute();
sascha@180: 
sascha@180:                 conn.commit();
sascha@180:                 return true;
sascha@180:             }
sascha@180:         }.runWrite();
sascha@156:     }
sascha@156: 
sascha@184:     public CollectionItem [] listCollectionArtifacts(
sascha@184:         final String collectionId
sascha@184:     ) {
sascha@184:         if (!isValidIdentifier(collectionId)) {
sascha@184:             logger.debug("Invalid collection id: '" + collectionId + "'");
sascha@184:             return null;
sascha@184:         }
sascha@184: 
sascha@184:         final ArrayList<CollectionItem> collectionItems =
sascha@184:             new ArrayList<CollectionItem>();
sascha@184: 
sascha@184:         SQLExecutor exec = new SQLExecutor() {
sascha@184:             public boolean doIt() throws SQLException {
sascha@184:                 prepareStatement(SQL_COLLECTION_ITEMS_LIST_GID);
sascha@184:                 stmnt.setString(1, collectionId);
sascha@184:                 result = stmnt.executeQuery();
sascha@184:                 while (result.next()) {
ingo@186:                     CollectionItem item = new DefaultCollectionItem(
sascha@184:                         result.getString(1),
sascha@184:                         result.getBytes(2));
sascha@184:                     collectionItems.add(item);
sascha@184:                 }
sascha@184:                 return true;
sascha@184:             }
sascha@184:         };
sascha@184: 
sascha@184:         return exec.runRead()
sascha@184:             ? collectionItems.toArray(
sascha@184:                 new CollectionItem[collectionItems.size()])
sascha@184:             : null;
sascha@156:     }
ingo@273: 
ingo@273: 
ingo@273:     public boolean setCollectionTTL(final String uuid, final Long ttl) {
ingo@273:         if (!isValidIdentifier(uuid)) {
ingo@273:             logger.debug("Invalid collection id: '" + uuid + "'");
ingo@273:             return false;
ingo@273:         }
ingo@273: 
ingo@273:         return new SQLExecutor() {
ingo@273:             public boolean doIt() throws SQLException {
ingo@273:                 prepareStatement(SQL_UPDATE_COLLECTION_TTL);
ingo@273:                 if (ttl == null) {
ingo@273:                     stmnt.setNull(1, Types.BIGINT);
ingo@273:                 }
ingo@273:                 else {
ingo@273:                     stmnt.setLong(1, ttl);
ingo@273:                 }
ingo@273:                 stmnt.setString(2, uuid);
ingo@273:                 stmnt.execute();
ingo@273:                 conn.commit();
ingo@273: 
ingo@273:                 return true;
ingo@273:             }
ingo@273:         }.runWrite();
ingo@273:     }
ingo@275: 
ingo@275: 
ingo@275:     public boolean setCollectionName(final String uuid, final String name) {
ingo@275:         if (!isValidIdentifier(uuid)) {
ingo@275:             logger.debug("Invalid collection id: '" + uuid + "'");
ingo@275:             return false;
ingo@275:         }
ingo@275: 
ingo@275:         return new SQLExecutor() {
ingo@275:             public boolean doIt() throws SQLException {
ingo@275:                 prepareStatement(SQL_UPDATE_COLLECTION_NAME);
ingo@275:                 stmnt.setString(1, name);
ingo@275:                 stmnt.setString(2, uuid);
ingo@275:                 stmnt.execute();
ingo@275:                 conn.commit();
ingo@275: 
ingo@275:                 return true;
ingo@275:             }
ingo@275:         }.runWrite();
ingo@275:     }
sascha@13: }
ingo@79: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :