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