Mercurial > dive4elements > framework
view artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java @ 100:933bbc9fc11f
Added license file and license headers.
artifacts/trunk@1259 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Ingo Weinzierl <ingo.weinzierl@intevation.de> |
---|---|
date | Tue, 02 Nov 2010 17:23:36 +0000 |
parents | e27cf9c84eb8 |
children | bfa65a812c7a |
line wrap: on
line source
/* * Copyright (c) 2010 by Intevation GmbH * * This program is free software under the LGPL (>=v2.1) * Read the file LGPL.txt coming with the software for details * or visit http://www.gnu.org/licenses/ if it does not exist. */ package de.intevation.artifactdatabase; import de.intevation.artifacts.Artifact; import de.intevation.artifacts.ArtifactFactory; import de.intevation.artifacts.ArtifactSerializer; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import javax.sql.DataSource; import org.apache.log4j.Logger; /** * The backend implements the low level layer used to store artifacts * in a SQL database. * * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a> */ public class Backend implements DatabaseCleaner.ArtifactReviver { private static Logger logger = Logger.getLogger(Backend.class); /** * The SQL statement to create new artifact id inside the database. */ public static final String SQL_NEXT_ID = SQL.get("artifacts.id.nextval"); /** * The SQL statement to insert an artifact into the database. */ public static final String SQL_INSERT = SQL.get("artifacts.insert"); /** * The SQL statement to update some columns of an existing * artifact in the database. */ public static final String SQL_UPDATE = SQL.get("artifacts.update"); /** * The SQL statement to touch the access time of an * artifact inside the database. */ public static final String SQL_TOUCH = SQL.get("artifacts.touch"); /** * The SQL statement to load an artifact by a given * identifier from the database. */ public static final String SQL_LOAD_BY_GID = SQL.get("artifacts.select.gid"); /** * The SQL statement to get the database id of an artifact * identified by the identifier. */ public static final String SQL_GET_ID = SQL.get("artifacts.get.id"); /** * The SQL statement to replace the content of an * existing artifact inside the database. */ public static final String SQL_REPLACE = SQL.get("artifacts.replace"); /** * The database cleaner. Reference is stored here because * the cleaner is woken up if the backend finds an outdated * artifact. This artifact should be removed as soon as * possible. */ protected DatabaseCleaner cleaner; /** * To revive an artifact from the bytes coming from the database * we need the artifact factory which references the artifact * serializer which is able to do the reviving job. */ protected FactoryLookup factoryLookup; /** * Little helper interface to decouple the ArtifactDatabase * from the Backend. A ArtifactDatabase should depend on a * Backend but a Backend not from an ArtifactDatabase. */ public interface FactoryLookup { /** * Returns an ArtifactFactory which is bound to a given name. * @param factoryName The name of the artifact factory. * @return The ArtifactFactory bound to the factory name or * null if not matching factory is found. */ ArtifactFactory getArtifactFactory(String factoryName); } // interface FactoryLookup /** * Inner class that brigdes between the persisten form of the * artifact and the living one inside the artifact database. * After the describe(), feed(), advance() and out() operations * of the artifact it must be possible to write to modified artifact * back into the database. */ public final class PersistentArtifact extends Id { private Artifact artifact; private ArtifactSerializer serializer; private Long ttl; /** * Cronstructor to create a persistent artifact. * @param artifact The living artifact. * @param serializer The serializer to store the artifact * after the operations. * @param ttl The time to life of the artifact. * @param id The database id of the artifact. */ public PersistentArtifact( Artifact artifact, ArtifactSerializer serializer, Long ttl, int id ) { super(id); this.artifact = artifact; this.serializer = serializer; this.ttl = ttl; } /** * Returns the wrapped living artifact. * @return the living artifact. */ public Artifact getArtifact() { return artifact; } /** * Returns the serialized which is able to write a * modified artifact back into the database. * @return The serializer. */ public ArtifactSerializer getSerializer() { return serializer; } /** * The time to life of the artifact. * @return The time to live. */ public Long getTTL() { return ttl; } /** * Stores the living artifact back into the database. */ public void store() { if (logger.isDebugEnabled()) { logger.debug("storing artifact id = " + getId()); } Backend.this.store(this); } /** * Only touches the access time of the artifact. */ public void touch() { if (logger.isDebugEnabled()) { logger.debug("touching artifact id = " + getId()); } Backend.this.touch(this); } } // class ArtifactWithId /** * Default constructor */ public Backend() { } /** * Constructor to create a backend with a link to the database cleaner. * @param cleaner The clean which periodically removes outdated * artifacts from the database. */ public Backend(DatabaseCleaner cleaner) { this.cleaner = cleaner; } /** * Sets the factory lookup mechanism to decouple ArtifactDatabase * and Backend. * @param factoryLookup */ public void setFactoryLookup(FactoryLookup factoryLookup) { this.factoryLookup = factoryLookup; } /** * Sets the database cleaner explicitly. * @param cleaner The database cleaner */ public void setCleaner(DatabaseCleaner cleaner) { this.cleaner = cleaner; } /** * Returns a new unique identifier to external identify * the artifact across the system. This implementation * uses random UUIDs v4 to achieve this target. * @return the new identifier */ public String newIdentifier() { // TODO: check database for collisions. return StringUtils.newUUID(); } /** * Stores a new artifact into the database. * @param artifact The artifact to be stored * @param factory The factory which build the artifact * @param ttl The initial time to life of the artifact. * @return A persistent wrapper around the living * artifact to be able to write modification later. * @throws Exception Thrown if something went wrong with the * storage process. */ public PersistentArtifact storeInitially( Artifact artifact, ArtifactFactory factory, Long ttl ) throws Exception { return new PersistentArtifact( artifact, factory.getSerializer(), ttl, insertDatabase(artifact, factory, ttl)); } /** * Stores an artifact into database if it does not exist there. * If it exists there it is only updated. * @param artifact The artifact to store/update. * @param factory The factory which created the artifact. * @param ttl The initial time to live of the artifact. * @return A persistent version of the artifact to be able * to store a modification later. * @throws Exception Thrown if something went wrong during * storing/updating. */ public PersistentArtifact storeOrReplace( Artifact artifact, ArtifactFactory factory, Long ttl ) throws Exception { return new PersistentArtifact( artifact, factory.getSerializer(), ttl, storeOrReplaceDatabase(artifact, factory, ttl)); } /** * Implementors of this interface are able to process the raw * artifact data from the database for loading. */ public interface ArtifactLoader { /** * Creates a custom object from the raw artifact database data. * @param factory The factory that created this artifact. * @param ttl The current time to life of the artifact. * @param bytes The raw artifact bytes from the database. * @param id The database id of the artifact. * @return The custom object created by the implementation. */ Object load(ArtifactFactory factory, Long ttl, byte [] bytes, int id); } // interface ArtifactLoader /** * Fetches an artifact from the database identified by the * given identifier. * @param identifer The identifier of the artifact. * @return A persistent wrapper around the found artifact * to be able to write back a modifaction later or null * if no artifact is found for this identifier. */ public PersistentArtifact getArtifact(String identifer) { return (PersistentArtifact)loadArtifact( identifer, new ArtifactLoader() { public Object load( ArtifactFactory factory, Long ttl, byte [] bytes, int id ) { ArtifactSerializer serializer = factory.getSerializer(); Artifact artifact = serializer.fromBytes(bytes); return artifact == null ? null : new PersistentArtifact(artifact, serializer, ttl, id); } }); } /** * More general loading mechanism for artifacts. The concrete * load processing is delegated to the given loader. * @param identifer The identifier of the artifact. * @param loader The loader which processes the raw database data. * @return The object created by the loader. */ public Object loadArtifact(String identifer, ArtifactLoader loader) { if (!StringUtils.checkUUID(identifer)) { return null; } Connection connection = null; PreparedStatement stmnt_load = null; ResultSet load_result = null; DataSource dataSource = DBConnection.getDataSource(); try { connection = dataSource.getConnection(); stmnt_load = connection.prepareStatement(SQL_LOAD_BY_GID); stmnt_load.setString(1, identifer); load_result = stmnt_load.executeQuery(); if (!load_result.next()) { return null; } int id = load_result.getInt(1); long ttlX = load_result.getLong(3); Long ttl = load_result.wasNull() ? null : Long.valueOf(ttlX); if (ttl != null) { // real time to life long last_access = load_result.getTimestamp(2).getTime(); if (last_access + ttlX < System.currentTimeMillis()) { artifactOutdated(id); return null; } } String factoryName = load_result.getString(4); if (factoryLookup == null) { logger.error("factory lookup == null"); return null; } ArtifactFactory factory = factoryLookup .getArtifactFactory(factoryName); if (factory == null) { logger.error("factory '" + factoryName + "' not found"); return null; } byte [] bytes = load_result.getBytes(5); return loader.load(factory, ttl, bytes, id); } catch (SQLException sqle) { logger.error(sqle.getLocalizedMessage(), sqle); } finally { if (load_result != null) { try { load_result.close(); } catch (SQLException sqle) {} } if (stmnt_load != null) { try { load_result.close(); } catch (SQLException sqle) {} } if (connection != null) { try { connection.close(); } catch (SQLException sqle) {} } } return null; } /** * Called if the load mechanism found an outdated artifact. * It wakes up the database cleaner. * @param id The id of the outdated artifact. */ protected void artifactOutdated(int id) { if (logger.isDebugEnabled()) { logger.info("artifactOutdated: id = " + id); } if (cleaner != null) { cleaner.wakeup(); } } public Artifact reviveArtifact(String factoryName, byte [] bytes) { if (factoryLookup == null) { logger.error("reviveArtifact: factory lookup == null"); return null; } ArtifactFactory factory = factoryLookup .getArtifactFactory(factoryName); if (factory == null) { logger.error( "reviveArtifact: no factory '" + factoryName + "' found"); return null; } ArtifactSerializer serializer = factory.getSerializer(); return serializer.fromBytes(bytes); } /** * Internal method to store/replace an artifact inside the database. * If an artifact with the given identifier does not exists it is * created else only the content data is updated. * @param artifact The artifact to be store/update inside the database. * @param factory The factory that created the artifact. * @param ttl The initial time to life of the artifact. * @return The database id of the stored/updated artifact. */ protected int storeOrReplaceDatabase( Artifact artifact, ArtifactFactory factory, Long ttl ) { String uuid = artifact.identifier(); if (!StringUtils.checkUUID(uuid)) { throw new RuntimeException("No valid UUID"); } Connection connection = null; PreparedStatement stmnt = null; ResultSet result = null; DataSource dataSource = DBConnection.getDataSource(); try { connection = dataSource.getConnection(); try { connection.setAutoCommit(false); stmnt = connection.prepareStatement(SQL_GET_ID); stmnt.setString(1, uuid); result = stmnt.executeQuery(); Integer ID = result.next() ? Integer.valueOf(result.getInt(1)) : null; result.close(); result = null; stmnt.close(); stmnt = null; if (ID != null) { // already in database int id = ID.intValue(); stmnt = connection.prepareStatement(SQL_REPLACE); if (ttl == null) { stmnt.setNull(1, Types.BIGINT); } else { stmnt.setLong(1, ttl.longValue()); } stmnt.setString(2, factory.getName()); stmnt.setBytes( 3, factory.getSerializer().toBytes(artifact)); stmnt.setInt(4, id); stmnt.execute(); connection.commit(); return id; } stmnt = connection.prepareStatement(SQL_NEXT_ID); result = stmnt.executeQuery(); if (!result.next()) { throw new RuntimeException("No id generated"); } int id = result.getInt(1); result.close(); result = null; stmnt.close(); stmnt = null; stmnt = connection.prepareStatement(SQL_INSERT); stmnt.setInt(1, id); stmnt.setString(2, uuid); if (ttl == null) { stmnt.setNull(3, Types.BIGINT); } else { stmnt.setLong(3, ttl.longValue()); } stmnt.setString(4, factory.getName()); stmnt.setBytes( 5, factory.getSerializer().toBytes(artifact)); stmnt.execute(); connection.commit(); return id; } catch (SQLException sqle) { connection.rollback(); throw sqle; } } catch (SQLException sqle) { logger.error(sqle.getLocalizedMessage(), sqle); } finally { if (result != null) { try { result.close(); } catch (SQLException sqle) {} } if (stmnt != null) { try { stmnt.close(); } catch (SQLException sqle) {} } if (connection != null) { try { connection.close(); } catch (SQLException sqle) {} } } throw new RuntimeException("failed insert artifact into database"); } /** * Internal method to store an artifact inside the database. * @param artifact The artifact to be stored. * @param factory The factory which created the artifact. * @param ttl The initial time to live of the artifact. * @return The database id of the stored artifact. */ protected int insertDatabase( Artifact artifact, ArtifactFactory factory, Long ttl ) { String uuid = artifact.identifier(); Connection connection = null; PreparedStatement stmnt_next_id = null; PreparedStatement stmnt_insert = null; ResultSet res_id = null; DataSource dataSource = DBConnection.getDataSource(); try { connection = dataSource.getConnection(); try { connection.setAutoCommit(false); stmnt_next_id = connection.prepareStatement(SQL_NEXT_ID); stmnt_insert = connection.prepareStatement(SQL_INSERT); res_id = stmnt_next_id.executeQuery(); if (!res_id.next()) { throw new RuntimeException("No id generated"); } int id = res_id.getInt(1); stmnt_insert.setInt(1, id); stmnt_insert.setString(2, uuid); if (ttl == null) { stmnt_insert.setNull(3, Types.BIGINT); } else { stmnt_insert.setLong(3, ttl.longValue()); } stmnt_insert.setString(4, factory.getName()); stmnt_insert.setBytes( 5, factory.getSerializer().toBytes(artifact)); stmnt_insert.execute(); connection.commit(); return id; } catch (SQLException sqle) { connection.rollback(); throw sqle; } } catch (SQLException sqle) { logger.error(sqle.getLocalizedMessage(), sqle); } finally { if (res_id != null) { try { res_id.close(); } catch (SQLException sqle) {} } if (stmnt_insert != null) { try { stmnt_insert.close(); } catch (SQLException sqle) {} } if (stmnt_next_id != null) { try { stmnt_next_id.close(); } catch (SQLException sqle) {} } if (connection != null) { try { connection.close(); } catch (SQLException sqle) {} } } throw new RuntimeException("failed insert artifact into database"); } /** * Touches the access timestamp of a given artifact to prevent * that it will be removed from the database by the database cleaner. * @param artifact The persistent wrapper around the living artifact. */ public void touch(PersistentArtifact artifact) { try { Connection connection = null; PreparedStatement stmnt_touch = null; DataSource dataSource = DBConnection.getDataSource(); try { connection = dataSource.getConnection(); try { connection.setAutoCommit(false); stmnt_touch = connection.prepareStatement(SQL_TOUCH); stmnt_touch.setInt(1, artifact.getId()); stmnt_touch.execute(); connection.commit(); } catch (SQLException sqle) { connection.rollback(); } } catch (SQLException sqle) { logger.error(sqle.getLocalizedMessage(), sqle); } finally { if (stmnt_touch != null) { try { stmnt_touch.close(); } catch (SQLException sqle) {} } if (connection != null) { try { connection.close(); } catch (SQLException sqle) {} } } } catch (Exception e) { logger.error(e.getLocalizedMessage(), e); } } /** * Writes modification of an artifact back to the database. * @param artifact The persistent wrapper around a living * artifact. */ public void store(PersistentArtifact artifact) { try { Connection connection = null; PreparedStatement stmnt_update = null; DataSource dataSource = DBConnection.getDataSource(); try { connection = dataSource.getConnection(); try { connection.setAutoCommit(false); stmnt_update = connection.prepareStatement(SQL_UPDATE); stmnt_update.setInt(2, artifact.getId()); byte [] bytes = artifact .getSerializer() .toBytes(artifact.getArtifact()); stmnt_update.setBytes(1, bytes); stmnt_update.execute(); connection.commit(); } catch (SQLException sqle) { connection.rollback(); } } catch (SQLException sqle) { logger.error(sqle.getLocalizedMessage(), sqle); } finally { if (stmnt_update != null) { try { stmnt_update.close(); } catch (SQLException sqle) {} } if (connection != null) { try { connection.close(); } catch (SQLException sqle) {} } } } catch (Exception e) { logger.error(e.getLocalizedMessage(), e); } } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :