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    }