001: /* ====================================================================
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1997-2003 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowledgment may appear in the software
024: * itself, if and wherever such third-party acknowledgments
025: * normally appear.
026: *
027: * 4. The names "Jakarta", "Avalon", and "Apache Software Foundation"
028: * must not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation. For more
052: * information on the Apache Software Foundation, please see
053: * <http://www.apache.org/>.
054: */
055: package org.apache.log.output.db;
056:
057: import java.io.StringWriter;
058: import java.sql.Connection;
059: import java.sql.PreparedStatement;
060: import java.sql.SQLException;
061: import java.sql.Timestamp;
062: import javax.sql.DataSource;
063: import org.apache.log.ContextMap;
064: import org.apache.log.LogEvent;
065:
066: /**
067: * The basic DB target for configurable output formats.
068: *
069: * @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
070: * @author <a href="mailto:peter@apache.org">Peter Donald</a>
071: */
072: public class DefaultJDBCTarget extends AbstractJDBCTarget {
073: private final String m_table;
074: private final ColumnInfo[] m_columns;
075:
076: private PreparedStatement m_statement;
077:
078: /**
079: * Creation of a new JDBC logging target.
080: * @param dataSource the JDBC datasource
081: * @param table the table
082: * @param columns a ColumnInfo array
083: */
084: public DefaultJDBCTarget(final DataSource dataSource,
085: final String table, final ColumnInfo[] columns) {
086: super (dataSource);
087: m_table = table;
088: m_columns = columns;
089:
090: if (null == table) {
091: throw new NullPointerException(
092: "table property must not be null");
093: }
094:
095: if (null == columns) {
096: throw new NullPointerException(
097: "columns property must not be null");
098: }
099:
100: if (0 == columns.length) {
101: throw new NullPointerException(
102: "columns must have at least 1 element");
103: }
104:
105: open();
106: }
107:
108: /**
109: * Output a log event to DB.
110: * This must be implemented by subclasses.
111: *
112: * @param event the log event.
113: */
114: protected synchronized void output(final LogEvent event) {
115: //TODO: Retry logic so that this method is called multiple times if it fails
116: //Make retry configurable and if fail send event onto ErrorHandler
117: try {
118: for (int i = 0; i < m_columns.length; i++) {
119: specifyColumn(m_statement, i, event);
120: }
121:
122: m_statement.executeUpdate();
123: } catch (final SQLException se) {
124: getErrorHandler().error("Error executing statement", se,
125: event);
126: }
127: }
128:
129: /**
130: * Open connection to underlying database.
131: *
132: */
133: protected synchronized void openConnection() {
134: //if( null != m_statement ) return;
135: super .openConnection();
136:
137: m_statement = null;
138: try {
139: final Connection connection = getConnection();
140: if (null != connection) {
141: m_statement = connection
142: .prepareStatement(getStatementSQL());
143: }
144: } catch (final SQLException se) {
145: getErrorHandler().error("Error preparing statement", se,
146: null);
147: }
148: }
149:
150: /**
151: * Return the SQL insert statement.
152: * @return the statement
153: */
154: protected String getStatementSQL() {
155: final StringBuffer sb = new StringBuffer("INSERT INTO ");
156: sb.append(m_table);
157: sb.append(" (");
158: sb.append(m_columns[0].getName());
159:
160: for (int i = 1; i < m_columns.length; i++) {
161: sb.append(", ");
162: sb.append(m_columns[i].getName());
163: }
164:
165: sb.append(") VALUES (?");
166:
167: for (int i = 1; i < m_columns.length; i++) {
168: sb.append(", ?");
169: }
170:
171: sb.append(")");
172:
173: return sb.toString();
174: }
175:
176: /**
177: * Test if the target is stale.
178: * @return TRUE if the target is stale else FALSE
179: */
180: protected boolean isStale() {
181: return super .isStale();
182: //Check: "SELECT * FROM " + m_table + " WHERE 0 = 99" here ...
183: }
184:
185: /**
186: * Close connection to underlying database.
187: *
188: */
189: protected synchronized void closeConnection() {
190: //close prepared statement here
191: super .closeConnection();
192:
193: if (null != m_statement) {
194: try {
195: m_statement.close();
196: } catch (final SQLException se) {
197: getErrorHandler().error("Error closing statement", se,
198: null);
199: }
200:
201: m_statement = null;
202: }
203: }
204:
205: /**
206: * Adds a single object into statement.
207: * @param statement the prepard statement
208: * @param index the index
209: * @param event the log event
210: * @exception SQLException if an SQL related error occurs
211: * @exception IllegalStateException if the supplied index is out of bounds
212: */
213: protected void specifyColumn(final PreparedStatement statement,
214: final int index, final LogEvent event) throws SQLException,
215: IllegalStateException {
216: final ColumnInfo info = m_columns[index];
217:
218: switch (info.getType()) {
219: case ColumnType.RELATIVE_TIME:
220: statement.setLong(index + 1, event.getRelativeTime());
221: break;
222:
223: case ColumnType.TIME:
224: statement.setTimestamp(index + 1, new Timestamp(event
225: .getTime()));
226: break;
227:
228: case ColumnType.MESSAGE:
229: statement.setString(index + 1, event.getMessage());
230: break;
231:
232: case ColumnType.CATEGORY:
233: statement.setString(index + 1, event.getCategory());
234: break;
235:
236: case ColumnType.PRIORITY:
237: statement.setString(index + 1, event.getPriority()
238: .getName());
239: break;
240:
241: case ColumnType.CONTEXT:
242: statement.setString(index + 1, getContextMap(event
243: .getContextMap(), info.getAux()));
244: break;
245:
246: case ColumnType.STATIC:
247: statement.setString(index + 1, info.getAux());
248: break;
249:
250: case ColumnType.THROWABLE:
251: statement.setString(index + 1, getStackTrace(event
252: .getThrowable()));
253: break;
254:
255: default:
256: throw new IllegalStateException("Unknown ColumnType: "
257: + info.getType());
258: }
259: }
260:
261: /**
262: * Return the underlying table
263: * @return the table name
264: */
265: protected final String getTable() {
266: return m_table;
267: }
268:
269: /**
270: * Return the column info for an supplied index.
271: * @param index the index
272: * @return the column info
273: */
274: protected final ColumnInfo getColumn(final int index) {
275: return m_columns[index];
276: }
277:
278: private String getStackTrace(final Throwable throwable) {
279: if (null == throwable) {
280: return "";
281: }
282: final StringWriter sw = new StringWriter();
283: throwable.printStackTrace(new java.io.PrintWriter(sw));
284: return sw.toString();
285: }
286:
287: private String getContextMap(final ContextMap map, final String aux) {
288: if (null == map) {
289: return "";
290: } else {
291: return map.get(aux, "").toString();
292: }
293: }
294: }
|