001// Copyright 2014 Leo Przybylski. All rights reserved.
002//
003// Redistribution and use in source and binary forms, with or without modification, are
004// permitted provided that the following conditions are met:
005//
006//    1. Redistributions of source code must retain the above copyright notice, this list of
007//       conditions and the following disclaimer.
008//
009//    2. Redistributions in binary form must reproduce the above copyright notice, this list
010//       of conditions and the following disclaimer in the documentation and/or other materials
011//       provided with the distribution.
012//
013// THIS SOFTWARE IS PROVIDED BY Leo Przybylski ''AS IS'' AND ANY EXPRESS OR IMPLIED
014// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
015// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
016// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
017// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
018// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
019// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
020// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
021// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
022//
023// The views and conclusions contained in the software and documentation are those of the
024// authors and should not be interpreted as representing official policies, either expressed
025// or implied, of Leo Przybylski.
026package org.kualigan.maven.plugins.liquibase;
027
028
029import org.apache.maven.plugin.AbstractMojo;
030import org.apache.maven.plugin.MojoExecutionException;
031import org.apache.maven.plugin.MojoFailureException;
032import org.apache.maven.plugins.annotations.Component;
033import org.apache.maven.plugins.annotations.Mojo;
034import org.apache.maven.plugins.annotations.Parameter;
035import org.apache.maven.project.MavenProject;
036import org.apache.maven.artifact.manager.WagonManager;
037
038import org.liquibase.maven.plugins.MavenUtils;
039import org.liquibase.maven.plugins.AbstractLiquibaseMojo;
040import org.liquibase.maven.plugins.AbstractLiquibaseChangeLogMojo;
041import org.liquibase.maven.plugins.MavenResourceAccessor;
042
043import org.apache.maven.shared.invoker.DefaultInvocationRequest;
044import org.apache.maven.shared.invoker.DefaultInvoker;
045import org.apache.maven.shared.invoker.InvocationOutputHandler;
046import org.apache.maven.shared.invoker.InvocationRequest;
047import org.apache.maven.shared.invoker.InvocationResult;
048import org.apache.maven.shared.invoker.Invoker;
049import org.apache.maven.shared.invoker.InvokerLogger;
050import org.apache.maven.shared.invoker.MavenInvocationException;
051
052import org.apache.commons.cli.CommandLine;
053import org.apache.commons.cli.OptionBuilder;
054import org.apache.commons.cli.Options;
055import org.apache.commons.cli.PosixParser;
056
057import org.codehaus.plexus.util.FileUtils;
058import org.codehaus.plexus.util.IOUtil;
059import org.codehaus.plexus.util.StringUtils;
060import org.codehaus.plexus.util.cli.CommandLineUtils;
061
062import liquibase.CatalogAndSchema;
063import liquibase.Liquibase;
064import liquibase.resource.CompositeResourceAccessor;
065import liquibase.resource.FileSystemResourceAccessor;
066import liquibase.resource.ResourceAccessor;
067import liquibase.integration.ant.AntResourceAccessor;
068import liquibase.integration.ant.BaseLiquibaseTask;
069import liquibase.database.Database;
070import liquibase.database.DatabaseFactory;
071import liquibase.database.core.H2Database;
072import liquibase.database.jvm.JdbcConnection;
073import liquibase.structure.DatabaseObject;
074import liquibase.snapshot.DatabaseSnapshot;
075import liquibase.snapshot.SnapshotControl;
076import liquibase.snapshot.SnapshotGeneratorFactory;
077import org.apache.tools.ant.BuildException;
078
079import org.h2.tools.Backup;
080import org.h2.tools.DeleteDbFiles;
081
082import liquibase.ext.kualigan.diff.DiffGenerator;
083import liquibase.diff.DiffGeneratorFactory;
084import liquibase.diff.DiffResult;
085import liquibase.diff.compare.CompareControl;
086
087import liquibase.database.core.H2Database;
088import liquibase.database.jvm.JdbcConnection;
089import liquibase.exception.LiquibaseException;
090import liquibase.logging.LogFactory;
091import liquibase.serializer.ChangeLogSerializer;
092import liquibase.parser.core.xml.LiquibaseEntityResolver;
093import liquibase.parser.core.xml.XMLChangeLogSAXParser;
094
095import org.apache.maven.wagon.authentication.AuthenticationInfo;
096
097import liquibase.util.xml.DefaultXmlWriter;
098
099import org.w3c.dom.*;
100
101import javax.xml.parsers.DocumentBuilder;
102import javax.xml.parsers.DocumentBuilderFactory;
103import javax.xml.parsers.ParserConfigurationException;
104
105import java.lang.reflect.Field;
106import java.lang.reflect.Method;
107
108import java.io.File;
109import java.io.FileNotFoundException;
110import java.io.FileOutputStream;
111import java.io.InputStream;
112import java.io.IOException;
113import java.net.URL;
114import java.sql.Connection;
115import java.sql.DriverManager;
116import java.sql.Statement;
117import java.util.ArrayList;
118import java.util.Arrays;
119import java.util.Collection;
120import java.util.Iterator;
121import java.util.List;
122import java.util.Properties;
123import java.util.StringTokenizer;
124
125/**
126 * Copies a database including DDL/DML from one location to another.
127 *
128 * @author Leo Przybylski
129 */
130@Mojo(
131      name="copy-database",
132      requiresProject = false
133      )
134      public class CopyMojo extends AbstractLiquibaseChangeLogMojo {
135          public static final String DEFAULT_CHANGELOG_PATH = "src/main/changelogs";
136
137          /**
138           * Suffix for fields that are representing a default value for a another field.
139           */
140          private static final String DEFAULT_FIELD_SUFFIX = "Default";
141
142          private static final Options OPTIONS = new Options();
143
144          private static final char SET_SYSTEM_PROPERTY = 'D';
145
146          private static final char OFFLINE = 'o';
147
148          private static final char REACTOR = 'r';
149
150          private static final char QUIET = 'q';
151
152          private static final char DEBUG = 'X';
153
154          private static final char ERRORS = 'e';
155
156          private static final char NON_RECURSIVE = 'N';
157
158          private static final char UPDATE_SNAPSHOTS = 'U';
159
160          private static final char ACTIVATE_PROFILES = 'P';
161
162          private static final String FORCE_PLUGIN_UPDATES = "cpu";
163
164          private static final String FORCE_PLUGIN_UPDATES2 = "up";
165
166          private static final String SUPPRESS_PLUGIN_UPDATES = "npu";
167
168          private static final String SUPPRESS_PLUGIN_REGISTRY = "npr";
169
170          private static final char CHECKSUM_FAILURE_POLICY = 'C';
171
172          private static final char CHECKSUM_WARNING_POLICY = 'c';
173
174          private static final char ALTERNATE_USER_SETTINGS = 's';
175
176          private static final String FAIL_FAST = "ff";
177
178          private static final String FAIL_AT_END = "fae";
179
180          private static final String FAIL_NEVER = "fn";
181    
182          private static final String ALTERNATE_POM_FILE = "f";
183
184
185          @Parameter(property = "project", defaultValue = "${project}")
186          protected MavenProject project;
187
188          /**
189           * User settings use to check the interactiveMode.
190           *
191           */
192          @Parameter(property = "interactiveMode", defaultValue = "${settings.interactiveMode}")
193          protected Boolean interactiveMode;
194    
195          /**
196           * 
197           * The Maven Wagon manager to use when obtaining server authentication details.
198           */
199          @Component(role=org.apache.maven.artifact.manager.WagonManager.class)
200          protected WagonManager wagonManager;
201
202          /**
203           * 
204           * The Maven Wagon manager to use when obtaining server authentication details.
205           */
206          @Component(role=org.kualigan.maven.plugins.liquibase.MigrateHelper.class)
207          protected MigrateHelper migrator;
208
209          /**
210           * The server id in settings.xml to use when authenticating the source server with.
211           */
212          @Parameter(property = "lb.copy.source", required = true)
213          private String source;
214
215          /**
216           * The server id in settings.xml to use when authenticating the source server with.
217           */
218          @Parameter(property = "lb.copy.source.schema")
219          private String sourceSchema;
220
221          private String sourceUser;
222
223          private String sourcePass;
224
225          /**
226           * The server id in settings.xml to use when authenticating the source server with.
227           */
228          @Parameter(property = "lb.copy.source.driver")
229          private String sourceDriverClass;
230
231          /**
232           * The server id in settings.xml to use when authenticating the source server with.
233           */
234          @Parameter(property = "lb.copy.source.url", required = true)
235          private String sourceUrl;
236
237          /**
238           * The server id in settings.xml to use when authenticating the target server with.
239           */
240          @Parameter(property = "lb.copy.target", required = true)
241          private String target;
242
243          /**
244           * The server id in settings.xml to use when authenticating the target server with.
245           */
246          @Parameter(property = "lb.copy.target.schema")
247          private String targetSchema;
248
249          private String targetUser;
250
251          private String targetPass;
252
253          /**
254           * The server id in settings.xml to use when authenticating the source server with.
255           */
256          @Parameter(property = "lb.copy.target.driver")
257          private String targetDriverClass;
258
259          /**
260           * The server id in settings.xml to use when authenticating the source server with.
261           */
262          @Parameter(property = "lb.copy.target.url", required = true)
263          private String targetUrl;
264
265
266          /**
267           * Controls the verbosity of the output from invoking the plugin.
268           *
269           * @description Controls the verbosity of the plugin when executing
270           */
271          @Parameter(property = "liquibase.verbose", defaultValue = "false")
272          protected boolean verbose;
273
274          /**
275           * Controls the level of logging from Liquibase when executing. The value can be
276           * "all", "finest", "finer", "fine", "info", "warning", "severe" or "off". The value is
277           * case insensitive.
278           *
279           * @description Controls the verbosity of the plugin when executing
280           */
281          @Parameter(property = "liquibase.logging", defaultValue = "INFO")
282          protected String logging;
283
284          /**
285           * The Liquibase properties file used to configure the Liquibase {@link
286           * liquibase.Liquibase}.
287           */
288          @Parameter(property = "liquibase.propertyFile")
289          protected String propertyFile;
290    
291          /**
292           * Specifies the change log file to use for Liquibase. No longer needed with updatePath.
293           * @deprecated
294           */
295          @Parameter(property = "liquibase.changeLogFile")
296          protected String changeLogFile;
297
298          /**
299           */
300          @Parameter(property = "liquibase.changeLogSavePath", defaultValue = "${project.basedir}/target/changelogs")
301          protected File changeLogSavePath;
302    
303          /**
304           * Whether or not to perform a drop on the database before executing the change.
305           */
306          @Parameter(property = "liquibase.dropFirst", defaultValue = "false")
307          protected boolean dropFirst;
308    
309          /**
310           * Property to flag whether to copy data as well as structure of the database schema
311           */
312          @Parameter(property = "lb.copy.data", defaultValue = "true")
313          protected boolean stateSaved;
314    
315          protected Boolean isStateSaved() {
316              return stateSaved;
317          }
318
319          /**
320           * The {@code M2_HOME} parameter to use for forked Maven invocations.
321           *
322           */
323          @Parameter(defaultValue = "${maven.home}")
324          protected File mavenHome;
325
326          protected File getBasedir() {
327              return project.getBasedir();
328          }
329    
330          protected String getChangeLogFile() throws MojoExecutionException {
331              if (changeLogFile != null) {
332                  return changeLogFile;
333              }
334        
335              try {
336                  changeLogFile = changeLogSavePath.getCanonicalPath();
337                  new File(changeLogFile).mkdirs();
338                  changeLogFile += File.separator + targetUser;
339                  return changeLogFile;
340              }
341              catch (Exception e) {
342                  throw new MojoExecutionException("Exception getting the location of the change log file: " + e.getMessage(), e);
343              }
344          }
345
346          protected void doFieldHack() {
347              for (final Field field : getClass().getDeclaredFields()) {
348                  try {
349                      final Field parentField = getDeclaredField(getClass().getSuperclass(), field.getName());
350                      if (parentField != null) {
351                          getLog().debug("Setting " + field.getName() + " in " + parentField.getDeclaringClass().getName() + " to " + field.get(this));
352                          parentField.set(this, field.get(this));
353                      }
354                  }
355                  catch (Exception e) {
356                  }
357              }
358          }
359
360
361          /*
362            @Override
363            public void execute() throws MojoExecutionException, MojoFailureException {
364            doFieldHack();
365
366            try {
367            Method meth = AbstractLiquibaseMojo.class.getDeclaredMethod("processSystemProperties");
368            meth.setAccessible(true);
369            meth.invoke(this);
370            }
371            catch (Exception e) {
372            e.printStackTrace();
373            }
374
375            ClassLoader artifactClassLoader = getMavenArtifactClassLoader();
376            configureFieldsAndValues(getFileOpener(artifactClassLoader));
377        
378            doFieldHack();
379
380        
381            super.execute();
382            }
383          */
384    
385          public ClassLoader getMavenArtifactClassloader() throws MojoExecutionException {
386              try {
387                  return MavenUtils.getArtifactClassloader(project, true, false, getClass(), getLog(), false);
388              }
389              catch (Exception e) {
390                  throw new MojoExecutionException(e.getMessage(), e);
391              }
392          }
393    
394          public String lookupDriverFor(final String url) {
395              for (final Database databaseImpl : DatabaseFactory.getInstance().getImplementedDatabases()) {
396                  final String driver = databaseImpl.getDefaultDriver(url);
397                  if (driver != null) {
398                      return driver;
399                  }
400              }
401              return null;
402          }
403    
404          public void execute() throws MojoExecutionException, MojoFailureException {
405              if (project == null || project.getArtifactId().equalsIgnoreCase("standalone-pom")) {
406                  getLog().info("Using standalone-pom. No project. I have to create one.");
407                  generateArchetype(getMavenHome(), new Properties() {{
408                      setProperty("archetypeGroupId",      "org.kualigan.maven.archetypes");
409                      setProperty("archetypeArtifactId",   "lb-copy-archetype");
410                      setProperty("archetypeVersion",      "1.1.7");
411                      setProperty("groupId",               "org.kualigan.liquibase");
412                      setProperty("artifactId",            "copy");
413                      setProperty("version",               "1.0.0-SNAPSHOT");
414                  }});
415            
416                  invokeCopy(getMavenHome(), new Properties() {{
417                      setProperty("lb.copy.source",        source);
418                      setProperty("lb.copy.source.url",    sourceUrl);
419                      if (sourceDriverClass != null) {
420                          setProperty("lb.copy.source.driver", sourceDriverClass);
421                      }
422                      if (sourceSchema != null) {
423                          setProperty("lb.copy.source.schema", sourceSchema);
424                      }
425                      setProperty("lb.copy.target",        target);
426                      setProperty("lb.copy.target.url",    targetUrl);
427                      if (targetDriverClass != null) {
428                          setProperty("lb.copy.target.driver", targetDriverClass);
429                      }
430                      if (targetSchema != null) {
431                          setProperty("lb.copy.target.schema", targetSchema);
432                      }
433                  }});
434
435              }
436              else {
437                  doCopy();
438              }
439          }
440
441          /**
442           * Invokes the maven goal {@code archetype:generate} with the appropriate properties.
443           * 
444           */
445          public void invokeCopy(final File mavenHome, final Properties copyProperties) throws MojoExecutionException {
446              final Invoker invoker = new DefaultInvoker().setMavenHome(mavenHome);
447              invoker.setWorkingDirectory(new File(System.getProperty("user.dir") + File.separator + "copy"));
448        
449              final String additionalArguments = "";
450
451              final InvocationRequest req = new DefaultInvocationRequest()
452                  .setInteractive(true)
453                  .setProperties(copyProperties);
454        
455              setupRequest(req, additionalArguments);
456
457              req.setGoals(new ArrayList<String>() {{ add("lb:copy-database"); }});
458
459              try {
460                  final InvocationResult invocationResult = invoker.execute(req);
461
462                  if ( invocationResult.getExecutionException() != null ) {
463                      throw new MojoExecutionException("Error executing Maven.",
464                                                       invocationResult.getExecutionException());
465                  }
466                
467                  if (invocationResult.getExitCode() != 0) {
468                      throw new MojoExecutionException(
469                                                       "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'");
470                  }
471              }
472              catch (MavenInvocationException e) {
473                  throw new MojoExecutionException( "Failed to invoke Maven build.", e );
474              }
475          }
476
477          /**
478           * Invokes the maven goal {@code archetype:generate} with the appropriate properties.
479           * 
480           */
481          public void generateArchetype(final File mavenHome, final Properties archetypeProperties) throws MojoExecutionException {
482              final Invoker invoker = new DefaultInvoker().setMavenHome(mavenHome);
483        
484              final String additionalArguments = "";
485
486              final InvocationRequest req = new DefaultInvocationRequest()
487                  .setInteractive(false)
488                  .setProperties(archetypeProperties);
489                    
490              setupRequest(req, additionalArguments);
491
492              req.setGoals(new ArrayList<String>() {{ add("archetype:generate"); }});
493
494              try {
495                  final InvocationResult invocationResult = invoker.execute(req);
496
497                  if ( invocationResult.getExecutionException() != null ) {
498                      throw new MojoExecutionException("Error executing Maven.",
499                                                       invocationResult.getExecutionException());
500                  }
501                
502                  if (invocationResult.getExitCode() != 0) {
503                      throw new MojoExecutionException(
504                                                       "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'");
505                  }
506              }
507              catch (MavenInvocationException e) {
508                  throw new MojoExecutionException( "Failed to invoke Maven build.", e );
509              }
510          }
511
512          /**
513           * 
514           */
515          protected void setupRequest(final InvocationRequest req,
516                                      final String additionalArguments) throws MojoExecutionException {
517              try {
518                  final String[] args = CommandLineUtils.translateCommandline(additionalArguments);
519                  CommandLine cli = new PosixParser().parse(OPTIONS, args);
520
521                  if (cli.hasOption( SET_SYSTEM_PROPERTY)) {
522                      String[] properties = cli.getOptionValues(SET_SYSTEM_PROPERTY);
523                      Properties props = new Properties();
524                      for ( int i = 0; i < properties.length; i++ )
525                          {
526                              String property = properties[i];
527                              String name, value;
528                              int sep = property.indexOf( "=" );
529                              if ( sep <= 0 )
530                                  {
531                                      name = property.trim();
532                                      value = "true";
533                                  }
534                              else
535                                  {
536                                      name = property.substring( 0, sep ).trim();
537                                      value = property.substring( sep + 1 ).trim();
538                                  }
539                              props.setProperty( name, value );
540                          }
541
542                      req.setProperties( props );
543                  }
544
545                  if ( cli.hasOption( OFFLINE ) )
546                      {
547                          req.setOffline( true );
548                      }
549
550                  if ( cli.hasOption( QUIET ) )
551                      {
552                          // TODO: setQuiet() currently not supported by InvocationRequest
553                          req.setDebug( false );
554                      }
555                  else if ( cli.hasOption( DEBUG ) )
556                      {
557                          req.setDebug( true );
558                      }
559                  else if ( cli.hasOption( ERRORS ) )
560                      {
561                          req.setShowErrors( true );
562                      }
563
564                  if ( cli.hasOption( REACTOR ) )
565                      {
566                          req.setRecursive( true );
567                      }
568                  else if ( cli.hasOption( NON_RECURSIVE ) )
569                      {
570                          req.setRecursive( false );
571                      }
572
573                  if ( cli.hasOption( UPDATE_SNAPSHOTS ) )
574                      {
575                          req.setUpdateSnapshots( true );
576                      }
577
578                  if ( cli.hasOption( ACTIVATE_PROFILES ) )
579                      {
580                          String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES );
581                          List<String> activatedProfiles = new ArrayList<String>();
582                          List<String> deactivatedProfiles = new ArrayList<String>();
583
584                          if ( profiles != null )
585                              {
586                                  for ( int i = 0; i < profiles.length; ++i )
587                                      {
588                                          StringTokenizer profileTokens = new StringTokenizer( profiles[i], "," );
589
590                                          while ( profileTokens.hasMoreTokens() )
591                                              {
592                                                  String profileAction = profileTokens.nextToken().trim();
593
594                                                  if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) )
595                                                      {
596                                                          deactivatedProfiles.add( profileAction.substring( 1 ) );
597                                                      }
598                                                  else if ( profileAction.startsWith( "+" ) )
599                                                      {
600                                                          activatedProfiles.add( profileAction.substring( 1 ) );
601                                                      }
602                                                  else
603                                                      {
604                                                          activatedProfiles.add( profileAction );
605                                                      }
606                                              }
607                                      }
608                              }
609
610                          if ( !deactivatedProfiles.isEmpty() )
611                              {
612                                  getLog().warn( "Explicit profile deactivation is not yet supported. "
613                                                 + "The following profiles will NOT be deactivated: " + StringUtils.join(
614                                                                                                                         deactivatedProfiles.iterator(), ", " ) );
615                              }
616
617                          if ( !activatedProfiles.isEmpty() )
618                              {
619                                  req.setProfiles( activatedProfiles );
620                              }
621                      }
622
623                  if ( cli.hasOption( FORCE_PLUGIN_UPDATES ) || cli.hasOption( FORCE_PLUGIN_UPDATES2 ) )
624                      {
625                          getLog().warn( "Forcing plugin updates is not supported currently." );
626                      }
627                  else if ( cli.hasOption( SUPPRESS_PLUGIN_UPDATES ) )
628                      {
629                          req.setNonPluginUpdates( true );
630                      }
631
632                  if ( cli.hasOption( SUPPRESS_PLUGIN_REGISTRY ) )
633                      {
634                          getLog().warn( "Explicit suppression of the plugin registry is not supported currently." );
635                      }
636
637                  if ( cli.hasOption( CHECKSUM_FAILURE_POLICY ) )
638                      {
639                          req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL );
640                      }
641                  else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) )
642                      {
643                          req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN );
644                      }
645
646                  if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) )
647                      {
648                          req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) );
649                      }
650
651                  if ( cli.hasOption( FAIL_AT_END ) )
652                      {
653                          req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END );
654                      }
655                  else if ( cli.hasOption( FAIL_FAST ) )
656                      {
657                          req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST );
658                      }
659                  if ( cli.hasOption( FAIL_NEVER ) )
660                      {
661                          req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER );
662                      }
663                  if ( cli.hasOption( ALTERNATE_POM_FILE ) )
664                      {
665                          if ( req.getPomFileName() != null ) {
666                              getLog().info("pomFileName is already set, ignoring the -f argument" );
667                          }
668                          else {
669                              req.setPomFileName(cli.getOptionValue(ALTERNATE_POM_FILE));
670                          }
671                      }
672              }
673              catch (Exception e) {
674                  throw new MojoExecutionException("Failed to re-parse additional arguments for Maven invocation.", e );
675              }
676          }
677
678          protected void doCopy() throws MojoExecutionException, MojoFailureException {
679              getLog().info(MavenUtils.LOG_SEPARATOR);
680
681              if (source != null) {
682                  final AuthenticationInfo info = wagonManager.getAuthenticationInfo(source);
683                  if (info != null) {
684                      sourceUser = info.getUserName();
685                      sourcePass = info.getPassword();
686                  }
687              }
688
689              sourceDriverClass = lookupDriverFor(sourceUrl);
690        
691              if (sourceSchema == null) {
692                  sourceSchema = sourceUser;
693              }
694
695              if (target != null) {
696                  final AuthenticationInfo info = wagonManager.getAuthenticationInfo(target);
697                  if (info != null) {
698                      targetUser = info.getUserName();
699                      targetPass = info.getPassword();
700                  }
701              }
702        
703              if (targetSchema == null) {
704                  targetSchema = targetUser;
705              }
706        
707              targetDriverClass = lookupDriverFor(targetUrl);
708        
709              getLog().info("project " + project);
710
711              // processSystemProperties();
712              final ClassLoader artifactClassLoader = getMavenArtifactClassloader();
713              // configureFieldsAndValues(getFileOpener(artifactClassLoader));
714
715              try {
716                  LogFactory.setLoggingLevel(logging);
717              }
718              catch (IllegalArgumentException e) {
719                  throw new MojoExecutionException("Failed to set logging level: " + e.getMessage(),
720                                                   e);
721              }
722
723              // Displays the settings for the Mojo depending of verbosity mode.
724              // displayMojoSettings();
725
726              // Check that all the parameters that must be specified have been by the user.
727              //checkRequiredParametersAreSpecified();
728
729
730              final Database lbSource  = createSourceDatabase();
731              final Database lbTarget  = createTargetDatabase();
732
733              try {    
734                  exportSchema(lbSource, lbTarget);
735                  updateSchema(lbTarget);
736            
737                  if (isStateSaved()) {
738                      getLog().info("Starting data load from schema " + sourceSchema);
739                      migrator.migrate(lbSource, lbTarget, getLog(), interactiveMode);
740                      // exportData(lbSource, lbTarget);
741                  }
742            
743                  try {
744                      updateConstraints(lbTarget, artifactClassLoader);
745                  }
746                  catch (Exception e) {
747                      // Squash  errors for constraints
748                  }
749
750                  if (lbTarget instanceof H2Database) {
751                      final Statement st = ((JdbcConnection) lbTarget.getConnection()).createStatement();
752                      st.execute("SHUTDOWN DEFRAG");
753                  }
754            
755              } 
756              catch (Exception e) {
757                  throw new MojoExecutionException(e.getMessage(), e);
758              } 
759              finally {
760                  try {
761                      if (lbSource != null) {
762                          lbSource.close();
763                      }
764                      if (lbTarget != null) {
765                          lbTarget.close();
766                      }
767                  }
768                  catch (Exception e) {
769                  }
770              }
771
772
773              cleanup(lbSource);
774              cleanup(lbTarget);
775        
776              getLog().info(MavenUtils.LOG_SEPARATOR);
777              getLog().info("");
778          }
779    
780          protected void updateSchema(final Database target) throws MojoExecutionException {
781              final ClassLoader artifactClassLoader = getMavenArtifactClassloader();
782              updateTables   (target, artifactClassLoader);
783              updateSequences(target, artifactClassLoader);
784              updateViews    (target, artifactClassLoader);
785              updateIndexes  (target, artifactClassLoader);
786          }
787
788          protected void updateTables(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
789              try {
790                  final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-tab.xml", getFileOpener(artifactClassLoader), target);
791                  liquibase.update("");
792              }
793              catch (Exception e) {
794                  throw new MojoExecutionException(e.getMessage(), e);
795              }
796
797          }
798
799          protected void updateSequences(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
800              try {
801                  final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-seq.xml", getFileOpener(artifactClassLoader), target);
802                  liquibase.update("");
803              }
804              catch (Exception e) {
805                  throw new MojoExecutionException(e.getMessage(), e);
806              }
807          }
808
809          protected void updateViews(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
810              try {
811                  final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-vw.xml", getFileOpener(artifactClassLoader), target);
812                  liquibase.update("");
813              }
814              catch (Exception e) {
815                  throw new MojoExecutionException(e.getMessage(), e);
816              }
817          }
818
819          protected void updateIndexes(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
820              try {
821                  final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-idx.xml", getFileOpener(artifactClassLoader), target);
822                  liquibase.update("");
823              }
824              catch (Exception e) {
825                  throw new MojoExecutionException(e.getMessage(), e);
826              }
827          }
828
829          protected void updateConstraints(final Database target, final ClassLoader artifactClassLoader) throws MojoExecutionException {
830              try {
831                  final Liquibase liquibase = new Liquibase(getChangeLogFile() + "-cst.xml", getFileOpener(artifactClassLoader), target);
832                  liquibase.update("");
833              }
834              catch (Exception e) {
835                  throw new MojoExecutionException(e.getMessage(), e);
836              }
837          }
838
839          protected Database createSourceDatabase() throws MojoExecutionException {
840              try {
841                  final DatabaseFactory factory = DatabaseFactory.getInstance();
842                  final Database retval = factory.findCorrectDatabaseImplementation(openConnection(sourceUrl, sourceUser, sourcePass, sourceDriverClass, ""));
843                  retval.setDefaultSchemaName(sourceSchema);
844                  return retval;
845              }
846              catch (Exception e) {
847                  throw new MojoExecutionException(e.getMessage(), e);
848              }
849          }
850    
851          protected Database createTargetDatabase() throws MojoExecutionException {
852              try {   
853                  final DatabaseFactory factory = DatabaseFactory.getInstance();
854                  final Database retval = factory.findCorrectDatabaseImplementation(openConnection(targetUrl, targetUser, targetPass, targetDriverClass, ""));
855                  retval.setDefaultSchemaName(targetSchema);
856                  return retval;
857              }
858              catch (Exception e) {
859                  throw new MojoExecutionException(e.getMessage(), e);
860              }
861          }
862
863          /**
864           * Drops the database. Makes sure it's done right the first time.
865           *
866           * @param liquibase
867           * @throws LiquibaseException
868           */
869          protected void dropAll(final Liquibase liquibase) throws LiquibaseException {
870              boolean retry = true;
871              while (retry) {
872                  try {
873                      liquibase.dropAll();
874                      retry = false;
875                  }
876                  catch (LiquibaseException e2) {
877                      getLog().info(e2.getMessage());
878                      if (e2.getMessage().indexOf("ORA-02443") < 0 && e2.getCause() != null && retry) {
879                          retry = (e2.getCause().getMessage().indexOf("ORA-02443") > -1);
880                      }
881                
882                      if (!retry) {
883                          throw e2;
884                      }
885                      else {
886                          getLog().info("Got ORA-2443. Retrying...");
887                      }
888                  }
889              }        
890          }
891    
892          @Override
893          protected void printSettings(String indent) {
894              super.printSettings(indent);
895              getLog().info(indent + "drop first? " + dropFirst);
896
897          }
898
899          /**
900           * Parses a properties file and sets the assocaited fields in the plugin.
901           *
902           * @param propertiesInputStream The input stream which is the Liquibase properties that
903           *                              needs to be parsed.
904           * @throws org.apache.maven.plugin.MojoExecutionException
905           *          If there is a problem parsing
906           *          the file.
907           */
908          protected void parsePropertiesFile(InputStream propertiesInputStream)
909              throws MojoExecutionException {
910              if (propertiesInputStream == null) {
911                  throw new MojoExecutionException("Properties file InputStream is null.");
912              }
913              Properties props = new Properties();
914              try {
915                  props.load(propertiesInputStream);
916              }
917              catch (IOException e) {
918                  throw new MojoExecutionException("Could not load the properties Liquibase file", e);
919              }
920
921              for (Iterator it = props.keySet().iterator(); it.hasNext();) {
922                  String key = null;
923                  try {
924                      key = (String) it.next();
925                      Field field = getDeclaredField(this.getClass(), key);
926
927                      if (propertyFileWillOverride) {
928                          setFieldValue(field, props.get(key).toString());
929                      } 
930                      else {
931                          if (!isCurrentFieldValueSpecified(field)) {
932                              getLog().debug("  properties file setting value: " + field.getName());
933                              setFieldValue(field, props.get(key).toString());
934                          }
935                      }
936                  }
937                  catch (Exception e) {
938                      getLog().info("  '" + key + "' in properties file is not being used by this "
939                                    + "task.");
940                  }
941              }
942          }
943
944          /**
945           * This method will check to see if the user has specified a value different to that of
946           * the default value. This is not an ideal solution, but should cover most situations in
947           * the use of the plugin.
948           *
949           * @param f The Field to check if a user has specified a value for.
950           * @return <code>true</code> if the user has specified a value.
951           */
952          private boolean isCurrentFieldValueSpecified(Field f) throws IllegalAccessException {
953              Object currentValue = f.get(this);
954              if (currentValue == null) {
955                  return false;
956              }
957
958              Object defaultValue = getDefaultValue(f);
959              if (defaultValue == null) {
960                  return currentValue != null;
961              } else {
962                  // There is a default value, check to see if the user has selected something other
963                  // than the default
964                  return !defaultValue.equals(f.get(this));
965              }
966          }
967
968          private Object getDefaultValue(Field field) throws IllegalAccessException {
969              List<Field> allFields = new ArrayList<Field>();
970              allFields.addAll(Arrays.asList(getClass().getDeclaredFields()));
971              allFields.addAll(Arrays.asList(AbstractLiquibaseMojo.class.getDeclaredFields()));
972
973              for (Field f : allFields) {
974                  if (f.getName().equals(field.getName() + DEFAULT_FIELD_SUFFIX)) {
975                      f.setAccessible(true);
976                      return f.get(this);
977                  }
978              }
979              return null;
980          }
981
982    
983          /**
984           * Recursively searches for the field specified by the fieldName in the class and all
985           * the super classes until it either finds it, or runs out of parents.
986           * @param clazz The Class to start searching from.
987           * @param fieldName The name of the field to retrieve.
988           * @return The {@link Field} identified by the field name.
989           * @throws NoSuchFieldException If the field was not found in the class or any of its
990           * super classes.
991           */
992          protected Field getDeclaredField(Class clazz, String fieldName)
993              throws NoSuchFieldException {
994              getLog().debug("Checking " + clazz.getName() + " for '" + fieldName + "'");
995              try {
996                  Field f = clazz.getDeclaredField(fieldName);
997            
998                  if (f != null) {
999                      return f;
1000                  }
1001              }
1002              catch (Exception e) {
1003              }
1004        
1005              while (clazz.getSuperclass() != null) {        
1006                  clazz = clazz.getSuperclass();
1007                  getLog().debug("Checking " + clazz.getName() + " for '" + fieldName + "'");
1008                  try {
1009                      Field f = clazz.getDeclaredField(fieldName);
1010                
1011                      if (f != null) {
1012                          return f;
1013                      }
1014                  }
1015                  catch (Exception e) {
1016                  }
1017              }
1018
1019              throw new NoSuchFieldException("The field '" + fieldName + "' could not be "
1020                                             + "found in the class of any of its parent "
1021                                             + "classes.");
1022          }
1023
1024          private void setFieldValue(Field field, String value) throws IllegalAccessException {
1025              if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) {
1026                  field.set(this, Boolean.valueOf(value));
1027              } 
1028              else {
1029                  field.set(this, value);
1030              }
1031          }
1032
1033          /*
1034            protected void exportData(final Database source, final Database target) {
1035
1036            final DatabaseFactory factory = DatabaseFactory.getInstance();
1037            try {
1038            h2db = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("h2")));
1039            h2db.setDefaultSchemaName(h2Config.getSchema());
1040
1041            export(new Diff(source, getDefaultSchemaName()), h2db, "tables", "-dat.xml");
1042
1043            ResourceAccessor antFO = new AntResourceAccessor(getProject(), classpath);
1044            ResourceAccessor fsFO = new FileSystemResourceAccessor();
1045
1046            String changeLogFile = getChangeLogFile() + "-dat.xml";
1047
1048            Liquibase liquibase = new Liquibase(changeLogFile, new CompositeResourceAccessor(antFO, fsFO), h2db);
1049
1050            log("Loading Schema");
1051            liquibase.update(getContexts());
1052            log("Finished Loading the Schema");
1053
1054            }
1055            catch (Exception e) {
1056            }
1057            catch (Exception e) {
1058            throw new BuildException(e);
1059            }
1060            finally {
1061            try {
1062            if (h2db != null) {
1063            // hsqldb.getConnection().createStatement().execute("SHUTDOWN");                                                   
1064            log("Closing h2 database");
1065            h2db.close();
1066            }
1067            }
1068            catch (Exception e) {
1069            if (!(e instanceof java.sql.SQLNonTransientConnectionException)) {
1070            e.printStackTrace();
1071            }
1072            }
1073
1074            }
1075            }            
1076          */
1077    
1078          protected void exportConstraints(final Database source, Database target) throws MojoExecutionException {
1079              export(source, target, "foreignKeys", "-cst.xml");
1080          }
1081
1082          protected void exportIndexes(final Database source, Database target) throws MojoExecutionException {
1083              export(source, target, "indexes", "-idx.xml");
1084          }
1085
1086          protected void exportViews(final Database source, Database target) throws MojoExecutionException {
1087              export(source, target, "views", "-vw.xml");
1088          }
1089
1090          protected void exportTables(final Database source, Database target) throws MojoExecutionException  {
1091              export(source, target, "tables, primaryKeys, uniqueConstraints", "-tab.xml");
1092          }
1093
1094          protected void exportSequences(final Database source, Database target) throws MojoExecutionException {
1095              export(source, target, "sequences", "-seq.xml");
1096          }
1097    
1098          protected void export(final Database source, final Database target, final String sourceTypes, final String suffix) throws MojoExecutionException {
1099              try {
1100                  final CatalogAndSchema catalogAndSchema = source.getDefaultSchema();
1101                  final SnapshotControl snapshotControl = new SnapshotControl(source, sourceTypes);
1102                  final CompareControl compareControl   = new CompareControl(new CompareControl.SchemaComparison[]{new CompareControl.SchemaComparison(catalogAndSchema, catalogAndSchema)}, sourceTypes);
1103                  //        compareControl.addStatusListener(new OutDiffStatusListener());
1104            
1105                  final DatabaseSnapshot referenceSnapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(compareControl.getSchemas(CompareControl.DatabaseRole.REFERENCE), source, snapshotControl);
1106                  final DatabaseSnapshot comparisonSnapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(compareControl.getSchemas(CompareControl.DatabaseRole.REFERENCE), target, snapshotControl);
1107                  // diff.setDiffTypes(snapshotTypes);
1108            
1109                  final DiffResult results = DiffGeneratorFactory.getInstance().compare(referenceSnapshot, comparisonSnapshot, compareControl);
1110              }
1111              catch (Exception e) {
1112                  throw new MojoExecutionException("Exception while exporting to the target: " + e.getMessage(), e);
1113              }
1114          }
1115    
1116          protected void exportSchema(final Database source, final Database target) throws MojoExecutionException {
1117              try {
1118                  exportTables(source, target);
1119                  exportSequences(source, target);
1120                  exportViews(source, target);
1121                  exportIndexes(source, target);
1122                  exportConstraints(source, target);
1123              }
1124              catch (Exception e) {
1125                  throw new MojoExecutionException("Exception while exporting the source schema: " + e.getMessage(), e);
1126              }
1127          }
1128
1129          protected JdbcConnection openConnection(final String url, 
1130                                                  final String username, 
1131                                                  final String password, 
1132                                                  final String className, 
1133                                                  final String schema) throws MojoExecutionException {
1134              Connection retval = null;
1135              int retry_count = 0;
1136              final int max_retry = 5;
1137              while (retry_count < max_retry) {
1138                  try {
1139                      getLog().debug("Loading schema " + schema + " at url " + url);
1140                      Class.forName(className);
1141                      retval = DriverManager.getConnection(url, username, password);
1142                      retval.setAutoCommit(true);
1143                  }
1144                  catch (Exception e) {
1145                      if (!e.getMessage().contains("Database lock acquisition failure") && !(e instanceof NullPointerException)) {
1146                          throw new MojoExecutionException(e.getMessage(), e);
1147                      }
1148                  }
1149                  finally {
1150                      retry_count++;
1151                  }
1152              }
1153              return new JdbcConnection(retval);
1154          }
1155    
1156          @Override
1157          protected ResourceAccessor getFileOpener(final ClassLoader cl) {
1158              final ResourceAccessor mFO = new MavenResourceAccessor(cl);
1159              final ResourceAccessor fsFO = new FileSystemResourceAccessor(project.getBasedir().getAbsolutePath());
1160              return new CompositeResourceAccessor(mFO, fsFO);
1161          }
1162    
1163          public void setMavenHome(final File mavenHome) {
1164              this.mavenHome = mavenHome;
1165          }
1166    
1167          public File getMavenHome() {
1168              return this.mavenHome;
1169          }
1170
1171      }