View Javadoc

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