001    package org.apache.fulcrum.yaafi.interceptor.jamon;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.avalon.framework.activity.Disposable;
023    import org.apache.avalon.framework.activity.Initializable;
024    import org.apache.avalon.framework.configuration.Configuration;
025    import org.apache.avalon.framework.configuration.ConfigurationException;
026    import org.apache.avalon.framework.configuration.Reconfigurable;
027    import org.apache.avalon.framework.thread.ThreadSafe;
028    import org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext;
029    import org.apache.fulcrum.yaafi.framework.reflection.Clazz;
030    import org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl;
031    
032    import java.io.File;
033    import java.io.FileOutputStream;
034    import java.io.PrintWriter;
035    import java.lang.reflect.Method;
036    
037    /**
038     * A service using JAMon for performance monitoring. The implementation
039     * relies on reflection to invoke JAMON to avoid compile-time coupling.
040     *
041     * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
042     */
043    
044    public class JamonInterceptorServiceImpl
045        extends BaseInterceptorServiceImpl
046        implements JamonInterceptorService, Reconfigurable, ThreadSafe, Disposable, Initializable
047    {
048            /** are the JAMon classes in the classpath */
049            private boolean isJamonAvailable;
050    
051        /** the file to hold the report */
052        private File reportFile;
053    
054        /** the time in ms between two reports */
055        private long reportTimeout;
056    
057        /** do we create a report during disposal of the service */
058        private boolean reportOnExit;
059    
060        /** the time when the next report is due */
061        private long nextReportTimestamp;
062    
063        /** the implementation class name for the performance monitor */
064        private String performanceMonitorClassName;
065    
066        /** the implementation class name for the performance monitor */
067        private Class performanceMonitorClass;
068    
069        /** the class name of the JAMon MonitorFactory */
070        private static final String MONITORFACTORY_CLASSNAME = "com.jamonapi.MonitorFactory";
071    
072        /** the class name of the JAMon MonitorFactory */
073        private static final String DEFAULT_PERFORMANCEMONITOR_CLASSNAME = "org.apache.fulcrum.yaafi.interceptor.jamon.Jamon1PerformanceMonitorImpl";
074    
075        /////////////////////////////////////////////////////////////////////////
076        // Avalon Service Lifecycle Implementation
077        /////////////////////////////////////////////////////////////////////////
078    
079        /**
080         * Constructor
081         */
082        public JamonInterceptorServiceImpl()
083        {
084            super();
085        }
086    
087        /**
088         * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
089         */
090        public void configure(Configuration configuration) throws ConfigurationException
091        {
092            super.configure(configuration);
093            this.reportTimeout = configuration.getChild("reportTimeout").getValueAsLong(0);
094    
095            // parse the performance monitor class name
096            this.performanceMonitorClassName = configuration.getChild("performanceMonitorClassName").getValue(DEFAULT_PERFORMANCEMONITOR_CLASSNAME);
097    
098            // parse the report file name
099            String reportFileName = configuration.getChild("reportFile").getValue("./jamon.html");
100            this.reportFile = this.makeAbsoluteFile( reportFileName );
101    
102            // determine when to create the next report
103            this.nextReportTimestamp = System.currentTimeMillis() + this.reportTimeout;
104    
105            // do we create a report on disposal
106            this.reportOnExit = configuration.getChild("reportOnExit").getValueAsBoolean(false);
107        }
108    
109        /**
110         * @see org.apache.avalon.framework.activity.Initializable#initialize()
111         */
112        public void initialize() throws Exception
113        {
114            ClassLoader classLoader = this.getClassLoader();
115    
116            if (!Clazz.hasClazz(classLoader, MONITORFACTORY_CLASSNAME))
117            {
118                String msg = "The JamonInterceptorService is disabled since the JAMON classes are not found in the classpath";
119                this.getLogger().warn(msg);
120                this.isJamonAvailable = false;
121                return;
122            }
123            
124            if (!Clazz.hasClazz(classLoader, this.performanceMonitorClassName))
125            {
126                String msg = "The JamonInterceptorService is disabled since the performance monitor class is not found in the classpath";
127                this.getLogger().warn(msg);
128                this.isJamonAvailable = false;
129                return;
130            }
131    
132            // load the performance monitor class
133            this.performanceMonitorClass = Clazz.getClazz(this.getClassLoader(), this.performanceMonitorClassName);
134    
135            // check if we can create an instance of the performance monitor class
136            JamonPerformanceMonitor testMonitor = this.createJamonPerformanceMonitor(null, null, true);
137            if(testMonitor == null)
138            {
139                String msg = "The JamonInterceptorService is disabled since the performance monitor can't be instantiated";
140                this.getLogger().warn(msg);
141                this.isJamonAvailable = false;
142                return;
143            }
144    
145            this.getLogger().debug("The JamonInterceptorService is enabled");
146            this.isJamonAvailable = true;
147        }
148    
149            /**
150         * @see org.apache.avalon.framework.configuration.Reconfigurable#reconfigure(org.apache.avalon.framework.configuration.Configuration)
151         */
152        public void reconfigure(Configuration configuration) throws ConfigurationException
153        {
154            super.reconfigure(configuration);
155            this.configure(configuration);
156        }
157    
158        /**
159         * @see org.apache.avalon.framework.activity.Disposable#dispose()
160         */
161        public void dispose()
162        {
163            if( this.reportOnExit )
164            {
165                this.run();
166            }
167    
168            this.reportFile = null;
169        }
170    
171        /////////////////////////////////////////////////////////////////////////
172        // Service interface implementation
173        /////////////////////////////////////////////////////////////////////////
174    
175        /**
176         * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onEntry(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext)
177         */
178        public void onEntry(AvalonInterceptorContext interceptorContext)
179        {
180            if( this.isJamonAvailable()  )
181            {
182                this.writeReport();
183    
184                String serviceShortHand = interceptorContext.getServiceShorthand();
185                Method serviceMethod = interceptorContext.getMethod();
186                boolean isEnabled = this.isServiceMonitored(interceptorContext );
187                JamonPerformanceMonitor monitor = this.createJamonPerformanceMonitor(serviceShortHand, serviceMethod, isEnabled);
188                monitor.start();
189                interceptorContext.getRequestContext().put(this.getServiceName(), monitor);
190            }
191        }
192    
193        /**
194         * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onExit(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext, java.lang.Object)
195         */
196        public void onExit(AvalonInterceptorContext interceptorContext, Object result)
197        {
198            if( this.isJamonAvailable() )
199            {
200                JamonPerformanceMonitor monitor;
201                monitor = (JamonPerformanceMonitor) interceptorContext.getRequestContext().remove(this.getServiceName());
202                monitor.stop();
203            }
204        }
205    
206        /**
207         * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onError(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext, java.lang.Throwable)
208         */
209        public void onError(AvalonInterceptorContext interceptorContext,Throwable t)
210        {
211            if( this.isJamonAvailable() )
212            {
213                JamonPerformanceMonitor monitor;
214                monitor = (JamonPerformanceMonitor) interceptorContext.getRequestContext().remove(this.getServiceName());
215                monitor.stop(t);
216            }
217        }
218    
219        /**
220         * Writes the JAMON report to the file system.
221         *
222         * @see java.lang.Runnable#run()
223         */
224        public void run()
225        {
226            this.writeReport(this.reportFile);
227        }
228    
229        /////////////////////////////////////////////////////////////////////////
230        // Service Implementation
231        /////////////////////////////////////////////////////////////////////////
232    
233        /**
234         * @return Returns the isJamonAvailable.
235         */
236        protected final boolean isJamonAvailable()
237        {
238            return this.isJamonAvailable;
239        }
240    
241        /**
242         * Factory method for creating an implementation of a JamonPerformanceMonitor.
243         *
244         * @param serviceName the service name
245         * @param method the method
246         * @param isEnabled is the monitor enabled
247         * @return the instance or <b>null</b> if the creation failed
248         */
249        protected JamonPerformanceMonitor createJamonPerformanceMonitor(String serviceName, Method method, boolean isEnabled)
250        {
251            JamonPerformanceMonitor result = null;
252    
253            try
254            {
255                Class[] signature = { String.class, Method.class, Boolean.class };
256                Object[] args = { serviceName, method, (isEnabled) ? Boolean.TRUE : Boolean.FALSE};
257                result = (JamonPerformanceMonitor) Clazz.newInstance(this.performanceMonitorClass, signature, args);
258                return result;
259            }
260            catch(Exception e)
261            {
262                String msg = "Failed to create a performance monitor instance : " + this.performanceMonitorClassName;
263                this.getLogger().error(msg, e);
264                return result;
265            }
266        }
267    
268        /**
269         * Write a report file
270         */
271        protected void writeReport()
272        {
273            if( this.reportTimeout > 0 )
274            {
275                long currTimestamp = System.currentTimeMillis();
276    
277                if( currTimestamp > this.nextReportTimestamp )
278                {
279                    this.nextReportTimestamp = currTimestamp + this.reportTimeout;
280                    this.writeReport(this.reportFile);
281                }
282            }
283        }
284    
285        /**
286         * Write the HTML report to the given destination.
287         *
288         * @param reportFile the report destination
289         */
290        protected void writeReport( File reportFile )
291        {
292            PrintWriter printWriter = null;
293    
294            if( this.isJamonAvailable() )
295            {
296                try
297                {
298                    if( this.getLogger().isDebugEnabled() )
299                    {
300                        this.getLogger().debug( "Writing JAMOM report to " + reportFile.getAbsolutePath() );
301                    }
302    
303                    FileOutputStream fos = new FileOutputStream( reportFile );
304                    printWriter = new PrintWriter( fos );
305                    JamonPerformanceMonitor monitor = this.createJamonPerformanceMonitor(null, null, true);
306                    String report = monitor.createReport();
307                    printWriter.write( report );
308                    printWriter.close();
309                }
310                catch( Throwable t )
311                {
312                    String msg = "Generating the JAMON report failed for " + reportFile.getAbsolutePath();
313                    this.getLogger().error(msg,t);
314                }
315                finally
316                {
317                    if( printWriter != null )
318                    {
319                        printWriter.close();
320                    }
321                }
322            }
323        }
324    }