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