View Javadoc

1   package org.codehaus.mojo.sql;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
5    * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
7    * License. You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
12   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
13   * specific language governing permissions and limitations under the License.
14   */
15  
16  import java.io.BufferedOutputStream;
17  import java.io.BufferedReader;
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.FileOutputStream;
21  import java.io.FileReader;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.OutputStream;
26  import java.io.PrintStream;
27  import java.io.Reader;
28  import java.io.StringReader;
29  import java.sql.Connection;
30  import java.sql.Driver;
31  import java.sql.ResultSet;
32  import java.sql.ResultSetMetaData;
33  import java.sql.SQLException;
34  import java.sql.SQLWarning;
35  import java.sql.Statement;
36  import java.util.ArrayList;
37  import java.util.Collections;
38  import java.util.Enumeration;
39  import java.util.List;
40  import java.util.Properties;
41  import java.util.StringTokenizer;
42  import java.util.Vector;
43  
44  import org.apache.commons.io.IOUtils;
45  import org.apache.commons.lang.StringEscapeUtils;
46  import org.apache.maven.execution.MavenSession;
47  import org.apache.maven.plugin.AbstractMojo;
48  import org.apache.maven.plugin.MojoExecutionException;
49  import org.apache.maven.project.MavenProject;
50  import org.apache.maven.settings.Server;
51  import org.apache.maven.settings.Settings;
52  import org.apache.maven.shared.filtering.MavenFileFilter;
53  import org.apache.maven.shared.filtering.MavenFileFilterRequest;
54  import org.apache.maven.shared.filtering.MavenFilteringException;
55  import org.codehaus.plexus.util.FileUtils;
56  import org.codehaus.plexus.util.IOUtil;
57  import org.codehaus.plexus.util.StringUtils;
58  import org.springframework.core.io.DefaultResourceLoader;
59  import org.springframework.core.io.Resource;
60  import org.springframework.core.io.ResourceLoader;
61  
62  /**
63   * Executes SQL against a database.
64   *
65   * @goal execute
66   */
67  public class SqlExecMojo extends AbstractMojo {
68  
69      /**
70       * Call {@link #setOnError(String)} with this value to abort SQL command execution if an error is found.
71       */
72      public static final String ON_ERROR_ABORT = "abort";
73  
74      /**
75       * Call {@link #setOnError(String)} with this value to continue SQL command execution until all commands have been
76       * attempted, then abort the build if an SQL error occurred in any of the commands.
77       */
78      public static final String ON_ERROR_ABORT_AFTER = "abortAfter";
79  
80      /**
81       * Call {@link #setOnError(String)} with this value to continue SQL command execution if an error is found.
82       */
83      public static final String ON_ERROR_CONTINUE = "continue";
84  
85      // ////////////////////////// User Info ///////////////////////////////////
86  
87      /**
88       * Database username. If not given, it will be looked up through <code>settings.xml</code>'s server with
89       * <code>${settingsKey}</code> as key.
90       *
91       * @since 1.0
92       * @parameter expression="${username}"
93       */
94      private String username;
95  
96      /**
97       * Database password. If not given, it will be looked up through <code>settings.xml</code>'s server with
98       * <code>${settingsKey}</code> as key.
99       *
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 &quot;;&quot;;
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 }