001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.hivemind.service.impl;
016
017 import java.lang.reflect.Modifier;
018 import java.util.Iterator;
019 import java.util.List;
020
021 import org.apache.commons.logging.Log;
022 import org.apache.hivemind.InterceptorStack;
023 import org.apache.hivemind.methodmatch.MethodMatcher;
024 import org.apache.hivemind.service.BodyBuilder;
025 import org.apache.hivemind.service.ClassFab;
026 import org.apache.hivemind.service.ClassFabUtils;
027 import org.apache.hivemind.service.ClassFactory;
028 import org.apache.hivemind.service.MethodContribution;
029 import org.apache.hivemind.service.MethodFab;
030 import org.apache.hivemind.service.MethodIterator;
031 import org.apache.hivemind.service.MethodSignature;
032
033 /**
034 * Factory for creation of interceptor classes that add logging capability to a service.
035 * The logging is based upon the Jakarta
036 * <a href="http://jakarta.apache.org/commons/logging.html">commons-logging</a> toolkit,
037 * which makes it very transportable.
038 *
039 * <p>
040 * The interceptor will log entry to each method and exit from the method
041 * (with return value), plus log any exceptions thrown by the method.
042 * The logger used is the <em>id of the service</em>, which is not necessarily
043 * the name of the implementing class. Logging occurs at the debug level.
044 *
045 * @author Howard Lewis Ship
046 */
047 public class LoggingInterceptorClassFactory
048 {
049 private ClassFactory _factory;
050
051 public LoggingInterceptorClassFactory(ClassFactory factory)
052 {
053 _factory = factory;
054 }
055
056 /**
057 * Creates a method that delegates to the _delegate object; this is used for
058 * methods that are not logged.
059 */
060 private void addPassThruMethodImplementation(ClassFab classFab, MethodSignature sig)
061 {
062 BodyBuilder builder = new BodyBuilder();
063 builder.begin();
064
065 builder.add("return ($r) _delegate.");
066 builder.add(sig.getName());
067 builder.addln("($$);");
068
069 builder.end();
070
071 classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
072 }
073
074 protected void addServiceMethodImplementation(ClassFab classFab, MethodSignature sig)
075 {
076 Class returnType = sig.getReturnType();
077 String methodName = sig.getName();
078
079 boolean isVoid = (returnType == void.class);
080
081 BodyBuilder builder = new BodyBuilder();
082
083 builder.begin();
084 builder.addln("boolean debug = _log.isDebugEnabled();");
085
086 builder.addln("if (debug)");
087 builder.add(" org.apache.hivemind.service.impl.LoggingUtils.entry(_log, ");
088 builder.addQuoted(methodName);
089 builder.addln(", $args);");
090
091 if (!isVoid)
092 {
093 builder.add(ClassFabUtils.getJavaClassName(returnType));
094 builder.add(" result = ");
095 }
096
097 builder.add("_delegate.");
098 builder.add(methodName);
099 builder.addln("($$);");
100
101 if (isVoid)
102 {
103 builder.addln("if (debug)");
104 builder.add(" org.apache.hivemind.service.impl.LoggingUtils.voidExit(_log, ");
105 builder.addQuoted(methodName);
106 builder.addln(");");
107 }
108 else
109 {
110 builder.addln("if (debug)");
111 builder.add(" org.apache.hivemind.service.impl.LoggingUtils.exit(_log, ");
112 builder.addQuoted(methodName);
113 builder.addln(", ($w)result);");
114 builder.addln("return result;");
115 }
116
117 builder.end();
118
119 MethodFab methodFab = classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
120
121 builder.clear();
122
123 builder.begin();
124 builder.add("org.apache.hivemind.service.impl.LoggingUtils.exception(_log, ");
125 builder.addQuoted(methodName);
126 builder.addln(", $e);");
127 builder.addln("throw $e;");
128 builder.end();
129
130 String body = builder.toString();
131
132 Class[] exceptions = sig.getExceptionTypes();
133
134 int count = exceptions.length;
135
136 for (int i = 0; i < count; i++)
137 {
138 methodFab.addCatch(exceptions[i], body);
139 }
140
141 // Catch and log any runtime exceptions, in addition to the
142 // checked exceptions.
143
144 methodFab.addCatch(RuntimeException.class, body);
145 }
146
147 protected void addServiceMethods(InterceptorStack stack, ClassFab fab, List parameters)
148 {
149 MethodMatcher matcher = buildMethodMatcher(parameters);
150
151 MethodIterator mi = new MethodIterator(stack.getServiceInterface());
152
153 while (mi.hasNext())
154 {
155 MethodSignature sig = mi.next();
156
157 if (includeMethod(matcher, sig))
158 addServiceMethodImplementation(fab, sig);
159 else
160 addPassThruMethodImplementation(fab, sig);
161 }
162
163 if (!mi.getToString())
164 addToStringMethod(stack, fab);
165 }
166
167 /**
168 * Creates a toString() method that identify the interceptor service id,
169 * the intercepted service id, and the service interface class name).
170 */
171 protected void addToStringMethod(InterceptorStack stack, ClassFab fab)
172 {
173 ClassFabUtils.addToStringMethod(
174 fab,
175 "<LoggingInterceptor for "
176 + stack.getServiceExtensionPointId()
177 + "("
178 + stack.getServiceInterface().getName()
179 + ")>");
180
181 }
182
183 private MethodMatcher buildMethodMatcher(List parameters)
184 {
185 MethodMatcher result = null;
186
187 Iterator i = parameters.iterator();
188 while (i.hasNext())
189 {
190 MethodContribution mc = (MethodContribution) i.next();
191
192 if (result == null)
193 result = new MethodMatcher();
194
195 result.put(mc.getMethodPattern(), mc);
196 }
197
198 return result;
199 }
200
201 /**
202 * Creates the interceptor class.
203 */
204 public Class constructInterceptorClass(InterceptorStack stack, List parameters)
205 {
206 Class serviceInterfaceClass = stack.getServiceInterface();
207
208 String name = ClassFabUtils.generateClassName(serviceInterfaceClass);
209
210 ClassFab classFab = _factory.newClass(name, Object.class);
211
212 classFab.addInterface(serviceInterfaceClass);
213
214 createInfrastructure(stack, classFab);
215
216 addServiceMethods(stack, classFab, parameters);
217
218 return classFab.createClass();
219 }
220
221 private void createInfrastructure(InterceptorStack stack, ClassFab classFab)
222 {
223 Class topClass = ClassFabUtils.getInstanceClass(classFab, stack.peek(), stack.getServiceInterface());
224
225 classFab.addField("_log", Log.class);
226
227 // This is very important: since we know the instance of the top object (the next
228 // object in the pipeline for this service), we can build the instance variable
229 // and constructor to use the exact class rather than the service interface.
230 // That's more efficient at runtime, lowering the cost of using interceptors.
231 // One of the reasons I prefer Javassist over JDK Proxies.
232
233 classFab.addField("_delegate", topClass);
234
235 classFab.addConstructor(
236 new Class[] { Log.class, topClass },
237 null,
238 "{ _log = $1; _delegate = $2; }");
239 }
240
241 private boolean includeMethod(MethodMatcher matcher, MethodSignature sig)
242 {
243 if (matcher == null)
244 return true;
245
246 MethodContribution mc = (MethodContribution) matcher.get(sig);
247
248 return mc == null || mc.getInclude();
249 }
250
251 }