KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > aop > interceptor > CustomizableTraceInterceptor


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.aop.interceptor;
18
19 import java.util.Set JavaDoc;
20 import java.util.regex.Matcher JavaDoc;
21 import java.util.regex.Pattern JavaDoc;
22
23 import org.aopalliance.intercept.MethodInvocation;
24 import org.apache.commons.logging.Log;
25
26 import org.springframework.core.Constants;
27 import org.springframework.util.Assert;
28 import org.springframework.util.ClassUtils;
29 import org.springframework.util.StopWatch;
30 import org.springframework.util.StringUtils;
31
32 /**
33  * <code>MethodInterceptor</code> implementation that allows for highly customizable
34  * method-level tracing, using placeholders.
35  *
36  * <p>Trace messages are written on method entry, and if the method invocation succeeds
37  * on method exit. If an invocation results in an exception, then an exception message
38  * is written. The contents of these trace messages is fully customizable and special
39  * placeholders are available to allow you to include runtime information in your log
40  * messages. The placeholders available are:
41  *
42  * <p><ul>
43  * <li><code>$[methodName]</code> - replaced with the name of the method being invoked</li>
44  * <li><code>$[targetClassName]</code> - replaced with the name of the class that is
45  * the target of the invocation</li>
46  * <li><code>$[targetClassShortName]</code> - replaced with the short name of the class
47  * that is the target of the invocation</li>
48  * <li><code>$[returnValue]</code> - replaced with the value returned by the invocation</li>
49  * <li><code>$[argumentTypes]</code> - replaced with a comma-separated list of the
50  * short class names of the method arguments</li>
51  * <li><code>$[arguments]</code> - replaced with a comma-separated list of the
52  * <code>String</code> representation of the method arguments</li>
53  * <li><code>$[exception]</code> - replaced with the <code>String</code> representation
54  * of any <code>Throwable</code> raised during the invocation</li>
55  * <li><code>$[invocationTime]</code> - replaced with the time, in milliseconds,
56  * taken by the method invocation</li>
57  * </ul>
58  *
59  * <p>There are restrictions on which placeholders can be used in which messages:
60  * see the individual message properties for details on the valid placeholders.
61  *
62  * <p><b>NOTE: This class requires JDK 1.4 or later.</b> It uses the
63  * <code>java.util.regex</code> package for regular expression matching,
64  * which is only available on JDK 1.4+.
65  *
66  * @author Rob Harrop
67  * @since 1.2
68  * @see #setEnterMessage
69  * @see #setExitMessage
70  * @see #setExceptionMessage
71  * @see SimpleTraceInterceptor
72  */

73 public class CustomizableTraceInterceptor extends AbstractTraceInterceptor {
74
75     /**
76      * The <code>$[methodName]</code> placeholder.
77      * Replaced with the name of the method being invoked.
78      */

79     public static final String JavaDoc PLACEHOLDER_METHOD_NAME = "$[methodName]";
80
81     /**
82      * The <code>$[targetClassName]</code> placeholder.
83      * Replaced with the fully-qualifed name of the <code>Class</code>
84      * of the method invocation target.
85      */

86     public static final String JavaDoc PLACEHOLDER_TARGET_CLASS_NAME = "$[targetClassName]";
87
88     /**
89      * The <code>$[targetClassShortName]</code> placeholder.
90      * Replaced with the short name of the <code>Class</code> of the
91      * method invocation target.
92      */

93     public static final String JavaDoc PLACEHOLDER_TARGET_CLASS_SHORT_NAME = "$[targetClassShortName]";
94
95     /**
96      * The <code>$[returnValue]</code> placeholder.
97      * Replaced with the <code>String</code> representation of the value
98      * returned by the method invocation.
99      */

100     public static final String JavaDoc PLACEHOLDER_RETURN_VALUE = "$[returnValue]";
101
102     /**
103      * The <code>$[argumentTypes]</code> placeholder.
104      * Replaced with a comma-separated list of the argument types for the
105      * method invocation. Argument types are written as short class names.
106      */

107     public static final String JavaDoc PLACEHOLDER_ARGUMENT_TYPES = "$[argumentTypes]";
108
109     /**
110      * The <code>$[arguments]</code> placeholder.
111      * Replaced with a comma separated list of the argument values for the
112      * method invocation. Relies on the <code>toString()</code> method of
113      * each argument type.
114      */

115     public static final String JavaDoc PLACEHOLDER_ARGUMENTS = "$[arguments]";
116
117     /**
118      * The <code>$[exception]</code> placeholder.
119      * Replaced with the <code>String</code> representation of any
120      * <code>Throwable</code> raised during method invocation.
121      */

122     public static final String JavaDoc PLACEHOLDER_EXCEPTION = "$[exception]";
123
124     /**
125      * The <code>$[invocationTime]</code> placeholder.
126      * Replaced with the time taken by the invocation (in milliseconds).
127      */

128     public static final String JavaDoc PLACEHOLDER_INVOCATION_TIME = "$[invocationTime]";
129
130     /**
131      * The default message used for writing method entry messages.
132      */

133     private static final String JavaDoc DEFAULT_ENTER_MESSAGE =
134             "Entering method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]";
135
136     /**
137      * The default message used for writing method exit messages.
138      */

139     private static final String JavaDoc DEFAULT_EXIT_MESSAGE =
140             "Exiting method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]";
141
142     /**
143      * The default method used for writing exception messages.
144      */

145     private static final String JavaDoc DEFAULT_EXCEPTION_MESSAGE =
146             "Exception thrown in method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]";
147
148     /**
149      * The <code>Pattern</code> used to match placeholders.
150      */

151     private static final Pattern JavaDoc PATTERN = Pattern.compile("\\$\\[\\p{Alpha}+\\]");
152
153     /**
154      * The <code>Pattern</code> used to escape regex values in class names -
155      * specifically <code>$</code>.
156      */

157     private static final Pattern JavaDoc ESCAPE_PATTERN = Pattern.compile("\\$");
158
159     /**
160      * The <code>Set</code> of allowed placeholders.
161      */

162     private static final Set JavaDoc ALLOWED_PLACEHOLDERS =
163             new Constants(CustomizableTraceInterceptor.class).getValues("PLACEHOLDER_");
164
165
166     /**
167      * The message for method entry.
168      */

169     private String JavaDoc enterMessage = DEFAULT_ENTER_MESSAGE;
170
171     /**
172      * The message for method exit.
173      */

174     private String JavaDoc exitMessage = DEFAULT_EXIT_MESSAGE;
175
176     /**
177      * The message for exceptions during method execution.
178      */

179     private String JavaDoc exceptionMessage = DEFAULT_EXCEPTION_MESSAGE;
180
181
182     /**
183      * Ses the template used for method entry log messages.
184      * This template can contain any of the following placeholders:
185      * <ul>
186      * <li><code>$[targetClassName]</code></li>
187      * <li><code>$[targetClassShortName]</code></li>
188      * <li><code>$[argumentTypes]</code></li>
189      * <li><code>$[arguments]</code></li>
190      * </ul>
191      * @throws IllegalArgumentException if the message template is empty
192      * or contains any invalid placeholders
193      */

194     public void setEnterMessage(String JavaDoc enterMessage) throws IllegalArgumentException JavaDoc {
195         Assert.hasText(enterMessage, "'enterMessage' must not be empty");
196         checkForInvalidPlaceholders(enterMessage);
197         Assert.doesNotContain(enterMessage, PLACEHOLDER_RETURN_VALUE,
198                 "enterMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]");
199         Assert.doesNotContain(enterMessage, PLACEHOLDER_EXCEPTION,
200                 "enterMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]");
201         Assert.doesNotContain(enterMessage, PLACEHOLDER_INVOCATION_TIME,
202                 "enterMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]");
203         this.enterMessage = enterMessage;
204     }
205
206     /**
207      * Set the template used for method exit log messages.
208      * This template can contain any of the following placeholders:
209      * <ul>
210      * <li><code>$[targetClassName]</code></li>
211      * <li><code>$[targetClassShortName]</code></li>
212      * <li><code>$[argumentTypes]</code></li>
213      * <li><code>$[arguments]</code></li>
214      * <li><code>$[returnValue]</code></li>
215      * <li><code>$[invocationTime]</code></li>
216      * </ul>
217      * @throws IllegalArgumentException if the message template is empty
218      * or contains any invalid placeholders
219      */

220     public void setExitMessage(String JavaDoc exitMessage) {
221         Assert.hasText(exitMessage, "'exitMessage' must not be empty");
222         checkForInvalidPlaceholders(exitMessage);
223         Assert.doesNotContain(exitMessage, PLACEHOLDER_EXCEPTION,
224                 "exitMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]");
225         this.exitMessage = exitMessage;
226     }
227
228     /**
229      * Set the template used for method exception log messages.
230      * This template can contain any of the following placeholders:
231      * <ul>
232      * <li><code>$[targetClassName]</code></li>
233      * <li><code>$[targetClassShortName]</code></li>
234      * <li><code>$[argumentTypes]</code></li>
235      * <li><code>$[arguments]</code></li>
236      * <li><code>$[exception]</code></li>
237      * </ul>
238      * @throws IllegalArgumentException if the message template is empty
239      * or contains any invalid placeholders
240      */

241     public void setExceptionMessage(String JavaDoc exceptionMessage) {
242         Assert.hasText(exceptionMessage, "'exceptionMessage' must not be empty");
243         checkForInvalidPlaceholders(exceptionMessage);
244         Assert.doesNotContain(exceptionMessage, PLACEHOLDER_RETURN_VALUE,
245                 "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]");
246         Assert.doesNotContain(exceptionMessage, PLACEHOLDER_INVOCATION_TIME,
247                 "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]");
248         this.exceptionMessage = exceptionMessage;
249     }
250
251
252     /**
253      * Writes a log message before the invocation based on the value of <code>enterMessage</code>.
254      * If the invocation succeeds, then a log message is written on exit based on the value
255      * <code>exitMessage</code>. If an exception occurs during invocation, then a message is
256      * written based on the value of <code>exceptionMessage</code>.
257      * @see #setEnterMessage
258      * @see #setExitMessage
259      * @see #setExceptionMessage
260      */

261     protected Object JavaDoc invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable JavaDoc {
262         String JavaDoc name = invocation.getMethod().getDeclaringClass().getName() + "." + invocation.getMethod().getName();
263         StopWatch stopWatch = new StopWatch(name);
264         Object JavaDoc returnValue = null;
265         boolean exitThroughException = false;
266         try {
267             stopWatch.start(name);
268             writeToLog(logger,
269                     replacePlaceholders(this.enterMessage, invocation, null, null, -1));
270             returnValue = invocation.proceed();
271             return returnValue;
272         }
273         catch (Throwable JavaDoc ex) {
274             if(stopWatch.isRunning()) {
275                 stopWatch.stop();
276             }
277             exitThroughException = true;
278             writeToLog(logger,
279                     replacePlaceholders(this.exceptionMessage, invocation, null, ex, stopWatch.getTotalTimeMillis()), ex);
280             throw ex;
281         }
282         finally {
283             if (!exitThroughException) {
284                 if(stopWatch.isRunning()) {
285                     stopWatch.stop();
286                 }
287                 writeToLog(logger,
288                         replacePlaceholders(this.exitMessage, invocation, returnValue, null, stopWatch.getTotalTimeMillis()));
289             }
290         }
291     }
292
293     /**
294      * Writes the supplied message to the supplied <code>Log</code> instance.
295      * @see #writeToLog(org.apache.commons.logging.Log, String, Throwable)
296      */

297     protected void writeToLog(Log logger, String JavaDoc message) {
298         writeToLog(logger, message, null);
299     }
300
301     /**
302      * Writes the supplied message and {@link Throwable} to the
303      * supplied <code>Log</code> instance. By default messages are written
304      * at <code>TRACE</code> level. Sub-classes can override this method
305      * to control which level the message is written at.
306      */

307     protected void writeToLog(Log logger, String JavaDoc message, Throwable JavaDoc ex) {
308         if (ex != null) {
309             logger.trace(message, ex);
310         }
311         else {
312             logger.trace(message);
313         }
314     }
315
316     /**
317      * Replace the placeholders in the given message with the supplied values,
318      * or values derived from those supplied.
319      * @param message the message template containing the placeholders to be replaced
320      * @param methodInvocation the <code>MethodInvocation</code> being logged.
321      * Used to derive values for all placeholders except <code>$[exception]</code>
322      * and <code>$[returnValue]</code>.
323      * @param returnValue any value returned by the invocation.
324      * Used to replace the <code>$[returnValue]</code> placeholder. May be <code>null</code>.
325      * @param throwable any <code>Throwable</code> raised during the invocation.
326      * The value of <code>Throwable.toString()</code> is replaced for the
327      * <code>$[exception]</code> placeholder. May be <code>null</code>.
328      * @param invocationTime the value to write in place of the
329      * <code>$[invocationTime]</code> placeholder
330      * @return the formatted output to write to the log
331      */

332     protected String JavaDoc replacePlaceholders(String JavaDoc message, MethodInvocation methodInvocation,
333             Object JavaDoc returnValue, Throwable JavaDoc throwable, long invocationTime) {
334
335         Matcher JavaDoc matcher = PATTERN.matcher(message);
336
337         StringBuffer JavaDoc output = new StringBuffer JavaDoc();
338         while (matcher.find()) {
339             String JavaDoc match = matcher.group();
340             if (PLACEHOLDER_METHOD_NAME.equals(match)) {
341                 matcher.appendReplacement(output, methodInvocation.getMethod().getName());
342             }
343             else if (PLACEHOLDER_TARGET_CLASS_NAME.equals(match)) {
344                 String JavaDoc targetClassName = escape(methodInvocation.getThis().getClass().getName());
345                 matcher.appendReplacement(output, targetClassName);
346             }
347             else if (PLACEHOLDER_TARGET_CLASS_SHORT_NAME.equals(match)) {
348                 matcher.appendReplacement(output, escape(ClassUtils.getShortName(methodInvocation.getThis().getClass())));
349             }
350             else if (PLACEHOLDER_ARGUMENTS.equals(match)) {
351                 matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(methodInvocation.getArguments())));
352             }
353             else if (PLACEHOLDER_ARGUMENT_TYPES.equals(match)) {
354                 appendArgumentTypes(methodInvocation, matcher, output);
355             }
356             else if (PLACEHOLDER_RETURN_VALUE.equals(match)) {
357                 appendReturnValue(methodInvocation, matcher, output, returnValue);
358             }
359             else if (throwable != null && PLACEHOLDER_EXCEPTION.equals(match)) {
360                 matcher.appendReplacement(output, throwable.toString());
361             }
362             else if (PLACEHOLDER_INVOCATION_TIME.equals(match)) {
363                 matcher.appendReplacement(output, Long.toString(invocationTime));
364             }
365             else {
366                 // Should not happen since placeholders are checked earlier.
367
throw new IllegalArgumentException JavaDoc("Unknown placeholder [" + match + "]");
368             }
369         }
370         matcher.appendTail(output);
371
372         return output.toString();
373     }
374
375     /**
376      * Adds the <code>String</code> representation of the method return value
377      * to the supplied <code>StringBuffer</code>. Correctly handles
378      * <code>null</code> and <code>void</code> results.
379      * @param methodInvocation the <code>MethodInvocation</code> that returned the value
380      * @param matcher the <code>Matcher</code> containing the matched placeholder
381      * @param output the <code>StringBuffer</code> to write output to
382      * @param returnValue the value returned by the method invocatio.
383      */

384     private void appendReturnValue(
385             MethodInvocation methodInvocation, Matcher JavaDoc matcher, StringBuffer JavaDoc output, Object JavaDoc returnValue) {
386
387         if (methodInvocation.getMethod().getReturnType() == void.class) {
388             matcher.appendReplacement(output, "void");
389         }
390         else if (returnValue == null) {
391             matcher.appendReplacement(output, "null");
392         }
393         else {
394             matcher.appendReplacement(output, escape(returnValue.toString()));
395         }
396     }
397
398     /**
399      * Adds a comma-separated list of the short <code>Class</code> names of the
400      * method argument types to the output. For example, if a method has signature
401      * <code>put(java.lang.String, java.lang.Object)</code> then the value returned
402      * will be <code>String, Object</code>.
403      * @param methodInvocation the <code>MethodInvocation</code> being logged.
404      * Arguments will be retreived from the corresponding <code>Method</code>.
405      * @param matcher the <code>Matcher</code> containing the state of the output
406      * @param output the <code>StringBuffer</code> containing the output
407      */

408     private void appendArgumentTypes(MethodInvocation methodInvocation, Matcher JavaDoc matcher, StringBuffer JavaDoc output) {
409         Class JavaDoc[] argumentTypes = methodInvocation.getMethod().getParameterTypes();
410         String JavaDoc[] argumentTypeShortNames = new String JavaDoc[argumentTypes.length];
411         for (int i = 0; i < argumentTypeShortNames.length; i++) {
412             argumentTypeShortNames[i] = ClassUtils.getShortName(argumentTypes[i]);
413         }
414         matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(argumentTypeShortNames)));
415     }
416
417     /**
418      * Checks to see if the supplied <code>String</code> has any placeholders
419      * that are not specified as constants on this class and throws an
420      * <code>IllegalArgumentException</code> if so.
421      */

422     private void checkForInvalidPlaceholders(String JavaDoc message) throws IllegalArgumentException JavaDoc {
423         Matcher JavaDoc matcher = PATTERN.matcher(message);
424         while (matcher.find()) {
425             String JavaDoc match = matcher.group();
426             if (!ALLOWED_PLACEHOLDERS.contains(match)) {
427                 throw new IllegalArgumentException JavaDoc("Placeholder [" + match + "] is not valid");
428             }
429         }
430     }
431
432     /**
433      * Replaces <code>$</code> in inner class names with <code>\$</code>.
434      */

435     private String JavaDoc escape(String JavaDoc input) {
436         Matcher JavaDoc matcher = ESCAPE_PATTERN.matcher(input);
437         StringBuffer JavaDoc output = new StringBuffer JavaDoc(input.length());
438         while (matcher.find()) {
439             matcher.appendReplacement(output, "");
440             output.append("\\").append(matcher.group());
441         }
442         matcher.appendTail(output);
443         return output.toString();
444     }
445
446 }
447
Popular Tags