liquibase.integration.ant.BaseLiquibaseTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of liquibase-core Show documentation
Show all versions of liquibase-core Show documentation
Liquibase is a tool for managing and executing database changes.
The newest version!
package liquibase.integration.ant;
import liquibase.GlobalConfiguration;
import liquibase.Liquibase;
import liquibase.Scope;
import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.integration.IntegrationDetails;
import liquibase.integration.ant.type.ChangeLogParametersType;
import liquibase.integration.ant.type.DatabaseType;
import liquibase.integration.commandline.LiquibaseCommandLineConfiguration;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.ResourceAccessor;
import liquibase.resource.SearchPathResourceAccessor;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import static java.util.ResourceBundle.getBundle;
/**
* Base class for all Ant Liquibase tasks. This class sets up Liquibase and defines parameters
* that are common to all tasks.
*/
public abstract class BaseLiquibaseTask extends Task {
private static final ResourceBundle coreBundle = getBundle("liquibase/i18n/liquibase-core");
private final Map scopeValues = new HashMap<>();
private AntClassLoader classLoader;
private Liquibase liquibase;
private ResourceAccessor resourceAccessor;
private Path classpath;
private DatabaseType databaseType;
private ChangeLogParametersType changeLogParameters;
public BaseLiquibaseTask() {
super();
}
@Override
public void init() throws BuildException {
scopeValues.put(Scope.Attr.logService.name(), new AntTaskLogService(this));
scopeValues.put(Scope.Attr.integrationDetails.name(), new IntegrationDetails("ant"));
classpath = new Path(getProject());
}
@Override
public final void execute() throws BuildException {
super.execute();
log(coreBundle.getString("starting.liquibase"), Project.MSG_INFO);
classLoader = getProject().createClassLoader(classpath);
classLoader.setParent(this.getClass().getClassLoader());
classLoader.setThreadContextLoader();
validateParameters();
final Database[] database = {null};
try {
resourceAccessor = createResourceAccessor(classLoader);
scopeValues.put(Scope.Attr.resourceAccessor.name(), resourceAccessor);
scopeValues.put(Scope.Attr.classLoader.name(), classLoader);
Scope.child(scopeValues, () -> {
database[0] = createDatabaseFromType(databaseType, resourceAccessor);
liquibase = new Liquibase(getChangeLogFile(), resourceAccessor, database[0]);
if (changeLogParameters != null) {
changeLogParameters.applyParameters(liquibase);
}
if (shouldRun()) {
executeWithLiquibaseClassloader();
}
});
} catch (Exception e) {
throw new BuildException("Unable to initialize Liquibase: " + e.getMessage(), e);
} finally {
closeDatabase(database[0]);
classLoader.resetThreadContextLoader();
classLoader.cleanup();
classLoader = null;
}
}
protected abstract void executeWithLiquibaseClassloader() throws BuildException;
protected Database createDatabaseFromConfiguredDatabaseType() {
return createDatabaseFromType(databaseType, getResourceAccessor());
}
protected Database createDatabaseFromType(DatabaseType databaseType, ResourceAccessor resourceAccessor) {
return databaseType.createDatabase(resourceAccessor);
}
protected Liquibase getLiquibase() {
return liquibase;
}
protected ResourceAccessor getResourceAccessor() {
if (resourceAccessor == null) {
throw new IllegalStateException("The ResourceAccessor has not been initialized. This usually means this " +
"method has been called before the task's execute method has called.");
}
return resourceAccessor;
}
/**
* This method is designed to be overridden by subclasses when a change log is needed. By default it returns null.
*
* @return Returns null in this implementation. Subclasses that need a change log should implement.
* @see AbstractChangeLogBasedTask#getChangeLogDirectory()
*/
public String getChangeLogDirectory() {
return null;
}
/**
* This method is designed to be overridden by subclasses when a change log is needed. By default it returns null.
*
* @return Returns null in this implementation. Subclasses that need a change log should implement.
*/
public String getSearchPath() {
return null;
}
/**
* This method is designed to be overridden by subclasses when a change log is needed. By default it returns null.
*
* @return Returns null in this implementation. Subclasses that need a change log should implement.
* @see AbstractChangeLogBasedTask#getChangeLogFile()
*/
protected String getChangeLogFile() {
return null;
}
protected boolean shouldRun() {
if (!LiquibaseCommandLineConfiguration.SHOULD_RUN.getCurrentValue()) {
log("Liquibase did not run because " + LiquibaseCommandLineConfiguration.SHOULD_RUN.getKey() + " was set to false", Project.MSG_INFO);
return false;
}
return true;
}
protected String getDefaultOutputEncoding() {
return GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue();
}
/**
* Subclasses that override this method must always call super.validateParameters()
method.
*/
protected void validateParameters() {
if (databaseType == null) {
throw new BuildException("A database or databaseref is required.");
}
}
/**
* Creates a suitable ResourceAccessor for use in an Ant task.
*
* @param classLoader The ClassLoader to use in the ResourceAccessor. It is preferable that it is an AntClassLoader.
* @return A ResourceAccessor.
*/
@SuppressWarnings("java:S2095")
private ResourceAccessor createResourceAccessor(AntClassLoader classLoader) {
return new SearchPathResourceAccessor(getSearchPath(),
new AntResourceAccessor(classLoader, getChangeLogDirectory()),
new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader())
);
}
/*
* Ant parameters
*/
/**
* Convenience method to safely close the database connection.
*
* @param database The database to close.
*/
protected void closeDatabase(Database database) {
try {
if (database != null) {
database.close();
}
} catch (DatabaseException e) {
log("Error closing the database connection.", e, Project.MSG_WARN);
}
}
public Path createClasspath() {
if (this.classpath == null) {
this.classpath = new Path(getProject());
}
return this.classpath.createPath();
}
public void setClasspathRef(Reference r) {
createClasspath().setRefid(r);
}
public void addDatabase(DatabaseType databaseType) {
if (this.databaseType != null) {
throw new BuildException("Only one element is allowed.");
}
this.databaseType = databaseType;
}
public void setDatabaseRef(Reference databaseRef) {
databaseType = new DatabaseType(getProject());
databaseType.setRefid(databaseRef);
}
public void addChangeLogParameters(ChangeLogParametersType changeLogParameters) {
if (this.changeLogParameters != null) {
throw new BuildException("Only one element is allowed.");
}
this.changeLogParameters = changeLogParameters;
}
public void setChangeLogParametersRef(Reference changeLogParametersRef) {
changeLogParameters = new ChangeLogParametersType(getProject());
changeLogParameters.setRefid(changeLogParametersRef);
}
/**
* @deprecated no longer prompts
*/
public boolean isPromptOnNonLocalDatabase() {
return false;
}
/**
* @deprecated no longer prompts
*/
public void setPromptOnNonLocalDatabase(boolean promptOnNonLocalDatabase) {
log("NOTE: The promptOnLocalDatabase functionality has been removed", Project.MSG_INFO);
}
}