Java tutorial
// Copyright 2004, 2005 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.ops4j.gaderian.service.impl; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.ops4j.gaderian.ApplicationRuntimeException; import org.ops4j.gaderian.InterceptorStack; import org.ops4j.gaderian.ServiceInterceptorFactory; import org.ops4j.gaderian.internal.Module; import org.ops4j.gaderian.methodmatch.MethodMatcher; import org.ops4j.gaderian.service.BodyBuilder; import org.ops4j.gaderian.service.ClassFab; import org.ops4j.gaderian.service.ClassFabUtils; import org.ops4j.gaderian.service.ClassFactory; import org.ops4j.gaderian.service.MethodContribution; import org.ops4j.gaderian.service.MethodFab; import org.ops4j.gaderian.service.MethodIterator; import org.ops4j.gaderian.service.MethodSignature; /** * An interceptor factory that adds logging capability to a service. * The logging is based upon the Jakarta * <a href="http://jakarta.apache.org/commons/logging.html">commons-logging</a> toolkit, * which makes * it very transportable. * * <p> * The interceptor will log entry to each method and exit from the method * (with return value), plus log any exceptions thrown by the method. * The logger used is the <em>id of the service</em>, which is not necessarily * the name of the implementing class. Logging occurs at the debug level. * * @author Howard Lewis Ship */ public class LoggingInterceptorFactory implements ServiceInterceptorFactory { private ClassFactory _factory; private String _serviceId; /** * Creates a method that delegates to the _delegate object; this is used for * methods that are not logged. */ private void addPassThruMethodImplementation(ClassFab classFab, MethodSignature sig) { BodyBuilder builder = new BodyBuilder(); builder.begin(); builder.add("return ($r) _delegate."); builder.add(sig.getName()); builder.addln("($$);"); builder.end(); classFab.addMethod(Modifier.PUBLIC, sig, builder.toString()); } protected void addServiceMethodImplementation(ClassFab classFab, MethodSignature sig) { Class returnType = sig.getReturnType(); String methodName = sig.getName(); boolean isVoid = (returnType == void.class); BodyBuilder builder = new BodyBuilder(); builder.begin(); builder.addln("boolean debug = _log.isDebugEnabled();"); builder.addln("if (debug)"); builder.add(" org.ops4j.gaderian.service.impl.LoggingUtils.entry(_log, "); builder.addQuoted(methodName); builder.addln(", $args);"); if (!isVoid) { builder.add(ClassFabUtils.getJavaClassName(returnType)); builder.add(" result = "); } builder.add("_delegate."); builder.add(methodName); builder.addln("($$);"); if (isVoid) { builder.addln("if (debug)"); builder.add(" org.ops4j.gaderian.service.impl.LoggingUtils.voidExit(_log, "); builder.addQuoted(methodName); builder.addln(");"); } else { builder.addln("if (debug)"); builder.add(" org.ops4j.gaderian.service.impl.LoggingUtils.exit(_log, "); builder.addQuoted(methodName); builder.addln(", ($w)result);"); builder.addln("return result;"); } builder.end(); MethodFab methodFab = classFab.addMethod(Modifier.PUBLIC, sig, builder.toString()); builder.clear(); builder.begin(); builder.add("org.ops4j.gaderian.service.impl.LoggingUtils.exception(_log, "); builder.addQuoted(methodName); builder.addln(", $e);"); builder.addln("throw $e;"); builder.end(); String body = builder.toString(); Class[] exceptions = sig.getExceptionTypes(); int count = exceptions.length; for (int i = 0; i < count; i++) { methodFab.addCatch(exceptions[i], body); } // Catch and log any other exception, in addition to the // checked exceptions. methodFab.addCatch(Throwable.class, body); } protected void addServiceMethods(InterceptorStack stack, ClassFab fab, List parameters) { MethodMatcher matcher = buildMethodMatcher(parameters); MethodIterator mi = new MethodIterator(stack.getServiceInterface()); while (mi.hasNext()) { MethodSignature sig = mi.next(); if (includeMethod(matcher, sig)) addServiceMethodImplementation(fab, sig); else addPassThruMethodImplementation(fab, sig); } if (!mi.getToString()) addToStringMethod(stack, fab); } /** * Creates a toString() method that identify the interceptor service id, * the intercepted service id, and the service interface class name). */ protected void addToStringMethod(InterceptorStack stack, ClassFab fab) { ClassFabUtils.addToStringMethod(fab, "<LoggingInterceptor for " + stack.getServiceExtensionPointId() + "(" + stack.getServiceInterface().getName() + ")>"); } private MethodMatcher buildMethodMatcher(List parameters) { MethodMatcher result = null; Iterator i = parameters.iterator(); while (i.hasNext()) { MethodContribution mc = (MethodContribution) i.next(); if (result == null) result = new MethodMatcher(); result.put(mc.getMethodPattern(), mc); } return result; } private Class constructInterceptorClass(InterceptorStack stack, List parameters) { Class serviceInterfaceClass = stack.getServiceInterface(); String name = ClassFabUtils.generateClassName(serviceInterfaceClass); ClassFab classFab = _factory.newClass(name, Object.class); classFab.addInterface(serviceInterfaceClass); createInfrastructure(stack, classFab); addServiceMethods(stack, classFab, parameters); return classFab.createClass(); } private void createInfrastructure(InterceptorStack stack, ClassFab classFab) { Class topClass = ClassFabUtils.getInstanceClass(classFab, stack.peek(), stack.getServiceInterface()); classFab.addField("_log", Log.class); // This is very important: since we know the instance of the top object (the next // object in the pipeline for this service), we can build the instance variable // and constructor to use the exact class rather than the service interface. // That's more efficient at runtime, lowering the cost of using interceptors. // One of the reasons I prefer Javassist over JDK Proxies. classFab.addField("_delegate", topClass); classFab.addConstructor(new Class[] { Log.class, topClass }, null, "{ _log = $1; _delegate = $2; }"); } /** * Creates the interceptor. * The class that is created is cached; if an interceptor is requested * for the same extension point, then the previously constructed class * is reused (this can happen with the threaded service model, for example, * when a thread-local service implementation is created for different threads). */ public void createInterceptor(InterceptorStack stack, Module contributingModule, List parameters) { Class interceptorClass = constructInterceptorClass(stack, parameters); try { Object interceptor = instantiateInterceptor(stack, interceptorClass); stack.push(interceptor); } catch (Exception ex) { throw new ApplicationRuntimeException( ServiceMessages.errorInstantiatingInterceptor(_serviceId, stack, interceptorClass, ex), ex); } } private boolean includeMethod(MethodMatcher matcher, MethodSignature sig) { if (matcher == null) return true; MethodContribution mc = (MethodContribution) matcher.get(sig); return mc == null || mc.getInclude(); } private Object instantiateInterceptor(InterceptorStack stack, Class interceptorClass) throws Exception { Object stackTop = stack.peek(); Constructor c = interceptorClass.getConstructors()[0]; return c.newInstance(new Object[] { stack.getServiceLog(), stackTop }); } public void setFactory(ClassFactory factory) { _factory = factory; } public void setServiceId(String string) { _serviceId = string; } }