Search in sources :

Example 1 with LiquibaseChangesetsPending

use of com.peterphi.std.guice.liquibase.exception.LiquibaseChangesetsPending in project stdlib by petergeneric.

the class LiquibaseCore method executeAction.

/**
 * Executes the Liquibase update.
 */
private static void executeAction(InitialContext jndi, GuiceApplicationValueContainer config, Map<String, String> parameters, LiquibaseAction action) throws NamingException, SQLException, LiquibaseException, IOException, ParserConfigurationException {
    // N.B. liquibase may create a databasechangeloglock / databasechangelog table if one does not already exist
    if (action.isWriteAction() && StringUtils.equalsIgnoreCase("true", config.getValue(HIBERNATE_IS_READONLY))) {
        log.info("Changing liquibase action from " + action + " to ASSERT_UPDATED because hibernate is set to read only mode");
        action = LiquibaseAction.ASSERT_UPDATED;
    }
    // Fail if hbm2ddl is enabled (Hibernate should not be involved in schema management)
    if (StringUtils.isNotEmpty(config.getValue(HIBERNATE_SCHEMA_MANAGEMENT)) && action != LiquibaseAction.GENERATE_CHANGELOG) {
        throw new RuntimeException("Liquibase is enabled but so is " + HIBERNATE_SCHEMA_MANAGEMENT + ". Only one of these schema management methods may be used at a time.");
    }
    final String dataSourceName = config.getDataSource();
    final String changeLogFile = config.getValue(GuiceProperties.LIQUIBASE_CHANGELOG);
    final String contexts = config.getValue(GuiceProperties.LIQUIBASE_CONTEXTS);
    final String labels = config.getValue(GuiceProperties.LIQUIBASE_LABELS);
    final String defaultSchema = config.getDefaultSchema();
    final String jdbcUrl = config.getValue(AvailableSettings.URL);
    final String jdbcUsername = config.getValue(AvailableSettings.USER);
    final String jdbcPassword = config.getValue(AvailableSettings.PASS);
    if (StringUtils.isEmpty(dataSourceName) && StringUtils.isEmpty(jdbcUrl))
        throw new RuntimeException("Cannot run Liquibase: no JNDI datasource or JDBC URL set");
    else if (changeLogFile == null)
        throw new RuntimeException("Cannot run Liquibase: " + GuiceProperties.LIQUIBASE_CHANGELOG + " is not set");
    int storedTransactionIsolation = Integer.MIN_VALUE;
    Connection connection = null;
    Database database = null;
    try {
        // Set up the resource accessor
        final ResourceAccessor resourceAccessor;
        {
            final CompositeResourceAccessor composite;
            {
                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
                ResourceAccessor threadClFO = new ClassLoaderResourceAccessor(contextClassLoader);
                ResourceAccessor clFO = new ClassLoaderResourceAccessor();
                ResourceAccessor fsFO = new FileSystemResourceAccessor();
                composite = new CompositeResourceAccessor(clFO, fsFO, threadClFO);
            }
            // If loading a resource with an absolute path fails, re-try it as a path relative to /
            // This is for unit tests where /liquibase/changelog.xml needs to be accessed as liquibase/changelog.xml
            final ResourceAccessor fallback = new RetryAbsoluteAsRelativeResourceAccessor(composite);
            // Wrap the resource accessor in a filter that interprets ./ as the changeLogFile folder
            resourceAccessor = new RelativePathFilteringResourceAccessor(fallback, changeLogFile);
        }
        // Set up the database
        {
            if (StringUtils.isNotEmpty(dataSourceName)) {
                if (log.isDebugEnabled())
                    log.debug("Look up datasource for liquibase: " + dataSourceName);
                final DataSource dataSource = (DataSource) jndi.lookup(dataSourceName);
                connection = dataSource.getConnection();
            } else {
                if (log.isDebugEnabled())
                    log.debug("Create JDBC Connection directly: " + jdbcUrl);
                // N.B. do we need to call Class.forName on the JDBC Driver URL?
                // JDBC drivers should expose themselves using the service provider interface nowadays so this shouldn't be necessary
                connection = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
            }
            // Allow changing the transaction isolation away from the default for liquibase
            // This is a hack inserted for SQL Server where the SNAPSHOT isolation is being used
            // because in this mode it refuses to execute certain DDL statements because of metadata not being versioned
            {
                storedTransactionIsolation = connection.getTransactionIsolation();
                // In this case we change to READ UNCOMMITTED for the duration of the liquibase run
                if (storedTransactionIsolation == 4096) {
                    connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                }
            }
            database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
            database.setDefaultSchemaName(defaultSchema);
        }
        Liquibase liquibase = new Liquibase(changeLogFile, resourceAccessor, database);
        for (Map.Entry<String, String> param : parameters.entrySet()) {
            liquibase.setChangeLogParameter(param.getKey(), param.getValue());
        }
        if (log.isDebugEnabled())
            log.debug("Execute liquibase action: " + action);
        switch(action) {
            case ASSERT_UPDATED:
                // Figure out which changesets need to be run
                List<ChangeSet> unrun = liquibase.listUnrunChangeSets(new Contexts(contexts), new LabelExpression(labels));
                if (log.isDebugEnabled())
                    log.debug("Pending changesets: " + unrun);
                // If any need to be run, fail
                if (unrun.size() > 0)
                    throw new LiquibaseChangesetsPending(unrun);
                else
                    return;
            case UPDATE:
                // Perform a schema update
                liquibase.update(new Contexts(contexts), new LabelExpression(labels));
                return;
            case MARK_UPDATED:
                // Mark all pending changesets as run
                liquibase.changeLogSync(new Contexts(contexts), new LabelExpression(labels));
                return;
            case GENERATE_CHANGELOG:
                CatalogAndSchema catalogueAndSchema = CatalogAndSchema.DEFAULT;
                DiffToChangeLog writer = new DiffToChangeLog(new DiffOutputControl(false, false, false, new CompareControl.SchemaComparison[0]));
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                PrintStream pw = new PrintStream(bos);
                liquibase.generateChangeLog(catalogueAndSchema, writer, pw);
                System.out.println("********** GENERATED CHANGELOG START **********");
                System.out.println(new String(bos.toByteArray()));
                System.out.println("********** GENERATED CHANGELOG END **********");
                break;
            default:
                throw new RuntimeException("Unknown liquibase action: " + action);
        }
    } finally {
        // N.B. we don't return to isolations < 0 (isolation at db defaults)
        if (connection != null && connection.getTransactionIsolation() != storedTransactionIsolation && storedTransactionIsolation >= 0) {
            connection.setTransactionIsolation(storedTransactionIsolation);
        }
        if (database != null)
            database.close();
        else if (connection != null)
            connection.close();
    }
}
Also used : CompositeResourceAccessor(liquibase.resource.CompositeResourceAccessor) FileSystemResourceAccessor(liquibase.resource.FileSystemResourceAccessor) ClassLoaderResourceAccessor(liquibase.resource.ClassLoaderResourceAccessor) ResourceAccessor(liquibase.resource.ResourceAccessor) JdbcConnection(liquibase.database.jvm.JdbcConnection) Contexts(liquibase.Contexts) Database(liquibase.database.Database) DiffToChangeLog(liquibase.diff.output.changelog.DiffToChangeLog) ChangeSet(liquibase.changelog.ChangeSet) PrintStream(java.io.PrintStream) LiquibaseChangesetsPending(com.peterphi.std.guice.liquibase.exception.LiquibaseChangesetsPending) Connection(java.sql.Connection) JdbcConnection(liquibase.database.jvm.JdbcConnection) DiffOutputControl(liquibase.diff.output.DiffOutputControl) ByteArrayOutputStream(java.io.ByteArrayOutputStream) CatalogAndSchema(liquibase.CatalogAndSchema) DataSource(javax.sql.DataSource) CompositeResourceAccessor(liquibase.resource.CompositeResourceAccessor) Liquibase(liquibase.Liquibase) LabelExpression(liquibase.LabelExpression) FileSystemResourceAccessor(liquibase.resource.FileSystemResourceAccessor) ClassLoaderResourceAccessor(liquibase.resource.ClassLoaderResourceAccessor) HashMap(java.util.HashMap) Map(java.util.Map)

Aggregations

LiquibaseChangesetsPending (com.peterphi.std.guice.liquibase.exception.LiquibaseChangesetsPending)1 ByteArrayOutputStream (java.io.ByteArrayOutputStream)1 PrintStream (java.io.PrintStream)1 Connection (java.sql.Connection)1 HashMap (java.util.HashMap)1 Map (java.util.Map)1 DataSource (javax.sql.DataSource)1 CatalogAndSchema (liquibase.CatalogAndSchema)1 Contexts (liquibase.Contexts)1 LabelExpression (liquibase.LabelExpression)1 Liquibase (liquibase.Liquibase)1 ChangeSet (liquibase.changelog.ChangeSet)1 Database (liquibase.database.Database)1 JdbcConnection (liquibase.database.jvm.JdbcConnection)1 DiffOutputControl (liquibase.diff.output.DiffOutputControl)1 DiffToChangeLog (liquibase.diff.output.changelog.DiffToChangeLog)1 ClassLoaderResourceAccessor (liquibase.resource.ClassLoaderResourceAccessor)1 CompositeResourceAccessor (liquibase.resource.CompositeResourceAccessor)1 FileSystemResourceAccessor (liquibase.resource.FileSystemResourceAccessor)1 ResourceAccessor (liquibase.resource.ResourceAccessor)1