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 }