001 /** 002 * Copyright 2006-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.codehaus.mojo.sql; 017 018 import java.io.BufferedOutputStream; 019 import java.io.BufferedReader; 020 import java.io.File; 021 import java.io.FileInputStream; 022 import java.io.FileOutputStream; 023 import java.io.FileReader; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.InputStreamReader; 027 import java.io.OutputStream; 028 import java.io.PrintStream; 029 import java.io.Reader; 030 import java.io.StringReader; 031 import java.sql.Connection; 032 import java.sql.Driver; 033 import java.sql.ResultSet; 034 import java.sql.ResultSetMetaData; 035 import java.sql.SQLException; 036 import java.sql.SQLWarning; 037 import java.sql.Statement; 038 import java.util.ArrayList; 039 import java.util.Collections; 040 import java.util.Enumeration; 041 import java.util.List; 042 import java.util.Properties; 043 import java.util.StringTokenizer; 044 import java.util.Vector; 045 046 import org.apache.commons.io.IOUtils; 047 import org.apache.commons.lang.StringEscapeUtils; 048 import org.apache.maven.execution.MavenSession; 049 import org.apache.maven.plugin.AbstractMojo; 050 import org.apache.maven.plugin.MojoExecutionException; 051 import org.apache.maven.project.MavenProject; 052 import org.apache.maven.settings.Server; 053 import org.apache.maven.settings.Settings; 054 import org.apache.maven.shared.filtering.MavenFileFilter; 055 import org.apache.maven.shared.filtering.MavenFileFilterRequest; 056 import org.apache.maven.shared.filtering.MavenFilteringException; 057 import org.codehaus.plexus.util.FileUtils; 058 import org.codehaus.plexus.util.IOUtil; 059 import org.codehaus.plexus.util.StringUtils; 060 import org.springframework.core.io.DefaultResourceLoader; 061 import org.springframework.core.io.Resource; 062 import org.springframework.core.io.ResourceLoader; 063 064 /** 065 * Executes SQL against a database. 066 * 067 * @goal execute 068 */ 069 public class SqlExecMojo extends AbstractMojo { 070 071 /** 072 * Call {@link #setOnError(String)} with this value to abort SQL command execution if an error is found. 073 */ 074 public static final String ON_ERROR_ABORT = "abort"; 075 076 /** 077 * Call {@link #setOnError(String)} with this value to continue SQL command execution until all commands have been attempted, then abort 078 * the build if an SQL error occurred in any of the commands. 079 */ 080 public static final String ON_ERROR_ABORT_AFTER = "abortAfter"; 081 082 /** 083 * Call {@link #setOnError(String)} with this value to continue SQL command execution if an error is found. 084 */ 085 public static final String ON_ERROR_CONTINUE = "continue"; 086 087 // ////////////////////////// User Info /////////////////////////////////// 088 089 /** 090 * If true, sql is displayed as it is being executed 091 * 092 * @parameter expression="${sql.showSql}" 093 */ 094 private boolean showSql; 095 096 /** 097 * Database username. If not given, it will be looked up through <code>settings.xml</code>'s server with <code>${settingsKey}</code> as 098 * key. 099 * 100 * @since 1.0 101 * @parameter expression="${username}" 102 */ 103 private String username; 104 105 /** 106 * Database password. If not given, it will be looked up through <code>settings.xml</code>'s server with <code>${settingsKey}</code> as 107 * key. 108 * 109 * @since 1.0 110 * @parameter expression="${password}" 111 */ 112 private String password; 113 114 /** 115 * If enableAnonymousPassword is 'true', and the password provided is blank or equals 'NONE' do not pass the password to the jdbc 116 * driver. This is useful for databases like MySQL where the 'root' user is created by default without a password. The jdbc driver 117 * succeeds if no password property is passed to it, but fails if a password property is passed to it but equals NULL or the empty 118 * string. 119 * 120 * @since 1.4 121 * @parameter default-value="false" 122 */ 123 private boolean enableAnonymousPassword; 124 125 /** 126 * Additional key=value pairs separated by comma to be passed into JDBC driver. 127 * 128 * @since 1.0 129 * @parameter expression="${driverProperties}" default-value = "" 130 */ 131 private String driverProperties; 132 133 /** 134 * @parameter expression="${settings}" 135 * @required 136 * @since 1.0 137 * @readonly 138 */ 139 private Settings settings; 140 141 /** 142 * Server's <code>id</code> in <code>settings.xml</code> to look up username and password. Defaults to <code>${url}</code> if not given. 143 * 144 * @since 1.0 145 * @parameter expression="${settingsKey}" 146 */ 147 private String settingsKey; 148 149 /** 150 * Skip execution when there is an error obtaining a connection. This is a special case to support databases, such as embedded Derby, 151 * that can shutdown the database via the URL (i.e. <code>shutdown=true</code>). 152 * 153 * @since 1.1 154 * @parameter expression="${skipOnConnectionError}" default-value="false" 155 */ 156 private boolean skipOnConnectionError; 157 158 /** 159 * Setting this parameter to <code>true</code> will force the execution of this mojo, even if it would get skipped usually. 160 * 161 * @parameter expression="${forceOpenJpaExecution}" default-value=false 162 * @required 163 */ 164 private boolean forceMojoExecution; 165 166 /** 167 * The Maven Project Object 168 * 169 * @parameter default-value="${project}" 170 * @required 171 * @readonly 172 */ 173 protected MavenProject project; 174 175 /** 176 * @parameter default-value="${session}" 177 * @required 178 * @readonly 179 */ 180 private MavenSession mavenSession; 181 182 // ////////////////////////////// Source info ///////////////////////////// 183 184 /** 185 * SQL input commands separated by <code>${delimiter}</code>. 186 * 187 * @since 1.0 188 * @parameter expression="${sqlCommand}" default-value="" 189 */ 190 private String sqlCommand = ""; 191 192 /** 193 * List of files containing SQL statements to load. 194 * 195 * @since 1.0 196 * @parameter 197 */ 198 private File[] srcFiles; 199 200 /** 201 * Location that contains a list of Spring-style URL's to load, with one location listed per line in the file. 202 * 203 * @parameter 204 */ 205 private String resourceListingLocation; 206 207 /** 208 * List of resources containing SQL statements. This supports Spring-style resource URL's. eg classpath:org/kuali/db.sql 209 * 210 * @since 1.5 211 * @parameter 212 */ 213 private String[] resourceLocations; 214 215 /** 216 * File(s) containing SQL statements to load. 217 * 218 * @since 1.0 219 * @parameter 220 */ 221 private Fileset fileset; 222 223 /** 224 * When <code>true</code>, skip the execution. 225 * 226 * @since 1.0 227 * @parameter default-value="false" 228 */ 229 private boolean skip; 230 231 // //////////////////////////////// Database info ///////////////////////// 232 /** 233 * Database URL. 234 * 235 * @parameter expression="${url}" 236 * @required 237 * @since 1.0-beta-1 238 */ 239 private String url; 240 241 /** 242 * Database driver classname. 243 * 244 * @since 1.0 245 * @parameter expression="${driver}" 246 * @required 247 */ 248 private String driver; 249 250 // //////////////////////////// Operation Configuration //////////////////// 251 /** 252 * Set to <code>true</code> to execute none-transactional SQL. 253 * 254 * @since 1.0 255 * @parameter expression="${autocommit}" default-value="false" 256 */ 257 private boolean autocommit; 258 259 /** 260 * Action to perform if an error is found. Possible values are <code>abort</code> and <code>continue</code>. 261 * 262 * @since 1.0 263 * @parameter expression="${onError}" default-value="abort" 264 */ 265 private String onError = ON_ERROR_ABORT; 266 267 // //////////////////////////// Parser Configuration //////////////////// 268 269 /** 270 * Set the delimiter that separates SQL statements. 271 * 272 * @since 1.0 273 * @parameter expression="${delimiter}" default-value=";" 274 */ 275 private String delimiter = ";"; 276 277 /** 278 * <p> 279 * The delimiter type takes two values - "normal" and "row". Normal means that any occurrence of the delimiter terminate the SQL command 280 * whereas with row, only a line containing just the delimiter is recognized as the end of the command. 281 * </p> 282 * <p> 283 * For example, set this to "go" and delimiterType to "row" for Sybase ASE or MS SQL Server. 284 * </p> 285 * 286 * @since 1.2 287 * @parameter expression="${delimiterType}" default-value="normal" 288 */ 289 private String delimiterType = DelimiterType.NORMAL; 290 291 /** 292 * Set the order in which the SQL files will be executed. Possible values are <code>ASCENDING</code> and <code>DESCENDING</code> and 293 * <code>NONE</code>. 294 * 295 * @since 1.1 296 * @parameter expression="${orderFile}" default-value="NONE"; 297 */ 298 private Order orderFile; 299 300 /** 301 * When <code>true</code>, the whole SQL content in <code>sqlCommand</code>, <code>srcFiles</code> and <code>fileset</code> are sent 302 * directly to JDBC in one SQL statement. This option is for executing database stored procedures/functions. 303 * 304 * @deprecated used <i>delimiterType<i> instead. 305 * @since 1.1 306 * @parameter expression="${enableBlockMode}" 307 */ 308 309 @Deprecated 310 private boolean enableBlockMode = false; 311 312 /** 313 * Keep the format of an SQL block. 314 * 315 * @since 1.1 316 * @parameter expression="${keepFormat}" default-value="false" 317 */ 318 private boolean keepFormat = false; 319 320 // ///////////////////////////////////////////////////////////////////////////////////// 321 /** 322 * Print SQL results. 323 * 324 * @parameter 325 * @since 1.3 326 */ 327 private boolean printResultSet = false; 328 329 /** 330 * Print header columns. 331 */ 332 private boolean showheaders = true; 333 334 /** 335 * Dump the SQL exection's output to a file. Default is stdout. 336 * 337 * @parameter 338 * @since 1.3 339 */ 340 private File outputFile; 341 342 /** 343 * @parameter default-value="," 344 * @since 1.4 345 */ 346 private String outputDelimiter; 347 348 /** 349 * Encoding to use when reading SQL statements from a file. 350 * 351 * @parameter expression="${encoding}" default-value= "${project.build.sourceEncoding}" 352 * @since 1.1 353 */ 354 private String encoding = ""; 355 356 /** 357 * Append to an existing file or overwrite it? 358 */ 359 private boolean append = false; 360 361 /** 362 * Argument to Statement.setEscapeProcessing If you want the driver to use regular SQL syntax then set this to false. 363 * 364 * @since 1.4 365 * @parameter expression="${escapeProcessing}" default-value="true" 366 */ 367 private boolean escapeProcessing = true; 368 369 // //////////////////////////////// Internal 370 // properties////////////////////// 371 372 /** 373 * number of successful executed statements 374 */ 375 private int successfulStatements = 0; 376 377 /** 378 * number of total executed statements 379 */ 380 private int totalStatements = 0; 381 382 /** 383 * Database connection 384 */ 385 private Connection conn = null; 386 387 /** 388 * SQL statement 389 */ 390 private Statement statement = null; 391 392 /** 393 * SQL transactions to perform 394 */ 395 protected Vector<Transaction> transactions = new Vector<Transaction>(); 396 397 /** 398 * @component role="org.apache.maven.shared.filtering.MavenFileFilter" 399 * @since 1.4 400 */ 401 private MavenFileFilter fileFilter; 402 403 /** 404 * Set to true if you want to filter the srcFiles using system-, user- and project properties 405 * 406 * @parameter 407 * @since 1.4 408 */ 409 private boolean enableFiltering; 410 411 /** 412 * Set to false to disable executing SQL that appears without a delimiter at the end of a file (is enabled by default for backwards 413 * compatibility) 414 * 415 * @parameter expression="${sql.executeTrailingSQL}" default-value="true" 416 * @since 1.7 417 */ 418 private boolean executeTrailingSQL; 419 420 /** 421 * Interpolator especially for braceless expressions 422 */ 423 // private Interpolator interpolator = new 424 // RegexBasedInterpolator("\\$([^\\s;)]+?)", "(?=[\\s;)])"); 425 426 /** 427 * Add a SQL transaction to execute 428 * 429 * @return a new SqlExecMojo.Transaction 430 */ 431 public Transaction createTransaction() { 432 Transaction t = new Transaction(); 433 transactions.addElement(t); 434 return t; 435 } 436 437 /** 438 * Set an inline SQL command to execute. NB: Properties are not expanded in this text. 439 * 440 * @param sql 441 * the sql statement to add 442 */ 443 public void addText(String sql) { 444 this.sqlCommand += sql; 445 } 446 447 /** 448 * Set the file encoding to use on the SQL files read in 449 * 450 * @param encoding 451 * the encoding to use on the files 452 */ 453 public void setEncoding(String encoding) { 454 this.encoding = encoding; 455 } 456 457 /** 458 * Set the delimiter that separates SQL statements. Defaults to ";"; 459 * 460 * @param delimiter 461 * the new delimiter 462 */ 463 public void setDelimiter(String delimiter) { 464 this.delimiter = delimiter; 465 } 466 467 /** 468 * Set the delimiter type: "normal" or "row" (default "normal"). 469 * 470 * @param delimiterType 471 * the new delimiterType 472 */ 473 public void setDelimiterType(String delimiterType) { 474 this.delimiterType = delimiterType; 475 } 476 477 /** 478 * Print result sets from the statements; optional, default false 479 * 480 * @param print 481 * <code>true</code> to print the resultset, otherwise <code>false</code> 482 * @deprecated typo, use setPrintResultSet() 483 */ 484 @Deprecated 485 public void setPrintResutlSet(boolean print) { 486 setPrintResultSet(print); 487 } 488 489 /** 490 * Print result sets from the statements; optional, default false 491 * 492 * @param print 493 * <code>true</code> to print the resultset, otherwise <code>false</code> 494 */ 495 public void setPrintResultSet(boolean print) { 496 this.printResultSet = print; 497 } 498 499 /** 500 * Print headers for result sets from the statements; optional, default true. 501 * 502 * @param showheaders 503 * <code>true</code> to show the headers, otherwise <code>false</code> 504 */ 505 public void setShowheaders(boolean showheaders) { 506 this.showheaders = showheaders; 507 } 508 509 /** 510 * Set the output file; 511 * 512 * @param output 513 * the output file 514 */ 515 public void setOutputFile(File output) { 516 this.outputFile = output; 517 } 518 519 /** 520 * whether output should be appended to or overwrite an existing file. Defaults to false. 521 * 522 * @param append 523 * <code>true</code> to append, otherwise <code>false</code> to overwrite 524 */ 525 public void setAppend(boolean append) { 526 this.append = append; 527 } 528 529 /** 530 * whether or not format should be preserved. Defaults to false. 531 * 532 * @param keepformat 533 * The keepformat to set 534 */ 535 public void setKeepFormat(boolean keepformat) { 536 this.keepFormat = keepformat; 537 } 538 539 /** 540 * Set escape processing for statements. 541 * 542 * @param enable 543 * <code>true</code> to escape, otherwiser <code>false</code> 544 */ 545 public void setEscapeProcessing(boolean enable) { 546 escapeProcessing = enable; 547 } 548 549 /** 550 * <p> 551 * Determine if the mojo execution should get skipped. 552 * </p> 553 * This is the case if: 554 * <ul> 555 * <li>{@link #skip} is <code>true</code></li> 556 * <li>if the mojo gets executed on a project with packaging type 'pom' and {@link #forceMojoExecution} is <code>false</code></li> 557 * </ul> 558 * 559 * @return <code>true</code> if the mojo execution should be skipped. 560 */ 561 protected boolean skipMojo() { 562 if (skip) { 563 getLog().info("Skip sql execution"); 564 return true; 565 } 566 567 if (!forceMojoExecution && project != null && "pom".equals(project.getPackaging())) { 568 getLog().info("Skipping sql execution for project with packaging type 'pom'"); 569 return true; 570 } 571 572 return false; 573 } 574 575 /** 576 * Load the sql file and then execute it 577 * 578 * @throws MojoExecutionException 579 */ 580 @Override 581 public void execute() throws MojoExecutionException { 582 583 if (skipMojo()) { 584 return; 585 } 586 587 successfulStatements = 0; 588 589 totalStatements = 0; 590 591 loadUserInfoFromSettings(); 592 593 addCommandToTransactions(); 594 595 addFilesToTransactions(); 596 597 addFileSetToTransactions(); 598 599 addResourcesToTransactions(); 600 601 sortTransactions(); 602 603 try { 604 conn = getConnection(); 605 } catch (SQLException e) { 606 if (!this.skipOnConnectionError) { 607 throw new MojoExecutionException(e.getMessage(), e); 608 } else { 609 // error on get connection and user asked to skip the rest 610 return; 611 } 612 } 613 614 try { 615 statement = conn.createStatement(); 616 statement.setEscapeProcessing(escapeProcessing); 617 618 PrintStream out = System.out; 619 try { 620 if (outputFile != null) { 621 getLog().debug("Opening PrintStream to output file " + outputFile); 622 out = new PrintStream(new BufferedOutputStream(new FileOutputStream(outputFile.getAbsolutePath(), append))); 623 } 624 625 // Process all transactions 626 for (Enumeration<Transaction> e = transactions.elements(); e.hasMoreElements();) { 627 Transaction t = e.nextElement(); 628 629 t.runTransaction(out); 630 631 if (!autocommit) { 632 getLog().debug("Committing transaction"); 633 conn.commit(); 634 } 635 } 636 } finally { 637 if (out != null && out != System.out) { 638 out.close(); 639 } 640 } 641 } catch (IOException e) { 642 throw new MojoExecutionException(e.getMessage(), e); 643 } catch (SQLException e) { 644 if (!autocommit && conn != null && ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) { 645 try { 646 conn.rollback(); 647 } catch (SQLException ex) { 648 // ignore 649 } 650 } 651 throw new MojoExecutionException(e.getMessage(), e); 652 } finally { 653 try { 654 if (statement != null) { 655 statement.close(); 656 } 657 if (conn != null) { 658 conn.close(); 659 } 660 } catch (SQLException ex) { 661 // ignore 662 } 663 } 664 665 getLog().info(getSuccessfulStatements() + " of " + getTotalStatements() + " SQL statements executed successfully"); 666 667 if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(getOnError()) && totalStatements != successfulStatements) { 668 throw new MojoExecutionException("Some SQL statements failed to execute"); 669 } 670 671 } 672 673 /** 674 * Add sql command to transactions list. 675 * 676 */ 677 private void addCommandToTransactions() { 678 createTransaction().addText(sqlCommand.trim()); 679 } 680 681 /** 682 * Add user sql fileset to transation list 683 * 684 */ 685 private void addFileSetToTransactions() { 686 String[] includedFiles; 687 if (fileset != null) { 688 fileset.scan(); 689 includedFiles = fileset.getIncludedFiles(); 690 } else { 691 includedFiles = new String[0]; 692 } 693 694 for (int j = 0; j < includedFiles.length; j++) { 695 createTransaction().setSrc(new File(fileset.getBasedir(), includedFiles[j])); 696 } 697 } 698 699 protected Resource[] getResources(String[] locations, String resourceListingLocation) throws MojoExecutionException { 700 ResourceLoader loader = new DefaultResourceLoader(); 701 if (StringUtils.isBlank(resourceListingLocation)) { 702 return getResources(locations); 703 } 704 Resource resource = loader.getResource(resourceListingLocation); 705 if (!resource.exists()) { 706 throw new MojoExecutionException("Resource " + resourceListingLocation + " was not found"); 707 } 708 List<String> locs = readLines(resource); 709 if (locations == null) { 710 locs = trim(locs); 711 return getResources(locs.toArray(new String[locs.size()])); 712 } 713 for (String location : locations) { 714 locs.add(location); 715 } 716 locs = trim(locs); 717 return getResources(locs.toArray(new String[locs.size()])); 718 } 719 720 protected List<String> trim(List<String> lines) { 721 List<String> trimmed = new ArrayList<String>(); 722 for (String line : lines) { 723 trimmed.add(StringUtils.trim(line)); 724 } 725 return trimmed; 726 } 727 728 protected List<String> readLines(Resource resource) { 729 InputStream in = null; 730 try { 731 in = resource.getInputStream(); 732 if (StringUtils.isBlank(encoding)) { 733 return IOUtils.readLines(in); 734 } else { 735 return IOUtils.readLines(in, encoding); 736 } 737 } catch (IOException e) { 738 throw new IllegalStateException("Unexpected IO error", e); 739 } finally { 740 IOUtils.closeQuietly(in); 741 } 742 } 743 744 protected Resource[] getResources(String[] locations) throws MojoExecutionException { 745 ResourceLoader loader = new DefaultResourceLoader(); 746 if (locations == null || locations.length == 0) { 747 return new Resource[] {}; 748 } 749 List<Resource> resources = new ArrayList<Resource>(); 750 for (int i = 0; i < locations.length; i++) { 751 String location = locations[i]; 752 // Skip it if the location is empty 753 if (StringUtils.isEmpty(location)) { 754 continue; 755 } 756 Resource resource = loader.getResource(location); 757 if (!resource.exists()) { 758 // The location was not empty, but we couldn't find it 759 throw new MojoExecutionException("Resource " + location + " was not found"); 760 } 761 resources.add(resource); 762 } 763 return resources.toArray(new Resource[resources.size()]); 764 } 765 766 protected void copy(Resource resource, File file) throws IOException { 767 InputStream in = resource.getInputStream(); 768 OutputStream out = new FileOutputStream(file); 769 IOUtils.copyLarge(in, out); 770 } 771 772 /** 773 * Add user input of srcFiles to transaction list. 774 * 775 * @throws MojoExecutionException 776 */ 777 protected void addResourcesToTransactions() throws MojoExecutionException { 778 String[] locations = getResourceLocations(); 779 Resource[] resources = getResources(locations, resourceListingLocation); 780 781 MavenFileFilterRequest request = new MavenFileFilterRequest(); 782 request.setEncoding(encoding); 783 request.setMavenSession(mavenSession); 784 request.setMavenProject(project); 785 request.setFiltering(enableFiltering); 786 for (int i = 0; i < resources.length; i++) { 787 Resource resource = resources[i]; 788 789 if (!enableFiltering) { 790 createTransaction().setResource(resource); 791 continue; 792 } 793 794 String filename = resource.getFilename(); 795 String basename = FileUtils.basename(filename); 796 String extension = FileUtils.extension(filename); 797 if (!extension.startsWith(".")) { 798 extension = "." + extension; 799 } 800 File sourceFile = FileUtils.createTempFile(basename, extension, null); 801 if (!getLog().isDebugEnabled()) { 802 sourceFile.deleteOnExit(); 803 } 804 805 try { 806 copy(resource, sourceFile); 807 } catch (IOException e) { 808 throw new MojoExecutionException("Error copying resource " + resource + " to a local temporary file", e); 809 } 810 811 if (!enableFiltering) { 812 createTransaction().setSrc(sourceFile); 813 continue; 814 } 815 816 File targetFile = FileUtils.createTempFile(basename, extension, null); 817 if (!getLog().isDebugEnabled()) { 818 sourceFile.deleteOnExit(); 819 } 820 821 request.setFrom(sourceFile); 822 request.setTo(targetFile); 823 824 try { 825 fileFilter.copyFile(request); 826 } catch (MavenFilteringException e) { 827 throw new MojoExecutionException(e.getMessage()); 828 } 829 830 createTransaction().setSrc(targetFile); 831 } 832 } 833 834 /** 835 * Add user input of srcFiles to transaction list. 836 * 837 * @throws MojoExecutionException 838 */ 839 private void addFilesToTransactions() throws MojoExecutionException { 840 File[] files = getSrcFiles(); 841 842 MavenFileFilterRequest request = new MavenFileFilterRequest(); 843 request.setEncoding(encoding); 844 request.setMavenSession(mavenSession); 845 request.setMavenProject(project); 846 request.setFiltering(enableFiltering); 847 for (int i = 0; files != null && i < files.length; ++i) { 848 if (files[i] != null && !files[i].exists()) { 849 throw new MojoExecutionException(files[i].getPath() + " not found."); 850 } 851 852 if (!enableFiltering) { 853 createTransaction().setSrc(files[i]); 854 continue; 855 } 856 857 File sourceFile = files[i]; 858 String basename = FileUtils.basename(sourceFile.getName()); 859 String extension = FileUtils.extension(sourceFile.getName()); 860 if (!extension.startsWith(".")) { 861 extension = "." + extension; 862 } 863 File targetFile = FileUtils.createTempFile(basename, extension, null); 864 if (!getLog().isDebugEnabled()) { 865 targetFile.deleteOnExit(); 866 } 867 868 request.setFrom(sourceFile); 869 request.setTo(targetFile); 870 871 try { 872 fileFilter.copyFile(request); 873 } catch (MavenFilteringException e) { 874 throw new MojoExecutionException(e.getMessage()); 875 } 876 877 createTransaction().setSrc(targetFile); 878 } 879 } 880 881 /** 882 * Sort the transaction list. 883 */ 884 protected void sortTransactions() { 885 switch (orderFile) { 886 case ASCENDING: 887 Collections.sort(transactions); 888 break; 889 case DESCENDING: 890 Collections.sort(transactions, Collections.reverseOrder()); 891 break; 892 case NONE: 893 break; 894 default: 895 throw new RuntimeException("Unknown value for orderFile: " + orderFile); 896 } 897 } 898 899 /** 900 * Load username password from settings if user has not set them in JVM properties 901 * 902 * @throws MojoExecutionException 903 */ 904 private void loadUserInfoFromSettings() throws MojoExecutionException { 905 if (this.settingsKey == null) { 906 this.settingsKey = getUrl(); 907 } 908 909 if ((getUsername() == null || getPassword() == null) && (settings != null)) { 910 Server server = this.settings.getServer(this.settingsKey); 911 912 if (server != null) { 913 if (getUsername() == null) { 914 setUsername(server.getUsername()); 915 } 916 917 if (getPassword() == null) { 918 setPassword(server.getPassword()); 919 } 920 } 921 } 922 923 if (getUsername() == null) { 924 // allow emtpy username 925 setUsername(""); 926 } 927 928 if (getPassword() == null) { 929 // allow emtpy password 930 setPassword(""); 931 } 932 } 933 934 /** 935 * Creates a new Connection as using the driver, url, userid and password specified. 936 * 937 * The calling method is responsible for closing the connection. 938 * 939 * @return Connection the newly created connection. 940 * @throws MojoExecutionException 941 * if the UserId/Password/Url is not set or there is no suitable driver or the driver fails to load. 942 * @throws SQLException 943 * if there is problem getting connection with valid url 944 * 945 */ 946 private Connection getConnection() throws MojoExecutionException, SQLException { 947 getLog().debug("connecting to " + getUrl()); 948 949 Properties properties = getProperties(); 950 Connection conn = getDriverInstance().connect(getUrl(), properties); 951 952 if (conn == null) { 953 // Driver doesn't understand the URL 954 throw new SQLException("No suitable Driver for " + getUrl()); 955 } 956 957 conn.setAutoCommit(autocommit); 958 return conn; 959 } 960 961 protected boolean isBlankOrNone(String s) { 962 return StringUtils.isBlank(s) || "NONE".equals(s.trim()); 963 } 964 965 protected boolean isSkipPassword() { 966 return enableAnonymousPassword && isBlankOrNone(getPassword()); 967 } 968 969 protected Properties getProperties() throws MojoExecutionException { 970 Properties properties = new Properties(); 971 properties.put("user", getUsername()); 972 973 if (!isSkipPassword()) { 974 properties.put("password", getPassword()); 975 } 976 properties.putAll(this.getDriverProperties()); 977 return properties; 978 } 979 980 protected Driver getDriverInstance() throws MojoExecutionException { 981 try { 982 Class<?> dc = Class.forName(getDriver()); 983 return (Driver) dc.newInstance(); 984 } catch (ClassNotFoundException e) { 985 throw new MojoExecutionException("Driver class not found: " + getDriver(), e); 986 } catch (Exception e) { 987 throw new MojoExecutionException("Failure loading driver: " + getDriver(), e); 988 } 989 } 990 991 /** 992 * parse driverProperties into Properties set 993 * 994 * @return the driver properties 995 * @throws MojoExecutionException 996 */ 997 protected Properties getDriverProperties() throws MojoExecutionException { 998 Properties properties = new Properties(); 999 1000 if (!StringUtils.isEmpty(this.driverProperties)) { 1001 String[] tokens = StringUtils.split(this.driverProperties, ","); 1002 for (int i = 0; i < tokens.length; ++i) { 1003 String[] keyValueTokens = StringUtils.split(tokens[i].trim(), "="); 1004 if (keyValueTokens.length != 2) { 1005 throw new MojoExecutionException("Invalid JDBC Driver properties: " + this.driverProperties); 1006 } 1007 1008 properties.setProperty(keyValueTokens[0], keyValueTokens[1]); 1009 1010 } 1011 } 1012 1013 return properties; 1014 } 1015 1016 /** 1017 * read in lines and execute them 1018 * 1019 * @param reader 1020 * the reader 1021 * @param out 1022 * the outputstream 1023 * @throws SQLException 1024 * @throws IOException 1025 */ 1026 private void runStatements(Reader reader, PrintStream out) throws SQLException, IOException { 1027 String line; 1028 1029 if (enableBlockMode) { 1030 // no need to parse the content, ship it directly to jdbc in one sql 1031 // statement 1032 line = IOUtil.toString(reader); 1033 execSQL(line, out); 1034 return; 1035 } 1036 1037 StringBuffer sql = new StringBuffer(); 1038 1039 BufferedReader in = new BufferedReader(reader); 1040 1041 while ((line = in.readLine()) != null) { 1042 getLog().debug("line='" + line + "'"); 1043 if (!keepFormat) { 1044 line = line.trim(); 1045 } 1046 1047 if (!keepFormat) { 1048 if (line.startsWith("//")) { 1049 continue; 1050 } 1051 if (line.startsWith("--")) { 1052 continue; 1053 } 1054 StringTokenizer st = new StringTokenizer(line); 1055 if (st.hasMoreTokens()) { 1056 String token = st.nextToken(); 1057 if ("REM".equalsIgnoreCase(token)) { 1058 continue; 1059 } 1060 } 1061 } 1062 1063 if (!keepFormat) { 1064 sql.append(" ").append(line); 1065 } else { 1066 sql.append("\n").append(line); 1067 } 1068 1069 // SQL defines "--" as a comment to EOL 1070 // but in Oracle it may contain a hint 1071 // so we cannot just remove it, instead we must end it 1072 if (!keepFormat) { 1073 if (SqlSplitter.containsSqlEnd(line, delimiter) == SqlSplitter.NO_END) { 1074 sql.append("\n"); 1075 } 1076 } 1077 1078 if (isEnd(line)) { 1079 execSQL(sql.substring(0, sql.length() - delimiter.length()), out); 1080 sql.setLength(0); // clean buffer 1081 } 1082 } 1083 1084 // Catch any statements not followed by ; 1085 if (executeTrailingSQL && !sql.toString().equals("")) { 1086 execSQL(sql.toString(), out); 1087 } 1088 } 1089 1090 protected boolean isEnd(String line) { 1091 if (delimiterType.equals(DelimiterType.ROW)) { 1092 return line.trim().equals(delimiter); 1093 } 1094 int pos = SqlSplitter.containsSqlEnd(line, delimiter); 1095 if (delimiterType.equals(DelimiterType.NORMAL) && pos > 0) { 1096 return true; 1097 } 1098 return false; 1099 } 1100 1101 /** 1102 * Exec the sql statement. 1103 * 1104 * @param sql 1105 * query to execute 1106 * @param out 1107 * the outputstream 1108 */ 1109 private void execSQL(String sql, PrintStream out) throws SQLException { 1110 // Check and ignore empty statements 1111 if ("".equals(sql.trim())) { 1112 return; 1113 } 1114 1115 ResultSet resultSet = null; 1116 try { 1117 totalStatements++; 1118 if (showSql) { 1119 getLog().info("SQL-" + totalStatements + ": " + sql); 1120 } else { 1121 getLog().debug("SQL-" + totalStatements + ": " + sql); 1122 } 1123 1124 boolean ret; 1125 int updateCountTotal = 0; 1126 1127 ret = statement.execute(sql); 1128 do { 1129 if (!ret) { 1130 int updateCount = statement.getUpdateCount(); 1131 if (updateCount != -1) { 1132 updateCountTotal += updateCount; 1133 } 1134 } else { 1135 resultSet = statement.getResultSet(); 1136 if (printResultSet) { 1137 printResultSet(resultSet, out); 1138 } 1139 } 1140 ret = statement.getMoreResults(); 1141 } while (ret); 1142 1143 getLog().debug(updateCountTotal + " rows affected"); 1144 1145 if (printResultSet) { 1146 StringBuffer line = new StringBuffer(); 1147 line.append(updateCountTotal).append(" rows affected"); 1148 out.println(line); 1149 } 1150 1151 SQLWarning warning = conn.getWarnings(); 1152 while (warning != null) { 1153 getLog().debug(warning + " sql warning"); 1154 warning = warning.getNextWarning(); 1155 } 1156 conn.clearWarnings(); 1157 successfulStatements++; 1158 } catch (SQLException e) { 1159 getLog().error("Failed to execute: " + sql); 1160 if (ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) { 1161 throw e; 1162 } 1163 getLog().error(e.toString()); 1164 } finally { 1165 if (resultSet != null) { 1166 resultSet.close(); 1167 } 1168 } 1169 } 1170 1171 /** 1172 * print any results in the result set. 1173 * 1174 * @param rs 1175 * the resultset to print information about 1176 * @param out 1177 * the place to print results 1178 * @throws SQLException 1179 * on SQL problems. 1180 */ 1181 private void printResultSet(ResultSet rs, PrintStream out) throws SQLException { 1182 if (rs != null) { 1183 getLog().debug("Processing new result set."); 1184 ResultSetMetaData md = rs.getMetaData(); 1185 int columnCount = md.getColumnCount(); 1186 StringBuffer line = new StringBuffer(); 1187 if (showheaders) { 1188 boolean first = true; 1189 for (int col = 1; col <= columnCount; col++) { 1190 String columnValue = md.getColumnName(col); 1191 1192 if (columnValue != null) { 1193 columnValue = columnValue.trim(); 1194 1195 if (",".equals(outputDelimiter)) { 1196 columnValue = StringEscapeUtils.escapeCsv(columnValue); 1197 } 1198 } 1199 1200 if (first) { 1201 first = false; 1202 } else { 1203 line.append(outputDelimiter); 1204 } 1205 line.append(columnValue); 1206 } 1207 out.println(line); 1208 line = new StringBuffer(); 1209 } 1210 while (rs.next()) { 1211 boolean first = true; 1212 for (int col = 1; col <= columnCount; col++) { 1213 String columnValue = rs.getString(col); 1214 if (columnValue != null) { 1215 columnValue = columnValue.trim(); 1216 1217 if (",".equals(outputDelimiter)) { 1218 columnValue = StringEscapeUtils.escapeCsv(columnValue); 1219 } 1220 } 1221 1222 if (first) { 1223 first = false; 1224 } else { 1225 line.append(outputDelimiter); 1226 } 1227 line.append(columnValue); 1228 } 1229 out.println(line); 1230 line = new StringBuffer(); 1231 } 1232 } 1233 out.println(); 1234 } 1235 1236 /** 1237 * Contains the definition of a new transaction element. Transactions allow several files or blocks of statements to be executed using 1238 * the same JDBC connection and commit operation in between. 1239 */ 1240 protected class Transaction implements Comparable<Transaction> { 1241 1242 protected Resource resource = null; 1243 1244 protected File tSrcFile = null; 1245 1246 protected String tSqlCommand = ""; 1247 1248 /** 1249 * 1250 */ 1251 public void setResource(Resource resource) { 1252 this.resource = resource; 1253 } 1254 1255 /** 1256 * 1257 */ 1258 public void setSrc(File src) { 1259 this.tSrcFile = src; 1260 } 1261 1262 /** 1263 * 1264 */ 1265 public void addText(String sql) { 1266 this.tSqlCommand += sql; 1267 } 1268 1269 /** 1270 * 1271 */ 1272 private void runTransaction(PrintStream out) throws IOException, SQLException { 1273 if (tSqlCommand.length() != 0) { 1274 getLog().info("Executing commands"); 1275 1276 runStatements(new StringReader(tSqlCommand), out); 1277 } 1278 1279 if (tSrcFile != null) { 1280 getLog().info("Executing file: " + tSrcFile.getAbsolutePath()); 1281 1282 Reader reader = null; 1283 1284 if (StringUtils.isEmpty(encoding)) { 1285 reader = new FileReader(tSrcFile); 1286 } else { 1287 reader = new InputStreamReader(new FileInputStream(tSrcFile), encoding); 1288 } 1289 1290 try { 1291 runStatements(reader, out); 1292 } finally { 1293 IOUtils.closeQuietly(reader); 1294 } 1295 } 1296 1297 if (resource != null) { 1298 getLog().info("Executing: " + resource.getURL()); 1299 1300 Reader reader = null; 1301 1302 if (StringUtils.isEmpty(encoding)) { 1303 reader = new InputStreamReader(resource.getInputStream()); 1304 } else { 1305 reader = new InputStreamReader(resource.getInputStream(), encoding); 1306 } 1307 1308 try { 1309 runStatements(reader, out); 1310 } finally { 1311 IOUtils.closeQuietly(reader); 1312 } 1313 } 1314 } 1315 1316 @Override 1317 public int compareTo(Transaction transaction) { 1318 1319 if (transaction.tSrcFile == null) { 1320 if (this.tSrcFile == null) { 1321 return 0; 1322 } else { 1323 return Integer.MAX_VALUE; 1324 } 1325 } else { 1326 if (this.tSrcFile == null) { 1327 return Integer.MIN_VALUE; 1328 } else { 1329 return this.tSrcFile.compareTo(transaction.tSrcFile); 1330 } 1331 } 1332 } 1333 } 1334 1335 // 1336 // helper accessors for unit test purposes 1337 // 1338 1339 public String getUsername() { 1340 return this.username; 1341 } 1342 1343 public void setUsername(String username) { 1344 this.username = username; 1345 } 1346 1347 public String getPassword() { 1348 return this.password; 1349 } 1350 1351 public void setPassword(String password) { 1352 this.password = password; 1353 } 1354 1355 public String getUrl() { 1356 return this.url; 1357 } 1358 1359 public void setUrl(String url) { 1360 this.url = url; 1361 } 1362 1363 public String getDriver() { 1364 return this.driver; 1365 } 1366 1367 public void setDriver(String driver) { 1368 this.driver = driver; 1369 } 1370 1371 void setAutocommit(boolean autocommit) { 1372 this.autocommit = autocommit; 1373 } 1374 1375 void setFileset(Fileset fileset) { 1376 this.fileset = fileset; 1377 } 1378 1379 public File[] getSrcFiles() { 1380 return this.srcFiles; 1381 } 1382 1383 public void setSrcFiles(File[] files) { 1384 this.srcFiles = files; 1385 } 1386 1387 /** 1388 * @deprecated use {@link #getSuccessfulStatements()} 1389 */ 1390 @Deprecated 1391 int getGoodSqls() { 1392 return this.getSuccessfulStatements(); 1393 } 1394 1395 /** 1396 * Number of SQL statements executed so far that caused errors. 1397 * 1398 * @return the number 1399 */ 1400 public int getSuccessfulStatements() { 1401 return successfulStatements; 1402 } 1403 1404 /** 1405 * Number of SQL statements executed so far, including the ones that caused errors. 1406 * 1407 * @return the number 1408 */ 1409 public int getTotalStatements() { 1410 return totalStatements; 1411 } 1412 1413 public String getOnError() { 1414 return this.onError; 1415 } 1416 1417 public void setOnError(String action) { 1418 if (ON_ERROR_ABORT.equalsIgnoreCase(action)) { 1419 this.onError = ON_ERROR_ABORT; 1420 } else if (ON_ERROR_CONTINUE.equalsIgnoreCase(action)) { 1421 this.onError = ON_ERROR_CONTINUE; 1422 } else if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(action)) { 1423 this.onError = ON_ERROR_ABORT_AFTER; 1424 } else { 1425 throw new IllegalArgumentException(action + " is not a valid value for onError, only '" + ON_ERROR_ABORT + "', '" + ON_ERROR_ABORT_AFTER + "', or '" 1426 + ON_ERROR_CONTINUE + "'."); 1427 } 1428 } 1429 1430 void setSettings(Settings settings) { 1431 this.settings = settings; 1432 } 1433 1434 void setSettingsKey(String key) { 1435 this.settingsKey = key; 1436 } 1437 1438 void setSkip(boolean skip) { 1439 this.skip = skip; 1440 } 1441 1442 public void setDriverProperties(String driverProperties) { 1443 this.driverProperties = driverProperties; 1444 } 1445 1446 public boolean isEnableBlockMode() { 1447 return enableBlockMode; 1448 } 1449 1450 public void setEnableBlockMode(boolean enableBlockMode) { 1451 this.enableBlockMode = enableBlockMode; 1452 } 1453 1454 public String getSqlCommand() { 1455 return sqlCommand; 1456 } 1457 1458 public void setSqlCommand(String sqlCommand) { 1459 this.sqlCommand = sqlCommand; 1460 } 1461 1462 public Vector<Transaction> getTransactions() { 1463 return transactions; 1464 } 1465 1466 public void setTransactions(Vector<Transaction> transactions) { 1467 this.transactions = transactions; 1468 } 1469 1470 public void setFileFilter(MavenFileFilter filter) { 1471 this.fileFilter = filter; 1472 } 1473 1474 public String[] getResourceLocations() { 1475 return resourceLocations; 1476 } 1477 1478 public void setResourceLocations(String[] resourceLocations) { 1479 this.resourceLocations = resourceLocations; 1480 } 1481 1482 public boolean isExecuteTrailingSQL() { 1483 return executeTrailingSQL; 1484 } 1485 1486 public void setExecuteTrailingSQL(boolean executeTrailingSQL) { 1487 this.executeTrailingSQL = executeTrailingSQL; 1488 } 1489 1490 public Order getOrderFile() { 1491 return orderFile; 1492 } 1493 1494 public void setOrderFile(String orderFile) { 1495 this.orderFile = Order.valueOf(orderFile.toUpperCase()); 1496 } 1497 1498 public String getResourceListingLocation() { 1499 return resourceListingLocation; 1500 } 1501 1502 public void setResourceListingLocation(String resourceListingLocation) { 1503 this.resourceListingLocation = resourceListingLocation; 1504 } 1505 }