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