25369df94d5157b17d5ad438b11431bf75a75410
[jalview.git] / srcjar2 / org / apache / log4j / jdbc / JDBCAppender.java
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.log4j.jdbc;
18
19 import java.sql.Connection;
20 import java.sql.DriverManager;
21 import java.sql.SQLException;
22 import java.sql.Statement;
23 import java.util.ArrayList;
24 import java.util.Iterator;
25
26 import org.apache.log4j.PatternLayout;
27 import org.apache.log4j.spi.ErrorCode;
28 import org.apache.log4j.spi.LoggingEvent;
29
30
31 /**
32   The JDBCAppender provides for sending log events to a database.
33   
34  <p><b><font color="#FF2222">WARNING: This version of JDBCAppender
35  is very likely to be completely replaced in the future. Moreoever,
36  it does not log exceptions</font></b>.
37
38   <p>Each append call adds to an <code>ArrayList</code> buffer.  When
39   the buffer is filled each log event is placed in a sql statement
40   (configurable) and executed.
41
42   <b>BufferSize</b>, <b>db URL</b>, <b>User</b>, & <b>Password</b> are
43   configurable options in the standard log4j ways.
44
45   <p>The <code>setSql(String sql)</code> sets the SQL statement to be
46   used for logging -- this statement is sent to a
47   <code>PatternLayout</code> (either created automaticly by the
48   appender or added by the user).  Therefore by default all the
49   conversion patterns in <code>PatternLayout</code> can be used
50   inside of the statement.  (see the test cases for examples)
51
52   <p>Overriding the {@link #getLogStatement} method allows more
53   explicit control of the statement used for logging.
54
55   <p>For use as a base class:
56
57     <ul>
58
59     <li>Override <code>getConnection()</code> to pass any connection
60     you want.  Typically this is used to enable application wide
61     connection pooling.
62
63      <li>Override <code>closeConnection(Connection con)</code> -- if
64      you override getConnection make sure to implement
65      <code>closeConnection</code> to handle the connection you
66      generated.  Typically this would return the connection to the
67      pool it came from.
68
69      <li>Override <code>getLogStatement(LoggingEvent event)</code> to
70      produce specialized or dynamic statements. The default uses the
71      sql option value.
72
73     </ul>
74
75     @author Kevin Steppe (<A HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
76
77 */
78 public class JDBCAppender extends org.apache.log4j.AppenderSkeleton {
79
80   /**
81    * URL of the DB for default connection handling
82    */
83   protected String databaseURL = "jdbc:odbc:myDB";
84
85   /**
86    * User to connect as for default connection handling
87    */
88   protected String databaseUser = "me";
89
90   /**
91    * User to use for default connection handling
92    */
93   protected String databasePassword = "mypassword";
94
95   /**
96    * Connection used by default.  The connection is opened the first time it
97    * is needed and then held open until the appender is closed (usually at
98    * garbage collection).  This behavior is best modified by creating a
99    * sub-class and overriding the <code>getConnection</code> and
100    * <code>closeConnection</code> methods.
101    */
102   protected Connection connection = null;
103
104   /**
105    * Stores the string given to the pattern layout for conversion into a SQL
106    * statement, eg: insert into LogTable (Thread, Class, Message) values
107    * ("%t", "%c", "%m").
108    *
109    * Be careful of quotes in your messages!
110    *
111    * Also see PatternLayout.
112    */
113   protected String sqlStatement = "";
114
115   /**
116    * size of LoggingEvent buffer before writting to the database.
117    * Default is 1.
118    */
119   protected int bufferSize = 1;
120
121   /**
122    * ArrayList holding the buffer of Logging Events.
123    */
124   protected ArrayList buffer;
125
126   /**
127    * Helper object for clearing out the buffer
128    */
129   protected ArrayList removes;
130   
131   private boolean locationInfo = false;
132
133   public JDBCAppender() {
134     super();
135     buffer = new ArrayList(bufferSize);
136     removes = new ArrayList(bufferSize);
137   }
138
139   /**
140    * Gets whether the location of the logging request call
141    * should be captured.
142    *
143    * @since 1.2.16
144    * @return the current value of the <b>LocationInfo</b> option.
145    */
146   public boolean getLocationInfo() {
147     return locationInfo;
148   }
149   
150   /**
151    * The <b>LocationInfo</b> option takes a boolean value. By default, it is
152    * set to false which means there will be no effort to extract the location
153    * information related to the event. As a result, the event that will be
154    * ultimately logged will likely to contain the wrong location information
155    * (if present in the log format).
156    * <p/>
157    * <p/>
158    * Location information extraction is comparatively very slow and should be
159    * avoided unless performance is not a concern.
160    * </p>
161    * @since 1.2.16
162    * @param flag true if location information should be extracted.
163    */
164   public void setLocationInfo(final boolean flag) {
165     locationInfo = flag;
166   }
167   
168
169   /**
170    * Adds the event to the buffer.  When full the buffer is flushed.
171    */
172   public void append(LoggingEvent event) {
173     event.getNDC();
174     event.getThreadName();
175     // Get a copy of this thread's MDC.
176     event.getMDCCopy();
177     if (locationInfo) {
178       event.getLocationInformation();
179     }
180     event.getRenderedMessage();
181     event.getThrowableStrRep();
182     buffer.add(event);
183
184     if (buffer.size() >= bufferSize) {
185         flushBuffer();
186     }
187   }
188
189   /**
190    * By default getLogStatement sends the event to the required Layout object.
191    * The layout will format the given pattern into a workable SQL string.
192    *
193    * Overriding this provides direct access to the LoggingEvent
194    * when constructing the logging statement.
195    *
196    */
197   protected String getLogStatement(LoggingEvent event) {
198     return getLayout().format(event);
199   }
200
201   /**
202    *
203    * Override this to provide an alertnate method of getting
204    * connections (such as caching).  One method to fix this is to open
205    * connections at the start of flushBuffer() and close them at the
206    * end.  I use a connection pool outside of JDBCAppender which is
207    * accessed in an override of this method.
208    * */
209   protected void execute(String sql) throws SQLException {
210
211     Connection con = null;
212     Statement stmt = null;
213
214     try {
215         con = getConnection();
216
217         stmt = con.createStatement();
218         stmt.executeUpdate(sql);
219     } finally {
220         if(stmt != null) {
221             stmt.close();
222         }
223         closeConnection(con);
224     }
225
226     //System.out.println("Execute: " + sql);
227   }
228
229
230   /**
231    * Override this to return the connection to a pool, or to clean up the
232    * resource.
233    *
234    * The default behavior holds a single connection open until the appender
235    * is closed (typically when garbage collected).
236    */
237   protected void closeConnection(Connection con) {
238   }
239
240   /**
241    * Override this to link with your connection pooling system.
242    *
243    * By default this creates a single connection which is held open
244    * until the object is garbage collected.
245    */
246   protected Connection getConnection() throws SQLException {
247       if (!DriverManager.getDrivers().hasMoreElements()) {
248         setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
249     }
250
251       if (connection == null) {
252         connection = DriverManager.getConnection(databaseURL, databaseUser,
253                                         databasePassword);
254       }
255
256       return connection;
257   }
258
259   /**
260    * Closes the appender, flushing the buffer first then closing the default
261    * connection if it is open.
262    */
263   public void close()
264   {
265     flushBuffer();
266
267     try {
268       if (connection != null && !connection.isClosed()) {
269         connection.close();
270     }
271     } catch (SQLException e) {
272         errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
273     }
274     this.closed = true;
275   }
276
277   /**
278    * loops through the buffer of LoggingEvents, gets a
279    * sql string from getLogStatement() and sends it to execute().
280    * Errors are sent to the errorHandler.
281    *
282    * If a statement fails the LoggingEvent stays in the buffer!
283    */
284   public void flushBuffer() {
285     //Do the actual logging
286     removes.ensureCapacity(buffer.size());
287     for (Iterator i = buffer.iterator(); i.hasNext();) {
288       LoggingEvent logEvent = (LoggingEvent)i.next();
289       try {
290             String sql = getLogStatement(logEvent);
291             execute(sql);
292       }
293       catch (SQLException e) {
294             errorHandler.error("Failed to excute sql", e,
295                            ErrorCode.FLUSH_FAILURE);
296       } finally {
297         removes.add(logEvent);
298       }
299     }
300     
301     // remove from the buffer any events that were reported
302     buffer.removeAll(removes);
303     
304     // clear the buffer of reported events
305     removes.clear();
306   }
307
308
309   /** closes the appender before disposal */
310   public void finalize() {
311     close();
312   }
313
314
315   /**
316    * JDBCAppender requires a layout.
317    * */
318   public boolean requiresLayout() {
319     return true;
320   }
321
322
323   /**
324    *
325    */
326   public void setSql(String sql) {
327     sqlStatement = sql;
328     if (getLayout() == null) {
329         this.setLayout(new PatternLayout(sql));
330     }
331     else {
332         ((PatternLayout)getLayout()).setConversionPattern(sql);
333     }
334   }
335
336
337   /**
338    * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
339    */
340   public String getSql() {
341     return sqlStatement;
342   }
343
344
345   public void setUser(String user) {
346     databaseUser = user;
347   }
348
349
350   public void setURL(String url) {
351     databaseURL = url;
352   }
353
354
355   public void setPassword(String password) {
356     databasePassword = password;
357   }
358
359
360   public void setBufferSize(int newBufferSize) {
361     bufferSize = newBufferSize;
362     buffer.ensureCapacity(bufferSize);
363     removes.ensureCapacity(bufferSize);
364   }
365
366
367   public String getUser() {
368     return databaseUser;
369   }
370
371
372   public String getURL() {
373     return databaseURL;
374   }
375
376
377   public String getPassword() {
378     return databasePassword;
379   }
380
381
382   public int getBufferSize() {
383     return bufferSize;
384   }
385
386
387   /**
388    * Ensures that the given driver class has been loaded for sql connection
389    * creation.
390    */
391   public void setDriver(String driverClass) {
392     try {
393       Class.forName(driverClass);
394     } catch (Exception e) {
395       errorHandler.error("Failed to load driver", e,
396                          ErrorCode.GENERIC_FAILURE);
397     }
398   }
399 }
400