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