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 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 	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 &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 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 }