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