Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/objectstyle/cayenne/access/QueryLogger.java


1   /* ====================================================================
2    * 
3    * The ObjectStyle Group Software License, Version 1.0 
4    *
5    * Copyright (c) 2002-2003 The ObjectStyle Group 
6    * and individual authors of the software.  All rights reserved.
7    *
8    * Redistribution and use in source and binary forms, with or without
9    * modification, are permitted provided that the following conditions
10   * are met:
11   *
12   * 1. Redistributions of source code must retain the above copyright
13   *    notice, this list of conditions and the following disclaimer. 
14   *
15   * 2. Redistributions in binary form must reproduce the above copyright
16   *    notice, this list of conditions and the following disclaimer in
17   *    the documentation and/or other materials provided with the
18   *    distribution.
19   *
20   * 3. The end-user documentation included with the redistribution, if
21   *    any, must include the following acknowlegement:  
22   *       "This product includes software developed by the 
23   *        ObjectStyle Group (http://objectstyle.org/)."
24   *    Alternately, this acknowlegement may appear in the software itself,
25   *    if and wherever such third-party acknowlegements normally appear.
26   *
27   * 4. The names "ObjectStyle Group" and "Cayenne" 
28   *    must not be used to endorse or promote products derived
29   *    from this software without prior written permission. For written 
30   *    permission, please contact andrus@objectstyle.org.
31   *
32   * 5. Products derived from this software may not be called "ObjectStyle"
33   *    nor may "ObjectStyle" appear in their names without prior written
34   *    permission of the ObjectStyle Group.
35   *
36   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39   * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47   * SUCH DAMAGE.
48   * ====================================================================
49   *
50   * This software consists of voluntary contributions made by many
51   * individuals on behalf of the ObjectStyle Group.  For more
52   * information on the ObjectStyle Group, please see
53   * <http://objectstyle.org/>.
54   *
55   */
56  package org.objectstyle.cayenne.access;
57  
58  import java.sql.SQLException;
59  import java.util.List;
60  
61  import org.apache.log4j.Level;
62  import org.apache.log4j.Logger;
63  import org.objectstyle.cayenne.conn.DataSourceInfo;
64  import org.objectstyle.cayenne.query.Query;
65  import org.objectstyle.cayenne.util.Util;
66  
67  /** 
68   * A QueryLogger is intended to log special events during query executions.
69   * This includes generated SQL statements, result counts, connection events etc.
70   * It is a single consistent place for that kind of logging and should be used
71   * by all Cayenne classes that work with the database directly.
72   * 
73   * <p>In many cases it is important to use this class as opposed to logging 
74   * from the class that performs a particular operation, since QueryLogger 
75   * will generate consistently formatted logs that are easy to analyze
76   * and turn on/off.</p>
77   * 
78   * <p><i>For more information see <a href="../../../../../../userguide/index.html"
79   * target="_top">Cayenne User Guide.</a></i></p>
80   * 
81   * @author Andrei Adamchik
82   */
83  public class QueryLogger {
84      private static Logger logObj = Logger.getLogger(QueryLogger.class);
85  
86      public static final Level DEFAULT_LOG_LEVEL = Query.DEFAULT_LOG_LEVEL;
87      public static final int TRIM_VALUES_THRESHOLD = 300;
88  
89      /** 
90       * Utility method that appends SQL literal for the specified object to the buffer.
91      *
92      * <p>Note: this method is not intended to build SQL queries, rather this is used 
93      * in logging routines only. In particular it will trim large values to avoid 
94      * flooding the logs.</p> 
95      *
96      *  @param buf buffer to append value
97      *  @param anObject object to be transformed to SQL literal.
98      */
99      public static void sqlLiteralForObject(StringBuffer buf, Object anObject) {
100         // 0. Null
101         if (anObject == null) {
102             buf.append("NULL");
103         }
104         // 1. String literal
105         else if (anObject instanceof String) {
106             buf.append('\'');
107             // lets escape quotes
108             String literal = (String) anObject;
109             if (literal.length() > TRIM_VALUES_THRESHOLD) {
110                 literal = literal.substring(0, TRIM_VALUES_THRESHOLD) + "...";
111             }
112 
113             int curPos = 0;
114             int endPos = 0;
115 
116             while ((endPos = literal.indexOf('\'', curPos)) >= 0) {
117                 buf.append(literal.substring(curPos, endPos + 1)).append('\'');
118                 curPos = endPos + 1;
119             }
120 
121             if (curPos < literal.length())
122                 buf.append(literal.substring(curPos));
123 
124             buf.append('\'');
125         }
126         // 2. Numeric literal
127         else if (anObject instanceof Number) {
128             // process numeric value (do something smart in the future)
129             buf.append(anObject);
130         }
131         // 3. Date
132         else if (anObject instanceof java.sql.Date) {
133             buf.append('\'').append(anObject).append('\'');
134         }
135         // 4. Date
136         else if (anObject instanceof java.sql.Time) {
137             buf.append('\'').append(anObject).append('\'');
138         }
139         // 5 Date
140         else if (anObject instanceof java.util.Date) {
141             long time = ((java.util.Date) anObject).getTime();
142             buf.append('\'').append(new java.sql.Timestamp(time)).append('\'');
143         }
144         // 6. byte[]
145         else if (anObject instanceof byte[]) {
146             buf.append("< ");
147             byte[] b = (byte[]) anObject;
148 
149             int len = b.length;
150             boolean trimming = false;
151             if (len > TRIM_VALUES_THRESHOLD) {
152                 len = TRIM_VALUES_THRESHOLD;
153                 trimming = true;
154             }
155 
156             for (int i = 0; i < len; i++) {
157                 appendFormattedByte(buf, b[i]);
158                 buf.append(' ');
159             }
160 
161             if (trimming) {
162                 buf.append("...");
163             }
164             buf.append('>');
165         } else if (anObject instanceof Boolean) {
166             buf.append('\'').append(anObject).append('\'');
167         } else {
168             // unknown
169             buf
170                 .append("[")
171                 .append(anObject.getClass().getName())
172                 .append(": ")
173                 .append(anObject)
174                 .append("]");
175         }
176     }
177 
178     /**
179      * Prints a byte value to a StringBuffer as a double digit hex value.
180      */
181     protected static void appendFormattedByte(
182         StringBuffer buf,
183         byte byteValue) {
184 
185         String hexDecode = "0123456789ABCDEF";
186 
187         buf.append(hexDecode.charAt(byteValue >>> 4));
188         buf.append(hexDecode.charAt(byteValue & 0x0F));
189     }
190 
191     /** 
192      * Returns current logging level.
193      */
194     public static Level getLoggingLevel() {
195         Level level = logObj.getLevel();
196         return (level != null) ? level : DEFAULT_LOG_LEVEL;
197     }
198 
199     /**
200      * Sets logging level.
201      */
202     public static void setLoggingLevel(Level level) {
203         logObj.setLevel(level);
204     }
205 
206     /**
207      * Logs database connection event using container data source.
208      */
209     public static void logConnect(Level logLevel, String dataSource) {
210         if (isLoggable(logLevel)) {
211             logObj.log(logLevel, "Connecting. JNDI path: " + dataSource);
212         }
213     }
214 
215     public static void logConnect(
216         Level logLevel,
217         String url,
218         String userName,
219         String password) {
220         if (isLoggable(logLevel)) {
221             StringBuffer buf = new StringBuffer("Opening connection: ");
222 
223             // append URL on the same line to make log somewhat grep-friendly
224             buf.append(url);
225             buf.append("\n\tLogin: ").append(userName);
226             buf.append("\n\tPassword: *******");
227 
228             logObj.log(logLevel, buf.toString());
229         }
230     }
231 
232     /**
233      * Logs database connection event.
234      */
235     public static void logPoolCreated(Level logLevel, DataSourceInfo dsi) {
236         if (isLoggable(logLevel)) {
237             StringBuffer buf = new StringBuffer("Created connection pool: ");
238 
239             if (dsi != null) {
240                 // append URL on the same line to make log somewhat grep-friendly
241                 buf.append(dsi.getDataSourceUrl());
242 
243                 if (dsi.getAdapterClassName() != null) {
244                     buf.append("\n\tCayenne DbAdapter: ").append(
245                         dsi.getAdapterClassName());
246                 }
247 
248                 buf.append("\n\tDriver class: ").append(dsi.getJdbcDriver());
249 
250                 if (dsi.getMinConnections() >= 0) {
251                     buf.append("\n\tMin. connections in the pool: ").append(
252                         dsi.getMinConnections());
253                 }
254                 if (dsi.getMaxConnections() >= 0) {
255                     buf.append("\n\tMax. connections in the pool: ").append(
256                         dsi.getMaxConnections());
257                 }
258 
259                 buf.append("\n\tLogin: ").append(dsi.getUserName());
260                 buf.append("\n\tPassword: *******");
261             } else {
262                 buf.append(" pool information unavailable");
263             }
264 
265             logObj.log(logLevel, buf.toString());
266         }
267     }
268 
269     public static void logConnectSuccess(Level logLevel) {
270         logObj.log(logLevel, "+++ Connecting: SUCCESS.");
271     }
272 
273     public static void logConnectFailure(Level logLevel, Throwable th) {
274         logObj.log(logLevel, "*** Connecting: FAILURE.", th);
275     }
276 
277     public static void logQuery(Level logLevel, String queryStr, List params) {
278         logQuery(logLevel, queryStr, params, -1);
279     }
280 
281     /** 
282      * Log query content using Log4J Category with "INFO" priority.
283      *
284      * @param queryStr Query SQL string
285      * @param params optional list of query parameters that are used when 
286      * executing query in prepared statement.
287      */
288     public static void logQuery(
289         Level logLevel,
290         String queryStr,
291         List params,
292         long time) {
293         if (isLoggable(logLevel)) {
294             StringBuffer buf = new StringBuffer(queryStr);
295             if (params != null && params.size() > 0) {
296                 buf.append(" [bind: ");
297                 sqlLiteralForObject(buf, params.get(0));
298 
299                 int len = params.size();
300                 for (int i = 1; i < len; i++) {
301                     buf.append(", ");
302                     sqlLiteralForObject(buf, params.get(i));
303                 }
304 
305                 buf.append(']');
306             }
307 
308             // log preparation time only if it is something significant
309             if (time > 10) {
310                 buf.append(" - prepared in ").append(time).append(" ms.");
311             }
312 
313             logObj.log(logLevel, buf.toString());
314         }
315     }
316 
317     public static void logQueryParameters(
318         Level logLevel,
319         String label,
320         List parameters) {
321 
322         if (isLoggable(logLevel) && parameters.size() > 0) {
323             int len = parameters.size();
324             StringBuffer buf = new StringBuffer("[");
325             buf.append(label).append(": ");
326 
327             sqlLiteralForObject(buf, parameters.get(0));
328 
329             for (int i = 1; i < len; i++) {
330                 buf.append(", ");
331                 sqlLiteralForObject(buf, parameters.get(i));
332             }
333 
334             buf.append(']');
335             logObj.log(logLevel, buf.toString());
336         }
337     }
338 
339     public static void logSelectCount(Level logLevel, int count) {
340         logSelectCount(logLevel, count, -1);
341     }
342 
343     public static void logSelectCount(Level logLevel, int count, long time) {
344         if (isLoggable(logLevel)) {
345             StringBuffer buf = new StringBuffer();
346 
347             if (count == 1) {
348                 buf.append("=== returned 1 row.");
349             } else {
350                 buf.append("=== returned ").append(count).append(" rows.");
351             }
352 
353             if (time >= 0) {
354                 buf.append(" - took ").append(time).append(" ms.");
355             }
356 
357             logObj.log(logLevel, buf.toString());
358         }
359     }
360 
361     public static void logUpdateCount(Level logLevel, int count) {
362         if (isLoggable(logLevel)) {
363             String countStr =
364                 (count == 1)
365                     ? "=== updated 1 row."
366                     : "=== updated " + count + " rows.";
367             logObj.log(logLevel, countStr);
368         }
369     }
370 
371     public static void logCommitTransaction(Level logLevel) {
372         logObj.log(logLevel, "+++ transaction committed.");
373     }
374 
375     public static void logRollbackTransaction(Level logLevel) {
376         logObj.log(logLevel, "*** transaction rolled back.");
377     }
378 
379     public static void logQueryError(Level logLevel, Throwable th) {
380         if (th != null) {
381             th = Util.unwindException(th);
382         }
383 
384         logObj.log(logLevel, "*** error.", th);
385 
386         if (th instanceof SQLException) {
387             SQLException sqlException = ((SQLException) th).getNextException();
388             while (sqlException != null) {
389                 logObj.log(logLevel, "*** nested SQL error.", sqlException);
390                 sqlException = sqlException.getNextException();
391             }
392         }
393     }
394 
395     public static void logQueryStart(Level logLevel, int count) {
396         if (isLoggable(logLevel)) {
397             String countStr =
398                 (count == 1)
399                     ? "--- will run 1 query."
400                     : "--- will run " + count + " queries.";
401             logObj.log(logLevel, countStr);
402         }
403     }
404 
405     public static boolean isLoggable(Level logLevel) {
406         return logObj.isEnabledFor(logLevel);
407     }
408 
409 }