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
28
29 public class SQLExecutor {
30 private static final Log log = LogFactory.getLog(SQLExecutor.class);
31
32
33
34
35 public static final String ON_ERROR_ABORT = "abort";
36
37
38
39
40
41 public static final String ON_ERROR_ABORT_AFTER = "abortAfter";
42
43
44
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
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
213
214
215
216
217
218
219 protected void execSQL(String sql, PrintStream out) throws SQLException {
220
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
279
280
281
282
283
284
285
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
322
323
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);
333 }
334 }
335
336
337 if (!sql.toString().equals("")) {
338 execSQL(sql.toString(), out);
339 }
340 }
341
342
343
344
345
346
347
348
349
350
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 }