Java tutorial
/* * Copyright 2004-2011 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.dbflute.helper.jdbc.sqlfile; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tools.ant.BuildException; import org.seasar.dbflute.DfBuildProperties; import org.seasar.dbflute.exception.SQLFailureException; import org.seasar.dbflute.exception.factory.ExceptionMessageBuilder; import org.seasar.dbflute.helper.jdbc.DfRunnerInformation; import org.seasar.dbflute.util.Srl; /** * @author jflute */ public abstract class DfSqlFileRunnerBase implements DfSqlFileRunner { // =================================================================================== // Definition // ========== /** Log instance. */ private static Log _log = LogFactory.getLog(DfSqlFileRunnerBase.class); // =================================================================================== // Attribute // ========= protected final DfRunnerInformation _runInfo; protected DataSource _dataSource; // may be switched (e.g. LazyConnection) protected File _sqlFile; protected DfSqlFileRunnerResult _result; protected int _goodSqlCount = 0; protected int _totalSqlCount = 0; protected int _skippedSqlCount = 0; // for sub-class process use protected Connection _currentConnection; protected Statement _currentStatement; // =================================================================================== // Constructor // =========== public DfSqlFileRunnerBase(DfRunnerInformation runInfo, DataSource dataSource) { _runInfo = runInfo; _dataSource = dataSource; } public void prepare(File sqlFile) { _sqlFile = sqlFile; _result = new DfSqlFileRunnerResult(sqlFile); } // =================================================================================== // Run Transaction // =============== public DfSqlFileRunnerResult runTransaction() { _goodSqlCount = 0; _totalSqlCount = 0; _skippedSqlCount = 0; if (_sqlFile == null) { String msg = "The attribute '_srcFile' should not be null."; throw new IllegalStateException(msg); } String currentSql = null; Reader reader = null; try { reader = newInputStreamReader(); final List<String> sqlList = extractSqlList(reader); setupConnection(); setupStatement(); for (String sql : sqlList) { currentSql = sql; if (!isTargetFile(sql)) { break; } if (!isTargetSql(sql)) { continue; } _totalSqlCount++; final String realSql = filterSql(sql); if (!_runInfo.isSuppressLoggingSql()) { traceSql(realSql); } execSQL(realSql); } rollbackOrCommit(); } catch (SQLFailureException breakCause) { if (_runInfo.isBreakCauseThrow()) { throw breakCause; } else { _result.setGoodSqlCount(_goodSqlCount); _result.setTotalSqlCount(-1); _result.setBreakCause(breakCause); return _result; } } catch (SQLException e) { // here is for the exception except executing SQL // so it always does not continue throwSQLFailureException(currentSql, e); } finally { try { rollback(); } catch (SQLException ignored) { } closeStatement(); closeConnection(); closeReader(reader); } // re-calculate total count with skipped count _totalSqlCount = _totalSqlCount - _skippedSqlCount; traceResult(_goodSqlCount, _totalSqlCount); _result.setGoodSqlCount(_goodSqlCount); _result.setTotalSqlCount(_totalSqlCount); return _result; } protected boolean isTargetFile(String sql) { return true; } protected boolean isTargetSql(String sql) { return true; } protected void traceSql(String sql) { if (sql.contains(ln())) { sql = ln() + sql; } _log.info(sql); } protected void traceResult(int goodSqlCount, int totalSqlCount) { _log.info(" -> success=" + goodSqlCount + " failure=" + (totalSqlCount - goodSqlCount)); } protected String filterSql(String sql) { // for override return sql; } protected InputStreamReader newInputStreamReader() { try { final String encoding = _runInfo.isEncodingNull() ? "UTF-8" : _runInfo.getEncoding(); return new InputStreamReader(new FileInputStream(_sqlFile), encoding); } catch (FileNotFoundException e) { throw new BuildException("The file does not exist: " + _sqlFile, e); } catch (UnsupportedEncodingException e) { throw new BuildException("The encoding is unsupported: " + _runInfo.getEncoding(), e); } } protected void setupConnection() { if (_dataSource == null) { return; } try { _currentConnection = _dataSource.getConnection(); final boolean autoCommit = _currentConnection.getAutoCommit(); if (autoCommit != _runInfo.isAutoCommit()) { // if different _currentConnection.setAutoCommit(_runInfo.isAutoCommit()); } } catch (SQLException e) { String msg = "DataSource#getConnection() threw the exception:"; msg = msg + " dataSource=" + _dataSource; throw new SQLFailureException(msg, e); } } protected void checkConnection() { if (_currentConnection == null) { String msg = "The connection should not be null at this timing!"; throw new IllegalStateException(msg); } } protected void closeConnection() { try { if (_currentConnection != null) { _currentConnection.rollback(); } } catch (SQLException ignored) { } try { if (_currentConnection != null) { _currentConnection.close(); } } catch (SQLException ignored) { } finally { _currentConnection = null; } } protected Boolean getAutoCommit() { Boolean autoCommit = null; try { autoCommit = _currentConnection.getAutoCommit(); } catch (SQLException continued) { // because it is possible that the connection would have already closed _log.warn("Connection#getAutoCommit() said: " + continued.getMessage()); } return autoCommit; } protected void rollbackOrCommit() throws SQLException { if (_currentConnection == null) { return; } final Boolean autoCommit = getAutoCommit(); if (autoCommit == null || autoCommit) { return; } try { if (_runInfo.isRollbackOnly()) { _currentConnection.rollback(); } else { _currentConnection.commit(); } } catch (SQLException mayContinued) { if (_runInfo.isIgnoreTxError()) { // e.g. SQLite may throw an exception (actually said: Database is locked!) _log.warn("Connection#rollback()/commit() said: " + mayContinued.getMessage()); } else { throw mayContinued; } } } protected void rollback() throws SQLException { if (_currentConnection == null) { return; } final Boolean autoCommit = getAutoCommit(); if (autoCommit == null || autoCommit) { return; } try { _currentConnection.rollback(); } catch (SQLException mayContinued) { if (_runInfo.isIgnoreTxError()) { // e.g. SQLite may throw an exception (actually said: Database is locked!) _log.warn("Connection#rollback()/commit() said: " + mayContinued.getMessage()); } else { throw mayContinued; } } } protected void setupStatement() { if (_currentConnection == null) { return; } try { _currentStatement = _currentConnection.createStatement(); } catch (SQLException e) { String msg = "Connection#createStatement() threw the exception:"; msg = msg + " connection=" + _currentConnection; throw new SQLFailureException(msg, e); } } protected void checkStatement(String sql) { if (_currentStatement == null) { String msg = "The statement should not be null at this timing:"; msg = msg + " sql=" + sql; throw new IllegalStateException(msg); } } protected void closeStatement() { try { if (_currentStatement != null) { _currentStatement.close(); } } catch (SQLException ignored) { } finally { _currentStatement = null; } } protected void closeReader(Reader reader) { try { if (reader != null) { reader.close(); } } catch (IOException ignored) { } finally { reader = null; } } // =================================================================================== // Extract SQL // =========== protected List<String> extractSqlList(Reader reader) { final List<String> sqlList = new ArrayList<String>(); final BufferedReader br = new BufferedReader(reader); final DelimiterChanger delimiterChanger = newDelimterChanger(); try { String sql = ""; String line = ""; boolean inGroup = false; boolean alwaysNeedsLineSeparator = false; boolean isAlreadyProcessUTF8Bom = false; while ((line = br.readLine()) != null) { if (!isAlreadyProcessUTF8Bom) { line = removeUTF8BomIfNeeds(line); isAlreadyProcessUTF8Bom = true; } if (!inGroup && isSqlTrimAndRemoveLineSeparator()) { line = line.trim(); } if (!alwaysNeedsLineSeparator && isSqlTrimAndRemoveLineSeparator() && isHandlingCommentOnLineSeparator()) { if (isDbCommentLine(line)) { alwaysNeedsLineSeparator = true; } } // SQL defines "--" as a comment to EOL // and in Oracle it may contain a hint // so we cannot just remove it, instead we must end it if (line.trim().startsWith("--")) { // If this line is comment only, ... // = = = = = = = = = = = // Line for Line Comment // = = = = = = = = = = = // Group Specification // /- - - - - - - - - - - - - - - - if (line.trim().contains("#df:begin#")) { inGroup = true; if (!sql.contains("#df:checkEnv(")) { // patch for checkEnv sql = ""; } continue; } else if (line.trim().contains("#df:end#")) { inGroup = false; sql = removeTerminater4ToolIfNeeds(sql); // [DBFLUTE-309] addSqlToList(sqlList, sql); // End Point of SQL! alwaysNeedsLineSeparator = false; sql = ""; continue; } // - - - - - - - - - -/ // real line comment line = replaceCommentQuestionMarkIfNeeds(line); if (inGroup) { sql = sql + line + ln(); continue; } sql = sql + line + ln(); } else { // = = = = = = = = = = // Line for SQL Clause // = = = = = = = = = = if (inGroup) { sql = sql + line + ln(); continue; } final String lineConnect; if (isSqlTrimAndRemoveLineSeparator()) { if (alwaysNeedsLineSeparator) { lineConnect = ln(); } else { lineConnect = " "; } } else { lineConnect = ""; } if (line.indexOf("--") >= 0) { // If this line contains both SQL and comment, ... // With Line Comment line = replaceCommentQuestionMarkIfNeeds(line); sql = sql + lineConnect + line + ln(); } else { // SQL Clause Only final String lineTerminator = isSqlTrimAndRemoveLineSeparator() ? "" : ln(); sql = sql + lineConnect + line + lineTerminator; } } if (sql.trim().endsWith(_runInfo.getDelimiter())) { // = = = = = = = = // End of the SQL // = = = = = = = = sql = sql.trim(); sql = sql.substring(0, sql.length() - _runInfo.getDelimiter().length()); sql = sql.trim(); if ("".equals(sql)) { continue; } if (!delimiterChanger.isDelimiterChanger(sql)) { addSqlToList(sqlList, sql); // End Point of SQL! alwaysNeedsLineSeparator = false; sql = ""; } else { _runInfo.setDelimiter(delimiterChanger.getNewDelimiter(sql, _runInfo.getDelimiter())); // End Point of SQL! alwaysNeedsLineSeparator = false; sql = ""; } } } sql = sql.trim(); if (sql.length() > 0) { addSqlToList(sqlList, sql); // for Last SQL } } catch (IOException e) { String msg = "The method 'extractSqlList()' threw the IOException!"; throw new IllegalStateException(msg, e); } finally { if (br != null) { try { br.close(); } catch (IOException ignore) { ignore.printStackTrace(); } } } return sqlList; } protected void addSqlToList(List<String> sqlList, String sql) { if (isSqlLineCommentOnly(sql)) { return; } sqlList.add(removeCR(sql)); } protected boolean isSqlLineCommentOnly(String sql) { sql = sql.trim(); String[] lines = sql.split("\n"); for (String line : lines) { line = line.trim(); if (line.length() == 0) { continue; } if (line.startsWith("--")) { continue; } return false; } _log.info("The SQL has line comments only so skip it:" + ln() + sql); return true; } protected boolean isDbCommentLine(String line) { line = line.trim().toLowerCase(); // basic pattern if (line.startsWith("comment on ") && line.contains("is") && line.contains("'")) { return true; } return false; } protected String removeTerminater4ToolIfNeeds(String sql) { String terminater = getTerminater4Tool(); if (terminater == null || terminater.trim().length() == 0) { return sql; } sql = sql.trim(); if (sql.endsWith(terminater)) { String rear = sql.length() > 30 ? ": ..." + sql.substring(sql.length() - 30) : "."; _log.info("...Removing terminater '" + terminater + "' for tools" + rear); sql = sql.substring(0, sql.length() - terminater.length()); } return sql; } protected String getTerminater4Tool() {// for override. return null; } public DelimiterChanger newDelimterChanger() { final String databaseName = DfBuildProperties.getInstance().getBasicProperties().getTargetDatabase(); final String className = DelimiterChanger.class.getName() + "_" + databaseName; DelimiterChanger changer = null; try { changer = (DelimiterChanger) Class.forName(className).newInstance(); } catch (Exception ignore) { changer = new DelimiterChanger_null(); } return changer; } protected String removeUTF8BomIfNeeds(String str) { if (_runInfo.isEncodingNull()) { return str; } if ("UTF-8".equalsIgnoreCase(_runInfo.getEncoding()) && str.length() > 0 && str.charAt(0) == '\uFEFF') { String front = str.length() > 5 ? ": " + str.substring(0, 5) + "..." : "."; _log.info("...Removing UTF-8 bom" + front); str = str.substring(1); } return str; } protected String removeCR(String str) { return str.replaceAll("\r", ""); } protected String replaceCommentQuestionMarkIfNeeds(String line) { final int lineCommentIndex = line.indexOf("--"); if (lineCommentIndex < 0) { return line; } final String sqlClause; if (lineCommentIndex == 0) { sqlClause = ""; } else { sqlClause = line.substring(0, lineCommentIndex); } String lineComment = line.substring(lineCommentIndex); if (lineComment.indexOf("?") >= 0) { lineComment = Srl.replace(line, "?", "Q"); } return sqlClause + lineComment; } // =================================================================================== // Exception Handling // ================== protected void throwSQLFailureException(String sql, SQLException e) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice("Failed to execute the SQL!"); br.addItem("SQL File"); br.addElement(_sqlFile); br.addItem("Executed SQL"); br.addElement(sql); br.addItem("SQLState"); br.addElement(e.getSQLState()); br.addItem("ErrorCode"); br.addElement(e.getErrorCode()); br.addItem("SQLException"); br.addElement(e.getClass().getName()); br.addElement(extractMessage(e)); final SQLException nextEx = e.getNextException(); if (nextEx != null) { br.addItem("NextException"); br.addElement(nextEx.getClass().getName()); br.addElement(extractMessage(nextEx)); final SQLException nextNextEx = nextEx.getNextException(); if (nextNextEx != null) { br.addItem("NextNextException"); br.addElement(nextNextEx.getClass().getName()); br.addElement(extractMessage(nextNextEx)); } } final String msg = br.buildExceptionMessage(); throw new SQLFailureException(msg, e); } protected String extractMessage(SQLException e) { String message = e.getMessage(); // Because a message of Oracle contains a line separator. return message != null ? message.trim() : message; } // =================================================================================== // For Override // ============ /** * Execute the SQL statement. * @param sql SQL. (NotNull) */ protected abstract void execSQL(String sql); /** * @return The determination, true or false. */ protected boolean isSqlTrimAndRemoveLineSeparator() { return false; // as Default } /** * @return The determination, true or false. */ protected boolean isHandlingCommentOnLineSeparator() { return false; // as Default } // =================================================================================== // General Helper // ============== protected String ln() { // (DBFLUTE-264) return "\n"; } // =================================================================================== // Delimiter Changer // ================= protected static interface DelimiterChanger { public boolean isDelimiterChanger(String sql); public String getNewDelimiter(String sql, String preDelimiter); } protected static class DelimiterChanger_firebird implements DelimiterChanger { public static final String CHANGE_COMMAND = "set term "; public static final int CHANGE_COMMAND_LENGTH = CHANGE_COMMAND.length(); public boolean isDelimiterChanger(String sql) { sql = sql.trim(); if (sql.length() > CHANGE_COMMAND_LENGTH) { if (sql.substring(0, CHANGE_COMMAND_LENGTH).equalsIgnoreCase(CHANGE_COMMAND)) { return true; } } return false; } public String getNewDelimiter(String sql, String preDelimiter) { String tmp = sql.substring(CHANGE_COMMAND.length()); if (tmp.indexOf(" ") >= 0) { tmp = tmp.substring(0, tmp.indexOf(" ")); } return tmp; } } protected static class DelimiterChanger_mysql implements DelimiterChanger { public static final String CHANGE_COMMAND = "delimiter "; public static final int CHANGE_COMMAND_LENGTH = CHANGE_COMMAND.length(); public boolean isDelimiterChanger(String sql) { sql = sql.trim(); if (sql.length() > CHANGE_COMMAND_LENGTH) { if (sql.substring(0, CHANGE_COMMAND_LENGTH).equalsIgnoreCase(CHANGE_COMMAND)) { return true; } } return false; } public String getNewDelimiter(String sql, String preDelimiter) { String tmp = sql.substring(CHANGE_COMMAND.length()); if (tmp.indexOf(" ") >= 0) { tmp = tmp.substring(0, tmp.indexOf(" ")); } return tmp; } } protected static class DelimiterChanger_null implements DelimiterChanger { public boolean isDelimiterChanger(String sql) { return false; } public String getNewDelimiter(String sql, String preDelimiter) { return preDelimiter; } } // =================================================================================== // Accessor // ======== public DfSqlFileRunnerResult getResult() { return _result; } }