package liquibase.integration.cdi;
import liquibase.Contexts;
import liquibase.LabelExpression;
import liquibase.Liquibase;
import liquibase.configuration.GlobalConfiguration;
import liquibase.configuration.LiquibaseConfiguration;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.integration.cdi.annotations.LiquibaseType;
import liquibase.logging.LogFactory;
import liquibase.logging.Logger;
import liquibase.resource.ResourceAccessor;
import liquibase.util.LiquibaseUtil;
import liquibase.util.NetUtil;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.Extension;
import javax.inject.Inject;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
/**
* A CDI wrapper for Liquibase.
* <p/>
* Example Configuration:
* <p/>
* <p/>
* This CDI configuration example will cause liquibase to run
* automatically when the CDI container is initialized. It will load
* <code>db-changelog.xml</code> from the classpath and apply it against
* <code>myDataSource</code>.
* <p/>
* Various producers methods are required to resolve the dependencies
* i.e.
* <code>
* public class CDILiquibaseProducer {
*
* @Produces @LiquibaseType
* public CDILiquibaseConfig createConfig() {
* CDILiquibaseConfig config = new CDILiquibaseConfig();
* config.setChangeLog("liquibase/parser/core/xml/simpleChangeLog.xml");
* return config;
* }
*
* @Produces @LiquibaseType
* public DataSource createDataSource() throws SQLException {
* jdbcDataSource ds = new jdbcDataSource();
* ds.setDatabase("jdbc:hsqldb:mem:test");
* ds.setUser("sa");
* ds.setPassword("");
* return ds;
* }
*
* @Produces @LiquibaseType
* public ResourceAccessor create() {
* return new ClassLoaderResourceAccessor(getClass().getClassLoader());
* }
*
* }
* </code>
*
* @author Aaron Walker (http://github.com/aaronwalker)
*/
@ApplicationScoped
public class CDILiquibase implements Extension {
private Logger log = LogFactory.getLogger(CDILiquibase.class.getName());
@Inject @LiquibaseType
private CDILiquibaseConfig config;
@Inject @LiquibaseType
private DataSource dataSource;
@Inject @LiquibaseType
ResourceAccessor resourceAccessor;
private boolean initialized = false;
private boolean updateSuccessful = false;
public boolean isInitialized() {
return initialized;
}
public boolean isUpdateSuccessful() {
return updateSuccessful;
}
@PostConstruct
public void onStartup() {
log.info("Booting Liquibase " + LiquibaseUtil.getBuildVersion());
String hostName;
try {
hostName = NetUtil.getLocalHostName();
} catch (Exception e) {
log.warning("Cannot find hostname: " + e.getMessage());
log.debug("", e);
return;
}
LiquibaseConfiguration liquibaseConfiguration = LiquibaseConfiguration.getInstance();
if (!liquibaseConfiguration.getConfiguration(GlobalConfiguration.class).getShouldRun()) {
log.info("Liquibase did not run on " + hostName + " because " + liquibaseConfiguration.describeValueLookupLogic(GlobalConfiguration.class, GlobalConfiguration.SHOULD_RUN) + " was set to false");
return;
}
initialized = true;
try {
performUpdate();
} catch (LiquibaseException e) {
throw new UnexpectedLiquibaseException(e);
}
}
private void performUpdate() throws LiquibaseException {
Connection c = null;
Liquibase liquibase = null;
try {
c = dataSource.getConnection();
liquibase = createLiquibase(c);
liquibase.getDatabase();
liquibase.update(new Contexts(config.getContexts()), new LabelExpression(config.getLabels()));
updateSuccessful = true;
} catch (SQLException e) {
throw new DatabaseException(e);
} catch (LiquibaseException ex) {
updateSuccessful = false;
throw ex;
} finally {
if (liquibase != null && liquibase.getDatabase() != null) {
liquibase.getDatabase().close();
} else if (c != null) {
try {
c.rollback();
c.close();
} catch (SQLException e) {
//nothing to do
}
}
}
}
protected Liquibase createLiquibase(Connection c) throws LiquibaseException {
Liquibase liquibase = new Liquibase(config.getChangeLog(), resourceAccessor, createDatabase(c));
if (config.getParameters() != null) {
for(Map.Entry<String, String> entry: config.getParameters().entrySet()) {
liquibase.setChangeLogParameter(entry.getKey(), entry.getValue());
}
}
if (config.isDropFirst()) {
liquibase.dropAll();
}
return liquibase;
}
/**
* Subclasses may override this method add change some database settings such as
* default schema before returning the database object.
* @param c
* @return a Database implementation retrieved from the {@link liquibase.database.DatabaseFactory}.
* @throws DatabaseException
*/
protected Database createDatabase(Connection c) throws DatabaseException {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(c));
if (config.getDefaultSchema() != null) {
database.setDefaultSchemaName(config.getDefaultSchema());
}
return database;
}
}