Mercurial > dive4elements > framework
view artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java @ 129:10d726907419
Bugfix: The artifacts package can be compiled with local modifications again.
artifacts/trunk@1353 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Ingo Weinzierl <ingo.weinzierl@intevation.de> |
---|---|
date | Wed, 02 Mar 2011 07:28:20 +0000 |
parents | 933bbc9fc11f |
children | b2115f484edb |
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 java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.apache.log4j.Logger; /** * 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.<br> * Before the artifact is finally removed from the system it is * revived one last time an the #endOfLife() method of the artifact * is called.<br> * The artifact implementations may e.g. use this to remove some extrenal * resources form the system. * * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a> */ 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 a 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(); this.context = context; 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); if (sleepTimeString == null) { return SLEEP_DEFAULT; } try { // sleep at least one second return Math.max(Long.parseLong(sleepTimeString), 1000L); } catch (NumberFormatException nfe) { logger.warn("Cleaner sleep time defaults to " + SLEEP_DEFAULT); } return SLEEP_DEFAULT; } private static final class IdData extends Id { byte [] data; String factoryName; public IdData(int id, String factoryName, byte [] data) { super(id); this.factoryName = factoryName; this.data = data; } } // class IdData /** * Cleaning is done in two phases. First we fetch a list of ids * of artifacts. If there are artifacts the cleaning is done. * Second we load the artifacts one by one one and call there * endOfLife() method. In this loop we remove them from database, too. * Each deletion is commited to ensure that a sudden failure * of the artifact database server does delete artifacts twice * or does not delete them at all. After this the first step * is repeated. */ protected void cleanup() { logger.info("database cleanup"); Connection connection = null; PreparedStatement fetchIds = null; PreparedStatement deleteId = null; ResultSet result = null; int removedArtifacts = 0; DataSource dataSource = DBConnection.getDataSource(); try { connection = dataSource.getConnection(); connection.setAutoCommit(false); fetchIds = connection.prepareStatement(SQL_OUTDATED); deleteId = connection.prepareStatement(SQL_DELETE); // some dbms like derby do not support LIMIT // in SQL statements. fetchIds.setMaxRows(MAX_ROWS); for (;;) { List ids = new ArrayList(); result = fetchIds.executeQuery(); while (result.next()) { ids.add(new IdData( result.getInt(1), result.getString(2), result.getBytes(3))); } result.close(); result = null; if (ids.isEmpty()) { break; } if (filter != null) { ids = filter.filterIds(ids); } for (int i = ids.size()-1; i >= 0; --i) { IdData idData = (IdData)ids.get(i); Artifact artifact = reviver.reviveArtifact( idData.factoryName, idData.data); idData.data = null; deleteId.setInt(1, idData.id); deleteId.execute(); connection.commit(); try { if (artifact != null) { artifact.endOfLife(context); } } catch (Exception e) { logger.error(e.getLocalizedMessage(), e); } } // for all fetched data removedArtifacts += ids.size(); } } catch (SQLException sqle) { logger.error(sqle.getLocalizedMessage(), sqle); } finally { if (result != null) { try { result.close(); } catch (SQLException sqle) {} } if (fetchIds != null) { try { fetchIds.close(); } catch (SQLException sqle) {} } if (deleteId != null) { try { deleteId.close(); } catch (SQLException sqle) {} } if (connection != null) { try { connection.close(); } catch (SQLException sqle) {} } } 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 (;;) { cleanup(); long startTime = System.currentTimeMillis(); try { synchronized (sleepLock) { sleepLock.wait(sleepTime); } } catch (InterruptedException ie) { } long stopTime = System.currentTimeMillis(); if (logger.isDebugEnabled()) { logger.debug("Cleaner slept " + (stopTime - startTime) + "ms"); } } // for (;;) } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :