/* * Copyright 1999,2004 The Apache Software Foundation. * * 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.apache.log4j.jdbcplus; import java.rmi.server.UID; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import org.apache.log4j.Layout; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; /** * This class encapsulate the logic which is necessary to log into a table. Used * by JDBCAppender * * @author * <a href="mailto:t.fenner@klopotek.de">Thomas Fenner</a>, * <a href="http://www.mannhaupt.com/danko/contact/">Danko Mannhaupt</a>, * many contributions by the community * @since 1.0 * @version see jdbcappender.jar/META-INF/MANIFEST.MF for version information */ public class JDBCLogger { //All columns of the log-table private ArrayList logcols = null; //Only columns which will be provided by logging private String column_list = null; //Number of all columns private int num = 0; //Status for successful execution of method configure() private boolean isconfigured = false; //Status for ready to do logging with method append() private boolean ready = false; //This message will be filled with a error-string when method ready() // failes, and can be got by calling getMsg() private String errormsg = ""; //Log-message incrementor for columns with JDBCLogType INC private long inc = 0; /** * Defines whether updated messages should be committed to the database */ private boolean commit = true; /** * whether to replace single quotes (') by 2 single quotes ('') in sql * parameters */ private boolean quoteReplace = true; /** * character to separate parts of layout if more than one part is to be used */ protected String layoutPartsDelimiter = "#"; /** * Defines whether to use Prepared Statements instead of updateable result * sets (default false) */ protected boolean usePreparedStatements = false; /** * Defines the maximum number of characters in a throwable stack trace. -1 if there is no limit. */ private int throwableMaxChars = -1; private JDBCSqlHandler sqlHandler = null; private JDBCPoolConnectionHandler poolConnectionHandler = null; private Connection con = null; private Statement stmt = null; private ResultSet rs = null; private String table = null; private String procedure = null; //Variables for static SQL-statement logging private String sql = null; // cached prepared statement private String preparedSql = null; private final static String MSG_WILDCARD = "@" + JDBCLogType.MSG_VAR + "@"; private final static String PRIO_WILDCARD = "@" + JDBCLogType.PRIO_VAR + "@"; private final static String CAT_WILDCARD = "@" + JDBCLogType.CAT_VAR + "@"; private final static String THREAD_WILDCARD = "@" + JDBCLogType.THREAD_VAR + "@"; private final static String TIMESTAMP_WILDCARD = "@" + JDBCLogType.TIMESTAMP_VAR + "@"; private final static String INC_WILDCARD = "@" + JDBCLogType.INC_VAR + "@"; private final static String THROWABLE_WILDCARD = "@" + JDBCLogType.THROWABLE_VAR + "@"; private final static String NDC_WILDCARD = "@" + JDBCLogType.NDC_VAR + "@"; // just the beginning private final static String MDC_WILDCARD = "@" + JDBCLogType.MDC_VAR + ":"; // just the beginning private final static String LAYOUT_WILDCARD = "@" + JDBCLogType.LAYOUT_VAR; private final static String IPRIO_WILDCARD = "@" + JDBCLogType.IPRIO_VAR + "@"; /** * Sets a connection. Throws an exception, if the connection is not open ! * * @param obj * The new Connection value * @exception Exception * Description of Exception */ public void setConnection(Object obj) throws Exception { if (obj instanceof JDBCPoolConnectionHandler) { poolConnectionHandler = (JDBCPoolConnectionHandler) obj; } else if (obj instanceof Connection) { con = (Connection) obj; if (!isConnected()) { throw new Exception( "JDBCLogger::setConnection(), Given connection isnt connected to database !"); } } else { throw new Exception("JDBCLogger::setConnection(), Unvalid type of argument " + obj.getClass().getName() + " !"); } } /** * Sets the SqlHandler attribute of the JDBCLogger object * * @param sqlHandler * The new SqlHandler value * @exception Exception * Description of Exception */ public void setSqlHandler(JDBCSqlHandler sqlHandler) throws Exception { this.sqlHandler = sqlHandler; isconfigured = true; } /** * Sets a columns logtype (LogTypes) and value, which depends on that * logtype. Throws an exception, if the given arguments arent correct ! * * @param _name * The new JDBCLogType value * @param _logtype * The new JDBCLogType value * @param _value * The new JDBCLogType value * @exception Exception * Description of Exception */ public void setLogType(String _name, int _logtype, Object _value) throws Exception { if (!isconfigured) { throw new Exception("JDBCLogger::setLogType(), Not configured !"); } //setLogType() makes only sense for further configuration of setTable() if (sql != null || sqlHandler != null) { return; } _name = _name.toUpperCase(); if (_name == null || !(_name.trim().length() > 0)) { throw new Exception( "JDBCLogger::setLogType(), Missing argument name !"); } if (!JDBCLogType.isLogType(_logtype)) { throw new Exception( "JDBCLogger::setLogType(), Invalid JDBCLogType '" + _logtype + "' !"); } JDBCLogColumn logcol; for (int i = 0; i < num; i++) { logcol = (JDBCLogColumn) logcols.get(i); if (logcol.name.equals(_name)) { if (!logcol.isWritable) { throw new Exception("JDBCLogger::setLogType(), Column " + _name + " is not writeable !"); } //Column will be provided by JDBCIDHandler::getID() if (_logtype == JDBCLogType.ID) { if (_value == null) { throw new Exception( "JDBCLogger::setLogType(), Missing argument value !"); } try { //Try to cast directly Object to JDBCIDHandler logcol.idhandler = (JDBCIDHandler) _value; } catch (Exception e) { try { //Assuming _value is of class string which contains // the classname of a JDBCIDHandler logcol.idhandler = (JDBCIDHandler) (Class.forName((String) _value) .newInstance()); } catch (Exception e2) { throw new Exception( "JDBCLogger::setLogType(), Cannot cast value of class " + _value.getClass() + " to class JDBCIDHandler !"); } } logcol.logtype = _logtype; return; } //Columns content will be provided by a JDBCColumnHandler else if (_logtype == JDBCLogType.DYNAMIC) { if (_value == null) { throw new Exception( "JDBCLogger::setLogType(), Missing argument value !"); } try { //Try to cast directly Object to JDBCColumnHandler logcol.columnHandler = (JDBCColumnHandler) _value; } catch (Exception e) { try { //Assuming _value is of class string which contains // the classname of a JDBCColumnHandler logcol.columnHandler = (JDBCColumnHandler) (Class .forName((String) _value).newInstance()); } catch (Exception e2) { throw new Exception( "JDBCLogger::setLogType(), Cannot cast value of class " + _value.getClass() + " to class JDBCColumnHandler !"); } } logcol.logtype = _logtype; return; } //Column will be statically defined with Object _value else if (_logtype == JDBCLogType.STATIC) { if (_value == null) { throw new Exception( "JDBCLogger::setLogType(), Missing argument value STATIC !"); } logcol.logtype = _logtype; logcol.value = _value; return; } //Column will be statically defined with Object _value else if (_logtype == JDBCLogType.LAYOUT) { Integer layoutIndex = null; if (_value == null) { // default: use first pattern layoutIndex = new Integer(1); } else if (_value instanceof Number) { layoutIndex = new Integer(((Number) _value).intValue()); } else if (_value instanceof String) { try { layoutIndex = Integer.valueOf((String) _value); } catch (NumberFormatException numberFormatException) { throw new Exception( "JDBCLogger::setLogType(), layout index (Number/String) expected. Found: " + _value); } } else { throw new Exception( "JDBCLogger::setLogType(), layout index (Number/String) expected. Found: " + _value.getClass()); } logcol.logtype = _logtype; logcol.value = layoutIndex; return; } //Column will be MDC with key value. else if (_logtype == JDBCLogType.MDC) { if (_value == null) { throw new Exception( "JDBCLogger::setLogType(), Missing argument value MDC !"); } logcol.logtype = _logtype; logcol.value = _value; return; } //Column will be using ORACLE_SEQUENCE else if (_logtype == JDBCLogType.ORACLE_SEQUENCE) { if (_value == null) { throw new Exception( "JDBCLogger::setLogType(), Missing argument value ORACLE_SEQUENCE !"); } logcol.logtype = _logtype; logcol.value = _value; return; } //Column will be fully ignored during process. //If this column is not nullable, the column has to be filled // by a database trigger, //else a database error occurs ! //Columns which are not nullable, but should be not filled, // must be explicit assigned with JDBCLogType.EMPTY, //else a value is required ! else if (_logtype == JDBCLogType.EMPTY) { logcol.logtype = _logtype; logcol.ignore = true; return; } else { logcol.logtype = _logtype; return; } } } } /** * Configures this class, by reading in the structure of the log-table * Throws an exception, if an database-error occurs ! * * @param _table * Description of Parameter * @exception Exception * Description of Exception */ public void setTable(String _table) throws Exception { if (isconfigured) { return; } if (poolConnectionHandler != null) { con = poolConnectionHandler.getConnection(); if (!isConnected()) { throw new Exception( "JDBCLogger::setTable(), Given connection isnt connected to database !"); } } //Fill logcols with META-informations of the table-columns stmt = this.createUpdatableStatement(); rs = stmt.executeQuery("SELECT " + _table + ".* FROM " + _table + " WHERE 1 = 2"); JDBCLogColumn logcol; ResultSetMetaData rsmd = rs.getMetaData(); num = rsmd.getColumnCount(); logcols = new ArrayList(num); for (int i = 1; i <= num; i++) { logcol = new JDBCLogColumn(); logcol.name = rsmd.getColumnName(i).toUpperCase(); logcol.sqlType = rsmd.getColumnType(i); logcol.type = rsmd.getColumnTypeName(i); logcol.nullable = (rsmd.isNullable(i) == ResultSetMetaData.columnNullable); logcol.isWritable = rsmd.isWritable(i); if (!logcol.isWritable) { logcol.ignore = true; } logcols.add(logcol); } table = _table; rs.close(); stmt.close(); freeConnection(); isconfigured = true; } /** * Configures this class, by storing and parsing the given sql-statement. * Throws an exception, if somethings wrong ! * * @param _sql * Description of Parameter * @exception Exception * Description of Exception */ public void setSQL(String _sql) throws Exception { if (isconfigured) { return; } if (_sql == null || _sql.trim().equals("")) { throw new Exception( "JDBCLogger::setSQL(), Invalid SQL-Statement !"); } sql = _sql.trim(); isconfigured = true; } /** * Return true, if this class is configured, else false. * * @return The Configured value */ public boolean isConfigured() { return isconfigured; } /** * Return true, if this connection is open, else false. * * @return The Connected value */ public boolean isConnected() { try { return (con != null && !con.isClosed()); } catch (Exception e) { return false; } } /** * Return the internal error message stored in instance variable msg. * * @return The ErrorMsg value */ public String getErrorMsg() { if (errormsg == null) return ""; String r = new String(errormsg); errormsg = null; return r; } /** * Description of the Method * * @exception Exception * Description of Exception */ public void freeConnection() throws Exception { if (poolConnectionHandler != null) { // con == null may only happen in error situations if (con != null) { if (!con.isClosed() && !con.getAutoCommit()) { if (this.isCommit()) { con.commit(); } } poolConnectionHandler.freeConnection(con); } } } /** * Calls <code>freeConnection</code> but catches all raising exceptions. * @see #freeConnection() */ public void tryToFreeConnection() { try { freeConnection(); } catch (Exception e) { LogLog.error(e.getMessage()); } } /** * prepare connection * * @return Description of the Returned Value * @exception Exception * Description of Exception */ public boolean prepareConnection() throws Exception { if (poolConnectionHandler != null) { con = poolConnectionHandler.getConnection(); } if (!isConnected()) { throw new Exception( "JDBCLogger::prepareConnection(), Given connection isnt connected to database !"); } if (procedure != null) { // procedure call stmt = this.createCallableStatement(this.logcols.size()); } else if (sql == null && sqlHandler == null && !this.isUsePreparedStatements()) { // uses updateable result set try { stmt = this.createUpdatableStatement(); } catch (SQLException exception) { // if this fails, connection may be broken. Try again with new // connection. if (poolConnectionHandler != null) { con = poolConnectionHandler.getConnection(); stmt = this.createUpdatableStatement(); } else { throw exception; } } rs = stmt.executeQuery("SELECT " + column_list + " FROM " + table + " WHERE 1 = 2"); } else { // non-updatable statement try { stmt = this.createStatement(); } catch (SQLException exception) { // if this fails, connection may be broken. Try again with new // connection. if (poolConnectionHandler != null) { con = poolConnectionHandler.getConnection(); stmt = this.createStatement(); } else { throw exception; } } } return true; } /** * Writes a message into the database table. Throws an exception, if an * database-error occurs ! * * @param event * the LoggingEvent to log * @param layout * layout to use for message * @exception Exception * Description of Exception */ public void append(LoggingEvent event, Layout layout) throws Exception { boolean errorOccurred = false; boolean usePrepStmts = this.isUsePreparedStatements(); boolean useCallStmts = procedure != null; PreparedStatement prepStmt = null; CallableStatement callStmt = null; if (!ready) { if (!ready()) { throw new Exception("JDBCLogger::append(), Not ready to append !"); } } try { prepareConnection(); if (sql != null || sqlHandler != null) { appendSQL(event, layout); } else { JDBCLogColumn logcol; int paramIndex = 1; if (useCallStmts) { callStmt = (CallableStatement) stmt; } if (usePrepStmts || useCallStmts) { prepStmt = (PreparedStatement) stmt; } else { rs.moveToInsertRow(); } for (int i = 0; i < num; i++) { logcol = (JDBCLogColumn) logcols.get(i); int indexToUse = 0; if (usePrepStmts) { indexToUse = paramIndex; } else if (useCallStmts) { indexToUse = i + 1; } if (logcol.logtype == JDBCLogType.MSG) { String parameter = event.getRenderedMessage(); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setString(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateString(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.LAYOUT) { String parameter = layout.format(event); // default: use first pattern int layoutIndex = ((Integer) logcol.value).intValue(); // LAYOUT~x. use tokens in layout string List tokenList = getTokenList(parameter); String token = getTokenFromList(tokenList, layoutIndex); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setString(indexToUse, token); } paramIndex = paramIndex + 1; } else { rs.updateString(logcol.name, token); } } else if (logcol.logtype == JDBCLogType.PRIO) { String parameter = event.getLevel().toString(); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setString(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateString(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.CAT) { String parameter = event.getLoggerName(); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setString(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateString(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.THREAD) { String parameter = event.getThreadName(); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setString(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateString(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.ID) { Object parameter = logcol.idhandler.getID(); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setObject(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateObject(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.STATIC) { Object parameter = logcol.value; if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setObject(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateObject(logcol.name, logcol.value); } } else if (logcol.logtype == JDBCLogType.TIMESTAMP) { Timestamp parameter = new Timestamp((new java.util.Date()).getTime()); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setTimestamp(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateTimestamp(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.INC) { long parameter = ++inc; if (usePrepStmts || useCallStmts) { prepStmt.setLong(indexToUse, parameter); paramIndex = paramIndex + 1; } else { rs.updateLong(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.DYNAMIC) { Object parameter = logcol.columnHandler .getObject(event, table, logcol.name); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setObject(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateObject(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.THROWABLE) { // extract throwable information from loggingEvent if // available String parameter = quotedString(this .getThrowableRepresentationFromLoggingEvent(event)); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setString(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateString(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.NDC) { String parameter = event.getNDC(); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setString(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateString(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.MDC) { Object mdcObject = event.getMDC(logcol.value.toString()); String parameter = mdcObject == null ? null : mdcObject.toString(); if (usePrepStmts || useCallStmts) { if (parameter == null) { prepStmt.setNull(indexToUse, logcol.sqlType); } else { prepStmt.setObject(indexToUse, parameter); } paramIndex = paramIndex + 1; } else { rs.updateObject(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.IPRIO) { int parameter = event.getLevel().toInt(); if (usePrepStmts || useCallStmts) { prepStmt.setInt(indexToUse, parameter); paramIndex = paramIndex + 1; } else { rs.updateInt(logcol.name, parameter); } } else if (logcol.logtype == JDBCLogType.ORACLE_SEQUENCE) { // do nothing } } if (useCallStmts) { callStmt.execute(); } else if (usePrepStmts) { prepStmt.executeUpdate(); } else { rs.insertRow(); } } } catch (Exception e) { // e.printStackTrace(); errorOccurred = true; throw (e); } finally { try { if (stmt != null) { stmt.close(); } freeConnection(); } catch (Exception exception) { if (errorOccurred) { // consume exception } else { throw (exception); } } } } /** * Writes a message into the database using a given sql-statement. Throws an * exception, if an database-error occurs ! * * @param event * Description of Parameter * @param layout * layout to use for message * @exception Exception * Description of Exception */ public void appendSQL(LoggingEvent aEvent, Layout layout) throws Exception { String new_sql = ""; if (!ready) { if (!ready()) { throw new Exception("JDBCLogger::appendSQL(), Not ready to append !"); } } if (sql == null && sqlHandler == null) { throw new Exception( "JDBCLogger::appendSQL(), No SQL-Statement configured !"); } try { if (sqlHandler == null) { // These strings may contain embedded single-quote characters. // Therefore, scan the strings and replace all single-quote // characters with a pair of single-quote characters. // only done if option "quoteReplace" is not unset String t_msg = quotedString(aEvent.getRenderedMessage()); String t_layout = layout.format(aEvent); String t_prio = quotedString(aEvent.getLevel().toString()); String t_cat = quotedString(aEvent.getLoggerName()); String t_thread = quotedString(aEvent.getThreadName()); String t_ndc = quotedString(aEvent.getNDC()); int t_iprio = aEvent.getLevel().toInt(); new_sql = sql; new_sql = replace(new_sql, MSG_WILDCARD, t_msg); new_sql = replace(new_sql, PRIO_WILDCARD, t_prio); new_sql = replace(new_sql, CAT_WILDCARD, t_cat); new_sql = replace(new_sql, THREAD_WILDCARD, t_thread); new_sql = replace(new_sql, TIMESTAMP_WILDCARD, new Timestamp((new java.util.Date()) .getTime()).toString()); new_sql = replace(new_sql, INC_WILDCARD, String.valueOf(++inc)); new_sql = replace(new_sql, THROWABLE_WILDCARD, quotedString(this .getThrowableRepresentationFromLoggingEvent(aEvent))); new_sql = replace(new_sql, NDC_WILDCARD, t_ndc); new_sql = replace(new_sql, IPRIO_WILDCARD, t_iprio); // MDC. determine key parameters boolean finishedMdc = false; do { // find match int index = new_sql.indexOf(MDC_WILDCARD); boolean contains = index != -1; if (contains) { // have found start. now find exact key String substringStartingWithKey = new_sql.substring(index + MDC_WILDCARD.length()); // find first @ that follows. key is between : and @ int indexOfKeyEnd = substringStartingWithKey.indexOf("@"); String key = substringStartingWithKey.substring(0, indexOfKeyEnd); Object mdcObject = aEvent.getMDC(key); String t_mdc = ""; if (mdcObject != null) { t_mdc = quotedString(mdcObject.toString()); } new_sql = new_sql.substring(0, index) + t_mdc + new_sql.substring(index + MDC_WILDCARD.length() + 1 + indexOfKeyEnd); } else { finishedMdc = true; } } while (!finishedMdc); // LAYOUT:x. replace by tokens in layout string List tokenList = getTokenList(t_layout); boolean finishedLayout = false; do { // find match int index = new_sql.indexOf(LAYOUT_WILDCARD); boolean contains = index != -1; if (contains) { // have found start. now find Number (2 in @LAYOUT:2@) String substringStartingWithNumber = new_sql.substring(index + LAYOUT_WILDCARD.length()); // starts with :? consume one more character if (substringStartingWithNumber.startsWith(":")) { substringStartingWithNumber = substringStartingWithNumber.substring(1); } // find first @ that follows. key is between : and @ int indexOfNumberEnd = substringStartingWithNumber.indexOf("@"); String numberString = substringStartingWithNumber.substring(0, indexOfNumberEnd); // default: 1 int number = 1; if (!numberString.equals("")) { number = Integer.valueOf(numberString).intValue(); } String token = getTokenFromList(tokenList, number); new_sql = new_sql.substring(0, index) + token + substringStartingWithNumber.substring(+indexOfNumberEnd + 1); } else { finishedLayout = true; } } while (!finishedLayout); } else { new_sql = sqlHandler.getStatement(aEvent); } LogLog.debug("sql statement: " + new_sql); try { this.executeUpdateWhenNotEmpty(new_sql); } catch (SQLException exception) { this.tryToFreeConnection(); // try again with new connection. May have happened due to broken db connection this.prepareConnection(); this.executeUpdateWhenNotEmpty(new_sql); } } catch (Exception e) { LogLog.error("error during logging. " + new_sql, e); errormsg = new_sql; throw e; } } /** * @param new_sql * @throws SQLException */ private void executeUpdateWhenNotEmpty(String newSql) throws SQLException { if (newSql != null && newSql.length() > 0) { stmt.executeUpdate(newSql); } } /** * @param tokenList * @param index * list index, 1-based. * @return (index-1)'th element in list * @throws Exception */ protected String getTokenFromList(List tokenList, int index) throws Exception { // retrieve the value from tokenList, 0-based if (index > tokenList.size()) { throw new Exception("missing token " + index + " separated by '" + this.getLayoutPartsDelimiter() + "' in .layoutPartsDelimiter parameter."); } String token = (String) tokenList.get(index - 1); token = quotedString(token); return token; } /** * create token list from layout string. Requires JDK 1.4, therefore * commented out here. * * @param t_layout * layout string to be tokenized * @return list of layout parts * * protected List getTokenList(String t_layout) { * String[] tokens = t_layout.split(getLayoutPartsDelimiter()); * * ArrayList list = new ArrayList(tokens.length); for(int i=1; i * <tokens.length; i++) { list.add(tokens[i]); } * * return list; } */ /** * create token list from layout string * * @param t_layout * @return list of layout parts * */ protected List getTokenList(String t_layout) { List tokenList = new ArrayList(5); // empty? return one token. if (t_layout.equals("")) { tokenList.add(""); } else { // delimiter with one character? Use StringTokenizer. if (this.getLayoutPartsDelimiter().length() == 1) { // use StringBuffer to separate layout parts first StringTokenizer stringTokenizer = new StringTokenizer(t_layout, String.valueOf(this .getLayoutPartsDelimiter()), true); // put tokens to List // used to determine if two delimiters follow each other boolean lastWasDelimiter = false; while (stringTokenizer.hasMoreTokens()) { String token = stringTokenizer.nextToken(); if (token.equals(this.getLayoutPartsDelimiter())) { if (lastWasDelimiter) { // two token after another. add empty String tokenList.add(""); } else { lastWasDelimiter = true; } } else { tokenList.add(token); lastWasDelimiter = false; } } } else { // more than one character. // requires J2SDK 1.4 // problem: two following delimiters would be removed. // therefore, add randomdummy in that place. // Same applies to begin and end of string. String dummy = new UID().toString(); if (t_layout.startsWith(this.getLayoutPartsDelimiter())) { t_layout = dummy + t_layout; } if (t_layout.endsWith(this.getLayoutPartsDelimiter())) { t_layout = t_layout + dummy; } // replace double occurrences String[] tokens = t_layout.split(this.getLayoutPartsDelimiter()); for (int i = 0; i < tokens.length; i++) { if (tokens[i].equals(dummy)) { tokenList.add(""); } else { tokenList.add(tokens[i]); } } } } return tokenList; } /** * Return true, if this class is ready to append(), else false. When not * ready, a reason-String is stored in the instance-variable msg. * * @return Description of the Returned Value */ public boolean ready() { if (ready) { return true; } if (!isconfigured) { errormsg = "Not ready to append, because not configured !"; return false; } //No need to doing the whole rest... if (sql != null || sqlHandler != null) { ready = true; return true; } boolean msgcol_defined = false; JDBCLogColumn logcol; for (int i = 0; i < num; i++) { logcol = (JDBCLogColumn) logcols.get(i); if (logcol.ignore || !logcol.isWritable) { continue; } if (!logcol.nullable && logcol.logtype == JDBCLogType.EMPTY) { errormsg = "Not ready to append ! Column " + logcol.name + " is not nullable, and must be specified by setLogType() !"; return false; } if (logcol.logtype == JDBCLogType.ID && logcol.idhandler == null) { errormsg = "Not ready to append ! Column " + logcol.name + " is specified as an ID-column, and a JDBCIDHandler has to be set !"; return false; } else if (logcol.logtype == JDBCLogType.STATIC && logcol.value == null) { errormsg = "Not ready to append ! Column " + logcol.name + " is specified as a static field, and a value has to be set !"; return false; } else if (logcol.logtype == JDBCLogType.MDC && logcol.value == null) { errormsg = "Not ready to append ! Column " + logcol.name + " is specified as a MDC field, and a key has to be set !"; return false; } else if (logcol.logtype == JDBCLogType.MSG) { msgcol_defined = true; } } if (!msgcol_defined) { // return false; } //create the column_list for (int i = 0; i < num; i++) { logcol = (JDBCLogColumn) logcols.get(i); if (logcol.ignore || !logcol.isWritable) { continue; } if (logcol.logtype != JDBCLogType.EMPTY) { if (column_list == null) { column_list = logcol.name; } else { column_list += ", " + logcol.name; } } } ready = true; return true; } /** * Creates a SQL-quoted string. Any single-quotes ['] are doubled [''] in * order to make the string "SQL compatible". */ private String quotedString(String s) { if (s == null) return ""; if (this.isQuoteReplace()) { s = replace(s, "'", "''"); } return s; } /** * String-replacer * * @param source * Description of Parameter * @param find * Description of Parameter * @param replacement * Description of Parameter * @return Description of the Returned Value */ public String replace(String source, String find, String replacement) { int i = 0; int j; int k = find.length(); int m = replacement.length(); while (i < source.length()) { j = source.indexOf(find, i); if (j == -1) { break; } source = replace(source, j, j + k, replacement); i = j + m; } return source; } /** * int-replacer * * @param source * Description of Parameter * @param find * Description of Parameter * @param replacement * Description of Parameter * @return Description of the Returned Value */ public String replace(String source, String find, int replacement) { String sReplacement = Integer.toString(replacement); return replace(source, find, sReplacement); } /** * String-replacer * * @param source * Description of Parameter * @param start * Description of Parameter * @param end * Description of Parameter * @param replacement * Description of Parameter * @return Description of the Returned Value */ public String replace(String source, int start, int end, String replacement) { if (start == 0) { source = replacement + source.substring(end); } else if (end == source.length()) { source = source.substring(0, start) + replacement; } else { source = source.substring(0, start) + replacement + source.substring(end); } return source; } /** * int-replacer * * @param source * Description of Parameter * @param start * Description of Parameter * @param end * Description of Parameter * @param replacement * Description of Parameter * @return Description of the Returned Value */ public String replace(String source, int start, int end, int replacement) { String sReplacement = Integer.toString(replacement); return replace(source, start, end, sReplacement); } private Statement createStatement() throws Exception { Statement retVal = null; if (this.isUsePreparedStatements()) { // create sql statement if (this.preparedSql == null) { String sql = "insert into " + table + " (" + column_list + ") values ("; for (int i = 0; i < num; i++) { JDBCLogColumn logcol = (JDBCLogColumn) logcols.get(i); // only required columns if (!logcol.ignore) { // add , if required if (sql.endsWith(" ")) { sql += ", "; } if (logcol.logtype == JDBCLogType.ORACLE_SEQUENCE) { sql += logcol.value.toString() + ".NEXTVAL "; } else { sql += "? "; } } } sql += ")"; LogLog.debug("prepared statement: " + sql); this.preparedSql = sql; } retVal = con.prepareStatement(this.preparedSql); } else { retVal = con.createStatement(); } return retVal; } private CallableStatement createCallableStatement(int numberOfParameters) throws Exception { CallableStatement cStmt = null; String callString = "{call " + procedure + "( "; for (int i = 0; i < numberOfParameters - 1; i++) { callString = callString + "?, "; } if (numberOfParameters > 0) { callString = callString + "? "; } callString = callString + " )}"; cStmt = con.prepareCall(callString); return cStmt; } private Statement createUpdatableStatement() throws Exception { Statement retVal = null; retVal = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); return retVal; } /** * Extracts Stack trace of Throwable contained in LogginEvent, if there is * any * * @param aLoggingEvent * logging event * @return stack trace of throwable */ public String getThrowableRepresentationFromLoggingEvent(LoggingEvent aLoggingEvent) { // extract throwable information from loggingEvent if available ThrowableInformation throwableinfo = aLoggingEvent.getThrowableInformation(); StringBuffer throwableStringBuffer = new StringBuffer(); if (throwableinfo != null) { String[] lines = throwableinfo.getThrowableStrRep(); for (int index = 0; index < lines.length; index++) { throwableStringBuffer = (StringBuffer) throwableStringBuffer.append(lines[index] + "\r\n"); } } String result = throwableStringBuffer.toString(); if (this.getThrowableMaxChars() != -1 && result.length() > this.getThrowableMaxChars()) { result = result.substring(0, this.getThrowableMaxChars() - 1); } return result; } /** * @return docommit attribute */ public boolean isCommit() { return commit; } /** * @param b */ public void setCommit(boolean b) { commit = b; } /** * @return quoteReplace */ public boolean isQuoteReplace() { return quoteReplace; } /** * @param b */ public void setQuoteReplace(boolean b) { quoteReplace = b; } /** * @return layoutPartsDelimiter */ public String getLayoutPartsDelimiter() { return layoutPartsDelimiter; } /** * @param c */ public void setLayoutPartsDelimiter(String c) { layoutPartsDelimiter = c; } /** * @return Returns the usePreparedStatements. */ public boolean isUsePreparedStatements() { return usePreparedStatements; } /** * @param usePreparedStatements * The usePreparedStatements to set. */ public void setUsePreparedStatements(boolean usePreparedStatements) { this.usePreparedStatements = usePreparedStatements; } /** * @return Returns the procedure. */ public String getProcedure() { return procedure; } /** * @param procedure The procedure to set. * @param columns columns */ public void setProcedure(String procedure, ArrayList columns) throws Exception { if (isconfigured) { return; } if (poolConnectionHandler != null) { con = poolConnectionHandler.getConnection(); if (!isConnected()) { throw new Exception( "JDBCLogger::setProcedure(), Given connection isnt connected to database !"); } } this.procedure = procedure; // prepare call CallableStatement cStmt = this.createCallableStatement(columns.size()); JDBCLogColumn logcol; ParameterMetaData pmd; // 2.6.2005 jschmied // ParameterMetaData is supported on different levels by Oracle try { // J2SDK 1.4+; limited support by Oracle drivers 10.x and 9.x pmd = cStmt.getParameterMetaData(); num = pmd.getParameterCount(); if (num >= 1) { // oracle 10.1.0.4 has some stubs in ParameterMetaData, // try if a function throws a UnsupportedFeature exception pmd.getParameterType(1); pmd.getParameterTypeName(1); pmd.isNullable(1); } } catch (Exception e) { pmd = null; num = columns.size(); } logcols = new ArrayList(num); for (int i = 1; i <= num; i++) { logcol = new JDBCLogColumn(); JDBCColumnStorage col = (JDBCColumnStorage) columns.get(i - 1); logcol.name = col.column.toUpperCase(); if (pmd == null) { logcol.type = col.type; logcol.sqlType = col.sqlType; logcol.nullable = true; // assume true } else { logcol.type = pmd.getParameterTypeName(i); logcol.sqlType = pmd.getParameterType(i); logcol.nullable = (pmd.isNullable(i) == ParameterMetaData.parameterNullable); } logcol.isWritable = true; logcols.add(logcol); } cStmt.close(); freeConnection(); isconfigured = true; } /** * @return Returns the throwableMaxChars. */ public int getThrowableMaxChars() { return throwableMaxChars; } /** * @param throwableMaxChars The throwableMaxChars to set. */ public void setThrowableMaxChars(int throwableMaxChars) { this.throwableMaxChars = throwableMaxChars; } }