All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kualigan.maven.plugins.liquibase.CopyMojo Maven / Gradle / Ivy

Go to download

Liquibase Maven Plugin with emphasis on structured development and conventions.

There is a newer version: 2.0.3
Show newest version
// Copyright 2011 Leo Przybylski. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//    1. Redistributions of source code must retain the above copyright notice, this list of
//       conditions and the following disclaimer.
//
//    2. Redistributions in binary form must reproduce the above copyright notice, this list
//       of conditions and the following disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY  ''AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of Leo Przybylski.
package org.kualigan.maven.plugins.liquibase;


import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.artifact.manager.WagonManager;

import org.liquibase.maven.plugins.MavenUtils;
import org.liquibase.maven.plugins.AbstractLiquibaseMojo;
import org.liquibase.maven.plugins.AbstractLiquibaseChangeLogMojo;
import org.liquibase.maven.plugins.MavenResourceAccessor;

import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationOutputHandler;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.InvokerLogger;
import org.apache.maven.shared.invoker.MavenInvocationException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;

import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineUtils;

import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.core.H2Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.logging.LogFactory;
import liquibase.serializer.ChangeLogSerializer;
import liquibase.parser.core.xml.LiquibaseEntityResolver;
import liquibase.parser.core.xml.XMLChangeLogSAXParser;
import liquibase.resource.CompositeResourceAccessor;
import liquibase.resource.FileSystemResourceAccessor;
import liquibase.resource.ResourceAccessor;

import org.apache.maven.wagon.authentication.AuthenticationInfo;

import liquibase.util.xml.DefaultXmlWriter;

import org.kualigan.tools.liquibase.Diff;
import org.kualigan.tools.liquibase.DiffResult;

import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

/**
 * Copies a database including DDL/DML from one location to another.
 *
 * @author Leo Przybylski
 */
 @Mojo(
     name="copy-database",
     requiresProject = false
     )
public class CopyMojo extends AbstractLiquibaseChangeLogMojo {
    public static final String DEFAULT_CHANGELOG_PATH = "src/main/changelogs";

    /**
     * Suffix for fields that are representing a default value for a another field.
     */
    private static final String DEFAULT_FIELD_SUFFIX = "Default";

    private static final Options OPTIONS = new Options();

    private static final char SET_SYSTEM_PROPERTY = 'D';

    private static final char OFFLINE = 'o';

    private static final char REACTOR = 'r';

    private static final char QUIET = 'q';

    private static final char DEBUG = 'X';

    private static final char ERRORS = 'e';

    private static final char NON_RECURSIVE = 'N';

    private static final char UPDATE_SNAPSHOTS = 'U';

    private static final char ACTIVATE_PROFILES = 'P';

    private static final String FORCE_PLUGIN_UPDATES = "cpu";

    private static final String FORCE_PLUGIN_UPDATES2 = "up";

    private static final String SUPPRESS_PLUGIN_UPDATES = "npu";

    private static final String SUPPRESS_PLUGIN_REGISTRY = "npr";

    private static final char CHECKSUM_FAILURE_POLICY = 'C';

    private static final char CHECKSUM_WARNING_POLICY = 'c';

    private static final char ALTERNATE_USER_SETTINGS = 's';

    private static final String FAIL_FAST = "ff";

    private static final String FAIL_AT_END = "fae";

    private static final String FAIL_NEVER = "fn";
    
    private static final String ALTERNATE_POM_FILE = "f";


    @Parameter(property = "project", defaultValue = "${project}")
    protected MavenProject project;

    /**
     * User settings use to check the interactiveMode.
     *
     */
    @Parameter(property = "interactiveMode", defaultValue = "${settings.interactiveMode}")
    protected Boolean interactiveMode;
    
    /**
     * 
     * The Maven Wagon manager to use when obtaining server authentication details.
     */
    @Component(role=org.apache.maven.artifact.manager.WagonManager.class)
    protected WagonManager wagonManager;

    /**
     * 
     * The Maven Wagon manager to use when obtaining server authentication details.
     */
    @Component(role=org.kualigan.maven.plugins.liquibase.MigrateHelper.class)
    protected MigrateHelper migrator;

    /**
     * The server id in settings.xml to use when authenticating the source server with.
     */
    @Parameter(property = "lb.copy.source", required = true)
    private String source;

    /**
     * The server id in settings.xml to use when authenticating the source server with.
     */
    @Parameter(property = "lb.copy.source.schema")
    private String sourceSchema;

    private String sourceUser;

    private String sourcePass;

    /**
     * The server id in settings.xml to use when authenticating the source server with.
     */
    @Parameter(property = "lb.copy.source.driver")
    private String sourceDriverClass;

    /**
     * The server id in settings.xml to use when authenticating the source server with.
     */
    @Parameter(property = "lb.copy.source.url", required = true)
    private String sourceUrl;

    /**
     * The server id in settings.xml to use when authenticating the target server with.
     */
    @Parameter(property = "lb.copy.target", required = true)
    private String target;

    /**
     * The server id in settings.xml to use when authenticating the target server with.
     */
    @Parameter(property = "lb.copy.target.schema")
    private String targetSchema;

    private String targetUser;

    private String targetPass;

    /**
     * The server id in settings.xml to use when authenticating the source server with.
     */
    @Parameter(property = "lb.copy.target.driver")
    private String targetDriverClass;

    /**
     * The server id in settings.xml to use when authenticating the source server with.
     */
    @Parameter(property = "lb.copy.target.url", required = true)
    private String targetUrl;


    /**
     * Controls the verbosity of the output from invoking the plugin.
     *
     * @description Controls the verbosity of the plugin when executing
     */
    @Parameter(property = "liquibase.verbose", defaultValue = "false")
    protected boolean verbose;

    /**
     * Controls the level of logging from Liquibase when executing. The value can be
     * "all", "finest", "finer", "fine", "info", "warning", "severe" or "off". The value is
     * case insensitive.
     *
     * @description Controls the verbosity of the plugin when executing
     */
    @Parameter(property = "liquibase.logging", defaultValue = "INFO")
    protected String logging;

    /**
     * The Liquibase properties file used to configure the Liquibase {@link
     * liquibase.Liquibase}.
     */
    @Parameter(property = "liquibase.propertyFile")
    protected String propertyFile;
    
    /**
     * Specifies the change log file to use for Liquibase. No longer needed with updatePath.
     * @deprecated
     */
    @Parameter(property = "liquibase.changeLogFile")
    protected String changeLogFile;

    /**
     */
    @Parameter(property = "liquibase.changeLogSavePath", defaultValue = "${project.basedir}/target/changelogs")
    protected File changeLogSavePath;
    
    /**
     * Whether or not to perform a drop on the database before executing the change.
     */
    @Parameter(property = "liquibase.dropFirst", defaultValue = "false")
    protected boolean dropFirst;
    
    /**
     * Property to flag whether to copy data as well as structure of the database schema
     */
    @Parameter(property = "lb.copy.data", defaultValue = "true")
    protected boolean stateSaved;
    
    protected Boolean isStateSaved() {
        return stateSaved;
    }

    /**
     * The {@code M2_HOME} parameter to use for forked Maven invocations.
     *
     */
    @Parameter(defaultValue = "${maven.home}")
    protected File mavenHome;

    protected File getBasedir() {
        return project.getBasedir();
    }
    
    protected String getChangeLogFile() throws MojoExecutionException {
        if (changeLogFile != null) {
            return changeLogFile;
        }
        
        try {
            changeLogFile = changeLogSavePath.getCanonicalPath();
            new File(changeLogFile).mkdirs();
            changeLogFile += File.separator + targetUser;
            return changeLogFile;
        }
        catch (Exception e) {
            throw new MojoExecutionException("Exception getting the location of the change log file: " + e.getMessage(), e);
        }
    }

    protected void doFieldHack() {
        for (final Field field : getClass().getDeclaredFields()) {
            try {
                final Field parentField = getDeclaredField(getClass().getSuperclass(), field.getName());
                if (parentField != null) {
                    getLog().debug("Setting " + field.getName() + " in " + parentField.getDeclaringClass().getName() + " to " + field.get(this));
                    parentField.set(this, field.get(this));
                }
            }
            catch (Exception e) {
            }
        }
    }


    /*
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        doFieldHack();

        try {
            Method meth = AbstractLiquibaseMojo.class.getDeclaredMethod("processSystemProperties");
            meth.setAccessible(true);
            meth.invoke(this);
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        ClassLoader artifactClassLoader = getMavenArtifactClassLoader();
        configureFieldsAndValues(getFileOpener(artifactClassLoader));
        
        doFieldHack();

        
        super.execute();
    }
    */
    
    public ClassLoader getMavenArtifactClassloader() throws MojoExecutionException {
        try {
            return MavenUtils.getArtifactClassloader(project, true, false, getClass(), getLog(), false);
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }
    
    public String lookupDriverFor(final String url) {
        for (final Database databaseImpl : DatabaseFactory.getInstance().getImplementedDatabases()) {
            final String driver = databaseImpl.getDefaultDriver(url);
            if (driver != null) {
                return driver;
            }
        }
        return null;
    }
    
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (project == null || project.getArtifactId().equalsIgnoreCase("standalone-pom")) {
            getLog().info("Using standalone-pom. No project. I have to create one.");
            generateArchetype(getMavenHome(), new Properties() {{
                        setProperty("archetypeGroupId",      "org.kualigan.maven.archetypes");
                        setProperty("archetypeArtifactId",   "lb-copy-archetype");
                        setProperty("archetypeVersion",      "1.1.7");
                        setProperty("groupId",               "org.kualigan.liquibase");
                        setProperty("artifactId",            "copy");
                        setProperty("version",               "1.0.0-SNAPSHOT");
                    }});
            
            invokeCopy(getMavenHome(), new Properties() {{
                        setProperty("lb.copy.source",        source);
                        setProperty("lb.copy.source.url",    sourceUrl);
                        if (sourceDriverClass != null) {
                            setProperty("lb.copy.source.driver", sourceDriverClass);
                        }
                        if (sourceSchema != null) {
                            setProperty("lb.copy.source.schema", sourceSchema);
                        }
                        setProperty("lb.copy.target",        target);
                        setProperty("lb.copy.target.url",    targetUrl);
                        if (targetDriverClass != null) {
                            setProperty("lb.copy.target.driver", targetDriverClass);
                        }
                        if (targetSchema != null) {
                            setProperty("lb.copy.target.schema", targetSchema);
                        }
                    }});

        }
        else {
            doCopy();
        }
    }

    /**
     * Invokes the maven goal {@code archetype:generate} with the appropriate properties.
     * 
     */
    public void invokeCopy(final File mavenHome, final Properties copyProperties) throws MojoExecutionException {
        final Invoker invoker = new DefaultInvoker().setMavenHome(mavenHome);
        invoker.setWorkingDirectory(new File(System.getProperty("user.dir") + File.separator + "copy"));
        
        final String additionalArguments = "";

        final InvocationRequest req = new DefaultInvocationRequest()
                .setInteractive(true)
                .setProperties(copyProperties);
        
        setupRequest(req, additionalArguments);

        req.setGoals(new ArrayList() {{ add("lb:copy-database"); }});

        try {
            final InvocationResult invocationResult = invoker.execute(req);

            if ( invocationResult.getExecutionException() != null ) {
                throw new MojoExecutionException("Error executing Maven.",
                                                 invocationResult.getExecutionException());
            }
                
            if (invocationResult.getExitCode() != 0) {
                throw new MojoExecutionException(
                    "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'");
            }
        }
        catch (MavenInvocationException e) {
            throw new MojoExecutionException( "Failed to invoke Maven build.", e );
        }
    }

    /**
     * Invokes the maven goal {@code archetype:generate} with the appropriate properties.
     * 
     */
    public void generateArchetype(final File mavenHome, final Properties archetypeProperties) throws MojoExecutionException {
        final Invoker invoker = new DefaultInvoker().setMavenHome(mavenHome);
        
        final String additionalArguments = "";

        final InvocationRequest req = new DefaultInvocationRequest()
                .setInteractive(false)
                .setProperties(archetypeProperties);
                    
        setupRequest(req, additionalArguments);

        req.setGoals(new ArrayList() {{ add("archetype:generate"); }});

        try {
            final InvocationResult invocationResult = invoker.execute(req);

            if ( invocationResult.getExecutionException() != null ) {
                throw new MojoExecutionException("Error executing Maven.",
                                                 invocationResult.getExecutionException());
            }
                
            if (invocationResult.getExitCode() != 0) {
                throw new MojoExecutionException(
                    "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'");
            }
        }
        catch (MavenInvocationException e) {
            throw new MojoExecutionException( "Failed to invoke Maven build.", e );
        }
    }

    /**
     * 
     */
    protected void setupRequest(final InvocationRequest req,
                              final String additionalArguments) throws MojoExecutionException {
        try {
            final String[] args = CommandLineUtils.translateCommandline(additionalArguments);
            CommandLine cli = new PosixParser().parse(OPTIONS, args);

            if (cli.hasOption( SET_SYSTEM_PROPERTY)) {
                String[] properties = cli.getOptionValues(SET_SYSTEM_PROPERTY);
                Properties props = new Properties();
                for ( int i = 0; i < properties.length; i++ )
                {
                    String property = properties[i];
                    String name, value;
                    int sep = property.indexOf( "=" );
                    if ( sep <= 0 )
                    {
                        name = property.trim();
                        value = "true";
                    }
                    else
                    {
                        name = property.substring( 0, sep ).trim();
                        value = property.substring( sep + 1 ).trim();
                    }
                    props.setProperty( name, value );
                }

                req.setProperties( props );
            }

            if ( cli.hasOption( OFFLINE ) )
            {
                req.setOffline( true );
            }

            if ( cli.hasOption( QUIET ) )
            {
                // TODO: setQuiet() currently not supported by InvocationRequest
                req.setDebug( false );
            }
            else if ( cli.hasOption( DEBUG ) )
            {
                req.setDebug( true );
            }
            else if ( cli.hasOption( ERRORS ) )
            {
                req.setShowErrors( true );
            }

            if ( cli.hasOption( REACTOR ) )
            {
                req.setRecursive( true );
            }
            else if ( cli.hasOption( NON_RECURSIVE ) )
            {
                req.setRecursive( false );
            }

            if ( cli.hasOption( UPDATE_SNAPSHOTS ) )
            {
                req.setUpdateSnapshots( true );
            }

            if ( cli.hasOption( ACTIVATE_PROFILES ) )
            {
                String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES );
                List activatedProfiles = new ArrayList();
                List deactivatedProfiles = new ArrayList();

                if ( profiles != null )
                {
                    for ( int i = 0; i < profiles.length; ++i )
                    {
                        StringTokenizer profileTokens = new StringTokenizer( profiles[i], "," );

                        while ( profileTokens.hasMoreTokens() )
                        {
                            String profileAction = profileTokens.nextToken().trim();

                            if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) )
                            {
                                deactivatedProfiles.add( profileAction.substring( 1 ) );
                            }
                            else if ( profileAction.startsWith( "+" ) )
                            {
                                activatedProfiles.add( profileAction.substring( 1 ) );
                            }
                            else
                            {
                                activatedProfiles.add( profileAction );
                            }
                        }
                    }
                }

                if ( !deactivatedProfiles.isEmpty() )
                {
                    getLog().warn( "Explicit profile deactivation is not yet supported. "
                                          + "The following profiles will NOT be deactivated: " + StringUtils.join(
                        deactivatedProfiles.iterator(), ", " ) );
                }

                if ( !activatedProfiles.isEmpty() )
                {
                    req.setProfiles( activatedProfiles );
                }
            }

            if ( cli.hasOption( FORCE_PLUGIN_UPDATES ) || cli.hasOption( FORCE_PLUGIN_UPDATES2 ) )
            {
                getLog().warn( "Forcing plugin updates is not supported currently." );
            }
            else if ( cli.hasOption( SUPPRESS_PLUGIN_UPDATES ) )
            {
                req.setNonPluginUpdates( true );
            }

            if ( cli.hasOption( SUPPRESS_PLUGIN_REGISTRY ) )
            {
                getLog().warn( "Explicit suppression of the plugin registry is not supported currently." );
            }

            if ( cli.hasOption( CHECKSUM_FAILURE_POLICY ) )
            {
                req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL );
            }
            else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) )
            {
                req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN );
            }

            if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) )
            {
                req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) );
            }

            if ( cli.hasOption( FAIL_AT_END ) )
            {
                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END );
            }
            else if ( cli.hasOption( FAIL_FAST ) )
            {
                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST );
            }
            if ( cli.hasOption( FAIL_NEVER ) )
            {
                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER );
            }
            if ( cli.hasOption( ALTERNATE_POM_FILE ) )
            {
                if ( req.getPomFileName() != null ) {
                    getLog().info("pomFileName is already set, ignoring the -f argument" );
                }
                else {
                    req.setPomFileName(cli.getOptionValue(ALTERNATE_POM_FILE));
                }
            }
        }
        catch (Exception e) {
            throw new MojoExecutionException("Failed to re-parse additional arguments for Maven invocation.", e );
        }
    }

    protected void doCopy() throws MojoExecutionException, MojoFailureException {
        getLog().info(MavenUtils.LOG_SEPARATOR);

        if (source != null) {
            try {
                final AuthenticationInfo info = wagonManager.getAuthenticationInfo(source);
                if (info != null) {
                    sourceUser = info.getUserName();
                    sourcePass = info.getPassword();
                }
            }
            catch (Exception e) {
                // error getting authentication 
            }
        }

        sourceDriverClass = lookupDriverFor(sourceUrl);
        
        if (sourceSchema == null) {
            sourceSchema = sourceUser;
        }

        if (target != null) {
            try {
                final AuthenticationInfo info = wagonManager.getAuthenticationInfo(target);
                if (info != null) {
                    targetUser = info.getUserName();
                    targetPass = info.getPassword();
                }
            }
            catch (Exception e) {
                // Error getting authentication info 
            }
        }
        
        if (targetSchema == null) {
            targetSchema = targetUser;
        }
        
        targetDriverClass = lookupDriverFor(targetUrl);
        
        final String shouldRunProperty = System.getProperty(Liquibase.SHOULD_RUN_SYSTEM_PROPERTY);
        if (shouldRunProperty != null && !Boolean.valueOf(shouldRunProperty)) {
            getLog().info("Liquibase did not run because '" + Liquibase.SHOULD_RUN_SYSTEM_PROPERTY
                    + "' system property was set to false");
            return;
        }

        if (skip) {
            getLog().warn("Liquibase skipped due to maven configuration");
            return;
        }
        
        getLog().info("project " + project);

        // processSystemProperties();
        final ClassLoader artifactClassLoader = getMavenArtifactClassloader();
        // configureFieldsAndValues(getFileOpener(artifactClassLoader));

        try {
            LogFactory.setLoggingLevel(logging);
        }
        catch (IllegalArgumentException e) {
            throw new MojoExecutionException("Failed to set logging level: " + e.getMessage(),
                    e);
        }

        // Displays the settings for the Mojo depending of verbosity mode.
        // displayMojoSettings();

        // Check that all the parameters that must be specified have been by the user.
        //checkRequiredParametersAreSpecified();


        final Database lbSource  = createSourceDatabase();
        final Database lbTarget  = createTargetDatabase();

        try {    
            exportSchema(lbSource, lbTarget);
            updateSchema(lbTarget);
            
            if (isStateSaved()) {
                getLog().info("Starting data load from schema " + sourceSchema);
                migrator.migrate(lbSource, lbTarget, getLog(), interactiveMode);
                // exportData(lbSource, lbTarget);
            }
            
            try {
                updateConstraints(lbTarget, artifactClassLoader);
            }
            catch (Exception e) {
                // Squash  errors for constraints
            }

            if (lbTarget instanceof H2Database) {
                final Statement st = ((JdbcConnection) lbTarget.getConnection()).createStatement();
                st.execute("SHUTDOWN DEFRAG");
            }
            
        } 
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        } 
        finally {
            try {
                if (lbSource != null) {
                    lbSource.close();
                }
                if (lbTarget != null) {
                    lbTarget.close();
                }
            }
            catch (Exception e) {
            }
        }


        cleanup(lbSource);
        cleanup(lbTarget);
        
        getLog().info(MavenUtils.LOG_SEPARATOR);
        getLog().info("");
    }
    
    protected void updateSchema(final Database target) throws MojoExecutionException {
        final ClassLoader artifactClassLoader = getMavenArtifactClassloader();
        updateTables   (target, artifactClassLoader);
        updateSequences(target, artifactClassLoader);
        updateViews    (target, artifactClassLoader);
        updateIndexes  (target, artifactClassLoader);
    }

    protected void updateTables(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
        try {
            final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-tab.xml", getFileOpener(artifactClassLoader), target);
            liquibase.update(null);
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

    }

    protected void updateSequences(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
        try {
            final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-seq.xml", getFileOpener(artifactClassLoader), target);
            liquibase.update(null);
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    protected void updateViews(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
        try {
            final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-vw.xml", getFileOpener(artifactClassLoader), target);
            liquibase.update(null);
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    protected void updateIndexes(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
        try {
            final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-idx.xml", getFileOpener(artifactClassLoader), target);
            liquibase.update(null);
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    protected void updateConstraints(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
        try {
            final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-cst.xml", getFileOpener(artifactClassLoader), target);
            liquibase.update(null);
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    protected Database createSourceDatabase() throws MojoExecutionException {
        try {
            final DatabaseFactory factory = DatabaseFactory.getInstance();
            final Database retval = factory.findCorrectDatabaseImplementation(openConnection(sourceUrl, sourceUser, sourcePass, sourceDriverClass, ""));
            retval.setDefaultSchemaName(sourceSchema);
            return retval;
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }
    
    protected Database createTargetDatabase() throws MojoExecutionException {
        try {   
            final DatabaseFactory factory = DatabaseFactory.getInstance();
            final Database retval = factory.findCorrectDatabaseImplementation(openConnection(targetUrl, targetUser, targetPass, targetDriverClass, ""));
            retval.setDefaultSchemaName(targetSchema);
            return retval;
        }
        catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    /**
     * Drops the database. Makes sure it's done right the first time.
     *
     * @param liquibase
     * @throws LiquibaseException
     */
    protected void dropAll(final Liquibase liquibase) throws LiquibaseException {
        boolean retry = true;
        while (retry) {
            try {
                liquibase.dropAll();
                retry = false;
            }
            catch (LiquibaseException e2) {
                getLog().info(e2.getMessage());
                if (e2.getMessage().indexOf("ORA-02443") < 0 && e2.getCause() != null && retry) {
                    retry = (e2.getCause().getMessage().indexOf("ORA-02443") > -1);
                }
                
                if (!retry) {
                    throw e2;
                }
                else {
                    getLog().info("Got ORA-2443. Retrying...");
                }
            }
        }        
    }
    
    @Override
    protected void printSettings(String indent) {
        super.printSettings(indent);
        getLog().info(indent + "drop first? " + dropFirst);

    }

    /**
     * Parses a properties file and sets the assocaited fields in the plugin.
     *
     * @param propertiesInputStream The input stream which is the Liquibase properties that
     *                              needs to be parsed.
     * @throws org.apache.maven.plugin.MojoExecutionException
     *          If there is a problem parsing
     *          the file.
     */
    protected void parsePropertiesFile(InputStream propertiesInputStream)
            throws MojoExecutionException {
        if (propertiesInputStream == null) {
            throw new MojoExecutionException("Properties file InputStream is null.");
        }
        Properties props = new Properties();
        try {
            props.load(propertiesInputStream);
        }
        catch (IOException e) {
            throw new MojoExecutionException("Could not load the properties Liquibase file", e);
        }

        for (Iterator it = props.keySet().iterator(); it.hasNext();) {
            String key = null;
            try {
                key = (String) it.next();
                Field field = getDeclaredField(this.getClass(), key);

                if (propertyFileWillOverride) {
                    setFieldValue(field, props.get(key).toString());
                } 
                else {
                    if (!isCurrentFieldValueSpecified(field)) {
                        getLog().debug("  properties file setting value: " + field.getName());
                        setFieldValue(field, props.get(key).toString());
                    }
                }
            }
            catch (Exception e) {
                getLog().info("  '" + key + "' in properties file is not being used by this "
                        + "task.");
            }
        }
    }

    /**
     * This method will check to see if the user has specified a value different to that of
     * the default value. This is not an ideal solution, but should cover most situations in
     * the use of the plugin.
     *
     * @param f The Field to check if a user has specified a value for.
     * @return true if the user has specified a value.
     */
    private boolean isCurrentFieldValueSpecified(Field f) throws IllegalAccessException {
        Object currentValue = f.get(this);
        if (currentValue == null) {
            return false;
        }

        Object defaultValue = getDefaultValue(f);
        if (defaultValue == null) {
            return currentValue != null;
        } else {
            // There is a default value, check to see if the user has selected something other
            // than the default
            return !defaultValue.equals(f.get(this));
        }
    }

    private Object getDefaultValue(Field field) throws IllegalAccessException {
        List allFields = new ArrayList();
        allFields.addAll(Arrays.asList(getClass().getDeclaredFields()));
        allFields.addAll(Arrays.asList(AbstractLiquibaseMojo.class.getDeclaredFields()));

        for (Field f : allFields) {
            if (f.getName().equals(field.getName() + DEFAULT_FIELD_SUFFIX)) {
                f.setAccessible(true);
                return f.get(this);
            }
        }
        return null;
    }

    
    /**
     * Recursively searches for the field specified by the fieldName in the class and all
     * the super classes until it either finds it, or runs out of parents.
     * @param clazz The Class to start searching from.
     * @param fieldName The name of the field to retrieve.
     * @return The {@link Field} identified by the field name.
     * @throws NoSuchFieldException If the field was not found in the class or any of its
     * super classes.
     */
    protected Field getDeclaredField(Class clazz, String fieldName)
        throws NoSuchFieldException {
        getLog().debug("Checking " + clazz.getName() + " for '" + fieldName + "'");
        try {
            Field f = clazz.getDeclaredField(fieldName);
            
            if (f != null) {
                return f;
            }
        }
        catch (Exception e) {
        }
        
        while (clazz.getSuperclass() != null) {        
            clazz = clazz.getSuperclass();
            getLog().debug("Checking " + clazz.getName() + " for '" + fieldName + "'");
            try {
                Field f = clazz.getDeclaredField(fieldName);
                
                if (f != null) {
                    return f;
                }
            }
            catch (Exception e) {
            }
        }

        throw new NoSuchFieldException("The field '" + fieldName + "' could not be "
                                       + "found in the class of any of its parent "
                                       + "classes.");
    }

    private void setFieldValue(Field field, String value) throws IllegalAccessException {
        if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) {
            field.set(this, Boolean.valueOf(value));
        } 
        else {
            field.set(this, value);
        }
    }

/*
    protected void exportData(final Database source, final Database target) {

        final DatabaseFactory factory = DatabaseFactory.getInstance();
        try {
            h2db = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("h2")));
            h2db.setDefaultSchemaName(h2Config.getSchema());

            export(new Diff(source, getDefaultSchemaName()), h2db, "tables", "-dat.xml");

            ResourceAccessor antFO = new AntResourceAccessor(getProject(), classpath);
            ResourceAccessor fsFO = new FileSystemResourceAccessor();

            String changeLogFile = getChangeLogFile() + "-dat.xml";

            Liquibase liquibase = new Liquibase(changeLogFile, new CompositeResourceAccessor(antFO, fsFO), h2db);

            log("Loading Schema");
            liquibase.update(getContexts());
            log("Finished Loading the Schema");

        }
        catch (Exception e) {
        }
        catch (Exception e) {
            throw new BuildException(e);
        }
        finally {
            try {
                if (h2db != null) {
                    // hsqldb.getConnection().createStatement().execute("SHUTDOWN");                                                   
                    log("Closing h2 database");
                    h2db.close();
                }
            }
            catch (Exception e) {
                if (!(e instanceof java.sql.SQLNonTransientConnectionException)) {
                    e.printStackTrace();
                }
            }

        }
    }            
    */
    
    protected void exportConstraints(Diff diff, Database target) throws MojoExecutionException {
        export(diff, target, "foreignKeys", "-cst.xml");
    }

    protected void exportIndexes(Diff diff, Database target) throws MojoExecutionException {
        export(diff, target, "indexes", "-idx.xml");
    }

    protected void exportViews(Diff diff, Database target) throws MojoExecutionException {
    export(diff, target, "views", "-vw.xml");
    }

    protected void exportTables(Diff diff, Database target) throws MojoExecutionException  {
        export(diff, target, "tables, primaryKeys, uniqueConstraints", "-tab.xml");
    }

    protected void exportSequences(Diff diff, Database target) throws MojoExecutionException {
        export(diff, target, "sequences", "-seq.xml");
    }
    
    protected void export(final Diff diff, final Database target, final String diffTypes, final String suffix) throws MojoExecutionException {
        diff.setDiffTypes(diffTypes);

        try {
            DiffResult results = diff.compare();
            results.printChangeLog(getChangeLogFile() + suffix, target);
        }
        catch (Exception e) {
            throw new MojoExecutionException("Exception while exporting to the target: " + e.getMessage(), e);
        }
    }

    protected void exportSchema(final Database source, final Database target) throws MojoExecutionException {
        try {
            Diff diff = new Diff(source, source.getDefaultSchemaName());
            exportTables(diff, target);
            exportSequences(diff, target);
            exportViews(diff, target);
            exportIndexes(diff, target);
            exportConstraints(diff, target);
        }
        catch (Exception e) {
            throw new MojoExecutionException("Exception while exporting the source schema: " + e.getMessage(), e);
        }
    }

    protected JdbcConnection openConnection(final String url, 
                                            final String username, 
                                            final String password, 
                                            final String className, 
                                            final String schema) throws MojoExecutionException {
        Connection retval = null;
        int retry_count = 0;
        final int max_retry = 5;
        while (retry_count < max_retry) {
            try {
                getLog().debug("Loading schema " + schema + " at url " + url);
                Class.forName(className);
                retval = DriverManager.getConnection(url, username, password);
                retval.setAutoCommit(true);
            }
            catch (Exception e) {
                if (!e.getMessage().contains("Database lock acquisition failure") && !(e instanceof NullPointerException)) {
                    throw new MojoExecutionException(e.getMessage(), e);
                }
            }
            finally {
                retry_count++;
            }
        }
        return new JdbcConnection(retval);
    }
    
    @Override
    protected ResourceAccessor getFileOpener(final ClassLoader cl) {
        final ResourceAccessor mFO = new MavenResourceAccessor(cl);
        final ResourceAccessor fsFO = new FileSystemResourceAccessor(project.getBasedir().getAbsolutePath());
        return new CompositeResourceAccessor(mFO, fsFO);
    }
    
    public void setMavenHome(final File mavenHome) {
        this.mavenHome = mavenHome;
    }
    
    public File getMavenHome() {
        return this.mavenHome;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy