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