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: */ teichmann@475: package org.dive4elements.artifactdatabase; sascha@13: teichmann@475: import org.dive4elements.artifacts.Artifact; teichmann@475: import org.dive4elements.artifacts.ArtifactCollection; teichmann@475: import org.dive4elements.artifacts.ArtifactCollectionFactory; teichmann@475: import org.dive4elements.artifacts.ArtifactDatabase.ArtifactLoadedCallback; teichmann@475: import org.dive4elements.artifacts.ArtifactFactory; teichmann@475: import org.dive4elements.artifacts.ArtifactSerializer; teichmann@475: import org.dive4elements.artifacts.CollectionItem; teichmann@475: import org.dive4elements.artifacts.User; teichmann@475: import org.dive4elements.artifacts.UserFactory; sascha@13: teichmann@475: import org.dive4elements.artifacts.common.utils.StringUtils; teichmann@475: import org.dive4elements.artifacts.common.utils.XMLUtils; teichmann@475: import org.dive4elements.artifacts.common.utils.LRUCache; sascha@138: teichmann@475: import org.dive4elements.artifactdatabase.db.SQLExecutor; teichmann@475: import org.dive4elements.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; bjoern@413: public String SQL_USERS_SELECT_ACCOUNT; 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; gernotbelger@550: public String SQL_FIND_USER_BY_ARTIFACT; 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: teichmann@541: public SQLExecutor getSQLExecutor() { teichmann@541: return sqlExecutor; teichmann@541: } teichmann@541: 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"); bjoern@413: SQL_USERS_SELECT_ACCOUNT = sql.get("users.select.account"); 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"); gernotbelger@550: SQL_FIND_USER_BY_ARTIFACT = sql.get("find.user.by.artifact"); 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() { teichmann@541: @Override 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, bjoern@410: final String account, 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); bjoern@410: stmnt.setString(4, account); sascha@138: sascha@138: if (roleData == null) { rrenkert@481: stmnt.setNull(5, Types.BINARY); sascha@138: } sascha@138: else { bjoern@410: stmnt.setBytes(5, roleData); sascha@138: } sascha@138: sascha@174: stmnt.execute(); sascha@174: conn.commit(); sascha@138: sascha@174: user[0] = factory.createUser( bjoern@410: identifier, name, account, 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); bjoern@410: String account = result.getString(3); bjoern@410: byte [] roleData = result.getBytes(4); sascha@174: bjoern@432: Document role = null; bjoern@432: if (roleData != null) { bjoern@432: role = XMLUtils.fromByteArray(roleData, true); bjoern@432: } sascha@174: sascha@174: user[0] = factory.createUser( bjoern@410: identifier, name, account, role, context); sascha@174: return true; sascha@147: } sascha@174: }; sascha@147: sascha@174: return exec.runRead() ? user[0] : null; sascha@133: } sascha@133: bjoern@413: /** felix@451: * Find/Get user by account. bjoern@413: */ bjoern@413: public User findUser( bjoern@413: final String account, bjoern@413: final UserFactory factory, bjoern@413: final Object context bjoern@413: ) { bjoern@413: bjoern@413: final User [] user = new User[1]; felix@451: logger.debug("Trying to find user by account " + account); bjoern@413: bjoern@413: SQLExecutor.Instance exec = sqlExecutor.new Instance() { bjoern@413: public boolean doIt() throws SQLException { bjoern@413: prepareStatement(SQL_USERS_SELECT_ACCOUNT); bjoern@413: stmnt.setString(1, account); bjoern@413: result = stmnt.executeQuery(); bjoern@413: if (!result.next()) { // no such user bjoern@413: logger.debug("No user found."); bjoern@413: return false; bjoern@413: } bjoern@413: String identifier = result.getString(1); bjoern@413: String name = result.getString(2); bjoern@413: String account = result.getString(3); bjoern@413: byte [] roleData = result.getBytes(4); bjoern@413: bjoern@432: Document role = null; bjoern@432: if (roleData != null) { bjoern@432: role = XMLUtils.fromByteArray(roleData, true); bjoern@432: } bjoern@413: bjoern@413: user[0] = factory.createUser( bjoern@413: identifier, name, account, role, context); bjoern@413: return true; bjoern@413: } bjoern@413: }; bjoern@413: bjoern@413: return exec.runRead() ? user[0] : null; bjoern@413: } gernotbelger@550: gernotbelger@550: /** Find the owner of a given artifact */ gernotbelger@550: public String findUserName(final String artifactGid) { gernotbelger@550: gernotbelger@550: final String[] returnValue = new String[1]; gernotbelger@550: gernotbelger@550: final SQLExecutor.Instance exec = this.sqlExecutor.new Instance() { gernotbelger@550: gernotbelger@550: @Override gernotbelger@550: public boolean doIt() throws SQLException { gernotbelger@550: gernotbelger@550: prepareStatement(Backend.this.SQL_FIND_USER_BY_ARTIFACT); gernotbelger@550: this.stmnt.setString(1, artifactGid); gernotbelger@550: gernotbelger@550: this.result = this.stmnt.executeQuery(); gernotbelger@550: gernotbelger@550: // final HashMap users = new HashMap(); gernotbelger@550: gernotbelger@550: while (this.result.next()) { gernotbelger@550: // final String userIdentifier = this.result.getString(1); gernotbelger@550: final String userName = this.result.getString(2); gernotbelger@550: gernotbelger@550: // We only need the name at the moment, else we could do this: User user = new LazyBackendUser( gernotbelger@550: // userIdentifier, userFactory, Backend.this, context); gernotbelger@550: returnValue[0] = userName; gernotbelger@550: return true; gernotbelger@550: } gernotbelger@550: gernotbelger@550: return true; gernotbelger@550: } gernotbelger@550: }; gernotbelger@550: gernotbelger@550: if (exec.runRead()) gernotbelger@550: return returnValue[0]; gernotbelger@550: gernotbelger@550: return null; gernotbelger@550: } bjoern@413: 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); bjoern@410: String account = result.getString(4); bjoern@410: byte [] roleData = result.getBytes(5); sascha@174: sascha@235: Document role = XMLUtils.fromByteArray(roleData, true); sascha@174: User user = factory.createUser( bjoern@410: identifier, name, account, 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 :