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    }