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 }