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 private 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 private 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 String filename = resource.getFilename(); 789 String basename = FileUtils.basename(filename); 790 String extension = FileUtils.extension(filename); 791 if (!extension.startsWith(".")) { 792 extension = "." + extension; 793 } 794 File sourceFile = FileUtils.createTempFile(basename, extension, null); 795 if (!getLog().isDebugEnabled()) { 796 sourceFile.deleteOnExit(); 797 } 798 799 try { 800 copy(resource, sourceFile); 801 } catch (IOException e) { 802 throw new MojoExecutionException("Error copying resource " + resource + " to a local temporary file", e); 803 } 804 805 if (!enableFiltering) { 806 createTransaction().setSrc(sourceFile); 807 continue; 808 } 809 810 File targetFile = FileUtils.createTempFile(basename, extension, null); 811 if (!getLog().isDebugEnabled()) { 812 sourceFile.deleteOnExit(); 813 } 814 815 request.setFrom(sourceFile); 816 request.setTo(targetFile); 817 818 try { 819 fileFilter.copyFile(request); 820 } catch (MavenFilteringException e) { 821 throw new MojoExecutionException(e.getMessage()); 822 } 823 824 createTransaction().setSrc(targetFile); 825 } 826 } 827 828 /** 829 * Add user input of srcFiles to transaction list. 830 * 831 * @throws MojoExecutionException 832 */ 833 private void addFilesToTransactions() throws MojoExecutionException { 834 File[] files = getSrcFiles(); 835 836 MavenFileFilterRequest request = new MavenFileFilterRequest(); 837 request.setEncoding(encoding); 838 request.setMavenSession(mavenSession); 839 request.setMavenProject(project); 840 request.setFiltering(enableFiltering); 841 for (int i = 0; files != null && i < files.length; ++i) { 842 if (files[i] != null && !files[i].exists()) { 843 throw new MojoExecutionException(files[i].getPath() + " not found."); 844 } 845 846 if (!enableFiltering) { 847 createTransaction().setSrc(files[i]); 848 continue; 849 } 850 851 File sourceFile = files[i]; 852 String basename = FileUtils.basename(sourceFile.getName()); 853 String extension = FileUtils.extension(sourceFile.getName()); 854 if (!extension.startsWith(".")) { 855 extension = "." + extension; 856 } 857 File targetFile = FileUtils.createTempFile(basename, extension, null); 858 if (!getLog().isDebugEnabled()) { 859 targetFile.deleteOnExit(); 860 } 861 862 request.setFrom(sourceFile); 863 request.setTo(targetFile); 864 865 try { 866 fileFilter.copyFile(request); 867 } catch (MavenFilteringException e) { 868 throw new MojoExecutionException(e.getMessage()); 869 } 870 871 createTransaction().setSrc(targetFile); 872 } 873 } 874 875 /** 876 * Sort the transaction list. 877 */ 878 protected void sortTransactions() { 879 switch (orderFile) { 880 case ASCENDING: 881 Collections.sort(transactions); 882 break; 883 case DESCENDING: 884 Collections.sort(transactions, Collections.reverseOrder()); 885 break; 886 case NONE: 887 break; 888 default: 889 throw new RuntimeException("Unknown value for orderFile: " + orderFile); 890 } 891 } 892 893 /** 894 * Load username password from settings if user has not set them in JVM properties 895 * 896 * @throws MojoExecutionException 897 */ 898 private void loadUserInfoFromSettings() throws MojoExecutionException { 899 if (this.settingsKey == null) { 900 this.settingsKey = getUrl(); 901 } 902 903 if ((getUsername() == null || getPassword() == null) && (settings != null)) { 904 Server server = this.settings.getServer(this.settingsKey); 905 906 if (server != null) { 907 if (getUsername() == null) { 908 setUsername(server.getUsername()); 909 } 910 911 if (getPassword() == null) { 912 setPassword(server.getPassword()); 913 } 914 } 915 } 916 917 if (getUsername() == null) { 918 // allow emtpy username 919 setUsername(""); 920 } 921 922 if (getPassword() == null) { 923 // allow emtpy password 924 setPassword(""); 925 } 926 } 927 928 /** 929 * Creates a new Connection as using the driver, url, userid and password specified. 930 * 931 * The calling method is responsible for closing the connection. 932 * 933 * @return Connection the newly created connection. 934 * @throws MojoExecutionException 935 * if the UserId/Password/Url is not set or there is no suitable driver or the driver fails to load. 936 * @throws SQLException 937 * if there is problem getting connection with valid url 938 * 939 */ 940 private Connection getConnection() throws MojoExecutionException, SQLException { 941 getLog().debug("connecting to " + getUrl()); 942 943 Properties properties = getProperties(); 944 Connection conn = getDriverInstance().connect(getUrl(), properties); 945 946 if (conn == null) { 947 // Driver doesn't understand the URL 948 throw new SQLException("No suitable Driver for " + getUrl()); 949 } 950 951 conn.setAutoCommit(autocommit); 952 return conn; 953 } 954 955 protected boolean isBlankOrNone(String s) { 956 return StringUtils.isBlank(s) || "NONE".equals(s.trim()); 957 } 958 959 protected boolean isSkipPassword() { 960 return enableAnonymousPassword && isBlankOrNone(getPassword()); 961 } 962 963 protected Properties getProperties() throws MojoExecutionException { 964 Properties properties = new Properties(); 965 properties.put("user", getUsername()); 966 967 if (!isSkipPassword()) { 968 properties.put("password", getPassword()); 969 } 970 properties.putAll(this.getDriverProperties()); 971 return properties; 972 } 973 974 protected Driver getDriverInstance() throws MojoExecutionException { 975 try { 976 Class<?> dc = Class.forName(getDriver()); 977 return (Driver) dc.newInstance(); 978 } catch (ClassNotFoundException e) { 979 throw new MojoExecutionException("Driver class not found: " + getDriver(), e); 980 } catch (Exception e) { 981 throw new MojoExecutionException("Failure loading driver: " + getDriver(), e); 982 } 983 } 984 985 /** 986 * parse driverProperties into Properties set 987 * 988 * @return the driver properties 989 * @throws MojoExecutionException 990 */ 991 protected Properties getDriverProperties() throws MojoExecutionException { 992 Properties properties = new Properties(); 993 994 if (!StringUtils.isEmpty(this.driverProperties)) { 995 String[] tokens = StringUtils.split(this.driverProperties, ","); 996 for (int i = 0; i < tokens.length; ++i) { 997 String[] keyValueTokens = StringUtils.split(tokens[i].trim(), "="); 998 if (keyValueTokens.length != 2) { 999 throw new MojoExecutionException("Invalid JDBC Driver properties: " + this.driverProperties); 1000 } 1001 1002 properties.setProperty(keyValueTokens[0], keyValueTokens[1]); 1003 1004 } 1005 } 1006 1007 return properties; 1008 } 1009 1010 /** 1011 * read in lines and execute them 1012 * 1013 * @param reader 1014 * the reader 1015 * @param out 1016 * the outputstream 1017 * @throws SQLException 1018 * @throws IOException 1019 */ 1020 private void runStatements(Reader reader, PrintStream out) throws SQLException, IOException { 1021 String line; 1022 1023 if (enableBlockMode) { 1024 // no need to parse the content, ship it directly to jdbc in one sql 1025 // statement 1026 line = IOUtil.toString(reader); 1027 execSQL(line, out); 1028 return; 1029 } 1030 1031 StringBuffer sql = new StringBuffer(); 1032 1033 BufferedReader in = new BufferedReader(reader); 1034 1035 while ((line = in.readLine()) != null) { 1036 getLog().debug("line='" + line + "'"); 1037 if (!keepFormat) { 1038 line = line.trim(); 1039 } 1040 1041 if (!keepFormat) { 1042 if (line.startsWith("//")) { 1043 continue; 1044 } 1045 if (line.startsWith("--")) { 1046 continue; 1047 } 1048 StringTokenizer st = new StringTokenizer(line); 1049 if (st.hasMoreTokens()) { 1050 String token = st.nextToken(); 1051 if ("REM".equalsIgnoreCase(token)) { 1052 continue; 1053 } 1054 } 1055 } 1056 1057 if (!keepFormat) { 1058 sql.append(" ").append(line); 1059 } else { 1060 sql.append("\n").append(line); 1061 } 1062 1063 // SQL defines "--" as a comment to EOL 1064 // but in Oracle it may contain a hint 1065 // so we cannot just remove it, instead we must end it 1066 if (!keepFormat) { 1067 if (SqlSplitter.containsSqlEnd(line, delimiter) == SqlSplitter.NO_END) { 1068 sql.append("\n"); 1069 } 1070 } 1071 1072 if (isEnd(line)) { 1073 execSQL(sql.substring(0, sql.length() - delimiter.length()), out); 1074 sql.setLength(0); // clean buffer 1075 } 1076 } 1077 1078 // Catch any statements not followed by ; 1079 if (executeTrailingSQL && !sql.toString().equals("")) { 1080 execSQL(sql.toString(), out); 1081 } 1082 } 1083 1084 protected boolean isEnd(String line) { 1085 if (delimiterType.equals(DelimiterType.ROW)) { 1086 return line.trim().equals(delimiter); 1087 } 1088 int pos = SqlSplitter.containsSqlEnd(line, delimiter); 1089 if (delimiterType.equals(DelimiterType.NORMAL) && pos > 0) { 1090 return true; 1091 } 1092 return false; 1093 } 1094 1095 /** 1096 * Exec the sql statement. 1097 * 1098 * @param sql 1099 * query to execute 1100 * @param out 1101 * the outputstream 1102 */ 1103 private void execSQL(String sql, PrintStream out) throws SQLException { 1104 // Check and ignore empty statements 1105 if ("".equals(sql.trim())) { 1106 return; 1107 } 1108 1109 ResultSet resultSet = null; 1110 try { 1111 totalStatements++; 1112 if (showSql) { 1113 getLog().info("SQL-" + totalStatements + ": " + sql); 1114 } else { 1115 getLog().debug("SQL-" + totalStatements + ": " + sql); 1116 } 1117 1118 boolean ret; 1119 int updateCountTotal = 0; 1120 1121 ret = statement.execute(sql); 1122 do { 1123 if (!ret) { 1124 int updateCount = statement.getUpdateCount(); 1125 if (updateCount != -1) { 1126 updateCountTotal += updateCount; 1127 } 1128 } else { 1129 resultSet = statement.getResultSet(); 1130 if (printResultSet) { 1131 printResultSet(resultSet, out); 1132 } 1133 } 1134 ret = statement.getMoreResults(); 1135 } while (ret); 1136 1137 getLog().debug(updateCountTotal + " rows affected"); 1138 1139 if (printResultSet) { 1140 StringBuffer line = new StringBuffer(); 1141 line.append(updateCountTotal).append(" rows affected"); 1142 out.println(line); 1143 } 1144 1145 SQLWarning warning = conn.getWarnings(); 1146 while (warning != null) { 1147 getLog().debug(warning + " sql warning"); 1148 warning = warning.getNextWarning(); 1149 } 1150 conn.clearWarnings(); 1151 successfulStatements++; 1152 } catch (SQLException e) { 1153 getLog().error("Failed to execute: " + sql); 1154 if (ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) { 1155 throw e; 1156 } 1157 getLog().error(e.toString()); 1158 } finally { 1159 if (resultSet != null) { 1160 resultSet.close(); 1161 } 1162 } 1163 } 1164 1165 /** 1166 * print any results in the result set. 1167 * 1168 * @param rs 1169 * the resultset to print information about 1170 * @param out 1171 * the place to print results 1172 * @throws SQLException 1173 * on SQL problems. 1174 */ 1175 private void printResultSet(ResultSet rs, PrintStream out) throws SQLException { 1176 if (rs != null) { 1177 getLog().debug("Processing new result set."); 1178 ResultSetMetaData md = rs.getMetaData(); 1179 int columnCount = md.getColumnCount(); 1180 StringBuffer line = new StringBuffer(); 1181 if (showheaders) { 1182 boolean first = true; 1183 for (int col = 1; col <= columnCount; col++) { 1184 String columnValue = md.getColumnName(col); 1185 1186 if (columnValue != null) { 1187 columnValue = columnValue.trim(); 1188 1189 if (",".equals(outputDelimiter)) { 1190 columnValue = StringEscapeUtils.escapeCsv(columnValue); 1191 } 1192 } 1193 1194 if (first) { 1195 first = false; 1196 } else { 1197 line.append(outputDelimiter); 1198 } 1199 line.append(columnValue); 1200 } 1201 out.println(line); 1202 line = new StringBuffer(); 1203 } 1204 while (rs.next()) { 1205 boolean first = true; 1206 for (int col = 1; col <= columnCount; col++) { 1207 String columnValue = rs.getString(col); 1208 if (columnValue != null) { 1209 columnValue = columnValue.trim(); 1210 1211 if (",".equals(outputDelimiter)) { 1212 columnValue = StringEscapeUtils.escapeCsv(columnValue); 1213 } 1214 } 1215 1216 if (first) { 1217 first = false; 1218 } else { 1219 line.append(outputDelimiter); 1220 } 1221 line.append(columnValue); 1222 } 1223 out.println(line); 1224 line = new StringBuffer(); 1225 } 1226 } 1227 out.println(); 1228 } 1229 1230 /** 1231 * Contains the definition of a new transaction element. Transactions allow several files or blocks of statements to be executed using 1232 * the same JDBC connection and commit operation in between. 1233 */ 1234 private class Transaction implements Comparable<Transaction> { 1235 private File tSrcFile = null; 1236 1237 private String tSqlCommand = ""; 1238 1239 /** 1240 * 1241 */ 1242 public void setSrc(File src) { 1243 this.tSrcFile = src; 1244 } 1245 1246 /** 1247 * 1248 */ 1249 public void addText(String sql) { 1250 this.tSqlCommand += sql; 1251 } 1252 1253 /** 1254 * 1255 */ 1256 private void runTransaction(PrintStream out) throws IOException, SQLException { 1257 if (tSqlCommand.length() != 0) { 1258 getLog().info("Executing commands"); 1259 1260 runStatements(new StringReader(tSqlCommand), out); 1261 } 1262 1263 if (tSrcFile != null) { 1264 getLog().info("Executing file: " + tSrcFile.getAbsolutePath()); 1265 1266 Reader reader = null; 1267 1268 if (StringUtils.isEmpty(encoding)) { 1269 reader = new FileReader(tSrcFile); 1270 } else { 1271 reader = new InputStreamReader(new FileInputStream(tSrcFile), encoding); 1272 } 1273 1274 try { 1275 runStatements(reader, out); 1276 } finally { 1277 reader.close(); 1278 } 1279 } 1280 } 1281 1282 @Override 1283 public int compareTo(Transaction transaction) { 1284 1285 if (transaction.tSrcFile == null) { 1286 if (this.tSrcFile == null) { 1287 return 0; 1288 } else { 1289 return Integer.MAX_VALUE; 1290 } 1291 } else { 1292 if (this.tSrcFile == null) { 1293 return Integer.MIN_VALUE; 1294 } else { 1295 return this.tSrcFile.compareTo(transaction.tSrcFile); 1296 } 1297 } 1298 } 1299 } 1300 1301 // 1302 // helper accessors for unit test purposes 1303 // 1304 1305 public String getUsername() { 1306 return this.username; 1307 } 1308 1309 public void setUsername(String username) { 1310 this.username = username; 1311 } 1312 1313 public String getPassword() { 1314 return this.password; 1315 } 1316 1317 public void setPassword(String password) { 1318 this.password = password; 1319 } 1320 1321 public String getUrl() { 1322 return this.url; 1323 } 1324 1325 public void setUrl(String url) { 1326 this.url = url; 1327 } 1328 1329 public String getDriver() { 1330 return this.driver; 1331 } 1332 1333 public void setDriver(String driver) { 1334 this.driver = driver; 1335 } 1336 1337 void setAutocommit(boolean autocommit) { 1338 this.autocommit = autocommit; 1339 } 1340 1341 void setFileset(Fileset fileset) { 1342 this.fileset = fileset; 1343 } 1344 1345 public File[] getSrcFiles() { 1346 return this.srcFiles; 1347 } 1348 1349 public void setSrcFiles(File[] files) { 1350 this.srcFiles = files; 1351 } 1352 1353 /** 1354 * @deprecated use {@link #getSuccessfulStatements()} 1355 */ 1356 @Deprecated 1357 int getGoodSqls() { 1358 return this.getSuccessfulStatements(); 1359 } 1360 1361 /** 1362 * Number of SQL statements executed so far that caused errors. 1363 * 1364 * @return the number 1365 */ 1366 public int getSuccessfulStatements() { 1367 return successfulStatements; 1368 } 1369 1370 /** 1371 * Number of SQL statements executed so far, including the ones that caused errors. 1372 * 1373 * @return the number 1374 */ 1375 public int getTotalStatements() { 1376 return totalStatements; 1377 } 1378 1379 public String getOnError() { 1380 return this.onError; 1381 } 1382 1383 public void setOnError(String action) { 1384 if (ON_ERROR_ABORT.equalsIgnoreCase(action)) { 1385 this.onError = ON_ERROR_ABORT; 1386 } else if (ON_ERROR_CONTINUE.equalsIgnoreCase(action)) { 1387 this.onError = ON_ERROR_CONTINUE; 1388 } else if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(action)) { 1389 this.onError = ON_ERROR_ABORT_AFTER; 1390 } else { 1391 throw new IllegalArgumentException(action + " is not a valid value for onError, only '" + ON_ERROR_ABORT + "', '" + ON_ERROR_ABORT_AFTER + "', or '" 1392 + ON_ERROR_CONTINUE + "'."); 1393 } 1394 } 1395 1396 void setSettings(Settings settings) { 1397 this.settings = settings; 1398 } 1399 1400 void setSettingsKey(String key) { 1401 this.settingsKey = key; 1402 } 1403 1404 void setSkip(boolean skip) { 1405 this.skip = skip; 1406 } 1407 1408 public void setDriverProperties(String driverProperties) { 1409 this.driverProperties = driverProperties; 1410 } 1411 1412 public boolean isEnableBlockMode() { 1413 return enableBlockMode; 1414 } 1415 1416 public void setEnableBlockMode(boolean enableBlockMode) { 1417 this.enableBlockMode = enableBlockMode; 1418 } 1419 1420 public String getSqlCommand() { 1421 return sqlCommand; 1422 } 1423 1424 public void setSqlCommand(String sqlCommand) { 1425 this.sqlCommand = sqlCommand; 1426 } 1427 1428 public Vector<Transaction> getTransactions() { 1429 return transactions; 1430 } 1431 1432 public void setTransactions(Vector<Transaction> transactions) { 1433 this.transactions = transactions; 1434 } 1435 1436 public void setFileFilter(MavenFileFilter filter) { 1437 this.fileFilter = filter; 1438 } 1439 1440 public String[] getResourceLocations() { 1441 return resourceLocations; 1442 } 1443 1444 public void setResourceLocations(String[] resourceLocations) { 1445 this.resourceLocations = resourceLocations; 1446 } 1447 1448 public boolean isExecuteTrailingSQL() { 1449 return executeTrailingSQL; 1450 } 1451 1452 public void setExecuteTrailingSQL(boolean executeTrailingSQL) { 1453 this.executeTrailingSQL = executeTrailingSQL; 1454 } 1455 1456 public Order getOrderFile() { 1457 return orderFile; 1458 } 1459 1460 public void setOrderFile(String orderFile) { 1461 this.orderFile = Order.valueOf(orderFile.toUpperCase()); 1462 } 1463 1464 public String getResourceListingLocation() { 1465 return resourceListingLocation; 1466 } 1467 1468 public void setResourceListingLocation(String resourceListingLocation) { 1469 this.resourceListingLocation = resourceListingLocation; 1470 } 1471 }