/*****************************************************************************
* Copyright 2012 celum Slovakia s r.o.
*
* 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.celum.dbtool;
import com.celum.dbtool.configuration.DefaultMigrationConfiguration;
import com.celum.dbtool.installer.DbEventListener;
import com.celum.dbtool.installer.DbInstaller;
import com.celum.dbtool.resource.*;
import com.celum.dbtool.step.Version;
import com.celum.dbtool.sql.*;
import java.io.File;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* This is the main facade for DB tool. You will use
* mostly this class.
*
* This class needs JDBC Connection but doesn't close or
* establish this connection. Connection management is in
* your application control.
*
* @author Zdenko Vrabel (zdenko.vrabel@celum.com)
*/
public class Db {
/** database connection */
private Connection dbConnection;
/** resource from where scripts comes */
private DbStepResource steps;
/** variables which will be replaced as placeholders in SQLs */
private Map<String, Object> variables;
/** listener reacting on events like start executing SQL etc. */
private DbEventListener eventListener;
/** select that returns version for DB */
private String versionSql;
/** determines what strategy will be used for patching */
private PatchStrategy patchStrategy = PatchStrategy.SIMPLE;
/** Sql step which updates the version in DB */
private String versionUpdateSqlScript;
/** holds current DB version, it's filled by getCurrentVersion() method */
private Version currentDbVersion;
/**
* Builder helps with construction of Db.
*/
public class DbBuilder
{
private DbBuilder() { }
public DbBuilder withEventsListener(DbEventListener eventListener)
{
eventListener = eventListener;
return this;
}
public DbBuilder withVariable(String name, Object value)
{
variables.put(name, value);
return this;
}
public DbBuilder withVariables(Map<?,?> vars)
{
if (vars != null) {
for (Map.Entry<?,?> entry : vars.entrySet()) {
withVariable(entry.getKey().toString(), entry.getValue());
}
}
return this;
}
public DbBuilder withProperties(Properties properties)
{
for (Map.Entry<Object,Object> e : properties.entrySet()) {
withVariable(e.getKey().toString(), e.getValue().toString());
}
return this;
}
public DbBuilder withVersionUpdate(String versionUpdateSql)
{
Db.this.versionUpdateSqlScript = versionUpdateSql;
return this;
}
public DbBuilder withVersionSql(String versionSql)
{
Db.this.versionSql = versionSql;
return this;
}
public DbBuilder patchAs(PatchStrategy strategy)
{
Db.this.patchStrategy = strategy;
return this;
}
public Db andDbSteps(DbStepResource steps)
{
if (steps == null) {
throw new IllegalArgumentException("sqlScripts resource is null");
}
Db.this.steps = steps;
return Db.this;
}
public Db andDbScriptsInDir(File dir)
{
return andDbSteps(new DirResource(dir));
}
public Db andDbScriptsInPackage(Package pckg)
{
return andDbSteps(new PackageResource(pckg));
}
public Db andDbScriptsInPackage(String packageName)
{
return andDbSteps(new PackageResource(packageName, this.getClass().getClassLoader()));
}
public DefaultMigrationConfiguration createDefaultMigrationConfiguration(String patchesTable)
{
return new DefaultMigrationConfiguration(this, patchesTable);
}
}
/**
* Hidden constructor. Use one of the factory methods.
*/
private Db(Connection con) {
this.dbConnection = con;
this.variables = new HashMap<String, Object>();
}
/**
* Method start building of Db instance with
* various parameters.
*/
public static DbBuilder withJdbcConnection(Connection con)
{
return (new Db(con)).new DbBuilder();
}
/**
* method executes the scripts without any 'version' updating
* checking etc. It's usable for DB initialization or dropping.
*/
public void run()
{
DbInstaller installer = new DbInstaller(dbConnection, variables);
installer.setEventListener(eventListener);
installer.install(steps);
}
/**
* install method instead of patch method executes all scripts without
* any version check but the version table is updated anyway (if it's
* defined via 'withVersionUpdate()'
*/
public void install()
{
DbInstaller installer = new DbInstaller(dbConnection, variables);
installer.setEventListener(eventListener);
installer.setVersionUpdateSql(this.versionUpdateSqlScript);
installer.install(steps);
}
/**
* Method update the database to latest version. There are 2
* strategies how patches could be applied.
*
* Simple strategy, when is 'versionSql' defined and
* apply all patches greater than current DB. This strategy
* is used when you've got VERSION table only with one value.
*
* Conditional strategy, when is precondition SQL defined. This
* strategy apply all patches they're missing. This strategy
* is used when you've got in VERSION table list of all applied
* patches.
*
*/
public void patch()
{
DbStepResource filteredScripts = applyFilter();
DbInstaller installer = new DbInstaller(dbConnection, variables);
installer.setEventListener(eventListener);
installer.setVersionUpdateSql(versionUpdateSqlScript);
installer.install(filteredScripts);
}
/**
* Method update the database to version you wish.
*/
public void patchTo(Version v)
{
DbStepResource filteredScripts = applyFilter();
filteredScripts = VersionFilter.filter(filteredScripts).smallerOrEqualsTo(v);
DbInstaller installer = new DbInstaller(dbConnection, variables);
installer.setEventListener(eventListener);
installer.setVersionUpdateSql(versionUpdateSqlScript);
installer.install(filteredScripts);
}
/**
* get and fill the current DB version value to property
*/
private void getCurrentDbVersion()
{
String version =
Sql.asString(versionSql)
.interceptingWith(new VelocityInterceptor(variables))
.run(dbConnection)
.andReturnString();
currentDbVersion = Version.of(version);
}
/**
* Method apply filter on resource by patching strategy
*/
private DbStepResource applyFilter()
{
DbStepResource filteredScripts = null;
switch (patchStrategy) {
case SIMPLE:
getCurrentDbVersion();
filteredScripts =
VersionFilter
.filter(steps)
.largerThan(currentDbVersion);
break;
case CONDITIONAL:
filteredScripts = new AppliedPatchesFiter(steps, dbConnection, versionSql, variables);
break;
}
return filteredScripts;
}
}