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.net.URL;
032    import java.sql.Connection;
033    import java.sql.Driver;
034    import java.sql.ResultSet;
035    import java.sql.ResultSetMetaData;
036    import java.sql.SQLException;
037    import java.sql.SQLWarning;
038    import java.sql.Statement;
039    import java.util.ArrayList;
040    import java.util.Collections;
041    import java.util.Enumeration;
042    import java.util.List;
043    import java.util.Properties;
044    import java.util.StringTokenizer;
045    import java.util.Vector;
046    
047    import org.apache.commons.io.IOUtils;
048    import org.apache.commons.lang.StringEscapeUtils;
049    import org.apache.commons.lang.StringUtils;
050    import org.apache.maven.execution.MavenSession;
051    import org.apache.maven.plugin.AbstractMojo;
052    import org.apache.maven.plugin.MojoExecutionException;
053    import org.apache.maven.project.MavenProject;
054    import org.apache.maven.settings.Server;
055    import org.apache.maven.settings.Settings;
056    import org.apache.maven.shared.filtering.MavenFileFilter;
057    import org.apache.maven.shared.filtering.MavenFileFilterRequest;
058    import org.apache.maven.shared.filtering.MavenFilteringException;
059    import org.codehaus.plexus.util.FileUtils;
060    import org.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            protected 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 SqlResource[] 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                    getLog().info("Examining [" + getURL(resource) + "]");
709                    List<String> locs = readLines(resource);
710                    if (locations == null) {
711                            locs = trim(locs);
712                            getLog().info("Found " + locs.size() + " locations");
713                            return getResources(locs.toArray(new String[locs.size()]));
714                    }
715                    for (String location : locations) {
716                            locs.add(location);
717                    }
718                    locs = trim(locs);
719                    return getResources(locs.toArray(new String[locs.size()]));
720            }
721    
722            protected URL getURL(Resource resource) {
723                    try {
724                            return resource.getURL();
725                    } catch (IOException e) {
726                            throw new IllegalArgumentException("Unexpected IO error", e);
727                    }
728            }
729    
730            protected List<String> trim(List<String> lines) {
731                    List<String> trimmed = new ArrayList<String>();
732                    for (String line : lines) {
733                            trimmed.add(StringUtils.trim(line));
734                    }
735                    return trimmed;
736            }
737    
738            protected List<String> readLines(Resource resource) {
739                    InputStream in = null;
740                    try {
741                            in = resource.getInputStream();
742                            if (StringUtils.isBlank(encoding)) {
743                                    return IOUtils.readLines(in);
744                            } else {
745                                    return IOUtils.readLines(in, encoding);
746                            }
747                    } catch (IOException e) {
748                            throw new IllegalStateException("Unexpected IO error", e);
749                    } finally {
750                            IOUtils.closeQuietly(in);
751                    }
752            }
753    
754            protected SqlResource[] getResources(String[] locations) throws MojoExecutionException {
755                    ResourceLoader loader = new DefaultResourceLoader();
756                    if (locations == null || locations.length == 0) {
757                            return new SqlResource[] {};
758                    }
759                    List<SqlResource> resources = new ArrayList<SqlResource>();
760                    for (int i = 0; i < locations.length; i++) {
761                            String location = locations[i];
762                            // Skip it if the location is empty
763                            if (StringUtils.isEmpty(location)) {
764                                    continue;
765                            }
766                            Resource resource = loader.getResource(location);
767                            if (!resource.exists()) {
768                                    // The location was not empty, but we couldn't find it
769                                    throw new MojoExecutionException("Resource " + location + " was not found");
770                            }
771                            SqlResource sqlResource = new SqlResource();
772                            sqlResource.setLocation(location);
773                            sqlResource.setResource(resource);
774                            resources.add(sqlResource);
775                    }
776                    return resources.toArray(new SqlResource[resources.size()]);
777            }
778    
779            protected void copy(Resource resource, File file) throws IOException {
780                    InputStream in = null;
781                    OutputStream out = null;
782                    try {
783                            in = resource.getInputStream();
784                            out = new FileOutputStream(file);
785                            IOUtils.copyLarge(in, out);
786                    } finally {
787                            IOUtils.closeQuietly(in);
788                            IOUtils.closeQuietly(out);
789                    }
790            }
791    
792            /**
793             * Add user input of srcFiles to transaction list.
794             *
795             * @throws MojoExecutionException
796             */
797            protected void addResourcesToTransactions() throws MojoExecutionException {
798                    String[] locations = getResourceLocations();
799                    SqlResource[] resources = getResources(locations, resourceListingLocation);
800    
801                    MavenFileFilterRequest request = new MavenFileFilterRequest();
802                    request.setEncoding(encoding);
803                    request.setMavenSession(mavenSession);
804                    request.setMavenProject(project);
805                    request.setFiltering(enableFiltering);
806                    for (int i = 0; i < resources.length; i++) {
807                            SqlResource resource = resources[i];
808    
809                            if (!enableFiltering) {
810                                    createTransaction().setResource(resource);
811                                    continue;
812                            }
813    
814                            String filename = resource.getResource().getFilename();
815                            String basename = FileUtils.basename(filename);
816                            String extension = FileUtils.extension(filename);
817                            if (!extension.startsWith(".")) {
818                                    extension = "." + extension;
819                            }
820                            File sourceFile = FileUtils.createTempFile(basename, extension, null);
821                            if (!getLog().isDebugEnabled()) {
822                                    sourceFile.deleteOnExit();
823                            }
824    
825                            try {
826                                    copy(resource.getResource(), sourceFile);
827                            } catch (IOException e) {
828                                    throw new MojoExecutionException("Error copying resource " + resource.getResource() + " to a local temporary file", e);
829                            }
830    
831                            if (!enableFiltering) {
832                                    createTransaction().setSrc(sourceFile);
833                                    continue;
834                            }
835    
836                            File targetFile = FileUtils.createTempFile(basename, extension, null);
837                            if (!getLog().isDebugEnabled()) {
838                                    sourceFile.deleteOnExit();
839                            }
840    
841                            request.setFrom(sourceFile);
842                            request.setTo(targetFile);
843    
844                            try {
845                                    fileFilter.copyFile(request);
846                            } catch (MavenFilteringException e) {
847                                    throw new MojoExecutionException(e.getMessage());
848                            }
849    
850                            createTransaction().setSrc(targetFile);
851                    }
852            }
853    
854            /**
855             * Add user input of srcFiles to transaction list.
856             *
857             * @throws MojoExecutionException
858             */
859            private void addFilesToTransactions() throws MojoExecutionException {
860                    File[] files = getSrcFiles();
861    
862                    MavenFileFilterRequest request = new MavenFileFilterRequest();
863                    request.setEncoding(encoding);
864                    request.setMavenSession(mavenSession);
865                    request.setMavenProject(project);
866                    request.setFiltering(enableFiltering);
867                    for (int i = 0; files != null && i < files.length; ++i) {
868                            if (files[i] != null && !files[i].exists()) {
869                                    throw new MojoExecutionException(files[i].getPath() + " not found.");
870                            }
871    
872                            if (!enableFiltering) {
873                                    createTransaction().setSrc(files[i]);
874                                    continue;
875                            }
876    
877                            File sourceFile = files[i];
878                            String basename = FileUtils.basename(sourceFile.getName());
879                            String extension = FileUtils.extension(sourceFile.getName());
880                            if (!extension.startsWith(".")) {
881                                    extension = "." + extension;
882                            }
883                            File targetFile = FileUtils.createTempFile(basename, extension, null);
884                            if (!getLog().isDebugEnabled()) {
885                                    targetFile.deleteOnExit();
886                            }
887    
888                            request.setFrom(sourceFile);
889                            request.setTo(targetFile);
890    
891                            try {
892                                    fileFilter.copyFile(request);
893                            } catch (MavenFilteringException e) {
894                                    throw new MojoExecutionException(e.getMessage());
895                            }
896    
897                            createTransaction().setSrc(targetFile);
898                    }
899            }
900    
901            /**
902             * Sort the transaction list.
903             */
904            protected void sortTransactions() {
905                    switch (orderFile) {
906                    case ASCENDING:
907                            Collections.sort(transactions);
908                            break;
909                    case DESCENDING:
910                            Collections.sort(transactions, Collections.reverseOrder());
911                            break;
912                    case NONE:
913                            break;
914                    default:
915                            throw new RuntimeException("Unknown value for orderFile: " + orderFile);
916                    }
917            }
918    
919            /**
920             * Load username password from settings if user has not set them in JVM properties
921             *
922             * @throws MojoExecutionException
923             */
924            private void loadUserInfoFromSettings() throws MojoExecutionException {
925                    if (this.settingsKey == null) {
926                            this.settingsKey = getUrl();
927                    }
928    
929                    if ((getUsername() == null || getPassword() == null) && (settings != null)) {
930                            Server server = this.settings.getServer(this.settingsKey);
931    
932                            if (server != null) {
933                                    if (getUsername() == null) {
934                                            setUsername(server.getUsername());
935                                    }
936    
937                                    if (getPassword() == null) {
938                                            setPassword(server.getPassword());
939                                    }
940                            }
941                    }
942    
943                    if (getUsername() == null) {
944                            // allow emtpy username
945                            setUsername("");
946                    }
947    
948                    if (getPassword() == null) {
949                            // allow emtpy password
950                            setPassword("");
951                    }
952            }
953    
954            /**
955             * Creates a new Connection as using the driver, url, userid and password specified.
956             *
957             * The calling method is responsible for closing the connection.
958             *
959             * @return Connection the newly created connection.
960             * @throws MojoExecutionException
961             *             if the UserId/Password/Url is not set or there is no suitable driver or the driver fails to load.
962             * @throws SQLException
963             *             if there is problem getting connection with valid url
964             *
965             */
966            private Connection getConnection() throws MojoExecutionException, SQLException {
967                    getLog().debug("connecting to " + getUrl());
968    
969                    Properties properties = getProperties();
970                    Connection conn = getDriverInstance().connect(getUrl(), properties);
971    
972                    if (conn == null) {
973                            // Driver doesn't understand the URL
974                            throw new SQLException("No suitable Driver for " + getUrl());
975                    }
976    
977                    conn.setAutoCommit(autocommit);
978                    return conn;
979            }
980    
981            protected boolean isBlankOrNone(String s) {
982                    return StringUtils.isBlank(s) || "NONE".equals(s.trim());
983            }
984    
985            protected boolean isSkipPassword() {
986                    return enableAnonymousPassword && isBlankOrNone(getPassword());
987            }
988    
989            protected Properties getProperties() throws MojoExecutionException {
990                    Properties properties = new Properties();
991                    properties.put("user", getUsername());
992    
993                    if (!isSkipPassword()) {
994                            properties.put("password", getPassword());
995                    }
996                    properties.putAll(this.getDriverProperties());
997                    return properties;
998            }
999    
1000            protected Driver getDriverInstance() throws MojoExecutionException {
1001                    try {
1002                            Class<?> dc = Class.forName(getDriver());
1003                            return (Driver) dc.newInstance();
1004                    } catch (ClassNotFoundException e) {
1005                            throw new MojoExecutionException("Driver class not found: " + getDriver(), e);
1006                    } catch (Exception e) {
1007                            throw new MojoExecutionException("Failure loading driver: " + getDriver(), e);
1008                    }
1009            }
1010    
1011            /**
1012             * parse driverProperties into Properties set
1013             *
1014             * @return the driver properties
1015             * @throws MojoExecutionException
1016             */
1017            protected Properties getDriverProperties() throws MojoExecutionException {
1018                    Properties properties = new Properties();
1019    
1020                    if (!StringUtils.isEmpty(this.driverProperties)) {
1021                            String[] tokens = StringUtils.split(this.driverProperties, ",");
1022                            for (int i = 0; i < tokens.length; ++i) {
1023                                    String[] keyValueTokens = StringUtils.split(tokens[i].trim(), "=");
1024                                    if (keyValueTokens.length != 2) {
1025                                            throw new MojoExecutionException("Invalid JDBC Driver properties: " + this.driverProperties);
1026                                    }
1027    
1028                                    properties.setProperty(keyValueTokens[0], keyValueTokens[1]);
1029    
1030                            }
1031                    }
1032    
1033                    return properties;
1034            }
1035    
1036            /**
1037             * read in lines and execute them
1038             *
1039             * @param reader
1040             *            the reader
1041             * @param out
1042             *            the outputstream
1043             * @throws SQLException
1044             * @throws IOException
1045             */
1046            private void runStatements(Reader reader, PrintStream out) throws SQLException, IOException {
1047                    String line;
1048    
1049                    if (enableBlockMode) {
1050                            // no need to parse the content, ship it directly to jdbc in one sql
1051                            // statement
1052                            line = IOUtils.toString(reader);
1053                            execSQL(line, out);
1054                            return;
1055                    }
1056    
1057                    StringBuffer sql = new StringBuffer();
1058    
1059                    BufferedReader in = new BufferedReader(reader);
1060    
1061                    while ((line = in.readLine()) != null) {
1062                            getLog().debug("line='" + line + "'");
1063                            if (!keepFormat) {
1064                                    line = line.trim();
1065                            }
1066    
1067                            if (!keepFormat) {
1068                                    if (line.startsWith("//")) {
1069                                            continue;
1070                                    }
1071                                    if (line.startsWith("--")) {
1072                                            continue;
1073                                    }
1074                                    StringTokenizer st = new StringTokenizer(line);
1075                                    if (st.hasMoreTokens()) {
1076                                            String token = st.nextToken();
1077                                            if ("REM".equalsIgnoreCase(token)) {
1078                                                    continue;
1079                                            }
1080                                    }
1081                            }
1082    
1083                            if (!keepFormat) {
1084                                    sql.append(" ").append(line);
1085                            } else {
1086                                    sql.append("\n").append(line);
1087                            }
1088    
1089                            // SQL defines "--" as a comment to EOL
1090                            // but in Oracle it may contain a hint
1091                            // so we cannot just remove it, instead we must end it
1092                            if (!keepFormat) {
1093                                    if (SqlSplitter.containsSqlEnd(line, delimiter) == SqlSplitter.NO_END) {
1094                                            sql.append("\n");
1095                                    }
1096                            }
1097    
1098                            if (isEnd(line)) {
1099                                    execSQL(sql.substring(0, sql.length() - delimiter.length()), out);
1100                                    sql.setLength(0); // clean buffer
1101                            }
1102                    }
1103    
1104                    // Catch any statements not followed by ;
1105                    if (executeTrailingSQL && !sql.toString().equals("")) {
1106                            execSQL(sql.toString(), out);
1107                    }
1108            }
1109    
1110            protected boolean isEnd(String line) {
1111                    if (delimiterType.equals(DelimiterType.ROW)) {
1112                            return line.trim().equals(delimiter);
1113                    }
1114                    int pos = SqlSplitter.containsSqlEnd(line, delimiter);
1115                    if (delimiterType.equals(DelimiterType.NORMAL) && pos > 0) {
1116                            return true;
1117                    }
1118                    return false;
1119            }
1120    
1121            /**
1122             * Exec the sql statement.
1123             *
1124             * @param sql
1125             *            query to execute
1126             * @param out
1127             *            the outputstream
1128             */
1129            private void execSQL(String sql, PrintStream out) throws SQLException {
1130                    // Check and ignore empty statements
1131                    if ("".equals(sql.trim())) {
1132                            return;
1133                    }
1134    
1135                    ResultSet resultSet = null;
1136                    try {
1137                            totalStatements++;
1138                            if (showSql) {
1139                                    getLog().info("SQL-" + totalStatements + ": " + sql);
1140                            } else {
1141                                    getLog().debug("SQL-" + totalStatements + ": " + sql);
1142                            }
1143    
1144                            boolean ret;
1145                            int updateCountTotal = 0;
1146    
1147                            ret = statement.execute(sql);
1148                            do {
1149                                    if (!ret) {
1150                                            int updateCount = statement.getUpdateCount();
1151                                            if (updateCount != -1) {
1152                                                    updateCountTotal += updateCount;
1153                                            }
1154                                    } else {
1155                                            resultSet = statement.getResultSet();
1156                                            if (printResultSet) {
1157                                                    printResultSet(resultSet, out);
1158                                            }
1159                                    }
1160                                    ret = statement.getMoreResults();
1161                            } while (ret);
1162    
1163                            getLog().debug(updateCountTotal + " rows affected");
1164    
1165                            if (printResultSet) {
1166                                    StringBuffer line = new StringBuffer();
1167                                    line.append(updateCountTotal).append(" rows affected");
1168                                    out.println(line);
1169                            }
1170    
1171                            SQLWarning warning = conn.getWarnings();
1172                            while (warning != null) {
1173                                    getLog().debug(warning + " sql warning");
1174                                    warning = warning.getNextWarning();
1175                            }
1176                            conn.clearWarnings();
1177                            successfulStatements++;
1178                    } catch (SQLException e) {
1179                            getLog().error("Failed to execute: " + sql);
1180                            if (ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) {
1181                                    throw e;
1182                            }
1183                            getLog().error(e.toString());
1184                    } finally {
1185                            if (resultSet != null) {
1186                                    resultSet.close();
1187                            }
1188                    }
1189            }
1190    
1191            /**
1192             * print any results in the result set.
1193             *
1194             * @param rs
1195             *            the resultset to print information about
1196             * @param out
1197             *            the place to print results
1198             * @throws SQLException
1199             *             on SQL problems.
1200             */
1201            private void printResultSet(ResultSet rs, PrintStream out) throws SQLException {
1202                    if (rs != null) {
1203                            getLog().debug("Processing new result set.");
1204                            ResultSetMetaData md = rs.getMetaData();
1205                            int columnCount = md.getColumnCount();
1206                            StringBuffer line = new StringBuffer();
1207                            if (showheaders) {
1208                                    boolean first = true;
1209                                    for (int col = 1; col <= columnCount; col++) {
1210                                            String columnValue = md.getColumnName(col);
1211    
1212                                            if (columnValue != null) {
1213                                                    columnValue = columnValue.trim();
1214    
1215                                                    if (",".equals(outputDelimiter)) {
1216                                                            columnValue = StringEscapeUtils.escapeCsv(columnValue);
1217                                                    }
1218                                            }
1219    
1220                                            if (first) {
1221                                                    first = false;
1222                                            } else {
1223                                                    line.append(outputDelimiter);
1224                                            }
1225                                            line.append(columnValue);
1226                                    }
1227                                    out.println(line);
1228                                    line = new StringBuffer();
1229                            }
1230                            while (rs.next()) {
1231                                    boolean first = true;
1232                                    for (int col = 1; col <= columnCount; col++) {
1233                                            String columnValue = rs.getString(col);
1234                                            if (columnValue != null) {
1235                                                    columnValue = columnValue.trim();
1236    
1237                                                    if (",".equals(outputDelimiter)) {
1238                                                            columnValue = StringEscapeUtils.escapeCsv(columnValue);
1239                                                    }
1240                                            }
1241    
1242                                            if (first) {
1243                                                    first = false;
1244                                            } else {
1245                                                    line.append(outputDelimiter);
1246                                            }
1247                                            line.append(columnValue);
1248                                    }
1249                                    out.println(line);
1250                                    line = new StringBuffer();
1251                            }
1252                    }
1253                    out.println();
1254            }
1255    
1256            /**
1257             * Contains the definition of a new transaction element. Transactions allow several files or blocks of statements to be executed using
1258             * the same JDBC connection and commit operation in between.
1259             */
1260            protected class Transaction implements Comparable<Transaction> {
1261    
1262                    protected SqlResource resource = null;
1263    
1264                    protected File tSrcFile = null;
1265    
1266                    protected String tSqlCommand = "";
1267    
1268                    /**
1269            *
1270            */
1271                    public void setResource(SqlResource resource) {
1272                            this.resource = resource;
1273                    }
1274    
1275                    /**
1276             *
1277             */
1278                    public void setSrc(File src) {
1279                            this.tSrcFile = src;
1280                    }
1281    
1282                    /**
1283             *
1284             */
1285                    public void addText(String sql) {
1286                            this.tSqlCommand += sql;
1287                    }
1288    
1289                    /**
1290             *
1291             */
1292                    private void runTransaction(PrintStream out) throws IOException, SQLException {
1293                            if (tSqlCommand.length() != 0) {
1294                                    getLog().info("Executing commands");
1295    
1296                                    runStatements(new StringReader(tSqlCommand), out);
1297                            }
1298    
1299                            if (tSrcFile != null) {
1300                                    getLog().info("Executing file - " + tSrcFile.getAbsolutePath());
1301    
1302                                    Reader reader = null;
1303    
1304                                    if (StringUtils.isEmpty(encoding)) {
1305                                            reader = new FileReader(tSrcFile);
1306                                    } else {
1307                                            reader = new InputStreamReader(new FileInputStream(tSrcFile), encoding);
1308                                    }
1309    
1310                                    try {
1311                                            runStatements(reader, out);
1312                                    } finally {
1313                                            IOUtils.closeQuietly(reader);
1314                                    }
1315                            }
1316    
1317                            if (resource != null) {
1318                                    getLog().info("Executing - [" + resource.getLocation() + "]");
1319                                    getLog().debug("Location - [" + getURL(resource.getResource()) + "]");
1320    
1321                                    Reader reader = null;
1322    
1323                                    if (StringUtils.isEmpty(encoding)) {
1324                                            reader = new InputStreamReader(resource.getResource().getInputStream());
1325                                    } else {
1326                                            reader = new InputStreamReader(resource.getResource().getInputStream(), encoding);
1327                                    }
1328    
1329                                    try {
1330                                            runStatements(reader, out);
1331                                    } finally {
1332                                            IOUtils.closeQuietly(reader);
1333                                    }
1334                            }
1335                    }
1336    
1337                    @Override
1338                    public int compareTo(Transaction transaction) {
1339    
1340                            if (transaction.tSrcFile == null) {
1341                                    if (this.tSrcFile == null) {
1342                                            return 0;
1343                                    } else {
1344                                            return Integer.MAX_VALUE;
1345                                    }
1346                            } else {
1347                                    if (this.tSrcFile == null) {
1348                                            return Integer.MIN_VALUE;
1349                                    } else {
1350                                            return this.tSrcFile.compareTo(transaction.tSrcFile);
1351                                    }
1352                            }
1353                    }
1354            }
1355    
1356            //
1357            // helper accessors for unit test purposes
1358            //
1359    
1360            public String getUsername() {
1361                    return this.username;
1362            }
1363    
1364            public void setUsername(String username) {
1365                    this.username = username;
1366            }
1367    
1368            public String getPassword() {
1369                    return this.password;
1370            }
1371    
1372            public void setPassword(String password) {
1373                    this.password = password;
1374            }
1375    
1376            public String getUrl() {
1377                    return this.url;
1378            }
1379    
1380            public void setUrl(String url) {
1381                    this.url = url;
1382            }
1383    
1384            public String getDriver() {
1385                    return this.driver;
1386            }
1387    
1388            public void setDriver(String driver) {
1389                    this.driver = driver;
1390            }
1391    
1392            void setAutocommit(boolean autocommit) {
1393                    this.autocommit = autocommit;
1394            }
1395    
1396            void setFileset(Fileset fileset) {
1397                    this.fileset = fileset;
1398            }
1399    
1400            public File[] getSrcFiles() {
1401                    return this.srcFiles;
1402            }
1403    
1404            public void setSrcFiles(File[] files) {
1405                    this.srcFiles = files;
1406            }
1407    
1408            /**
1409             * @deprecated use {@link #getSuccessfulStatements()}
1410             */
1411            @Deprecated
1412            int getGoodSqls() {
1413                    return this.getSuccessfulStatements();
1414            }
1415    
1416            /**
1417             * Number of SQL statements executed so far that caused errors.
1418             *
1419             * @return the number
1420             */
1421            public int getSuccessfulStatements() {
1422                    return successfulStatements;
1423            }
1424    
1425            /**
1426             * Number of SQL statements executed so far, including the ones that caused errors.
1427             *
1428             * @return the number
1429             */
1430            public int getTotalStatements() {
1431                    return totalStatements;
1432            }
1433    
1434            public String getOnError() {
1435                    return this.onError;
1436            }
1437    
1438            public void setOnError(String action) {
1439                    if (ON_ERROR_ABORT.equalsIgnoreCase(action)) {
1440                            this.onError = ON_ERROR_ABORT;
1441                    } else if (ON_ERROR_CONTINUE.equalsIgnoreCase(action)) {
1442                            this.onError = ON_ERROR_CONTINUE;
1443                    } else if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(action)) {
1444                            this.onError = ON_ERROR_ABORT_AFTER;
1445                    } else {
1446                            throw new IllegalArgumentException(action + " is not a valid value for onError, only '" + ON_ERROR_ABORT + "', '" + ON_ERROR_ABORT_AFTER + "', or '"
1447                                    + ON_ERROR_CONTINUE + "'.");
1448                    }
1449            }
1450    
1451            void setSettings(Settings settings) {
1452                    this.settings = settings;
1453            }
1454    
1455            void setSettingsKey(String key) {
1456                    this.settingsKey = key;
1457            }
1458    
1459            void setSkip(boolean skip) {
1460                    this.skip = skip;
1461            }
1462    
1463            public void setDriverProperties(String driverProperties) {
1464                    this.driverProperties = driverProperties;
1465            }
1466    
1467            public boolean isEnableBlockMode() {
1468                    return enableBlockMode;
1469            }
1470    
1471            public void setEnableBlockMode(boolean enableBlockMode) {
1472                    this.enableBlockMode = enableBlockMode;
1473            }
1474    
1475            public String getSqlCommand() {
1476                    return sqlCommand;
1477            }
1478    
1479            public void setSqlCommand(String sqlCommand) {
1480                    this.sqlCommand = sqlCommand;
1481            }
1482    
1483            public Vector<Transaction> getTransactions() {
1484                    return transactions;
1485            }
1486    
1487            public void setTransactions(Vector<Transaction> transactions) {
1488                    this.transactions = transactions;
1489            }
1490    
1491            public void setFileFilter(MavenFileFilter filter) {
1492                    this.fileFilter = filter;
1493            }
1494    
1495            public String[] getResourceLocations() {
1496                    return resourceLocations;
1497            }
1498    
1499            public void setResourceLocations(String[] resourceLocations) {
1500                    this.resourceLocations = resourceLocations;
1501            }
1502    
1503            public boolean isExecuteTrailingSQL() {
1504                    return executeTrailingSQL;
1505            }
1506    
1507            public void setExecuteTrailingSQL(boolean executeTrailingSQL) {
1508                    this.executeTrailingSQL = executeTrailingSQL;
1509            }
1510    
1511            public Order getOrderFile() {
1512                    return orderFile;
1513            }
1514    
1515            public void setOrderFile(String orderFile) {
1516                    this.orderFile = Order.valueOf(orderFile.toUpperCase());
1517            }
1518    
1519            public String getResourceListingLocation() {
1520                    return resourceListingLocation;
1521            }
1522    
1523            public void setResourceListingLocation(String resourceListingLocation) {
1524                    this.resourceListingLocation = resourceListingLocation;
1525            }
1526    }