/*==========================================================================*\ | $Id: UpdateEngine.java,v 1.2 2010/09/27 00:21:13 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2008 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.dbupdate; import java.sql.SQLException; import org.apache.log4j.Logger; // ------------------------------------------------------------------------- /** * An engine that automatically applies necessary updates to a database * schema in order to bring it up to date. This class is a singleton. * The core idea for this framework was inspired by a stepwise article by * James Seigel (wo_james@paddlethis.com) and Christian Pekeler * (christian@pekeler.org): * * http://www.stepwise.com/Articles/2005/DBChanges/index.html * * The original article's code was also modified and posted on WOCode by * Helge Staedtler (lgxxl@web.de) in July 2005: * * http://wocode.com/cgi-bin/WebObjects/WOCode.woa/2/wa/ShareCodeItem?itemId=424 * * The code in this framework uses ideas from both of these sources, together * with some additional redesign work. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.2 $, $Date: 2010/09/27 00:21:13 $ */ public final class UpdateEngine { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new UpdateEngine object. The constructor is private * because this is a singleton class. */ private UpdateEngine() { // everything is initialized lazily, on first use } // ---------------------------------------------------------- /** * Returns the singleton instance of this class. * @return the engine */ public static UpdateEngine instance() { if( engine == null ) { engine = new UpdateEngine(); } return engine; } //~ Public Methods ........................................................ // ---------------------------------------------------------- /** * Applies any necessary updates from the given set in order to bring * the database up to the most recent version. * * @param updates The collection of updates to apply */ public void applyNecessaryUpdates( UpdateSet updates ) { try { if ( !database().hasVersionTable() ) { database().initializeVersionTable(); } if ( database.tryToLock( updates.subsystemName() ) ) { // here all the action takes place... try { executeUpdates( updates ); } finally { database.unlock( updates.subsystemName() ); } } else { waitUntilDBUnlocked( updates.subsystemName() ); } } catch ( RuntimeException e ) { log.error( "updates for " + updates.subsystemName() + " failed", e ); throw e; } catch ( Exception e ) { log.error( "updates for " + updates.subsystemName() + " failed", e ); throw new com.webobjects.foundation.NSForwardException( e ); } finally { database.close(); } } // ---------------------------------------------------------- /** * Get the database to which updates will be applied. * @return the database */ public Database database() { if ( database == null ) { database = new MySQLDatabase(); } return database; } // ---------------------------------------------------------- /** * Set the database to which updates will be applied. * @param database the database to operate on */ public void setDatabase( Database database ) { this.database = database; } //~ Private Methods ....................................................... protected void executeUpdates( UpdateSet updates ) throws Exception { int latestVersion = database.currentVersionNumber( updates.subsystemName() ); String name = updates.subsystemName(); if ( updates.supportsVersion( latestVersion ) ) { updates.setDatabase( database() ); updates.setStartingVersion( latestVersion ); latestVersion++; while ( updates.applyUpdateIncrement( latestVersion ) ) { log.info( "updated " + name + " to v." + latestVersion ); database.setVersionNumber( name, latestVersion ); latestVersion++; } } else { Exception e = new IllegalStateException( "illegal database version " + latestVersion + " for subsystem " + updates.subsystemName() ); log.fatal( "unsupported database version", e ); throw e; } } protected void waitUntilDBUnlocked(String subsystemName) { int waitCount = 0; do { log.info("waiting while another application instance " + "performs DB updates"); try { Thread.sleep(5 * 1000); } catch (InterruptedException exception) { // do nothing } waitCount++; } while (database().isLocked(subsystemName) && waitCount <= WAIT_LIMIT); if (database().isLocked(subsystemName)) { // Give up and try to forcibly unlock, assuming that // the original lock holder is dead try { database().unlock(subsystemName); } catch (SQLException e) { log.error( "Unable to remove stale lock for " + subsystemName, e); } } } //~ Instance/static variables ............................................. private Database database; private static UpdateEngine engine; private static final int WAIT_LIMIT = 6; static Logger log = Logger.getLogger( UpdateEngine.class ); }