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.Constructor;
018 import java.lang.reflect.Modifier;
019 import java.util.Iterator;
020 import java.util.List;
021
022 import org.apache.commons.logging.Log;
023 import org.apache.hivemind.ApplicationRuntimeException;
024 import org.apache.hivemind.InterceptorStack;
025 import org.apache.hivemind.ServiceInterceptorFactory;
026 import org.apache.hivemind.internal.Module;
027 import org.apache.hivemind.methodmatch.MethodMatcher;
028 import org.apache.hivemind.service.BodyBuilder;
029 import org.apache.hivemind.service.ClassFab;
030 import org.apache.hivemind.service.ClassFabUtils;
031 import org.apache.hivemind.service.ClassFactory;
032 import org.apache.hivemind.service.MethodContribution;
033 import org.apache.hivemind.service.MethodFab;
034 import org.apache.hivemind.service.MethodIterator;
035 import org.apache.hivemind.service.MethodSignature;
036
037 /**
038 * An interceptor factory that adds logging capability to a service.
039 * The logging is based upon the Jakarta
040 * <a href="http://jakarta.apache.org/commons/logging.html">commons-logging</a> toolkit,
041 * which makes
042 * it very transportable.
043 *
044 * <p>
045 * The interceptor will log entry to each method and exit from the method
046 * (with return value), plus log any exceptions thrown by the method.
047 * The logger used is the <em>id of the service</em>, which is not necessarily
048 * the name of the implementing class. Logging occurs at the debug level.
049 *
050 * @author Howard Lewis Ship
051 */
052 public class LoggingInterceptorFactory implements ServiceInterceptorFactory
053 {
054 private ClassFactory _factory;
055 private String _serviceId;
056
057 /**
058 * Creates a method that delegates to the _delegate object; this is used for
059 * methods that are not logged.
060 */
061 private void addPassThruMethodImplementation(ClassFab classFab, MethodSignature sig)
062 {
063 BodyBuilder builder = new BodyBuilder();
064 builder.begin();
065
066 builder.add("return ($r) _delegate.");
067 builder.add(sig.getName());
068 builder.addln("($$);");
069
070 builder.end();
071
072 classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
073 }
074
075 protected void addServiceMethodImplementation(ClassFab classFab, MethodSignature sig)
076 {
077 Class returnType = sig.getReturnType();
078 String methodName = sig.getName();
079
080 boolean isVoid = (returnType == void.class);
081
082 BodyBuilder builder = new BodyBuilder();
083
084 builder.begin();
085 builder.addln("boolean debug = _log.isDebugEnabled();");
086
087 builder.addln("if (debug)");
088 builder.add(" org.apache.hivemind.service.impl.LoggingUtils.entry(_log, ");
089 builder.addQuoted(methodName);
090 builder.addln(", $args);");
091
092 if (!isVoid)
093 {
094 builder.add(ClassFabUtils.getJavaClassName(returnType));
095 builder.add(" result = ");
096 }
097
098 builder.add("_delegate.");
099 builder.add(methodName);
100 builder.addln("($$);");
101
102 if (isVoid)
103 {
104 builder.addln("if (debug)");
105 builder.add(" org.apache.hivemind.service.impl.LoggingUtils.voidExit(_log, ");
106 builder.addQuoted(methodName);
107 builder.addln(");");
108 }
109 else
110 {
111 builder.addln("if (debug)");
112 builder.add(" org.apache.hivemind.service.impl.LoggingUtils.exit(_log, ");
113 builder.addQuoted(methodName);
114 builder.addln(", ($w)result);");
115 builder.addln("return result;");
116 }
117
118 builder.end();
119
120 MethodFab methodFab = classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
121
122 builder.clear();
123
124 builder.begin();
125 builder.add("org.apache.hivemind.service.impl.LoggingUtils.exception(_log, ");
126 builder.addQuoted(methodName);
127 builder.addln(", $e);");
128 builder.addln("throw $e;");
129 builder.end();
130
131 String body = builder.toString();
132
133 Class[] exceptions = sig.getExceptionTypes();
134
135 int count = exceptions.length;
136
137 for (int i = 0; i < count; i++)
138 {
139 methodFab.addCatch(exceptions[i], body);
140 }
141
142 // Catch and log any runtime exceptions, in addition to the
143 // checked exceptions.
144
145 methodFab.addCatch(RuntimeException.class, body);
146 }
147
148 protected void addServiceMethods(InterceptorStack stack, ClassFab fab, List parameters)
149 {
150 MethodMatcher matcher = buildMethodMatcher(parameters);
151
152 MethodIterator mi = new MethodIterator(stack.getServiceInterface());
153
154 while (mi.hasNext())
155 {
156 MethodSignature sig = mi.next();
157
158 if (includeMethod(matcher, sig))
159 addServiceMethodImplementation(fab, sig);
160 else
161 addPassThruMethodImplementation(fab, sig);
162 }
163
164 if (!mi.getToString())
165 addToStringMethod(stack, fab);
166 }
167
168 /**
169 * Creates a toString() method that identify the interceptor service id,
170 * the intercepted service id, and the service interface class name).
171 */
172 protected void addToStringMethod(InterceptorStack stack, ClassFab fab)
173 {
174 ClassFabUtils.addToStringMethod(
175 fab,
176 "<LoggingInterceptor for "
177 + stack.getServiceExtensionPointId()
178 + "("
179 + stack.getServiceInterface().getName()
180 + ")>");
181
182 }
183
184 private MethodMatcher buildMethodMatcher(List parameters)
185 {
186 MethodMatcher result = null;
187
188 Iterator i = parameters.iterator();
189 while (i.hasNext())
190 {
191 MethodContribution mc = (MethodContribution) i.next();
192
193 if (result == null)
194 result = new MethodMatcher();
195
196 result.put(mc.getMethodPattern(), mc);
197 }
198
199 return result;
200 }
201
202 private Class constructInterceptorClass(InterceptorStack stack, List parameters)
203 {
204 Class serviceInterfaceClass = stack.getServiceInterface();
205
206 String name = ClassFabUtils.generateClassName(serviceInterfaceClass);
207
208 ClassFab classFab = _factory.newClass(name, Object.class);
209
210 classFab.addInterface(serviceInterfaceClass);
211
212 createInfrastructure(stack, classFab);
213
214 addServiceMethods(stack, classFab, parameters);
215
216 return classFab.createClass();
217 }
218
219 private void createInfrastructure(InterceptorStack stack, ClassFab classFab)
220 {
221 Class topClass = ClassFabUtils.getInstanceClass(stack.peek(), stack.getServiceInterface());
222
223 classFab.addField("_log", Log.class);
224
225 // This is very important: since we know the instance of the top object (the next
226 // object in the pipeline for this service), we can build the instance variable
227 // and constructor to use the exact class rather than the service interface.
228 // That's more efficient at runtime, lowering the cost of using interceptors.
229 // One of the reasons I prefer Javassist over JDK Proxies.
230
231 classFab.addField("_delegate", topClass);
232
233 classFab.addConstructor(
234 new Class[] { Log.class, topClass },
235 null,
236 "{ _log = $1; _delegate = $2; }");
237 }
238
239 /**
240 * Creates the interceptor.
241 * The class that is created is cached; if an interceptor is requested
242 * for the same extension point, then the previously constructed class
243 * is reused (this can happen with the threaded service model, for example,
244 * when a thread-local service implementation is created for different threads).
245 */
246 public void createInterceptor(
247 InterceptorStack stack,
248 Module contributingModule,
249 List parameters)
250 {
251 Class interceptorClass = constructInterceptorClass(stack, parameters);
252
253 try
254 {
255 Object interceptor = instantiateInterceptor(stack, interceptorClass);
256
257 stack.push(interceptor);
258 }
259 catch (Exception ex)
260 {
261 throw new ApplicationRuntimeException(
262 ServiceMessages.errorInstantiatingInterceptor(
263 _serviceId,
264 stack,
265 interceptorClass,
266 ex),
267 ex);
268 }
269 }
270
271
272
273 private boolean includeMethod(MethodMatcher matcher, MethodSignature sig)
274 {
275 if (matcher == null)
276 return true;
277
278 MethodContribution mc = (MethodContribution) matcher.get(sig);
279
280 return mc == null || mc.getInclude();
281 }
282
283 private Object instantiateInterceptor(InterceptorStack stack, Class interceptorClass)
284 throws Exception
285 {
286 Object stackTop = stack.peek();
287
288 Constructor c = interceptorClass.getConstructors()[0];
289
290 return c.newInstance(new Object[] { stack.getServiceLog(), stackTop });
291 }
292
293 public void setFactory(ClassFactory factory)
294 {
295 _factory = factory;
296 }
297
298 public void setServiceId(String string)
299 {
300 _serviceId = string;
301 }
302 }