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@303: import de.intevation.artifacts.ArtifactDatabase.ArtifactLoadedCallback;
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@301: import de.intevation.artifacts.common.utils.StringUtils;
sascha@138: import de.intevation.artifacts.common.utils.XMLUtils;
sascha@308: import de.intevation.artifacts.common.utils.LRUCache;
sascha@138:
sascha@305: import de.intevation.artifactdatabase.db.SQLExecutor;
sascha@305: import de.intevation.artifactdatabase.db.SQL;
sascha@305:
sascha@93: import java.sql.SQLException;
sascha@170: import java.sql.Timestamp;
sascha@93: import java.sql.Types;
sascha@93:
sascha@310: import java.util.List;
sascha@148: import java.util.ArrayList;
sascha@170: import java.util.Date;
sascha@235: import java.util.HashMap;
sascha@148:
sascha@310: import java.util.concurrent.CopyOnWriteArrayList;
sascha@310:
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 Sascha L. Teichmann
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@305: public String SQL_NEXT_ID;
sascha@14:
sascha@90: /**
sascha@90: * The SQL statement to insert an artifact into the database.
sascha@90: */
sascha@305: public String SQL_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@305: public String SQL_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@305: public String SQL_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@305: public String SQL_LOAD_BY_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: */
sascha@305: public String SQL_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: */
sascha@305: public String SQL_REPLACE;
ingo@80:
sascha@133: // USER SQL
sascha@133:
sascha@305: public String SQL_USERS_NEXT_ID;
sascha@305: public String SQL_USERS_INSERT;
sascha@305: public String SQL_USERS_SELECT_ID_BY_GID;
sascha@305: public String SQL_USERS_SELECT_GID;
sascha@305: public String SQL_USERS_DELETE_ID;
sascha@305: public String SQL_USERS_DELETE_COLLECTIONS;
sascha@305: public String SQL_USERS_SELECT_ALL;
sascha@305: public String SQL_USERS_COLLECTIONS;
sascha@305: public String SQL_USERS_COLLECTION_IDS;
sascha@305: public String SQL_USERS_DELETE_ALL_COLLECTIONS;
sascha@305: public String SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY;
sascha@305: public String SQL_OUTDATE_ARTIFACTS_COLLECTION;
sascha@305: public String SQL_UPDATE_COLLECTION_TTL;
sascha@305: public String SQL_UPDATE_COLLECTION_NAME;
sascha@305: public String SQL_OUTDATE_ARTIFACTS_USER;
sascha@305: public String SQL_DELETE_USER_COLLECTION_ITEMS;
sascha@305: public String SQL_COLLECTIONS_NEXT_ID;
sascha@305: public String SQL_COLLECTIONS_INSERT;
sascha@305: public String SQL_COLLECTIONS_SELECT_USER;
sascha@305: public String SQL_COLLECTIONS_SELECT_ALL;
sascha@305: public String SQL_COLLECTIONS_SELECT_GID;
sascha@305: public String SQL_COLLECTIONS_CREATION_TIME;
sascha@305: public String SQL_COLLECTIONS_ID_BY_GID;
felix@343: public String SQL_COLLECTIONS_OLDEST_ARTIFACT;
sascha@305: public String SQL_DELETE_COLLECTION_ITEMS;
sascha@305: public String SQL_DELETE_COLLECTION;
sascha@305: public String SQL_COLLECTION_CHECK_ARTIFACT;
sascha@305: public String SQL_COLLECTION_ITEMS_ID_NEXTVAL;
sascha@305: public String SQL_COLLECTION_ITEMS_INSERT;
sascha@305: public String SQL_COLLECTION_GET_ATTRIBUTE;
sascha@305: public String SQL_COLLECTION_SET_ATTRIBUTE;
sascha@305: public String SQL_COLLECTION_ITEM_GET_ATTRIBUTE;
sascha@305: public String SQL_COLLECTION_ITEM_SET_ATTRIBUTE;
sascha@305: public String SQL_COLLECTIONS_TOUCH_BY_GID;
sascha@305: public String SQL_COLLECTION_ITEM_ID_CID_AID;
sascha@305: public String SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT;
sascha@305: public String SQL_COLLECTION_ITEM_DELETE;
sascha@305: public String SQL_COLLECTIONS_TOUCH_BY_ID;
sascha@305: public String SQL_COLLECTION_ITEMS_LIST_GID;
sascha@305: public String SQL_ALL_ARTIFACTS;
sascha@303:
ingo@128: /** The singleton.*/
ingo@128: protected static Backend instance;
ingo@128:
sascha@305: protected SQLExecutor sqlExecutor;
sascha@305:
sascha@310: protected List listeners;
sascha@310:
sascha@305: protected DBConfig config;
sascha@305:
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@310: listeners = new CopyOnWriteArrayList();
sascha@13: }
sascha@13:
sascha@305: public Backend(DBConfig config) {
sascha@312: this();
sascha@305: this.config = config;
sascha@305: sqlExecutor = new SQLExecutor(config.getDBConnection());
sascha@305: setupSQL(config.getSQL());
sascha@305: }
sascha@305:
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@305: public Backend(DBConfig config, DatabaseCleaner cleaner) {
sascha@305: this(config);
sascha@30: this.cleaner = cleaner;
sascha@30: }
sascha@30:
sascha@305: public DBConfig getConfig() {
sascha@305: return config;
sascha@305: }
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) {
sascha@305: instance = new Backend(DBConfig.getInstance());
ingo@128: }
ingo@128:
ingo@128: return instance;
ingo@128: }
ingo@128:
sascha@305: protected void setupSQL(SQL sql) {
sascha@305: SQL_NEXT_ID = sql.get("artifacts.id.nextval");
sascha@305: SQL_INSERT = sql.get("artifacts.insert");
sascha@305: SQL_UPDATE = sql.get("artifacts.update");
sascha@305: SQL_TOUCH = sql.get("artifacts.touch");
sascha@305: SQL_LOAD_BY_GID = sql.get("artifacts.select.gid");
sascha@305: SQL_GET_ID = sql.get("artifacts.get.id");
sascha@305: SQL_REPLACE = sql.get("artifacts.replace");
sascha@305: SQL_USERS_NEXT_ID = sql.get("users.id.nextval");
sascha@305: SQL_USERS_INSERT = sql.get("users.insert");
sascha@305: SQL_USERS_SELECT_ID_BY_GID = sql.get("users.select.id.by.gid");
sascha@305: SQL_USERS_SELECT_GID = sql.get("users.select.gid");
sascha@305: SQL_USERS_DELETE_ID = sql.get("users.delete.id");
sascha@305: SQL_USERS_DELETE_COLLECTIONS = sql.get("users.delete.collections");
sascha@305: SQL_USERS_SELECT_ALL = sql.get("users.select.all");
sascha@305: SQL_USERS_COLLECTIONS = sql.get("users.collections");
sascha@305: SQL_USERS_COLLECTION_IDS = sql.get("users.collection.ids");
sascha@305: SQL_USERS_DELETE_ALL_COLLECTIONS =
sascha@307: sql.get("users.delete.collections");
sascha@305: SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY =
sascha@305: sql.get("artifacts.in.one.collection.only");
sascha@305: SQL_OUTDATE_ARTIFACTS_COLLECTION =
sascha@305: sql.get("outdate.artifacts.collection");
sascha@305: SQL_UPDATE_COLLECTION_TTL = sql.get("collections.update.ttl");
sascha@305: SQL_UPDATE_COLLECTION_NAME = sql.get("collections.update.name");
sascha@305: SQL_OUTDATE_ARTIFACTS_USER = sql.get("outdate.artifacts.user");
sascha@305: SQL_DELETE_USER_COLLECTION_ITEMS =
sascha@305: sql.get("delete.user.collection.items");
sascha@305: SQL_COLLECTIONS_NEXT_ID = sql.get("collections.id.nextval");
sascha@305: SQL_COLLECTIONS_INSERT = sql.get("collections.insert");
sascha@305: SQL_COLLECTIONS_SELECT_USER = sql.get("collections.select.user");
sascha@305: SQL_COLLECTIONS_SELECT_ALL = sql.get("collections.select.all");
sascha@305: SQL_COLLECTIONS_SELECT_GID = sql.get("collections.select.by.gid");
sascha@305: SQL_COLLECTIONS_CREATION_TIME = sql.get("collection.creation.time");
felix@343: SQL_COLLECTIONS_OLDEST_ARTIFACT = sql.get("collections.artifacts.oldest");
felix@344: SQL_COLLECTIONS_ID_BY_GID = sql.get("collections.id.by.gid");
sascha@305: SQL_DELETE_COLLECTION_ITEMS = sql.get("delete.collection.items");
sascha@305: SQL_DELETE_COLLECTION = sql.get("delete.collection");
sascha@305: SQL_COLLECTION_CHECK_ARTIFACT = sql.get("collection.check.artifact");
sascha@305: SQL_COLLECTION_ITEMS_ID_NEXTVAL =
sascha@305: sql.get("collection.items.id.nextval");
sascha@305: SQL_COLLECTION_ITEMS_INSERT = sql.get("collection.items.insert");
sascha@305: SQL_COLLECTION_GET_ATTRIBUTE = sql.get("collection.get.attribute");
sascha@305: SQL_COLLECTION_SET_ATTRIBUTE = sql.get("collection.set.attribute");
sascha@305: SQL_COLLECTION_ITEM_GET_ATTRIBUTE =
sascha@305: sql.get("collection.item.get.attribute");
sascha@305: SQL_COLLECTION_ITEM_SET_ATTRIBUTE =
sascha@305: sql.get("collection.item.set.attribute");
sascha@305: SQL_COLLECTIONS_TOUCH_BY_GID = sql.get("collections.touch.by.gid");
sascha@305: SQL_COLLECTION_ITEM_ID_CID_AID = sql.get("collection.item.id.cid.aid");
sascha@305: SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT =
sascha@305: sql.get("collection.item.outdate.artifact");
sascha@305: SQL_COLLECTION_ITEM_DELETE = sql.get("collection.item.delete");
sascha@305: SQL_COLLECTIONS_TOUCH_BY_ID = sql.get("collections.touch.by.id");
sascha@305: SQL_COLLECTION_ITEMS_LIST_GID = sql.get("collection.items.list.gid");
sascha@305: SQL_ALL_ARTIFACTS = sql.get("all.artifacts");
sascha@305: }
sascha@305:
sascha@310: public void addListener(BackendListener listener) {
sascha@310: listeners.add(listener);
sascha@313: logger.debug("# listeners: " + listeners.size());
sascha@310: }
sascha@310:
sascha@311: public void addAllListeners(List others) {
sascha@311: listeners.addAll(others);
sascha@313: logger.debug("# listeners: " + listeners.size());
sascha@311: }
sascha@311:
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@303: if (factoryLookup == null) {
sascha@303: logger.error("factory lookup == null");
sascha@303: return false;
sascha@303: }
sascha@303:
sascha@174: final Object [] loaded = new Object[1];
sascha@15:
sascha@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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: 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@313: final int [] id = new int[1];
sascha@313: final boolean [] stored = new boolean[1];
ingo@80:
sascha@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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:
sascha@313: if (stored[0] = 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@313: if (stored[0]) {
sascha@313: fireStoredArtifact(artifact);
sascha@313: }
sascha@313: else {
sascha@313: fireCreatedArtifact(artifact);
sascha@313: }
sascha@313:
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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@311: fireCreatedArtifact(artifact);
sascha@310:
sascha@174: return id[0];
sascha@14: }
sascha@14:
sascha@311: protected void fireCreatedArtifact(Artifact artifact) {
sascha@310: for (BackendListener listener: listeners) {
sascha@311: listener.createdArtifact(artifact, this);
sascha@310: }
sascha@310: }
sascha@310:
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@305: sqlExecutor.new Instance() {
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@310: boolean success = sqlExecutor.new Instance() {
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@310:
sascha@310: if (success) {
sascha@311: fireStoredArtifact(artifact.getArtifact());
sascha@310: }
sascha@14: }
sascha@133:
sascha@311: protected void fireStoredArtifact(Artifact artifact) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.storedArtifact(artifact, this);
sascha@311: }
sascha@311: }
sascha@311:
sascha@311:
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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@311: boolean success = exec.runWrite();
sascha@311:
sascha@311: if (success) {
sascha@311: fireCreatedUser(user[0]);
sascha@311: return user[0];
sascha@311: }
sascha@311:
sascha@311: return null;
sascha@311: }
sascha@311:
sascha@311: protected void fireCreatedUser(User user) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.createdUser(user, this);
sascha@311: }
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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@311: boolean success = exec.runWrite();
sascha@311:
sascha@311: if (success) {
sascha@311: fireDeletedUser(identifier);
sascha@311: }
sascha@311:
sascha@311: return success;
sascha@311: }
sascha@311:
sascha@311: protected void fireDeletedUser(String identifier) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.deletedUser(identifier, this);
sascha@311: }
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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 users = new ArrayList();
sascha@148:
sascha@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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@316: if (collection[0] != null) {
sascha@316: // XXX: Little hack to make the listeners happy
sascha@316: collection[0].setUser(new DefaultUser(ownerIdentifier));
sascha@316: }
sascha@316:
sascha@174: return true;
sascha@159: }
sascha@174: };
sascha@174:
sascha@311: boolean success = exec.runWrite();
sascha@311:
sascha@311: if (success) {
sascha@311: fireCreatedCollection(collection[0]);
sascha@311: return collection[0];
sascha@311: }
sascha@311: return null;
sascha@311: }
sascha@311:
sascha@311: protected void fireCreatedCollection(ArtifactCollection collection) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.createdCollection(collection, this);
sascha@311: }
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:
sascha@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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 collections =
sascha@174: new ArrayList();
sascha@167:
sascha@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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 users =
sascha@235: new HashMap();
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:
felix@343: public String getMasterArtifact(final String collectionId) {
felix@343: if (!isValidIdentifier(collectionId)) {
felix@343: logger.debug("Invalid collection id: '" + collectionId + "'");
felix@343: return null;
felix@343: }
sascha@345: final String [] uuid = new String[1];
sascha@345:
felix@343: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
felix@343: public boolean doIt() throws SQLException {
felix@343: // Fetch masters (oldest artifact) id.
felix@343: prepareStatement(SQL_COLLECTIONS_OLDEST_ARTIFACT);
felix@343: stmnt.setString(1, collectionId);
sascha@345: stmnt.setMaxRows(1); //
felix@343: result = stmnt.executeQuery();
felix@343: if (!result.next()) {
felix@343: logger.debug("No such collection: " + collectionId);
felix@343: return false;
felix@343: }
sascha@345: uuid[0] = result.getString(1);
sascha@345: if (logger.isDebugEnabled()) {
sascha@345: logger.debug("getMasterArtifact result.getString " +
sascha@345: uuid[0]);
sascha@345: }
felix@343: return true;
felix@343: }
felix@343: };
sascha@345: return exec.runRead() ? uuid[0] : null;
felix@343: }
felix@343:
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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
ingo@393: logger.info("Outdate Artifacts that belong to collection: " + id);
ingo@393:
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@311: boolean success = exec.runWrite();
sascha@311:
sascha@311: if (success) {
sascha@311: fireDeletedCollection(collectionId);
sascha@311: }
sascha@311:
sascha@311: return success;
sascha@311: }
sascha@311:
sascha@311: protected void fireDeletedCollection(String identifier) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.deletedCollection(identifier, this);
sascha@311: }
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:
sascha@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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:
sascha@311: boolean success = sqlExecutor.new Instance() {
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();
sascha@311:
sascha@311: if (success) {
sascha@311: fireChangedCollectionAttribute(collectionId, attribute);
sascha@311: }
sascha@311:
sascha@311: return success;
sascha@311: }
sascha@311:
sascha@311: protected void fireChangedCollectionAttribute(
sascha@311: String collectionId,
sascha@311: Document document
sascha@311: ) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.changedCollectionAttribute(collectionId, document, this);
sascha@311: }
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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@311: boolean success = sqlExecutor.new Instance() {
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@311:
sascha@311: if (success) {
sascha@311: fireChangedCollectionItemAttribute(
sascha@311: collectionId, artifactId, attribute);
sascha@311: }
sascha@311:
sascha@311: return success;
sascha@311: }
sascha@311:
sascha@311: protected void fireChangedCollectionItemAttribute(
sascha@311: String collectionId,
sascha@311: String artifactId,
sascha@311: Document document
sascha@311: ) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.changedCollectionItemAttribute(
sascha@311: collectionId, artifactId, document, this);
sascha@311: }
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@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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@311: boolean success = exec.runWrite();
sascha@311:
sascha@311: if (success) {
sascha@311: fireAddedArtifactToCollection(artifactId, collectionId);
sascha@311: }
sascha@311:
sascha@311: return success;
sascha@311: }
sascha@311:
sascha@311: protected void fireAddedArtifactToCollection(
sascha@311: String artifactId,
sascha@311: String collectionId
sascha@311: ) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.addedArtifactToCollection(
sascha@311: artifactId, collectionId, this);
sascha@311: }
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@311:
sascha@311: boolean success = sqlExecutor.new Instance() {
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@311:
sascha@311: if (success) {
sascha@311: fireRemovedArtifactFromCollection(artifactId, collectionId);
sascha@311: }
sascha@311:
sascha@311: return success;
sascha@311: }
sascha@311:
sascha@311: protected void fireRemovedArtifactFromCollection(
sascha@311: String artifactId,
sascha@311: String collectionId
sascha@311: ) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.removedArtifactFromCollection(
sascha@311: artifactId, collectionId, this);
sascha@311: }
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 collectionItems =
sascha@184: new ArrayList();
sascha@184:
sascha@305: SQLExecutor.Instance exec = sqlExecutor.new Instance() {
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:
sascha@305: return sqlExecutor.new Instance() {
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:
sascha@311: boolean success = sqlExecutor.new Instance() {
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();
sascha@311:
sascha@311: if (success) {
sascha@311: fireSetCollectionName(uuid, name);
sascha@311: }
sascha@311:
sascha@311: return success;
sascha@311: }
sascha@311:
sascha@311: protected void fireSetCollectionName(String identifier, String name) {
sascha@311: for (BackendListener listener: listeners) {
sascha@311: listener.setCollectionName(identifier, name);
sascha@311: }
ingo@275: }
sascha@303:
sascha@303: public boolean loadAllArtifacts(final ArtifactLoadedCallback alc) {
sascha@303:
sascha@308: logger.debug("loadAllArtifacts");
sascha@308:
sascha@303: if (factoryLookup == null) {
sascha@303: logger.error("factory lookup == null");
sascha@303: return false;
sascha@303: }
sascha@303:
sascha@308: boolean success = sqlExecutor.new Instance() {
sascha@308: @Override
sascha@303: public boolean doIt() throws SQLException {
sascha@303: // a little cache to avoid too much deserializations.
sascha@308: LRUCache alreadyLoaded =
sascha@308: new LRUCache(200);
sascha@303:
sascha@303: prepareStatement(SQL_ALL_ARTIFACTS);
sascha@303: result = stmnt.executeQuery();
sascha@303: while (result.next()) {
sascha@320: String userId = result.getString("u_gid");
sascha@320: String collectionId = result.getString("c_gid");
sascha@320: String collectionName = result.getString("c_name");
sascha@320: String artifactId = result.getString("a_gid");
sascha@320: String factoryName = result.getString("factory");
sascha@320: Date collectionCreated =
sascha@320: new Date(result.getTimestamp("c_creation").getTime());
sascha@320: Date artifactCreated =
sascha@320: new Date(result.getTimestamp("a_creation").getTime());
sascha@303:
sascha@303: Artifact artifact = alreadyLoaded.get(artifactId);
sascha@303:
sascha@303: if (artifact != null) {
sascha@303: alc.artifactLoaded(
sascha@317: userId,
sascha@317: collectionId, collectionName,
sascha@320: collectionCreated,
sascha@320: artifactId, artifactCreated, artifact);
sascha@303: continue;
sascha@303: }
sascha@303:
sascha@303: ArtifactFactory factory = factoryLookup
sascha@303: .getArtifactFactory(factoryName);
sascha@303:
sascha@303: if (factory == null) {
sascha@303: logger.error("factory '" + factoryName + "' not found");
sascha@303: continue;
sascha@303: }
sascha@303:
sascha@320: byte [] bytes = result.getBytes("data");
sascha@303:
sascha@303: artifact = factory.getSerializer().fromBytes(bytes);
sascha@303:
sascha@303: if (artifact != null) {
sascha@303: alc.artifactLoaded(
sascha@317: userId,
sascha@320: collectionId, collectionName, collectionCreated,
sascha@320: artifactId, artifactCreated, artifact);
sascha@303: }
sascha@303:
sascha@303: alreadyLoaded.put(artifactId, artifact);
sascha@303: }
sascha@303: return true;
sascha@303: }
sascha@303: }.runRead();
sascha@308:
sascha@308: if (logger.isDebugEnabled()) {
sascha@308: logger.debug("loadAllArtifacts success: " + success);
sascha@308: }
sascha@308:
sascha@308: return success;
sascha@303: }
sascha@314:
sascha@314: @Override
sascha@314: public void killedArtifacts(List identifiers) {
sascha@314: logger.debug("killedArtifacts");
sascha@314: for (BackendListener listener: listeners) {
sascha@314: listener.killedArtifacts(identifiers, this);
sascha@314: }
sascha@314: }
sascha@314:
sascha@314: @Override
sascha@314: public void killedCollections(List identifiers) {
sascha@314: logger.debug("killedCollections");
sascha@314: for (BackendListener listener: listeners) {
sascha@314: listener.killedCollections(identifiers, this);
sascha@314: }
sascha@314: }
sascha@13: }
ingo@79: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :