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 028import org.apache.maven.plugin.AbstractMojo; 029import org.apache.maven.plugin.MojoExecutionException; 030import org.apache.maven.plugin.MojoFailureException; 031import org.apache.maven.plugins.annotations.Component; 032import org.apache.maven.plugins.annotations.Mojo; 033import org.apache.maven.plugins.annotations.Parameter; 034import org.apache.maven.project.MavenProject; 035import org.apache.maven.artifact.manager.WagonManager; 036 037import org.liquibase.maven.plugins.AbstractLiquibaseMojo; 038import org.liquibase.maven.plugins.AbstractLiquibaseUpdateMojo; 039 040import liquibase.Liquibase; 041import liquibase.exception.LiquibaseException; 042import liquibase.serializer.ChangeLogSerializer; 043import liquibase.serializer.LiquibaseSerializable; 044import liquibase.parser.NamespaceDetails; 045import liquibase.parser.NamespaceDetailsFactory; 046import liquibase.parser.core.xml.LiquibaseEntityResolver; 047import liquibase.parser.core.xml.XMLChangeLogSAXParser; 048import liquibase.serializer.core.xml.XMLChangeLogSerializer; 049import org.apache.maven.wagon.authentication.AuthenticationInfo; 050 051import liquibase.util.xml.DefaultXmlWriter; 052 053import org.tmatesoft.svn.core.ISVNDirEntryHandler; 054import org.tmatesoft.svn.core.SVNDirEntry; 055import org.tmatesoft.svn.core.SVNDepth; 056import org.tmatesoft.svn.core.SVNException; 057import org.tmatesoft.svn.core.SVNURL; 058import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; 059import org.tmatesoft.svn.core.io.SVNRepository; 060import org.tmatesoft.svn.core.io.SVNRepositoryFactory; 061import org.tmatesoft.svn.core.wc.ISVNOptions; 062import org.tmatesoft.svn.core.wc.SVNWCUtil; 063import org.tmatesoft.svn.core.wc.SVNClientManager; 064import org.tmatesoft.svn.core.wc.SVNWCClient; 065import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; 066 067import org.w3c.dom.*; 068 069import javax.xml.parsers.DocumentBuilder; 070import javax.xml.parsers.DocumentBuilderFactory; 071import javax.xml.parsers.ParserConfigurationException; 072 073import java.lang.reflect.Field; 074import java.lang.reflect.Method; 075 076import java.io.File; 077import java.io.FilenameFilter; 078import java.io.FileNotFoundException; 079import java.io.FileOutputStream; 080import java.io.InputStream; 081import java.io.IOException; 082import java.net.URL; 083import java.util.ArrayList; 084import java.util.Arrays; 085import java.util.Collection; 086import java.util.HashMap; 087import java.util.Iterator; 088import java.util.List; 089import java.util.Map; 090import java.util.Properties; 091 092import static org.tmatesoft.svn.core.wc.SVNRevision.HEAD; 093import static org.tmatesoft.svn.core.wc.SVNRevision.WORKING; 094 095/** 096 * Migrate Liquibase changelogs 097 * 098 * @author Leo Przybylski 099 * @goal migrate 100 */ 101public class MigrateMojo extends AbstractLiquibaseUpdateMojo { 102 public static final String DEFAULT_CHANGELOG_PATH = "src/main/scripts/changelogs"; 103 public static final String DEFAULT_UPDATE_FILE = "target/changelogs/update.xml"; 104 public static final String DEFAULT_UPDATE_PATH = "target/changelogs/update"; 105 public static final String DEFAULT_LBPROP_PATH = "target/test-classes/liquibase/"; 106 public static final String TEST_ROLLBACK_TAG = "test"; 107 108 /** 109 * Suffix for fields that are representing a default value for a another field. 110 */ 111 private static final String DEFAULT_FIELD_SUFFIX = "Default"; 112 113 /** 114 * The fully qualified name of the driver class to use to connect to the database. 115 * 116 * @parameter expression="${liquibase.driver}" 117 */ 118 protected String driver; 119 120 /** 121 * The Database URL to connect to for executing Liquibase. 122 * 123 * @parameter expression="${liquibase.url}" 124 */ 125 protected String url; 126 127 /** 128 129 The Maven Wagon manager to use when obtaining server authentication details. 130 @component role="org.apache.maven.artifact.manager.WagonManager" 131 @required 132 @readonly 133 */ 134 protected WagonManager wagonManager; 135 /** 136 * The server id in settings.xml to use when authenticating with. 137 * 138 * @parameter expression="${liquibase.server}" 139 */ 140 private String server; 141 142 /** 143 * The database username to use to connect to the specified database. 144 * 145 * @parameter expression="${liquibase.username}" 146 */ 147 protected String username; 148 149 /** 150 * The database password to use to connect to the specified database. 151 * 152 * @parameter expression="${liquibase.password}" 153 */ 154 protected String password; 155 156 /** 157 * Use an empty string as the password for the database connection. This should not be 158 * used along side the {@link #password} setting. 159 * 160 * @parameter expression="${liquibase.emptyPassword}" default-value="false" 161 * @deprecated Use an empty or null value for the password instead. 162 */ 163 protected boolean emptyPassword; 164 165 /** 166 * The default schema name to use the for database connection. 167 * 168 * @parameter expression="${liquibase.defaultSchemaName}" 169 */ 170 protected String defaultSchemaName; 171 172 /** 173 * The class to use as the database object. 174 * 175 * @parameter expression="${liquibase.databaseClass}" 176 */ 177 protected String databaseClass; 178 179 /** 180 * Controls the prompting of users as to whether or not they really want to run the 181 * changes on a database that is not local to the machine that the user is current 182 * executing the plugin on. 183 * 184 * @parameter expression="${liquibase.promptOnNonLocalDatabase}" default-value="true" 185 */ 186 protected boolean promptOnNonLocalDatabase; 187 188 /** 189 * Allows for the maven project artifact to be included in the class loader for 190 * obtaining the Liquibase property and DatabaseChangeLog files. 191 * 192 * @parameter expression="${liquibase.includeArtifact}" default-value="true" 193 */ 194 protected boolean includeArtifact; 195 196 /** 197 * Allows for the maven test output directory to be included in the class loader for 198 * obtaining the Liquibase property and DatabaseChangeLog files. 199 * 200 * @parameter expression="${liquibase.includeTestOutputDirectory}" default-value="true" 201 */ 202 protected boolean includeTestOutputDirectory; 203 204 /** 205 * Controls the verbosity of the output from invoking the plugin. 206 * 207 * @parameter expression="${liquibase.verbose}" default-value="false" 208 * @description Controls the verbosity of the plugin when executing 209 */ 210 protected boolean verbose; 211 212 /** 213 * Controls the level of logging from Liquibase when executing. The value can be 214 * "all", "finest", "finer", "fine", "info", "warning", "severe" or "off". The value is 215 * case insensitive. 216 * 217 * @parameter expression="${liquibase.logging}" default-value="INFO" 218 * @description Controls the verbosity of the plugin when executing 219 */ 220 protected String logging; 221 222 /** 223 * The Liquibase properties file used to configure the Liquibase {@link 224 * liquibase.Liquibase}. 225 * 226 * @parameter expression="${liquibase.propertyFile}" 227 */ 228 protected String propertyFile; 229 230 /** 231 * Flag allowing for the Liquibase properties file to override any settings provided in 232 * the Maven plugin configuration. By default if a property is explicity specified it is 233 * not overridden if it also appears in the properties file. 234 * 235 * @parameter expression="${liquibase.propertyFileWillOverride}" default-value="false" 236 */ 237 protected boolean propertyFileWillOverride; 238 239 /** 240 * Flag for forcing the checksums to be cleared from teh DatabaseChangeLog table. 241 * 242 * @parameter expression="${liquibase.clearCheckSums}" default-value="false" 243 */ 244 protected boolean clearCheckSums; 245 246 /** 247 * List of system properties to pass to the database. 248 * 249 * @parameter 250 */ 251 protected Properties systemProperties; 252 253 protected String svnUsername; 254 protected String svnPassword; 255 256 /** 257 * The server id in settings.xml to use when authenticating with. 258 * 259 * @parameter expression="${lb.svnServer}" 260 */ 261 protected String svnServer; 262 263 /** 264 * @parameter default-value="${project.basedir}/src/main/scripts/changelogs" 265 */ 266 protected File changeLogSavePath; 267 268 /** 269 * @parameter expression="${lb.changeLogTagUrl}" 270 */ 271 protected URL changeLogTagUrl; 272 273 /** 274 * Location of an update.xml 275 * 276 * @parameter expression="${lb.updatePath}" default-value="${project.basedir}/src/main/changelogs" 277 */ 278 protected File updatePath; 279 280 /** 281 * The Maven project that plugin is running under. 282 * @parameter expression="${project}" 283 * @required 284 * @readonly 285 */ 286 @Parameter(property = "project", required = true) 287 protected MavenProject project; 288 289 290 /** 291 * Whether or not to perform a drop on the database before executing the change. 292 * @parameter expression="${liquibase.dropFirst}" default-value="false" 293 */ 294 protected boolean dropFirst; 295 296 protected File getBasedir() { 297 return project.getBasedir(); 298 } 299 300 protected SVNURL getChangeLogTagUrl() throws SVNException { 301 if (changeLogTagUrl == null) { 302 return getProjectSvnUrlFrom(getBasedir()).appendPath("tags", true); 303 } 304 return SVNURL.parseURIEncoded(changeLogTagUrl.toString()); 305 } 306 307 protected void doFieldHack() { 308 for (final Field field : getClass().getDeclaredFields()) { 309 try { 310 final Field parentField = getDeclaredField(getClass().getSuperclass(), field.getName()); 311 if (parentField != null) { 312 getLog().debug("Setting " + field.getName() + " in " + parentField.getDeclaringClass().getName() + " to " + field.get(this)); 313 parentField.set(this, field.get(this)); 314 } 315 } 316 catch (Exception e) { 317 } 318 } 319 } 320 321 protected File[] getLiquibasePropertiesFiles() throws MojoExecutionException { 322 try { 323 final File[] retval = new File(getBasedir(), DEFAULT_LBPROP_PATH).listFiles(new FilenameFilter() { 324 public boolean accept(final File dir, final String name) { 325 return name.endsWith(".properties"); 326 } 327 }); 328 if (retval == null) { 329 throw new NullPointerException(); 330 } 331 return retval; 332 } 333 catch (Exception e) { 334 getLog().warn("Unable to get liquibase properties files "); 335 return new File[0]; 336 // throw new MojoExecutionException("Unable to get liquibase properties files ", e); 337 } 338 } 339 340 @Override 341 public void execute() throws MojoExecutionException, MojoFailureException { 342 doFieldHack(); 343 344 try { 345 Method meth = AbstractLiquibaseMojo.class.getDeclaredMethod("processSystemProperties"); 346 meth.setAccessible(true); 347 meth.invoke(this); 348 } 349 catch (Exception e) { 350 e.printStackTrace(); 351 } 352 353 ClassLoader artifactClassLoader = getMavenArtifactClassLoader(); 354 final File[] propertyFiles = getLiquibasePropertiesFiles(); 355 356 // execute change logs on each database 357 for (final File props : propertyFiles) { 358 try { 359 propertyFile = props.getCanonicalPath(); 360 doFieldHack(); 361 362 configureFieldsAndValues(getFileOpener(artifactClassLoader)); 363 364 doFieldHack(); 365 } 366 catch (Exception e) { 367 throw new MojoExecutionException(e.getMessage(), e); 368 } 369 } 370 371 if (svnServer != null) { 372 final AuthenticationInfo info = wagonManager.getAuthenticationInfo(svnServer); 373 if (info != null) { 374 svnUsername = info.getUserName(); 375 svnPassword = info.getPassword(); 376 } 377 } 378 DAVRepositoryFactory.setup(); 379 380 if (!isUpdateRequired()) { 381 return; 382 } 383 384 385 boolean shouldLocalUpdate = false; 386 try { 387 final Collection<SVNURL> svnurls = getTagUrls(); 388 shouldLocalUpdate = (svnurls == null || svnurls.size() < 1); 389 390 for (final SVNURL tag : svnurls) { 391 final String tagBasePath = getLocalTagPath(tag); 392 393 final File tagPath = new File(tagBasePath, "update"); 394 tagPath.mkdirs(); 395 396 final SVNURL changeLogUrl = tag.appendPath(DEFAULT_CHANGELOG_PATH + "/update", true); 397 SVNClientManager.newInstance().getUpdateClient() 398 .doExport(changeLogUrl, tagPath, HEAD, HEAD, null, true, SVNDepth.INFINITY); 399 } 400 } 401 catch (Exception e) { 402 throw new MojoExecutionException("Exception when exporting changelogs from previous revisions", e); 403 } 404 405 changeLogFile = new File(changeLogSavePath, "update.xml").getPath(); 406 File changeLogSearchPath = changeLogSavePath; 407 408 if (shouldLocalUpdate) { 409 changeLogSavePath = new File(changeLogSavePath, "update"); 410 } 411 412 final Collection<File> changelogs = scanForChangelogs(changeLogSearchPath); 413 414 try { 415 generateUpdateLog(new File(changeLogFile), changelogs); 416 } 417 catch (Exception e) { 418 throw new MojoExecutionException("Failed to generate changelog file " + changeLogFile, e); 419 } 420 421 super.execute(); 422 } 423 424 protected String getLocalTagPath(final SVNURL tag) { 425 final String tagPath = tag.getPath(); 426 return changeLogSavePath + File.separator + tagPath.substring(tagPath.lastIndexOf("/") + 1); 427 } 428 429 protected boolean isUpdateRequired() throws MojoExecutionException { 430 try { 431 getLog().debug("Comparing " + getCurrentRevision() + " to " + getLocalRevision()); 432 final String[] updates = new File(DEFAULT_CHANGELOG_PATH + File.separator + "update").list(); 433 boolean hasUpdates = updates != null && updates.length > 0; 434 return getCurrentRevision() > getLocalRevision() || (hasUpdates); 435 } 436 catch (Exception e) { 437 throw new MojoExecutionException("Could not compare local and remote revisions ", e); 438 } 439 } 440 441 protected SVNURL getProjectSvnUrlFrom(final File path) throws SVNException { 442 SVNURL retval = getWCClient().doInfo(getBasedir(), HEAD).getURL(); 443 String removeToken = null; 444 if (retval.getPath().indexOf("/branches") > -1) { 445 removeToken = "/branches"; 446 } 447 else if (retval.getPath().indexOf("/tags") > -1) { 448 removeToken = "/tags"; 449 } 450 else if (retval.getPath().indexOf("/trunk") > -1) { 451 removeToken = "/trunk"; 452 } 453 454 getLog().debug("Checking path " + retval.getPath() + " for token " + removeToken); 455 while (retval.getPath().indexOf(removeToken) > -1) { 456 retval = retval.removePathTail(); 457 } 458 return retval; 459 } 460 461 protected Long getCurrentRevision() throws SVNException { 462 return getWCClient().doInfo(getBasedir(), HEAD).getCommittedRevision().getNumber(); 463 } 464 465 protected Long getLocalRevision() throws SVNException { 466 return getWCClient().doInfo(getBasedir(), WORKING).getRevision().getNumber(); 467 } 468 469 protected Long getTagRevision(final String tag) throws SVNException { 470 return getWCClient().doInfo(getChangeLogTagUrl(), WORKING, WORKING).getRevision().getNumber(); 471 } 472 473 protected Collection<SVNURL> getTagUrls() throws SVNException { 474 final Collection<SVNURL> retval = new ArrayList<SVNURL>(); 475 getLog().debug("Looking up tags in " + getChangeLogTagUrl().toString()); 476 clientManager().getLogClient() 477 .doList(getChangeLogTagUrl(), HEAD, HEAD, false, false, 478 new ISVNDirEntryHandler() { 479 public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException { 480 if (dirEntry.getRevision() >= getLocalRevision() 481 && dirEntry.getPath().trim().length() > 0) { 482 getLog().debug("Adding tag '" + dirEntry.getPath() + "'"); 483 retval.add(dirEntry.getURL()); 484 } 485 } 486 }); 487 return retval; 488 } 489 490 protected SVNWCClient getWCClient() { 491 return clientManager().getWCClient(); 492 } 493 494 protected Collection<File> scanForChangelogs(final File searchPath) { 495 final Collection<File> retval = new ArrayList<File>(); 496 497 if (searchPath.getName().endsWith("update")) { 498 return Arrays.asList(searchPath.listFiles()); 499 } 500 501 if (searchPath.isDirectory()) { 502 for (final File file : searchPath.listFiles()) { 503 if (file.isDirectory()) { 504 retval.addAll(scanForChangelogs(file)); 505 } 506 } 507 } 508 509 return retval; 510 } 511 512 protected SVNClientManager clientManager() { 513 final ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager("lprzybylski", "entr0py0"); 514 final ISVNOptions options = SVNWCUtil.createDefaultOptions(true); 515 final SVNClientManager clientManager = SVNClientManager.newInstance(options, authManager); 516 517 return clientManager; 518 } 519 520 protected void generateUpdateLog(final File changeLogFile, 521 final Collection<File> changelogs) throws FileNotFoundException, IOException { 522 changeLogFile.getParentFile().mkdirs(); 523 524 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 525 DocumentBuilder documentBuilder; 526 try { 527 documentBuilder = factory.newDocumentBuilder(); 528 } 529 catch (ParserConfigurationException e) { 530 throw new RuntimeException(e); 531 } 532 final XMLChangeLogSerializer serializer = new XMLChangeLogSerializer(); 533 documentBuilder.setEntityResolver(new LiquibaseEntityResolver(serializer)); 534 535 final Document doc = documentBuilder.newDocument(); 536 final Element changeLogElement = doc.createElementNS(LiquibaseSerializable.STANDARD_CHANGELOG_NAMESPACE, "databaseChangeLog"); 537 538 changeLogElement.setAttribute("xmlns", LiquibaseSerializable.STANDARD_CHANGELOG_NAMESPACE); 539 changeLogElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); 540 541 final Map<String, String> shortNameByNamespace = new HashMap<String, String>(); 542 final Map<String, String> urlByNamespace = new HashMap<String, String>(); 543 544 for (final NamespaceDetails details : NamespaceDetailsFactory.getInstance().getNamespaceDetails()) { 545 for (final String namespace : details.getNamespaces()) { 546 if (details.supports(serializer, namespace)){ 547 final String shortName = details.getShortName(namespace); 548 final String url = details.getSchemaUrl(namespace); 549 if (shortName != null && url != null) { 550 shortNameByNamespace.put(namespace, shortName); 551 urlByNamespace.put(namespace, url); 552 } 553 } 554 } 555 } 556 557 for (final Map.Entry<String, String> entry : shortNameByNamespace.entrySet()) { 558 if (!entry.getValue().equals("")) { 559 changeLogElement.setAttribute("xmlns:"+entry.getValue(), entry.getKey()); 560 } 561 } 562 563 564 String schemaLocationAttribute = ""; 565 for (final Map.Entry<String, String> entry : urlByNamespace.entrySet()) { 566 if (!entry.getValue().equals("")) { 567 schemaLocationAttribute += entry.getKey()+" "+entry.getValue()+" "; 568 } 569 } 570 571 changeLogElement.setAttribute("xsi:schemaLocation", schemaLocationAttribute.trim()); 572 573 doc.appendChild(changeLogElement); 574 575 for (final File changelog : changelogs) { 576 doc.getDocumentElement().appendChild(includeNode(doc, changelog)); 577 } 578 579 new DefaultXmlWriter().write(doc, new FileOutputStream(changeLogFile)); 580 } 581 582 protected Element includeNode(final Document parentChangeLog, final File changelog) throws IOException { 583 final Element retval = parentChangeLog.createElementNS(LiquibaseSerializable.STANDARD_CHANGELOG_NAMESPACE, 584 "databaseChangeLog"); 585 586 retval.setAttribute("file", changelog.getCanonicalPath()); 587 return retval; 588 } 589 590 @Override 591 protected void doUpdate(final Liquibase liquibase) throws LiquibaseException { 592 if (dropFirst) { 593 dropAll(liquibase); 594 } 595 596 liquibase.tag("undo"); 597 598 if (changesToApply > 0) { 599 liquibase.update(changesToApply, contexts); 600 } else { 601 liquibase.update(contexts); 602 } 603 } 604 605 /** 606 * Drops the database. Makes sure it's done right the first time. 607 * 608 * @param liquibase 609 * @throws LiquibaseException 610 */ 611 protected void dropAll(final Liquibase liquibase) throws LiquibaseException { 612 boolean retry = true; 613 while (retry) { 614 try { 615 liquibase.dropAll(); 616 retry = false; 617 } 618 catch (LiquibaseException e2) { 619 getLog().info(e2.getMessage()); 620 if (e2.getMessage().indexOf("ORA-02443") < 0 && e2.getCause() != null && retry) { 621 retry = (e2.getCause().getMessage().indexOf("ORA-02443") > -1); 622 } 623 624 if (!retry) { 625 throw e2; 626 } 627 else { 628 getLog().info("Got ORA-2443. Retrying..."); 629 } 630 } 631 } 632 } 633 634 @Override 635 protected void printSettings(String indent) { 636 super.printSettings(indent); 637 getLog().info(indent + "drop first? " + dropFirst); 638 639 } 640 641 /** 642 * Parses a properties file and sets the assocaited fields in the plugin. 643 * 644 * @param propertiesInputStream The input stream which is the Liquibase properties that 645 * needs to be parsed. 646 * @throws org.apache.maven.plugin.MojoExecutionException 647 * If there is a problem parsing 648 * the file. 649 */ 650 protected void parsePropertiesFile(InputStream propertiesInputStream) 651 throws MojoExecutionException { 652 if (propertiesInputStream == null) { 653 throw new MojoExecutionException("Properties file InputStream is null."); 654 } 655 Properties props = new Properties(); 656 try { 657 props.load(propertiesInputStream); 658 } 659 catch (IOException e) { 660 throw new MojoExecutionException("Could not load the properties Liquibase file", e); 661 } 662 663 for (Iterator it = props.keySet().iterator(); it.hasNext();) { 664 String key = null; 665 try { 666 key = (String) it.next(); 667 Field field = getDeclaredField(this.getClass(), key); 668 669 if (propertyFileWillOverride) { 670 setFieldValue(field, props.get(key).toString()); 671 } else { 672 if (!isCurrentFieldValueSpecified(field)) { 673 getLog().debug(" properties file setting value: " + field.getName()); 674 setFieldValue(field, props.get(key).toString()); 675 } 676 } 677 } 678 catch (Exception e) { 679 getLog().info(" '" + key + "' in properties file is not being used by this " 680 + "task."); 681 } 682 } 683 } 684 685 /** 686 * This method will check to see if the user has specified a value different to that of 687 * the default value. This is not an ideal solution, but should cover most situations in 688 * the use of the plugin. 689 * 690 * @param f The Field to check if a user has specified a value for. 691 * @return <code>true</code> if the user has specified a value. 692 */ 693 private boolean isCurrentFieldValueSpecified(Field f) throws IllegalAccessException { 694 Object currentValue = f.get(this); 695 if (currentValue == null) { 696 return false; 697 } 698 699 Object defaultValue = getDefaultValue(f); 700 if (defaultValue == null) { 701 return currentValue != null; 702 } else { 703 // There is a default value, check to see if the user has selected something other 704 // than the default 705 return !defaultValue.equals(f.get(this)); 706 } 707 } 708 709 private Object getDefaultValue(Field field) throws IllegalAccessException { 710 List<Field> allFields = new ArrayList<Field>(); 711 allFields.addAll(Arrays.asList(getClass().getDeclaredFields())); 712 allFields.addAll(Arrays.asList(AbstractLiquibaseMojo.class.getDeclaredFields())); 713 714 for (Field f : allFields) { 715 if (f.getName().equals(field.getName() + DEFAULT_FIELD_SUFFIX)) { 716 f.setAccessible(true); 717 return f.get(this); 718 } 719 } 720 return null; 721 } 722 723 724 /** 725 * Recursively searches for the field specified by the fieldName in the class and all 726 * the super classes until it either finds it, or runs out of parents. 727 * @param clazz The Class to start searching from. 728 * @param fieldName The name of the field to retrieve. 729 * @return The {@link Field} identified by the field name. 730 * @throws NoSuchFieldException If the field was not found in the class or any of its 731 * super classes. 732 */ 733 protected Field getDeclaredField(Class clazz, String fieldName) 734 throws NoSuchFieldException { 735 getLog().debug("Checking " + clazz.getName() + " for '" + fieldName + "'"); 736 try { 737 Field f = clazz.getDeclaredField(fieldName); 738 739 if (f != null) { 740 return f; 741 } 742 } 743 catch (Exception e) { 744 } 745 746 while (clazz.getSuperclass() != null) { 747 clazz = clazz.getSuperclass(); 748 getLog().debug("Checking " + clazz.getName() + " for '" + fieldName + "'"); 749 try { 750 Field f = clazz.getDeclaredField(fieldName); 751 752 if (f != null) { 753 return f; 754 } 755 } 756 catch (Exception e) { 757 } 758 } 759 760 throw new NoSuchFieldException("The field '" + fieldName + "' could not be " 761 + "found in the class of any of its parent " 762 + "classes."); 763 } 764 765 private void setFieldValue(Field field, String value) throws IllegalAccessException { 766 if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) { 767 field.set(this, Boolean.valueOf(value)); 768 } else { 769 field.set(this, value); 770 } 771 } 772}