Java tutorial
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at license/ESCIDOC.LICENSE * or http://www.escidoc.de/license. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at license/ESCIDOC.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006-2009 Fachinformationszentrum Karlsruhe Gesellschaft * fuer wissenschaftlich-technische Information mbH and Max-Planck- * Gesellschaft zur Foerderung der Wissenschaft e.V. * All rights reserved. Use is subject to license terms. */ package de.escidoc.core.admin.business; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; import java.util.Collection; import java.util.TreeSet; import javax.sql.DataSource; import org.apache.tools.ant.Project; import org.apache.tools.ant.Target; import org.apache.tools.ant.taskdefs.SQLExec; import org.apache.tools.ant.types.FileSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.ResultSetExtractor; import de.escidoc.core.admin.business.interfaces.AaMigrationInterface; import de.escidoc.core.admin.business.interfaces.DataBaseMigrationInterface; import de.escidoc.core.admin.business.interfaces.SmMigrationInterface; import de.escidoc.core.common.exceptions.system.ApplicationServerSystemException; import de.escidoc.core.common.exceptions.system.IntegritySystemException; import de.escidoc.core.common.util.Version; import de.escidoc.core.common.util.db.Fingerprint; /** * Provides a method used for migrating the database to the most current * version. * * @author André Schenk * * @spring.bean id="de.escidoc.core.admin.DataBaseMigrationTool" * */ public class DataBaseMigrationTool extends DbDao implements DataBaseMigrationInterface { /** * Database table name for the version information. */ private static final String VERSION_TABLE_NAME = "adm.version"; /** * Database column name for the major number. */ private static final String COLUMN_MAJOR_NUMBER = "major_number"; /** * Database column name for the minor number. */ private static final String COLUMN_MINOR_NUMBER = "minor_number"; /** * Database column name for the owner name. */ private static final String COLUMN_USENAME = "usename"; /** * Database column name for the revision number. */ private static final String COLUMN_REVISION_NUMBER = "revision_number"; /** * Database column name for the version date. */ private static final String COLUMN_DATE = "date"; /** * Database query to get the latest version. */ private static final String QUERY_LATEST_VERSION = "SELECT * FROM " + VERSION_TABLE_NAME + " WHERE \"" + COLUMN_DATE + "\"=(SELECT MAX(\"" + COLUMN_DATE + "\") FROM " + VERSION_TABLE_NAME + ")"; /** * Database query to get the owner. */ private static final String QUERY_OWNER = "SELECT * FROM pg_database, pg_user WHERE usesysid=pg_database.datdba " + "AND datname=''{0}''"; /** * Database query to check the creator id. */ private static final String QUERY_CREATOR_ID = "SELECT id FROM aa.user_account WHERE id=''{0}''"; /** * Directory which contains the SQL scripts. */ private static final String DIRECTORY_SCRIPTS = "db-processed"; private SmMigrationInterface smMigration; private AaMigrationInterface aaMigration; /** * The logger. */ private static Logger log = LoggerFactory.getLogger(DataBaseMigrationTool.class); /** * Database settings. */ private final String driverClassName; private final String url; private final String username; private final String password; private final String scriptPrefix; private final String creatorId; /** * Attributes needed to call an Ant target. */ private Project project = new Project(); private Target target = new Target(); /** * Construct a new DataBaseMigrationTool object. * * @param driverClassName * name of the JDBC driver * @param url * JDBCL URL * @param username * name of the database user * @param password * password of the database user * @param scriptPrefix * prefix for database script names (mainly for MySQL) * @param creatorId * id of a user account which will be inserted as creator in the * SQL scripts */ public DataBaseMigrationTool(final String driverClassName, final String url, final String username, final String password, final String scriptPrefix, final String creatorId) { this.driverClassName = driverClassName; this.url = url; this.username = username; this.password = password; this.scriptPrefix = scriptPrefix; this.creatorId = creatorId; project.init(); target.setProject(project); } /** * Check if the configured creator id exists in the database. * * @return true if the creator id exists * @throws SQLException * Thrown if the SQL query failed. */ private boolean creatorExists() throws SQLException { return getJdbcTemplate().query(MessageFormat.format(QUERY_CREATOR_ID, creatorId), new ResultSetExtractor() { public Object extractData(final ResultSet rs) throws SQLException { String result = null; if (rs.next()) { result = rs.getString(1); } return result; } }) != null; } /** * Get the owner of the current database. * * @return database owner * @throws SQLException * Thrown if the structure of the database could not be * determined */ private String getDBOwner() throws SQLException { return (String) getJdbcTemplate().query(MessageFormat.format(QUERY_OWNER, getConnection().getCatalog()), new ResultSetExtractor() { public Object extractData(final ResultSet rs) throws SQLException { String result = null; if (rs.next()) { result = rs.getString(COLUMN_USENAME); } return result; } }); } /** * Get the current database version from the database. * * @return current database version */ private Version getDBVersion() { Version result = null; try { result = (Version) getJdbcTemplate().query(QUERY_LATEST_VERSION, new ResultSetExtractor() { public Object extractData(final ResultSet rs) throws SQLException { Version result = null; if (rs.next()) { result = new Version(rs.getInt(COLUMN_MAJOR_NUMBER), rs.getInt(COLUMN_MINOR_NUMBER), rs.getInt(COLUMN_REVISION_NUMBER)); } return result; } }); if (result == null) { // version table is empty result = new Version(1, 0, 0); } } catch (DataAccessException e) { // version table doesn't exist result = new Version(1, 0, 0); } return result; } /** * Get an ordered list of all available updates found in the given * directory. * * @param dirName * directory which contains sub directories with the SQL scripts * * @return ordered list of all available updates */ private Collection<Version> getUpdates(final String dirName) { Collection<Version> result = new TreeSet<Version>(); File dir = new File(dirName); File[] updates = dir.listFiles(new FileFilter() { public boolean accept(final File pathname) { return (pathname != null) && (pathname.isDirectory()); } }); if (updates != null) { for (File update : updates) { if (!update.getName().startsWith(".")) { result.add(new Version(update.getName())); } } } return result; } /** * Compare the current database structure with the structure stored in an * XML file. * * @throws IntegritySystemException * Thrown in case the content of the database is not in a * consistent state. */ private void checkConsistency() throws IntegritySystemException { Version dbVersion = getDBVersion(); String dbSystem = null; if (scriptPrefix != null && scriptPrefix.equals("postgres")) { dbSystem = "PostgreSQL"; } else { dbSystem = "Oracle"; } String fingerprintFile = "/META-INF/db/fingerprints/" + dbSystem + "/" + dbVersion.toString() + ".xml"; try { Fingerprint currentFingerprint = new Fingerprint(getConnection()); Fingerprint storedFingerprint = Fingerprint.readObject(getClass().getResourceAsStream(fingerprintFile)); if (storedFingerprint.compareTo(currentFingerprint) != 0) { throw new IntegritySystemException("The database is not in the expected state to run " + "the migration. Please compare the file \"" + System.getProperty("java.io.tmpdir") + "/fingerprint.xml\" " + "with \"" + fingerprintFile + "\" which is included in the class path."); } } catch (IOException e) { throw new IntegritySystemException("could not check the database consistency " + e.getMessage(), e); } catch (SQLException e) { throw new IntegritySystemException("could not check the database consistency " + e.getMessage(), e); } } /** * See Interface for functional description. * * @throws IntegritySystemException * Thrown in case the content of the database is not as * expected. * @see de.escidoc.core.admin.business.interfaces.DataBaseMigrationInterface#migrate() */ public void migrate() throws IntegritySystemException { // check database owner if (scriptPrefix != null && scriptPrefix.equals("postgres")) { try { final String owner = getDBOwner(); if (!owner.equals(username)) { throw new IntegritySystemException("The configured database user \"" + username + "\" differs from the database owner \"" + owner + "\"."); } } catch (Exception e) { throw new IntegritySystemException("could not check the database consistency", e); } } // check if the creator exists try { if (!creatorExists()) { throw new IntegritySystemException( "The configured creator id \"" + creatorId + "\" does not exist in the database."); } } catch (SQLException e) { throw new IntegritySystemException("could not check if the creator id exists", e); } // search for all available updates Collection<Version> updates = getUpdates(DIRECTORY_SCRIPTS); log.info("available updates: " + updates); try { for (Version version : updates) { Version dbVersion = getDBVersion(); if (version.compareTo(dbVersion) > 0) { log.info("current DB version: " + dbVersion); // check database structure before migration checkConsistency(); // do the migration log.info("migrate to " + version + " ..."); if (version.toString().equals("1.3.0")) { aaMigration.migrate(); } update(version); if (version.toString().equals("1.3.0")) { smMigration.migrate(); } } } // check database structure after migration checkConsistency(); } catch (IOException e) { throw new IntegritySystemException(e); } catch (ApplicationServerSystemException e) { throw new IntegritySystemException(e); } } /** * Injects the data source. * * @spring.property ref="escidoc-core.DataSource" * @param myDataSource * data source from Spring */ public final void setMyDataSource(final DataSource myDataSource) { super.setDataSource(myDataSource); } /** * Injects the sm migration tool. * * @spring.property ref="de.escidoc.core.admin.SmMigrationTool" * @param smMigration * smMigrationTool */ public final void setSmMigrationTool(final SmMigrationInterface smMigration) { this.smMigration = smMigration; } /** * Injects the aa migration tool. * * @spring.property ref="de.escidoc.core.admin.AaMigrationTool" * @param aaMigration * aaMigrationTool */ public final void setAaMigrationTool(final AaMigrationInterface aaMigration) { this.aaMigration = aaMigration; } /** * Update the database to the given version. * * @param version * the new version of the database * * @throws IOException * Thrown if an error occurred while reading the SQL scripts */ private void update(final Version version) throws IOException { File sqlDir = new File(new File(DIRECTORY_SCRIPTS, version.toString()), scriptPrefix); SQLExec sqlExec = new SQLExec(); if (scriptPrefix != null && scriptPrefix.equals("oracle")) { sqlExec.setKeepformat(true); SQLExec.DelimiterType t = new SQLExec.DelimiterType(); t.setValue("row"); sqlExec.setDelimiterType(t); sqlExec.setDelimiter("/"); } String[] scripts = sqlDir.list(new FilenameFilter() { public boolean accept(final File dir, final String name) { return (name != null) && (name.endsWith(".sql")); } }); FileSet set = new FileSet(); sqlExec.setProject(project); sqlExec.setOwningTarget(target); sqlExec.setDriver(driverClassName); sqlExec.setUrl(url); sqlExec.setUserid(username); sqlExec.setPassword(password); set.setDir(sqlDir); set.setProject(project); for (String script : scripts) { set.setIncludes(script); } sqlExec.addFileset(set); sqlExec.execute(); } }