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