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