com.gs.obevo.db.impl.core.DbDeployer.java Source code

Java tutorial

Introduction

Here is the source code for com.gs.obevo.db.impl.core.DbDeployer.java

Source

/**
 * Copyright 2017 Goldman Sachs.
 * 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.gs.obevo.db.impl.core;

import java.sql.Connection;

import com.gs.obevo.api.appdata.Change;
import com.gs.obevo.api.appdata.ObjectTypeAndNamePredicateBuilder;
import com.gs.obevo.api.appdata.PhysicalSchema;
import com.gs.obevo.api.appdata.Schema;
import com.gs.obevo.api.factory.PlatformConfiguration;
import com.gs.obevo.api.platform.ChangeAuditDao;
import com.gs.obevo.api.platform.ChangeType;
import com.gs.obevo.api.platform.DeployExecutionDao;
import com.gs.obevo.db.api.appdata.DbEnvironment;
import com.gs.obevo.db.api.platform.SqlExecutor;
import com.gs.obevo.db.impl.core.checksum.ChecksumBreak;
import com.gs.obevo.db.impl.core.checksum.ChecksumEntry;
import com.gs.obevo.db.impl.core.checksum.ChecksumEntryInclusionPredicate;
import com.gs.obevo.db.impl.core.checksum.DbChecksumManager;
import com.gs.obevo.dbmetadata.api.DaCatalog;
import com.gs.obevo.dbmetadata.api.DaSchemaInfoLevel;
import com.gs.obevo.dbmetadata.api.DaTable;
import com.gs.obevo.dbmetadata.api.DbMetadataManager;
import com.gs.obevo.impl.Changeset;
import com.gs.obevo.impl.DeployMetricsCollector;
import com.gs.obevo.impl.DeployStrategy;
import com.gs.obevo.impl.DeployerPlugin;
import com.gs.obevo.util.lookuppredicate.LookupIndex;
import org.apache.commons.dbutils.BasicRowProcessor;
import org.eclipse.collections.api.block.function.Function;
import org.eclipse.collections.api.block.predicate.Predicate;
import org.eclipse.collections.api.block.procedure.Procedure;
import org.eclipse.collections.api.collection.ImmutableCollection;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.multimap.MutableMultimap;
import org.eclipse.collections.api.set.ImmutableSet;
import org.eclipse.collections.api.set.MutableSet;
import org.eclipse.collections.impl.block.factory.Predicates;
import org.eclipse.collections.impl.factory.Lists;
import org.eclipse.collections.impl.factory.Multimaps;
import org.eclipse.collections.impl.factory.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Deployer implementation targeted specifically for DBMS platforms.
 */
public class DbDeployer implements DeployerPlugin<DbEnvironment> {
    private static final String INIT_WARNING_MESSAGE = "WARNING: An INIT was requested; however, the system has been initialized in the past.\n"
            + "Please double-check that you need this INIT.";

    private static final Logger LOG = LoggerFactory.getLogger(DbDeployer.class);

    private final ChangeAuditDao artifactDeployerDao;
    private final DeployExecutionDao deployExecutionDao;
    private final DeployMetricsCollector deployMetricsCollector;
    private final DbMetadataManager dbMetadataManager;
    private final SqlExecutor sqlExecutor;
    private final DbChecksumManager dbChecksumManager;

    public DbDeployer(ChangeAuditDao artifactDeployerDao, DbMetadataManager dbMetadataManager,
            SqlExecutor sqlExecutor, DeployMetricsCollector deployMetricsCollector,
            DbChecksumManager dbChecksumManager, DeployExecutionDao deployExecutionDao) {
        this.artifactDeployerDao = artifactDeployerDao;
        this.deployExecutionDao = deployExecutionDao;
        this.deployMetricsCollector = deployMetricsCollector;
        this.dbMetadataManager = dbMetadataManager;
        this.sqlExecutor = sqlExecutor;
        this.dbChecksumManager = dbChecksumManager;
    }

    @Override
    public void initializeSchema(final DbEnvironment env, PhysicalSchema schema) {
        this.sqlExecutor.executeWithinContext(schema, new Procedure<Connection>() {
            @Override
            public void value(Connection conn) {
                env.getDbTranslationDialect().initSchema(sqlExecutor.getJdbcTemplate(), conn);
            }
        });
    }

    @Override
    public void printArtifactsToProcessForUser2(Changeset artifactsToProcess, DeployStrategy deployStrategy,
            DbEnvironment env, ImmutableCollection<Change> deployedChanges,
            ImmutableCollection<Change> sourceChanges) {
        if (deployStrategy.isInitAllowedOnHashExceptions() && !isInitRequired(env)) {
            LOG.info("*******************************************");
            LOG.info(INIT_WARNING_MESSAGE);
            LOG.info("*******************************************");
            LOG.info("");
        }
    }

    private boolean isInitRequired(DbEnvironment env) {
        return !isAuditTablePresent(env) && getExistingTablesInEnvironment(env).notEmpty();
    }

    private boolean isAuditTablePresent(DbEnvironment env) {
        return env.getPhysicalSchemas().allSatisfy(new Predicate<PhysicalSchema>() {
            @Override
            public boolean accept(PhysicalSchema each) {
                return dbMetadataManager.getTableInfo(each, getArtifactDeployerDao().getAuditContainerName(),
                        new DaSchemaInfoLevel().setRetrieveTables(true)) != null;
            }
        });
    }

    private ImmutableSet<DaTable> getExistingTablesInEnvironment(DbEnvironment env) {
        return env.getPhysicalSchemas().flatCollect(new Function<PhysicalSchema, Iterable<DaTable>>() {
            @Override
            public Iterable<DaTable> valueOf(PhysicalSchema physicalSchema) {
                DaCatalog database = dbMetadataManager.getDatabase(physicalSchema,
                        new DaSchemaInfoLevel().setRetrieveTables(true), true, false);
                return database.getTables().reject(DaTable.IS_VIEW);
            }
        });
    }

    @Override
    public void validatePriorToDeployment(DbEnvironment env, DeployStrategy deployStrategy,
            ImmutableList<Change> sourceChanges, ImmutableCollection<Change> deployedChanges,
            Changeset artifactsToProcess) {

        if (env.isChecksumDetectionEnabled() && dbChecksumManager.isInitialized()) {
            Predicate<? super ChecksumEntry> platformInclusionPredicate = getPlatformInclusionPredicate(env);
            ImmutableCollection<ChecksumBreak> checksumBreaks = this.dbChecksumManager
                    .determineChecksumDifferences(platformInclusionPredicate)
                    .reject(ChecksumBreak.IS_EXPECTED_BREAK);

            if (checksumBreaks.notEmpty()) {
                LOG.info("*******************************************");
                LOG.info("WARNING: The following objects were modified or managed outside of {}.",
                        PlatformConfiguration.getInstance().getToolName());
                LOG.info("Please revert these changes or incorporate into your {} codebase",
                        PlatformConfiguration.getInstance().getToolName());

                for (ChecksumBreak checksumBreak : checksumBreaks) {
                    LOG.info("\t" + checksumBreak.toDisplayString());
                }

                LOG.info("*******************************************");
                LOG.info("");
            }
        }
    }

    @Override
    public void doPostDeployAction(DbEnvironment env, final ImmutableList<Change> sourceChanges) {
        if (env.isChecksumDetectionEnabled()) {
            if (!dbChecksumManager.isInitialized()) {
                // need this check done here to account for existing clients
                dbChecksumManager.initialize();
            }

            dbChecksumManager.applyChecksumDiffs(Predicates.and(getPlatformInclusionPredicate(env),
                    getSourceChangesInclusionPredicate(env, sourceChanges)));
        }
    }

    private ChecksumEntryInclusionPredicate createLookupIndexForObjectType(DbEnvironment env,
            ImmutableList<Change> sourceChanges, final String changeTypeName) {
        LookupIndex objectTypeIndex = new LookupIndex(Sets.immutable.with(changeTypeName));
        ImmutableList<Change> objectTypeChanges = sourceChanges.select(new Predicate<Change>() {
            @Override
            public boolean accept(Change it) {
                return it.getChangeTypeName().equals(changeTypeName);
            }
        });
        MutableSet<String> objectNames = objectTypeChanges.collect(new Function<Change, String>() {
            @Override
            public String valueOf(Change change) {
                return change.getObjectName();
            }
        }).collect(env.getPlatform().convertDbObjectName()).toSet();
        LookupIndex objectNameIndex = new LookupIndex(objectNames.toImmutable());
        return new ChecksumEntryInclusionPredicate(Lists.immutable.with(objectTypeIndex),
                Lists.immutable.with(objectNameIndex));
    }

    private Predicate<? super ChecksumEntry> getPlatformInclusionPredicate(DbEnvironment env) {
        // 1) exclude those tables that are excluded by default from source code, e.g. explain tables or others that users configure
        ImmutableSet<Predicate<? super ChecksumEntry>> schemaObjectNamePredicates = env.getSchemas()
                .collect(new Function<Schema, Predicate<? super ChecksumEntry>>() {
                    @Override
                    public Predicate<? super ChecksumEntry> valueOf(Schema schema) {
                        return schema.getObjectExclusionPredicateBuilder().build(ChecksumEntry.TO_OBJECT_TYPE,
                                ChecksumEntry.TO_NAME1);
                    }
                });

        // 2) exclude the audit tables
        MutableMultimap<String, String> tablesToExclude = Multimaps.mutable.set.empty();

        tablesToExclude.putAll(ChangeType.TABLE_STR, Sets.immutable.with(
                env.getPlatform().convertDbObjectName().valueOf(getArtifactDeployerDao().getAuditContainerName()),
                env.getPlatform().convertDbObjectName().valueOf(dbChecksumManager.getChecksumContainerName()),
                env.getPlatform().convertDbObjectName()
                        .valueOf(getDeployExecutionDao().getExecutionContainerName()),
                env.getPlatform().convertDbObjectName()
                        .valueOf(getDeployExecutionDao().getExecutionAttributeContainerName())));
        ObjectTypeAndNamePredicateBuilder auditTablePredicateBuilder = new ObjectTypeAndNamePredicateBuilder(
                tablesToExclude.toImmutable(), ObjectTypeAndNamePredicateBuilder.FilterType.EXCLUDE);
        Predicates<? super ChecksumEntry> auditTablePredicate = auditTablePredicateBuilder
                .build(ChecksumEntry.TO_OBJECT_TYPE, ChecksumEntry.TO_NAME1);

        return auditTablePredicate.and(schemaObjectNamePredicates);
    }

    private Predicate<? super ChecksumEntry> getSourceChangesInclusionPredicate(final DbEnvironment env,
            final ImmutableList<Change> sourceChanges) {
        // 3) only include the predicate types that we care about
        ImmutableSet<String> requiredValidationObjectTypes = env.getPlatform().getRequiredValidationObjectTypes();
        ImmutableSet<ChecksumEntryInclusionPredicate> checksumEntryPredicates = requiredValidationObjectTypes
                .collect(new Function<String, ChecksumEntryInclusionPredicate>() {
                    @Override
                    public ChecksumEntryInclusionPredicate valueOf(String changeType) {
                        return createLookupIndexForObjectType(env, sourceChanges, changeType);
                    }
                });

        return Predicates.or(checksumEntryPredicates);
    }

    @Override
    public void validateSetup() {
        validateProperDbUtilsVersion();
    }

    private void validateProperDbUtilsVersion() {
        Package dbutilsPackage = BasicRowProcessor.class.getPackage();
        String dbutilsVersion = dbutilsPackage.getSpecificationVersion();

        if (dbutilsVersion == null) {
            return; // in case the jar is shaded, this information may not be available; hence, we are forced to return
        }
        String[] versionParts = dbutilsVersion.split("\\.");
        if (versionParts.length < 2) {
            throw new IllegalArgumentException(
                    "Improper dbutils version; must have at least two parts x.y; " + dbutilsVersion);
        }

        int majorVersion = Integer.valueOf(versionParts[0]).intValue();
        int minorVersion = Integer.valueOf(versionParts[1]).intValue();

        if (!(majorVersion >= 1 && minorVersion >= 6)) {
            throw new IllegalArgumentException(
                    "commons-dbutils:commons-dbutils version must be >= 1.6 to avoid bugs w/ JDBC getColumnName() usage in <= 1.5");
        }
    }

    @Override
    public void logEnvironmentMetrics(DbEnvironment env) {
        getDeployMetricsCollector().addMetric("dbDataSourceName", env.getDbDataSourceName());
    }

    private ChangeAuditDao getArtifactDeployerDao() {
        return artifactDeployerDao;
    }

    private DeployExecutionDao getDeployExecutionDao() {
        return deployExecutionDao;
    }

    private DeployMetricsCollector getDeployMetricsCollector() {
        return deployMetricsCollector;
    }
}