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