View Javadoc

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