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 }