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 }