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