# HG changeset patch # User Sascha L. Teichmann # Date 1269626390 0 # Node ID 68285f7bc476d17719db93cfb80870b9f95e35a3 # Parent d348fe1fd82261e90f184c23010a7c148a2ed6e0 More javadoc. artifacts/trunk@846 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r d348fe1fd822 -r 68285f7bc476 ChangeLog --- a/ChangeLog Fri Mar 26 16:16:32 2010 +0000 +++ b/ChangeLog Fri Mar 26 17:59:50 2010 +0000 @@ -1,3 +1,15 @@ +2010-03-26 Sascha L. Teichmann + + * artifact-database/src/main/java/de/intevation/artifactdatabase/ProxyArtifact.java, + artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultPreferredLocale.java, + artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java, + artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java, + artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java, + artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java, + artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultServiceFactory.java, + artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java: + Even more javadoc. + 2010-03-26 Sascha L. Teichmann * artifact-database/src/main/java/de/intevation/artifactdatabase/ProxyArtifact.java, diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java Fri Mar 26 17:59:50 2010 +0000 @@ -15,6 +15,9 @@ import org.apache.log4j.Logger; /** + * The backend implements the low level layer used to store artifacts + * in a SQL database. + * * @author Sascha L. Teichmann */ public class Backend @@ -22,37 +25,92 @@ { 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 { @@ -60,6 +118,14 @@ 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, @@ -72,18 +138,34 @@ 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()); @@ -91,6 +173,9 @@ Backend.this.store(this); } + /** + * Only touches the access time of the artifact. + */ public void touch() { if (logger.isDebugEnabled()) { logger.debug("touching artifact id = " + getId()); @@ -99,26 +184,59 @@ } } // 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, @@ -133,6 +251,17 @@ 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, @@ -147,12 +276,32 @@ 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( @@ -176,6 +325,13 @@ }); } + /** + * 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)) { @@ -250,6 +406,11 @@ 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); @@ -268,7 +429,8 @@ .getArtifactFactory(factoryName); if (factory == null) { - logger.error("reviveArtifact: no factory '" + factoryName + "' found"); + logger.error( + "reviveArtifact: no factory '" + factoryName + "' found"); return null; } @@ -277,6 +439,15 @@ 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, @@ -391,6 +562,13 @@ 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, @@ -470,6 +648,11 @@ 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 { @@ -508,6 +691,11 @@ } } + /** + * Writes modification of an artifact back to the database. + * @param artifact The persistent wrapper around a living + * artifact. + */ public void store(PersistentArtifact artifact) { try { diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java Fri Mar 26 17:59:50 2010 +0000 @@ -15,46 +15,111 @@ import java.util.List; /** + * The database cleaner runs in background. It sleep for a configurable + * while and when it wakes up it removes outdated artifacts from the + * database. Outdated means that the the last access to the artifact + * is longer aga then the time to live of this artifact.
+ * Before the artifact is finally removed from the system it is + * revived one last time an the #endOfLife() method of the artifact + * is called.
+ * The artifact implementations may e.g. use this to remove some extrenal + * resources form the system. + * * @author Sascha L. Teichmann */ public class DatabaseCleaner extends Thread { + /** + * Implementors of this interface are able to create a + * living artifact from a given byte array. + */ public interface ArtifactReviver { + /** + * Called to revive an artifact from a given byte array. + * @param factoryName The name of the factory which + * created this artifact. + * @param bytes The bytes of the serialized artifact. + * @return The revived artfiact. + */ Artifact reviveArtifact(String factoryName, byte [] bytes); } // interface ArtifactReviver private static Logger logger = Logger.getLogger(DatabaseCleaner.class); + /** + * Number of artifacts to be loaded at once. Used to + * mitigate the problem of a massive denial of service + * if too many artifacts have died since last cleanup. + */ public static final int MAX_ROWS = 50; + /** + * The SQL statement to select the outdated artifacts. + */ public static final String SQL_OUTDATED = SQL.get("artifacts.outdated"); + /** + * The SQL statement to delete some artifacts from the database. + */ public static final String SQL_DELETE = SQL.get("artifacts.delete"); + /** + * XPath to figure out how long the cleaner should sleep between + * cleanups. This is stored in the global configuration. + */ public static final String SLEEP_XPATH = "/artifact-database/cleaner/sleep-time/text()"; + /** + * Default nap time between cleanups: 5 minutes. + */ public static final long SLEEP_DEFAULT = 5 * 60 * 1000L; // 5 minutes + /** + * The configured nap time. + */ protected long sleepTime; + /** + * Internal locking mechanism to prevent some race conditions. + */ protected Object sleepLock = new Object(); + /** + * A reference to the global context. + */ protected Object context; + /** + * A specialized Id filter which only delete some artifacts. + * This is used to prevent deletion of living artifacts. + */ protected Id.Filter filter; + /** + * The reviver used to bring the dead artifact on last + * time back to live to call endOfLife() on them. + */ protected ArtifactReviver reviver; + /** + * Default constructor. + */ public DatabaseCleaner() { } + /** + * Constructor to create a cleaner with a given global context + * and an given reviver. + * @param context The global context of the artifact database + * @param reviver The reviver to awake artifact one last time. + */ public DatabaseCleaner(Object context, ArtifactReviver reviver) { setDaemon(true); sleepTime = getSleepTime(); @@ -62,16 +127,32 @@ this.reviver = reviver; } + /** + * Sets the filter that prevents deletion of living artifacts. + * Living artifacts are artifacts which are currently active + * inside the artifact database. Deleting them in this state + * would create severe internal problems. + * @param filter + */ public void setFilter(Id.Filter filter) { this.filter = filter; } + /** + * External hook to tell the cleaner to wake up before its + * regular nap time is over. This is the case when the artifact + * database finds an artifact which is already outdated. + */ public void wakeup() { synchronized (sleepLock) { sleepLock.notify(); } } + /** + * Fetches the sleep time from the global configuration. + * @return the time to sleep between database cleanups in ms. + */ protected static long getSleepTime() { String sleepTimeString = Config.getStringXPath(SLEEP_XPATH); @@ -202,6 +283,11 @@ logger.info("artifacts removed: " + removedArtifacts); } + /** + * The main code of the cleaner. It sleeps for the configured + * nap time, cleans up the database, sleeps again and so on. + */ + @Override public void run() { logger.info("sleep time: " + sleepTime + "ms"); for (;;) { @@ -224,4 +310,4 @@ } // for (;;) } } -// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8: +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactContext.java Fri Mar 26 17:59:50 2010 +0000 @@ -12,29 +12,62 @@ */ public class DefaultArtifactContext { + /** + * The global configuration document of the artifact database. + */ protected Document config; + /** + * Custom key/value pairs to be used globally in the whole server. + */ protected HashMap map; + /** + * Default constructor + */ public DefaultArtifactContext() { this(null); } + /** + * Constructor to create a context with a given global + * configuration document and an empty map of custom + * key/value pairs. + * @param config + */ public DefaultArtifactContext(Document config) { this.config = config; map = new HashMap(); } + /** + * Fetch a custom value from the global key/value map using + * a given key. + * @param key The key. + * @return The stored value or null if no value was found under + * this key. + */ public synchronized Object get(Object key) { return map.get(key); } + /** + * Store a custom key/value pair in the global map. + * @param key The key to store + * @param value The value to store + * @return The old value registered under the key or null + * if none wa there before. + */ public synchronized Object put(Object key, Object value) { return map.put(key, value); } + /** + * Returns a reference to the global configuration document. + * @return The global configuration document. + */ public Document getConfig() { return config; } } -// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8: +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultArtifactFactory.java Fri Mar 26 17:59:50 2010 +0000 @@ -9,6 +9,12 @@ import de.intevation.artifacts.ArtifactSerializer; /** + * Trivial implementation of the ArtifactFactory interface. + * Time to live (ttl), name and description are configured + * via the Node given to #setup(Document, Node) with attributes + * of same name. The class name of the artifacts to be build by this + * factory is configures with the attribute 'artifact'. + * * @author Sascha L. Teichmann */ public class DefaultArtifactFactory @@ -17,25 +23,60 @@ private static Logger logger = Logger.getLogger(DefaultArtifactFactory.class); + /** + * XPath to access the TTL of this artifact. + */ public static final String XPATH_TTL = "@ttl"; + /** + * XPath to access the name of this factory. + */ public static final String XPATH_NAME = "@name"; + /** + * XPath to access the description of this artifact factory. + */ public static final String XPATH_DESCRIPTION = "@description"; + /** + * XPath to access the class name of the artifacts to be build + * by this factory. + */ public static final String XPATH_ARTIFACT = "@artifact"; + /** + * Default description of this factory if none is given by the + * configuration. + */ public static final String DEFAULT_DESCRIPTION = "No description available"; + /** + * Class to load if no artifact class is given in the configuration. + */ public static final String DEFAULT_ARTIFACT = "de.intevation.artifactdatabase.DefaultArtifact"; + /** + * The Time to live of the artifacts build by this factory. + */ protected Long ttl; + /** + * The name of this factory. + */ protected String name; + /** + * The description of this factory. + */ protected String description; + /** + * The class of the artifacts to be build by this factory. + */ protected Class artifactClass; + /** + * Default constructor. + */ public DefaultArtifactFactory() { } @@ -47,10 +88,11 @@ return description; } - public Artifact createArtifact(String identifier, - Object context, - Document data) { - + public Artifact createArtifact( + String identifier, + Object context, + Document data + ) { try { Artifact artifact = (Artifact)artifactClass.newInstance(); diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultPreferredLocale.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultPreferredLocale.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultPreferredLocale.java Fri Mar 26 17:59:50 2010 +0000 @@ -7,7 +7,7 @@ /** * Models a pair of Locale and quality (0.0-1.0) to be used to * find best matching locale between server offerings and clients requests. - * + * * @author Sascha L. Teichmann */ public class DefaultPreferredLocale diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultService.java Fri Mar 26 17:59:50 2010 +0000 @@ -11,7 +11,7 @@ /** * Trivial implementation of an artifact database service. Useful to * be subclassed. - * + * * @author Sascha L. Teichmann */ public class DefaultService diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultServiceFactory.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultServiceFactory.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DefaultServiceFactory.java Fri Mar 26 17:59:50 2010 +0000 @@ -14,7 +14,7 @@ * #setup(Document, Node) via the 'name' and 'description' attributes. * The name of the class that provides the concrete serice is configured * by the 'service' attribute. - * + * * @author Sascha L. Teichmann */ public class DefaultServiceFactory diff -r d348fe1fd822 -r 68285f7bc476 artifact-database/src/main/java/de/intevation/artifactdatabase/ProxyArtifact.java --- a/artifact-database/src/main/java/de/intevation/artifactdatabase/ProxyArtifact.java Fri Mar 26 16:16:32 2010 +0000 +++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/ProxyArtifact.java Fri Mar 26 17:59:50 2010 +0000 @@ -21,11 +21,11 @@ * An inner artifact is able to replace itself by indirectly hand over * the replacement via the call context to the proxy artifact.
* To do so the proxied artifact has to call - * callContext.getContextValue(EPLACE_PROXY, replacement);. + * callContext.getContextValue(EPLACE_PROXY, replacement);. * After the current call (describe, feed, advance and out) of the proxied * artifact is finished the proxy artifact replaces the former proxied artifact * with the replacement. - * + * * @author Sascha L. Teichmann */ public class ProxyArtifact