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