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 }