com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of autopatch Show documentation
Show all versions of autopatch Show documentation
An automated Java patching system
/* Copyright 2004 Tacit Knowledge
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tacitknowledge.util.migration.jdbc;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContextEvent;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.tacitknowledge.util.migration.MigrationContext;
import com.tacitknowledge.util.migration.MigrationException;
import com.tacitknowledge.util.migration.MigrationListener;
import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil;
import com.tacitknowledge.util.migration.jdbc.util.NonPooledDataSource;
/**
* Creates and configures new JdbcMigrationContext
objects based on the values
* in the migration.properties file for the given system. This is a convenience
* class for systems that need to initialize the autopatch framework but do not to or can not
* configure the framework themselves.
*
* This factory expects a file named migration.properties
to be in the root of the
* class path. This file must contain these properties (where systemName is the name of
* the system being patched):
*
* Key description
* systemName.patch.path
* systemName.jdbc.database.type
* The database type; also accepts systemName.jdbc.dialect
* systemName.jdbc.driver The JDBC driver to use
* systemName.jdbc.url The JDBC URL to the database
* systemName.jdbc.username The database user name
* systemName.jdbc.password The database password
*
*
* Optional properties include:
*
* systemName.postpatch.path
* systemName.readonly boolean true to skip patch application
* systemName.jdbc.systems
* Set of names for multiple JDBC connections that
* should all have patches applied. Names will be
* looked up in the same properties file as
* systemName.jdbcname.database.type, where
* all of the jdbc entries above should be present
*
* systemName.listeners Comma separated list of fully qualified java class names that implement {@link MigrationListener}
*
*
* @author Scott Askew ([email protected])
* @author Alex Soto ([email protected])
*/
public class JdbcMigrationLauncherFactory
{
/** Class logger */
private static Log log = LogFactory.getLog(JdbcMigrationLauncherFactory.class);
/**
* Creates and configures a new JdbcMigrationLauncher
based on the
* values in the migration.properties file for the given system.
*
* @param systemName the system to patch
* @param propFile the name of the property file to configure from
* @return a fully configured JdbcMigrationLauncher
.
* @throws MigrationException if an unexpected error occurs
*/
public JdbcMigrationLauncher createMigrationLauncher(String systemName, String propFile)
throws MigrationException
{
log.info("Creating JdbcMigrationLauncher for system " + systemName);
JdbcMigrationLauncher launcher = getJdbcMigrationLauncher();
configureFromMigrationProperties(launcher, systemName, propFile);
return launcher;
}
/**
* Creates and configures a new JdbcMigrationLauncher
based on the
* values in the migration.properties file for the given system.
*
* @param systemName the system to patch
* @return a fully configured JdbcMigrationLauncher
.
* @throws MigrationException if an unexpected error occurs
*/
public JdbcMigrationLauncher createMigrationLauncher(String systemName)
throws MigrationException
{
log.info("Creating JdbcMigrationLauncher for system " + systemName);
JdbcMigrationLauncher launcher = getJdbcMigrationLauncher();
configureFromMigrationProperties(launcher, systemName);
return launcher;
}
/**
* Creates and configures a new JdbcMigrationLauncher
based on the
* values in the servlet context and JNDI for a web-application.
*
* @param sce the name of the context event to use in getting properties
* @return a fully configured JdbcMigrationLauncher
.
* @throws MigrationException if an unexpected error occurs
*/
public JdbcMigrationLauncher createMigrationLauncher(ServletContextEvent sce)
throws MigrationException
{
JdbcMigrationLauncher launcher = getJdbcMigrationLauncher();
configureFromServletContext(launcher, sce);
return launcher;
}
/**
* Used to configure the migration launcher with properties from a servlet
* context. You do not need migration.properties to use this method.
*
* @param launcher the launcher to configure
* @param sce the event to get the context and associated parameters from
* @throws MigrationException if a problem with the look up in JNDI occurs
*/
private void configureFromServletContext(JdbcMigrationLauncher launcher,
ServletContextEvent sce) throws MigrationException
{
String readOnly = sce.getServletContext().getInitParameter("migration.readonly");
launcher.setReadOnly(false);
if ("true".equals(readOnly))
{
launcher.setReadOnly(true);
}
// See if they want to override the lock after a certain amount of time
String lockPollRetries =
sce.getServletContext().getInitParameter("migration.lockPollRetries");
if (lockPollRetries != null)
{
launcher.setLockPollRetries(Integer.parseInt(lockPollRetries));
}
String patchPath = ConfigurationUtil.getRequiredParam("migration.patchpath", sce, this);
launcher.setPatchPath(patchPath);
String postPatchPath = sce.getServletContext().getInitParameter("migration.postpatchpath");
launcher.setPostPatchPath(postPatchPath);
String databases = sce.getServletContext().getInitParameter("migration.jdbc.systems");
String[] databaseNames;
if ((databases == null) || "".equals(databases))
{
databaseNames = new String[1];
databaseNames[0] = "";
log.debug("jdbc.systems was null or empty, not multi-node");
}
else
{
databaseNames = databases.split(",");
log.debug("jdbc.systems was set to " + databases + ", configuring multi-node");
}
for (int i = 0; i < databaseNames.length; i++)
{
String databaseName = databaseNames[i];
if (databaseName != "")
{
databaseName = databaseName + ".";
}
String databaseType =
ConfigurationUtil.getRequiredParam("migration." + databaseName + "databasetype",
sce, this);
String systemName =
ConfigurationUtil.getRequiredParam("migration.systemname",
sce, this);
String dataSource =
ConfigurationUtil.getRequiredParam("migration." + databaseName + "datasource",
sce, this);
DataSourceMigrationContext context = getDataSourceMigrationContext();
context.setSystemName(systemName);
context.setDatabaseType(new DatabaseType(databaseType));
try
{
Context ctx = new InitialContext();
if (ctx == null)
{
throw new IllegalArgumentException("A jndi context must be "
+ "present to use this configuration.");
}
DataSource ds = (DataSource) ctx.lookup("java:comp/env/" + dataSource);
context.setDataSource(ds);
log.debug("adding context with datasource " + dataSource
+ " of type " + databaseType);
launcher.addContext(context);
}
catch (NamingException e)
{
throw new MigrationException("Problem with JNDI look up of " + dataSource, e);
}
}
}
/**
* Loads the configuration from the migration config properties file.
*
* @param launcher the launcher to configure
* @param systemName the name of the system
* @throws MigrationException if an unexpected error occurs
*/
private void configureFromMigrationProperties(JdbcMigrationLauncher launcher,
String systemName)
throws MigrationException
{
configureFromMigrationProperties(launcher,
systemName,
MigrationContext.MIGRATION_CONFIG_FILE);
}
/**
* Loads the configuration from the migration config properties file.
*
* @param launcher the launcher to configure
* @param systemName the name of the system
* @param propFile the name of the prop file to configure from
* @throws MigrationException if an unexpected error occurs
*/
private void configureFromMigrationProperties(JdbcMigrationLauncher launcher,
String systemName,
String propFile)
throws MigrationException
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputStream is = cl.getResourceAsStream(propFile);
if (is != null)
{
try
{
Properties props = new Properties();
props.load(is);
configureFromMigrationProperties(launcher, systemName, props);
}
catch (IOException e)
{
throw new MigrationException("Error reading in autopatch properties file", e);
}
finally
{
try
{
is.close();
}
catch (IOException ioe)
{
throw new MigrationException("Error closing autopatch properties file", ioe);
}
}
}
else
{
throw new MigrationException("Unable to find autopatch properties file '"
+ propFile + "'");
}
}
/**
* Configure the launcher from the provided properties, system name
*
* @param launcher The launcher to configure
* @param system The name of the system we're configuring
* @param props The Properties object with our configuration information
* @throws IllegalArgumentException if a required parameter is missing
* @throws MigrationException
*/
private void configureFromMigrationProperties(JdbcMigrationLauncher launcher, String system,
Properties props)
throws IllegalArgumentException, MigrationException
{
launcher.setPatchPath(ConfigurationUtil.getRequiredParam(props, system + ".patch.path"));
launcher.setPostPatchPath(props.getProperty(system + ".postpatch.path"));
launcher.setReadOnly(false);
if ("true".equals(props.getProperty(system + ".readonly")))
{
launcher.setReadOnly(true);
}
// See if they want to override the lock after a certain amount of time
String lockPollRetries = props.getProperty(system + ".lockPollRetries");
if (lockPollRetries != null)
{
launcher.setLockPollRetries(Integer.parseInt(lockPollRetries));
}
// TODO refactor the database name extraction from this and the servlet example
String databases = props.getProperty(system + ".jdbc.systems");
String[] databaseNames;
if ((databases == null) || "".equals(databases))
{
databaseNames = new String[1];
databaseNames[0] = "jdbc";
}
else
{
databaseNames = databases.split(",");
}
for (int i = 0; i < databaseNames.length; i++)
{
String db = databaseNames[i];
if (db != "")
{
db = "." + db;
}
// Set up the data source
NonPooledDataSource dataSource = new NonPooledDataSource();
dataSource.setDriverClass(ConfigurationUtil.getRequiredParam(props,
system + db + ".driver"));
dataSource.setDatabaseUrl(ConfigurationUtil.getRequiredParam(props,
system + db + ".url"));
dataSource.setUsername(ConfigurationUtil.getRequiredParam(props,
system + db + ".username"));
dataSource.setPassword(ConfigurationUtil.getRequiredParam(props,
system + db + ".password"));
// Set up the JDBC migration context; accepts one of two property names
DataSourceMigrationContext context = getDataSourceMigrationContext();
String databaseType =
ConfigurationUtil.getRequiredParam(props,
system + db + ".database.type",
system + db + ".dialect");
log.debug("setting type to " + databaseType);
context.setDatabaseType(new DatabaseType(databaseType));
context.setDatabaseName(databaseNames[i]);
// Finish setting up the context
context.setSystemName(system);
context.setDataSource(dataSource);
// setup the user-defined listeners
List userDefinedListeners = loadMigrationListeners(system, props);
launcher.getMigrationProcess().addListeners(userDefinedListeners);
// done reading in config, set launcher's context
launcher.addContext(context);
}
}
/**
* Get a DataSourceMigrationContext
*
* @return DataSourceMigrationContext for use with the launcher
*/
public DataSourceMigrationContext getDataSourceMigrationContext()
{
return new DataSourceMigrationContext();
}
/**
* Get a JdbcMigrationLauncher
*
* @return JdbcMigrationLauncher
*/
public JdbcMigrationLauncher getJdbcMigrationLauncher()
{
return new JdbcMigrationLauncher();
}
/**
* Returns a list of MigrationListeners for the systemName specified in the properties.
* @param systemName The name of the system to load MigrationListeners for.
* @param properties The properties that has migration listeners specified.
* @return A List of zero or more MigrationListeners
* @throws MigrationException if unable to load listeners.
*/
protected List loadMigrationListeners(String systemName, Properties properties)
throws MigrationException
{
try
{
List listeners = new ArrayList();
String[] listenerClassNames = null;
String commaSeparatedList = properties.getProperty(systemName + ".listeners");
// if it's blank, then no listeners configured
if(StringUtils.isNotBlank(commaSeparatedList))
{
listenerClassNames = commaSeparatedList.split(",");
for(Iterator it = Arrays.asList(listenerClassNames).iterator(); it.hasNext(); )
{
String className = ((String) it.next()).trim();
// if it's blank, then there is likely a leading or trailing comma
if(StringUtils.isNotBlank(className))
{
Class c = Class.forName(className);
MigrationListener listener = (MigrationListener) c.newInstance();
listener.initialize(systemName, properties);
listeners.add(listener);
}
}
}
return listeners;
}
catch(Exception e)
{
throw new MigrationException("Exception while loading migration listeners", e);
}
}
}