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