KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > valves > JDBCAccessLogValve


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
18
19 package org.apache.catalina.valves;
20
21
22 import java.io.IOException JavaDoc;
23 import java.sql.Connection JavaDoc;
24 import java.sql.Driver JavaDoc;
25 import java.sql.PreparedStatement JavaDoc;
26 import java.sql.SQLException JavaDoc;
27 import java.sql.Timestamp JavaDoc;
28 import java.util.Properties JavaDoc;
29
30 import javax.servlet.ServletException JavaDoc;
31
32 import org.apache.catalina.Lifecycle;
33 import org.apache.catalina.LifecycleException;
34 import org.apache.catalina.LifecycleListener;
35 import org.apache.catalina.connector.Request;
36 import org.apache.catalina.connector.Response;
37 import org.apache.catalina.util.LifecycleSupport;
38 import org.apache.catalina.util.StringManager;
39
40 /**
41  * <p>
42  * This Tomcat extension logs server access directly to a database, and can
43  * be used instead of the regular file-based access log implemented in
44  * AccessLogValve.
45  * To use, copy into the server/classes directory of the Tomcat installation
46  * and configure in server.xml as:
47  * <pre>
48  * &lt;Valve className="org.apache.catalina.valves.JDBCAccessLogValve"
49  * driverName="<i>your_jdbc_driver</i>"
50  * connectionURL="<i>your_jdbc_url</i>"
51  * pattern="combined" resolveHosts="false"
52  * /&gt;
53  * </pre>
54  * </p>
55  * <p>
56  * Many parameters can be configured, such as the database connection (with
57  * <code>driverName</code> and <code>connectionURL</code>),
58  * the table name (<code>tableName</code>)
59  * and the field names (corresponding to the get/set method names).
60  * The same options as AccessLogValve are supported, such as
61  * <code>resolveHosts</code> and <code>pattern</code> ("common" or "combined"
62  * only).
63  * </p>
64  * <p>
65  * When Tomcat is started, a database connection (with autoReconnect option)
66  * is created and used for all the log activity. When Tomcat is shutdown, the
67  * database connection is closed.
68  * This logger can be used at the level of the Engine context (being shared
69  * by all the defined hosts) or the Host context (one instance of the logger
70  * per host, possibly using different databases).
71  * </p>
72  * <p>
73  * The database table can be created with the following command:
74  * </p>
75  * <pre>
76  * CREATE TABLE access (
77  * id INT UNSIGNED AUTO_INCREMENT NOT NULL,
78  * remoteHost CHAR(15) NOT NULL,
79  * userName CHAR(15),
80  * timestamp TIMESTAMP NOT NULL,
81  * virtualHost VARCHAR(64) NOT NULL,
82  * method VARCHAR(8) NOT NULL,
83  * query VARCHAR(255) NOT NULL,
84  * status SMALLINT UNSIGNED NOT NULL,
85  * bytes INT UNSIGNED NOT NULL,
86  * referer VARCHAR(128),
87  * userAgent VARCHAR(128),
88  * PRIMARY KEY (id),
89  * INDEX (timestamp),
90  * INDEX (remoteHost),
91  * INDEX (virtualHost),
92  * INDEX (query),
93  * INDEX (userAgent)
94  * );
95  * </pre>
96  * <p>
97  * If the table is created as above, its name and the field names don't need
98  * to be defined.
99  * </p>
100  * <p>
101  * If the request method is "common", only these fields are used:
102  * <code>remoteHost, user, timeStamp, query, status, bytes</code>
103  * </p>
104  * <p>
105  * <i>TO DO: provide option for excluding logging of certain MIME types.</i>
106  * </p>
107  *
108  * @author Andre de Jesus
109  * @author Peter Rossbach
110  */

111
112 public final class JDBCAccessLogValve
113     extends ValveBase
114     implements Lifecycle {
115
116     // ----------------------------------------------------------- Constructors
117

118
119     /**
120      * Class constructor. Initializes the fields with the default values.
121      * The defaults are:
122      * <pre>
123      * driverName = null;
124      * connectionURL = null;
125      * tableName = "access";
126      * remoteHostField = "remoteHost";
127      * userField = "userName";
128      * timestampField = "timestamp";
129      * virtualHostField = "virtualHost";
130      * methodField = "method";
131      * queryField = "query";
132      * statusField = "status";
133      * bytesField = "bytes";
134      * refererField = "referer";
135      * userAgentField = "userAgent";
136      * pattern = "common";
137      * resolveHosts = false;
138      * </pre>
139      */

140     public JDBCAccessLogValve() {
141         super();
142         driverName = null;
143         connectionURL = null;
144         tableName = "access";
145         remoteHostField = "remoteHost";
146         userField = "userName";
147         timestampField = "timestamp";
148         virtualHostField = "virtualHost";
149         methodField = "method";
150         queryField = "query";
151         statusField = "status";
152         bytesField = "bytes";
153         refererField = "referer";
154         userAgentField = "userAgent";
155         pattern = "common";
156         resolveHosts = false;
157         conn = null;
158         ps = null;
159         currentTimeMillis = new java.util.Date JavaDoc().getTime();
160     }
161
162
163     // ----------------------------------------------------- Instance Variables
164

165
166    /**
167      * The connection username to use when trying to connect to the database.
168      */

169     protected String JavaDoc connectionName = null;
170
171
172     /**
173      * The connection URL to use when trying to connect to the database.
174      */

175     protected String JavaDoc connectionPassword = null;
176
177    /**
178      * Instance of the JDBC Driver class we use as a connection factory.
179      */

180     protected Driver JavaDoc driver = null;
181
182
183     private String JavaDoc driverName;
184     private String JavaDoc connectionURL;
185     private String JavaDoc tableName;
186     private String JavaDoc remoteHostField;
187     private String JavaDoc userField;
188     private String JavaDoc timestampField;
189     private String JavaDoc virtualHostField;
190     private String JavaDoc methodField;
191     private String JavaDoc queryField;
192     private String JavaDoc statusField;
193     private String JavaDoc bytesField;
194     private String JavaDoc refererField;
195     private String JavaDoc userAgentField;
196     private String JavaDoc pattern;
197     private boolean resolveHosts;
198
199
200     private Connection JavaDoc conn;
201     private PreparedStatement JavaDoc ps;
202
203
204     private long currentTimeMillis;
205
206
207     /**
208      * The descriptive information about this implementation.
209      */

210     protected static String JavaDoc info =
211         "org.apache.catalina.valves.JDBCAccessLogValve/1.1";
212
213
214     /**
215      * The lifecycle event support for this component.
216      */

217     protected LifecycleSupport lifecycle = new LifecycleSupport(this);
218
219
220     /**
221      * The string manager for this package.
222      */

223     private StringManager sm = StringManager.getManager(Constants.Package);
224
225
226     /**
227      * Has this component been started yet?
228      */

229     private boolean started = false;
230
231
232     // ------------------------------------------------------------- Properties
233

234     /**
235      * Return the username to use to connect to the database.
236      *
237      */

238     public String JavaDoc getConnectionName() {
239         return connectionName;
240     }
241
242     /**
243      * Set the username to use to connect to the database.
244      *
245      * @param connectionName Username
246      */

247     public void setConnectionName(String JavaDoc connectionName) {
248         this.connectionName = connectionName;
249     }
250
251     /**
252      * Sets the database driver name.
253      *
254      * @param driverName The complete name of the database driver class.
255      */

256     public void setDriverName(String JavaDoc driverName) {
257         this.driverName = driverName;
258     }
259
260    /**
261      * Return the password to use to connect to the database.
262      *
263      */

264     public String JavaDoc getConnectionPassword() {
265         return connectionPassword;
266     }
267
268     /**
269      * Set the password to use to connect to the database.
270      *
271      * @param connectionPassword User password
272      */

273     public void setConnectionPassword(String JavaDoc connectionPassword) {
274         this.connectionPassword = connectionPassword;
275     }
276
277     /**
278      * Sets the JDBC URL for the database where the log is stored.
279      *
280      * @param connectionURL The JDBC URL of the database.
281      */

282     public void setConnectionURL(String JavaDoc connectionURL) {
283         this.connectionURL = connectionURL;
284     }
285
286
287     /**
288      * Sets the name of the table where the logs are stored.
289      *
290      * @param tableName The name of the table.
291      */

292     public void setTableName(String JavaDoc tableName) {
293         this.tableName = tableName;
294     }
295
296
297     /**
298      * Sets the name of the field containing the remote host.
299      *
300      * @param remoteHostField The name of the remote host field.
301      */

302     public void setRemoteHostField(String JavaDoc remoteHostField) {
303         this.remoteHostField = remoteHostField;
304     }
305
306
307     /**
308      * Sets the name of the field containing the remote user name.
309      *
310      * @param userField The name of the remote user field.
311      */

312     public void setUserField(String JavaDoc userField) {
313         this.userField = userField;
314     }
315
316
317     /**
318      * Sets the name of the field containing the server-determined timestamp.
319      *
320      * @param timestampField The name of the server-determined timestamp field.
321      */

322     public void setTimestampField(String JavaDoc timestampField) {
323         this.timestampField = timestampField;
324     }
325
326
327     /**
328      * Sets the name of the field containing the virtual host information
329      * (this is in fact the server name).
330      *
331      * @param virtualHostField The name of the virtual host field.
332      */

333     public void setVirtualHostField(String JavaDoc virtualHostField) {
334         this.virtualHostField = virtualHostField;
335     }
336
337
338     /**
339      * Sets the name of the field containing the HTTP request method.
340      *
341      * @param methodField The name of the HTTP request method field.
342      */

343     public void setMethodField(String JavaDoc methodField) {
344         this.methodField = methodField;
345     }
346
347
348     /**
349      * Sets the name of the field containing the URL part of the HTTP query.
350      *
351      * @param queryField The name of the field containing the URL part of
352      * the HTTP query.
353      */

354     public void setQueryField(String JavaDoc queryField) {
355         this.queryField = queryField;
356     }
357
358
359   /**
360    * Sets the name of the field containing the HTTP response status code.
361    *
362    * @param statusField The name of the HTTP response status code field.
363    */

364     public void setStatusField(String JavaDoc statusField) {
365         this.statusField = statusField;
366     }
367
368
369     /**
370      * Sets the name of the field containing the number of bytes returned.
371      *
372      * @param bytesField The name of the returned bytes field.
373      */

374     public void setBytesField(String JavaDoc bytesField) {
375         this.bytesField = bytesField;
376     }
377
378
379     /**
380      * Sets the name of the field containing the referer.
381      *
382      * @param refererField The referer field name.
383      */

384     public void setRefererField(String JavaDoc refererField) {
385         this.refererField = refererField;
386     }
387
388
389     /**
390      * Sets the name of the field containing the user agent.
391      *
392      * @param userAgentField The name of the user agent field.
393      */

394     public void setUserAgentField(String JavaDoc userAgentField) {
395         this.userAgentField = userAgentField;
396     }
397
398
399     /**
400      * Sets the logging pattern. The patterns supported correspond to the
401      * file-based "common" and "combined". These are translated into the use
402      * of tables containing either set of fields.
403      * <P><I>TO DO: more flexible field choices.</I></P>
404      *
405      * @param pattern The name of the logging pattern.
406      */

407     public void setPattern(String JavaDoc pattern) {
408         this.pattern = pattern;
409     }
410
411
412     /**
413      * Determines whether IP host name resolution is done.
414      *
415      * @param resolveHosts "true" or "false", if host IP resolution
416      * is desired or not.
417      */

418     public void setResolveHosts(String JavaDoc resolveHosts) {
419         this.resolveHosts = new Boolean JavaDoc(resolveHosts).booleanValue();
420     }
421
422
423     // --------------------------------------------------------- Public Methods
424

425
426     /**
427      * This method is invoked by Tomcat on each query.
428      *
429      * @param request The Request object.
430      * @param response The Response object.
431      *
432      * @exception IOException Should not be thrown.
433      * @exception ServletException Database SQLException is wrapped
434      * in a ServletException.
435      */

436     public void invoke(Request request, Response response)
437         throws IOException JavaDoc, ServletException JavaDoc {
438
439         getNext().invoke(request, response);
440
441         String JavaDoc remoteHost = "";
442         if(resolveHosts)
443             remoteHost = request.getRemoteHost();
444         else
445             remoteHost = request.getRemoteAddr();
446         String JavaDoc user = "";
447         if(request != null)
448             user = request.getRemoteUser();
449         String JavaDoc query="";
450         if(request != null)
451             query = request.getRequestURI();
452         int bytes = response.getContentCount();
453         if(bytes < 0)
454             bytes = 0;
455         int status = response.getStatus();
456         if (pattern.equals("combined")) {
457                 String JavaDoc virtualHost = "";
458                 if(request != null)
459                     virtualHost = request.getServerName();
460                 String JavaDoc method = "";
461                 if(request != null)
462                     method = request.getMethod();
463                 String JavaDoc referer = "";
464                 if(request != null)
465                     referer = request.getHeader("referer");
466                 String JavaDoc userAgent = "";
467                 if(request != null)
468                     userAgent = request.getHeader("user-agent");
469         }
470         synchronized (this) {
471           int numberOfTries = 2;
472           while (numberOfTries>0) {
473             try {
474                 open();
475     
476                 ps.setString(1, remoteHost);
477                 ps.setString(2, user);
478                 ps.setTimestamp(3, new Timestamp JavaDoc(getCurrentTimeMillis()));
479                 ps.setString(4, query);
480                 ps.setInt(5, status);
481                 ps.setInt(6, bytes);
482                 if (pattern.equals("combined")) {
483      
484                       String JavaDoc virtualHost = "";
485                       if(request != null)
486                          virtualHost = request.getServerName();
487                       String JavaDoc method = "";
488                       if(request != null)
489                          method = request.getMethod();
490                       String JavaDoc referer = "";
491                       if(request != null)
492                          referer = request.getHeader("referer");
493                       String JavaDoc userAgent = "";
494                       if(request != null)
495                          userAgent = request.getHeader("user-agent");
496                       ps.setString(7, virtualHost);
497                       ps.setString(8, method);
498                       ps.setString(9, referer);
499                       ps.setString(10, userAgent);
500                 }
501                 ps.executeUpdate();
502                 return;
503               } catch (SQLException JavaDoc e) {
504                 // Log the problem for posterity
505
container.getLogger().error(sm.getString("jdbcAccessLogValve.exception"), e);
506
507                 // Close the connection so that it gets reopened next time
508
if (conn != null)
509                     close();
510               }
511               numberOfTries--;
512            }
513         }
514
515     }
516
517
518     /**
519      * Adds a Lifecycle listener.
520      *
521      * @param listener The listener to add.
522      */

523     public void addLifecycleListener(LifecycleListener listener) {
524
525         lifecycle.addLifecycleListener(listener);
526
527     }
528
529
530     /**
531      * Get the lifecycle listeners associated with this lifecycle. If this
532      * Lifecycle has no listeners registered, a zero-length array is returned.
533      */

534     public LifecycleListener[] findLifecycleListeners() {
535
536         return lifecycle.findLifecycleListeners();
537
538     }
539
540
541     /**
542      * Removes a Lifecycle listener.
543      *
544      * @param listener The listener to remove.
545      */

546     public void removeLifecycleListener(LifecycleListener listener) {
547
548         lifecycle.removeLifecycleListener(listener);
549
550     }
551
552     /**
553      * Open (if necessary) and return a database connection for use by
554      * this AccessLogValve.
555      *
556      * @exception SQLException if a database error occurs
557      */

558     protected void open() throws SQLException JavaDoc {
559
560         // Do nothing if there is a database connection already open
561
if (conn != null)
562             return ;
563
564         // Instantiate our database driver if necessary
565
if (driver == null) {
566             try {
567                 Class JavaDoc clazz = Class.forName(driverName);
568                 driver = (Driver JavaDoc) clazz.newInstance();
569             } catch (Throwable JavaDoc e) {
570                 throw new SQLException JavaDoc(e.getMessage());
571             }
572         }
573
574         // Open a new connection
575
Properties JavaDoc props = new Properties JavaDoc();
576         props.put("autoReconnect", "true");
577         if (connectionName != null)
578             props.put("user", connectionName);
579         if (connectionPassword != null)
580             props.put("password", connectionPassword);
581         conn = driver.connect(connectionURL, props);
582         conn.setAutoCommit(true);
583         if (pattern.equals("common")) {
584                 ps = conn.prepareStatement
585                     ("INSERT INTO " + tableName + " ("
586                      + remoteHostField + ", " + userField + ", "
587                      + timestampField +", " + queryField + ", "
588                      + statusField + ", " + bytesField
589                      + ") VALUES(?, ?, ?, ?, ?, ?)");
590         } else if (pattern.equals("combined")) {
591                 ps = conn.prepareStatement
592                     ("INSERT INTO " + tableName + " ("
593                      + remoteHostField + ", " + userField + ", "
594                      + timestampField + ", " + queryField + ", "
595                      + statusField + ", " + bytesField + ", "
596                      + virtualHostField + ", " + methodField + ", "
597                      + refererField + ", " + userAgentField
598                      + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
599         }
600     }
601
602     /**
603      * Close the specified database connection.
604      */

605     protected void close() {
606
607         // Do nothing if the database connection is already closed
608
if (conn == null)
609             return;
610
611         // Close our prepared statements (if any)
612
try {
613             ps.close();
614         } catch (Throwable JavaDoc f) {
615             ;
616         }
617         this.ps = null;
618
619
620
621         // Close this database connection, and log any errors
622
try {
623             conn.close();
624         } catch (SQLException JavaDoc e) {
625             container.getLogger().error(sm.getString("jdbcAccessLogValeve.close"), e); // Just log it here
626
} finally {
627            this.conn = null;
628         }
629
630     }
631     /**
632      * Invoked by Tomcat on startup. The database connection is set here.
633      *
634      * @exception LifecycleException Can be thrown on lifecycle
635      * inconsistencies or on database errors (as a wrapped SQLException).
636      */

637     public void start() throws LifecycleException {
638
639         if (started)
640             throw new LifecycleException
641                 (sm.getString("accessLogValve.alreadyStarted"));
642         lifecycle.fireLifecycleEvent(START_EVENT, null);
643         started = true;
644
645         try {
646             open() ;
647         } catch (SQLException JavaDoc e) {
648             throw new LifecycleException(e);
649         }
650
651     }
652
653
654     /**
655      * Invoked by tomcat on shutdown. The database connection is closed here.
656      *
657      * @exception LifecycleException Can be thrown on lifecycle
658      * inconsistencies or on database errors (as a wrapped SQLException).
659      */

660     public void stop() throws LifecycleException {
661
662         if (!started)
663             throw new LifecycleException
664                 (sm.getString("accessLogValve.notStarted"));
665         lifecycle.fireLifecycleEvent(STOP_EVENT, null);
666         started = false;
667         
668         close() ;
669         
670     }
671
672
673     public long getCurrentTimeMillis() {
674         long systime = System.currentTimeMillis();
675         if ((systime - currentTimeMillis) > 1000) {
676             currentTimeMillis = new java.util.Date JavaDoc(systime).getTime();
677         }
678         return currentTimeMillis;
679     }
680
681 }
682
Popular Tags