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
015package org.apache.hivemind.service.impl;
016
017import java.lang.reflect.Constructor;
018import java.lang.reflect.Modifier;
019import java.util.Iterator;
020import java.util.List;
021
022import org.apache.commons.logging.Log;
023import org.apache.hivemind.ApplicationRuntimeException;
024import org.apache.hivemind.InterceptorStack;
025import org.apache.hivemind.ServiceInterceptorFactory;
026import org.apache.hivemind.internal.Module;
027import org.apache.hivemind.methodmatch.MethodMatcher;
028import org.apache.hivemind.service.BodyBuilder;
029import org.apache.hivemind.service.ClassFab;
030import org.apache.hivemind.service.ClassFabUtils;
031import org.apache.hivemind.service.ClassFactory;
032import org.apache.hivemind.service.MethodContribution;
033import org.apache.hivemind.service.MethodFab;
034import org.apache.hivemind.service.MethodIterator;
035import 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 */
052public 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}