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