View Javadoc

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