View Javadoc

1   package org.kuali.db;
2   
3   import java.io.BufferedReader;
4   import java.io.IOException;
5   import java.io.PrintStream;
6   import java.io.Reader;
7   import java.sql.Connection;
8   import java.sql.ResultSet;
9   import java.sql.ResultSetMetaData;
10  import java.sql.SQLException;
11  import java.sql.SQLWarning;
12  import java.sql.Statement;
13  import java.util.ArrayList;
14  import java.util.Enumeration;
15  import java.util.List;
16  import java.util.StringTokenizer;
17  import java.util.Vector;
18  
19  import org.apache.commons.lang.StringEscapeUtils;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import static org.apache.commons.io.IOUtils.*;
24  import static org.kuali.db.JDBCUtils.*;
25  
26  /**
27   * Executes SQL statements
28   */
29  public class SQLExecutor {
30  	private static final Log log = LogFactory.getLog(SQLExecutor.class);
31  
32  	/**
33  	 * Call {@link #setOnError(String)} with this value to abort SQL command execution if an error is found.
34  	 */
35  	public static final String ON_ERROR_ABORT = "abort";
36  
37  	/**
38  	 * Call {@link #setOnError(String)} with this value to continue SQL command execution until all commands have been
39  	 * attempted, then abort the build if an SQL error occurred in any of the commands.
40  	 */
41  	public static final String ON_ERROR_ABORT_AFTER = "abortAfter";
42  
43  	/**
44  	 * Call {@link #setOnError(String)} with this value to continue SQL command execution if an error is found.
45  	 */
46  	public static final String ON_ERROR_CONTINUE = "continue";
47  
48  	Vector<Transaction> transactions;
49  	boolean keepFormat = true;
50  	String delimiterType = "row";
51  	String delimiter = "/";
52  	boolean printResultSet = false;;
53  	String onError = ON_ERROR_ABORT;
54  	boolean showheaders = true;
55  	String outputDelimiter = ",";
56  	int totalStatements;
57  	int successfulStatements;
58  	Connection conn;
59  	Statement statement;
60  	boolean autocommit = false;
61  	boolean escapeProcessing = true;
62  	boolean skipOnConnectionError;
63  	boolean connectionError;
64  	boolean append = false;
65  	List<DatabaseListener> listeners = new ArrayList<DatabaseListener>();
66  
67  	public void addListener(DatabaseListener listener) {
68  		listeners.add(listener);
69  	}
70  
71  	protected void fireMessageLogged(String message) {
72  		fireMessageLogged(message, MessagePriority.INFO);
73  	}
74  
75  	protected void fireMessageLogged(String message, MessagePriority priority) {
76  		DatabaseEvent event = new DatabaseEvent(message, priority);
77  		for (DatabaseListener listener : listeners) {
78  			listener.messageLogged(event);
79  		}
80  	}
81  
82  	protected void fireBeginTransaction(Transaction transaction) {
83  		DatabaseEvent event = new DatabaseEvent();
84  		event.setTransaction(transaction);
85  		for (DatabaseListener listener : listeners) {
86  			listener.beginTransaction(event);
87  		}
88  	}
89  
90  	protected void fireFinishTransaction(Transaction transaction) {
91  		DatabaseEvent event = new DatabaseEvent();
92  		event.setTransaction(transaction);
93  		for (DatabaseListener listener : listeners) {
94  			listener.finishTransaction(event);
95  		}
96  	}
97  
98  	protected DatabaseEvent getExecuteSQLEvent(int totalStatements, String sql) {
99  		DatabaseEvent event = new DatabaseEvent();
100 		event.setSql(sql);
101 		event.setTotalStatements(totalStatements);
102 		return event;
103 	}
104 
105 	protected void fireBeforeExecuteSQL(int totalStatements, String sql) {
106 		for (DatabaseListener listener : listeners) {
107 			listener.beforeExecuteSQL(getExecuteSQLEvent(totalStatements, sql));
108 		}
109 	}
110 
111 	protected void fireAfterExecuteSQL(int totalStatements, String sql) {
112 		for (DatabaseListener listener : listeners) {
113 			listener.afterExecuteSQL(getExecuteSQLEvent(totalStatements, sql));
114 		}
115 	}
116 
117 	protected void fireAfterProcessingSQLResults(int totalStatements, int successfulStatements, int updateCountTotal, String sql) {
118 		DatabaseEvent event = getExecuteSQLEvent(totalStatements, sql);
119 		event.setSuccessfulStatements(successfulStatements);
120 		event.setUpdateCountTotal(updateCountTotal);
121 		for (DatabaseListener listener : listeners) {
122 			listener.afterExecuteSQL(event);
123 		}
124 	}
125 
126 	public void info(String message) {
127 		log.info(message);
128 		fireMessageLogged(message);
129 	}
130 
131 	public void debug(String message) {
132 		log.debug(message);
133 		fireMessageLogged(message, MessagePriority.DEBUG);
134 	}
135 
136 	public void error(Throwable throwable, String message) {
137 		if (throwable == null) {
138 			log.error(message);
139 		} else {
140 			log.error(message, throwable);
141 		}
142 		DatabaseEvent event = new DatabaseEvent(message, MessagePriority.ERROR);
143 		event.setException(throwable);
144 		for (DatabaseListener listener : listeners) {
145 			listener.messageLogged(event);
146 		}
147 	}
148 
149 	public void error(String message) {
150 		error(null, message);
151 	}
152 
153 	public void executeSql(String sql) throws SQLException {
154 		Transaction transaction = new Transaction();
155 		transaction.setSqlCommand(sql);
156 		executeTransaction(transaction);
157 	}
158 
159 	public void executeTransaction(Transaction transaction) throws SQLException {
160 		this.transactions = new Vector<Transaction>();
161 		transactions.add(transaction);
162 		execute();
163 	}
164 
165 	public void execute() throws SQLException {
166 		try {
167 			statement = conn.createStatement();
168 			statement.setEscapeProcessing(escapeProcessing);
169 
170 			// Process all transactions
171 			for (Enumeration<Transaction> e = transactions.elements(); e.hasMoreElements();) {
172 				Transaction t = (Transaction) e.nextElement();
173 
174 				fireBeginTransaction(t);
175 				runTransaction(t, System.out);
176 				fireFinishTransaction(t);
177 
178 				if (!autocommit) {
179 					debug("Committing transaction");
180 					conn.commit();
181 				}
182 			}
183 		} catch (IOException e) {
184 			throw new SQLException(e);
185 		} catch (SQLException e) {
186 			if (!autocommit && ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) {
187 				rollbackQuietly(conn);
188 			}
189 			throw e;
190 		} finally {
191 			closeQuietly(statement, conn);
192 		}
193 
194 		log.info(getSuccessfulStatements() + " of " + getTotalStatements() + " SQL statements executed successfully");
195 
196 		if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(getOnError()) && totalStatements != successfulStatements) {
197 			throw new SQLException("Some SQL statements failed to execute");
198 		}
199 	}
200 
201 	protected void runTransaction(Transaction t, PrintStream out) throws IOException, SQLException {
202 		Reader in = null;
203 		try {
204 			in = t.getReader();
205 			runStatements(in, out);
206 		} finally {
207 			closeQuietly(in);
208 		}
209 	}
210 
211 	/**
212 	 * Exec the sql statement.
213 	 * 
214 	 * @param sql
215 	 *            query to execute
216 	 * @param out
217 	 *            the outputstream
218 	 */
219 	protected void execSQL(String sql, PrintStream out) throws SQLException {
220 		// Check and ignore empty statements
221 		if ("".equals(sql.trim())) {
222 			return;
223 		}
224 
225 		ResultSet resultSet = null;
226 		try {
227 			totalStatements++;
228 			debug("SQL: " + sql);
229 
230 			boolean ret;
231 			int updateCountTotal = 0;
232 
233 			fireBeforeExecuteSQL(totalStatements, sql);
234 			ret = statement.execute(sql);
235 			fireAfterExecuteSQL(totalStatements, sql);
236 			do {
237 				if (!ret) {
238 					int updateCount = statement.getUpdateCount();
239 					if (updateCount != -1) {
240 						updateCountTotal += updateCount;
241 					}
242 				} else {
243 					resultSet = statement.getResultSet();
244 					if (printResultSet) {
245 						printResultSet(resultSet, out);
246 					}
247 				}
248 				ret = statement.getMoreResults();
249 			} while (ret);
250 
251 			debug(updateCountTotal + " rows affected");
252 
253 			if (printResultSet) {
254 				StringBuffer line = new StringBuffer();
255 				line.append(updateCountTotal).append(" rows affected");
256 				out.println(line);
257 			}
258 
259 			SQLWarning warning = conn.getWarnings();
260 			while (warning != null) {
261 				debug(warning + " sql warning");
262 				warning = warning.getNextWarning();
263 			}
264 			conn.clearWarnings();
265 			successfulStatements++;
266 			fireAfterProcessingSQLResults(totalStatements, successfulStatements, updateCountTotal, sql);
267 		} catch (SQLException e) {
268 			error("Failed to execute: " + sql + "\n\n" + e.getMessage());
269 			if (ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) {
270 				throw e;
271 			}
272 		} finally {
273 			closeQuietly(resultSet);
274 		}
275 	}
276 
277 	/**
278 	 * read in lines and execute them
279 	 * 
280 	 * @param reader
281 	 *            the reader
282 	 * @param out
283 	 *            the outputstream
284 	 * @throws SQLException
285 	 * @throws IOException
286 	 */
287 	protected void runStatements(Reader reader, PrintStream out) throws SQLException, IOException {
288 		String line;
289 
290 		StringBuffer sql = new StringBuffer();
291 
292 		BufferedReader in = new BufferedReader(reader);
293 
294 		while ((line = in.readLine()) != null) {
295 			if (!keepFormat) {
296 				line = line.trim();
297 			}
298 
299 			if (!keepFormat) {
300 				if (line.startsWith("//")) {
301 					continue;
302 				}
303 				if (line.startsWith("--")) {
304 					continue;
305 				}
306 				StringTokenizer st = new StringTokenizer(line);
307 				if (st.hasMoreTokens()) {
308 					String token = st.nextToken();
309 					if ("REM".equalsIgnoreCase(token)) {
310 						continue;
311 					}
312 				}
313 			}
314 
315 			if (!keepFormat) {
316 				sql.append(" ").append(line);
317 			} else {
318 				sql.append("\n").append(line);
319 			}
320 
321 			// SQL defines "--" as a comment to EOL
322 			// and in Oracle it may contain a hint
323 			// so we cannot just remove it, instead we must end it
324 			if (!keepFormat) {
325 				if (SqlSplitter.containsSqlEnd(line, delimiter) == SqlSplitter.NO_END) {
326 					sql.append("\n");
327 				}
328 			}
329 
330 			if ((delimiterType.equals(DelimiterType.NORMAL) && SqlSplitter.containsSqlEnd(line, delimiter) > 0) || (delimiterType.equals(DelimiterType.ROW) && line.trim().equals(delimiter))) {
331 				execSQL(sql.substring(0, sql.length() - delimiter.length()), out);
332 				sql.setLength(0); // clean buffer
333 			}
334 		}
335 
336 		// Catch any statements not followed by ;
337 		if (!sql.toString().equals("")) {
338 			execSQL(sql.toString(), out);
339 		}
340 	}
341 
342 	/**
343 	 * print any results in the result set.
344 	 * 
345 	 * @param rs
346 	 *            the resultset to print information about
347 	 * @param out
348 	 *            the place to print results
349 	 * @throws SQLException
350 	 *             on SQL problems.
351 	 */
352 	protected void printResultSet(ResultSet rs, PrintStream out) throws SQLException {
353 		if (rs == null) {
354 			return;
355 		}
356 		debug("Processing new result set.");
357 		ResultSetMetaData md = rs.getMetaData();
358 		int columnCount = md.getColumnCount();
359 		StringBuffer line = new StringBuffer();
360 		if (showheaders) {
361 			boolean first = true;
362 			for (int col = 1; col <= columnCount; col++) {
363 				String columnValue = md.getColumnName(col);
364 
365 				if (columnValue != null) {
366 					columnValue = columnValue.trim();
367 
368 					if (",".equals(outputDelimiter)) {
369 						columnValue = StringEscapeUtils.escapeCsv(columnValue);
370 					}
371 				}
372 
373 				if (first) {
374 					first = false;
375 				} else {
376 					line.append(outputDelimiter);
377 				}
378 				line.append(columnValue);
379 			}
380 			out.println(line);
381 			line = new StringBuffer();
382 		}
383 		while (rs.next()) {
384 			boolean first = true;
385 			for (int col = 1; col <= columnCount; col++) {
386 				String columnValue = rs.getString(col);
387 				if (columnValue != null) {
388 					columnValue = columnValue.trim();
389 
390 					if (",".equals(outputDelimiter)) {
391 						columnValue = StringEscapeUtils.escapeCsv(columnValue);
392 					}
393 				}
394 
395 				if (first) {
396 					first = false;
397 				} else {
398 					line.append(outputDelimiter);
399 				}
400 				line.append(columnValue);
401 			}
402 			out.println(line);
403 			line = new StringBuffer();
404 		}
405 		out.println();
406 	}
407 
408 	public boolean isKeepFormat() {
409 		return keepFormat;
410 	}
411 
412 	public void setKeepFormat(boolean keepFormat) {
413 		this.keepFormat = keepFormat;
414 	}
415 
416 	public String getDelimiterType() {
417 		return delimiterType;
418 	}
419 
420 	public void setDelimiterType(String delimiterType) {
421 		this.delimiterType = delimiterType;
422 	}
423 
424 	public String getDelimiter() {
425 		return delimiter;
426 	}
427 
428 	public void setDelimiter(String delimiter) {
429 		this.delimiter = delimiter;
430 	}
431 
432 	public boolean isPrintResultSet() {
433 		return printResultSet;
434 	}
435 
436 	public void setPrintResultSet(boolean printResultSet) {
437 		this.printResultSet = printResultSet;
438 	}
439 
440 	public String getOnError() {
441 		return onError;
442 	}
443 
444 	public void setOnError(String onError) {
445 		this.onError = onError;
446 	}
447 
448 	public boolean isShowheaders() {
449 		return showheaders;
450 	}
451 
452 	public void setShowheaders(boolean showheaders) {
453 		this.showheaders = showheaders;
454 	}
455 
456 	public String getOutputDelimiter() {
457 		return outputDelimiter;
458 	}
459 
460 	public void setOutputDelimiter(String outputDelimiter) {
461 		this.outputDelimiter = outputDelimiter;
462 	}
463 
464 	public int getTotalStatements() {
465 		return totalStatements;
466 	}
467 
468 	public void setTotalStatements(int totalStatements) {
469 		this.totalStatements = totalStatements;
470 	}
471 
472 	public int getSuccessfulStatements() {
473 		return successfulStatements;
474 	}
475 
476 	public void setSuccessfulStatements(int successfulStatements) {
477 		this.successfulStatements = successfulStatements;
478 	}
479 
480 	public Connection getConn() {
481 		return conn;
482 	}
483 
484 	public void setConn(Connection conn) {
485 		this.conn = conn;
486 	}
487 
488 	public Statement getStatement() {
489 		return statement;
490 	}
491 
492 	public void setStatement(Statement statement) {
493 		this.statement = statement;
494 	}
495 
496 	public boolean isAutocommit() {
497 		return autocommit;
498 	}
499 
500 	public void setAutocommit(boolean autocommit) {
501 		this.autocommit = autocommit;
502 	}
503 
504 	public boolean isEscapeProcessing() {
505 		return escapeProcessing;
506 	}
507 
508 	public void setEscapeProcessing(boolean escapeProcessing) {
509 		this.escapeProcessing = escapeProcessing;
510 	}
511 
512 	public boolean isAppend() {
513 		return append;
514 	}
515 
516 	public void setAppend(boolean append) {
517 		this.append = append;
518 	}
519 
520 	public boolean isSkipOnConnectionError() {
521 		return skipOnConnectionError;
522 	}
523 
524 	public void setSkipOnConnectionError(boolean skipOnConnectionError) {
525 		this.skipOnConnectionError = skipOnConnectionError;
526 	}
527 
528 	public boolean isConnectionError() {
529 		return connectionError;
530 	}
531 
532 	public void setConnectionError(boolean connectionError) {
533 		this.connectionError = connectionError;
534 	}
535 
536 	public List<DatabaseListener> getListeners() {
537 		return listeners;
538 	}
539 
540 	public void setListeners(List<DatabaseListener> listeners) {
541 		this.listeners = listeners;
542 	}
543 
544 	public Vector<Transaction> getTransactions() {
545 		return transactions;
546 	}
547 
548 	public void setTransactions(Vector<Transaction> transactions) {
549 		this.transactions = transactions;
550 	}
551 }