001package 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
022import java.io.File;
023import java.io.FileOutputStream;
024import java.io.OutputStreamWriter;
025import java.io.PrintWriter;
026import java.io.Writer;
027import java.lang.reflect.Method;
028
029import org.apache.avalon.framework.activity.Disposable;
030import org.apache.avalon.framework.activity.Initializable;
031import org.apache.avalon.framework.configuration.Configuration;
032import org.apache.avalon.framework.configuration.ConfigurationException;
033import org.apache.avalon.framework.configuration.Reconfigurable;
034import org.apache.avalon.framework.thread.ThreadSafe;
035import org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext;
036import org.apache.fulcrum.yaafi.framework.reflection.Clazz;
037import org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl;
038
039/**
040 * A service using JAMon for performance monitoring. The implementation relies
041 * on reflection to invoke JAMON to avoid compile-time coupling.
042 *
043 * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
044 */
045
046public class JamonInterceptorServiceImpl extends BaseInterceptorServiceImpl
047                implements JamonInterceptorService, Reconfigurable, ThreadSafe, Disposable, Initializable {
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.Jamon2PerformanceMonitorImpl";
074
075        /////////////////////////////////////////////////////////////////////////
076        // Avalon Service Lifecycle Implementation
077        /////////////////////////////////////////////////////////////////////////
078
079        /**
080         * Constructor
081         */
082        public JamonInterceptorServiceImpl() {
083                super();
084        }
085
086        /**
087         * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
088         */
089        public void configure(Configuration configuration) throws ConfigurationException {
090                super.configure(configuration);
091                this.reportTimeout = configuration.getChild("reportTimeout").getValueAsLong(0);
092
093                // parse the performance monitor class name
094                this.performanceMonitorClassName = configuration.getChild("performanceMonitorClassName")
095                                .getValue(DEFAULT_PERFORMANCEMONITOR_CLASSNAME);
096
097                // parse the report file name
098                String reportFileName = configuration.getChild("reportFile").getValue("./jamon.html");
099                this.reportFile = this.makeAbsoluteFile(reportFileName);
100
101                // determine when to create the next report
102                this.nextReportTimestamp = System.currentTimeMillis() + this.reportTimeout;
103
104                // do we create a report on disposal
105                this.reportOnExit = configuration.getChild("reportOnExit").getValueAsBoolean(false);
106        }
107
108        /**
109         * @see org.apache.avalon.framework.activity.Initializable#initialize()
110         */
111        public void initialize() throws Exception {
112                ClassLoader classLoader = this.getClassLoader();
113
114                if (!Clazz.hasClazz(classLoader, MONITORFACTORY_CLASSNAME)) {
115                        String msg = "The JamonInterceptorService is disabled since the JAMON classes are not found in the classpath";
116                        this.getLogger().warn(msg);
117                        this.isJamonAvailable = false;
118                        return;
119                }
120
121                if (!Clazz.hasClazz(classLoader, this.performanceMonitorClassName)) {
122                        String msg = "The JamonInterceptorService is disabled since the performance monitor class is not found in the classpath";
123                        this.getLogger().warn(msg);
124                        this.isJamonAvailable = false;
125                        return;
126                }
127
128                // load the performance monitor class
129                this.performanceMonitorClass = Clazz.getClazz(this.getClassLoader(), this.performanceMonitorClassName);
130
131                // check if we can create an instance of the performance monitor class
132                JamonPerformanceMonitor testMonitor = this.createJamonPerformanceMonitor(null, null, true);
133                if (testMonitor == null) {
134                        String msg = "The JamonInterceptorService is disabled since the performance monitor can't be instantiated";
135                        this.getLogger().warn(msg);
136                        this.isJamonAvailable = false;
137                        return;
138                }
139
140                this.getLogger().debug("The JamonInterceptorService is enabled");
141                this.isJamonAvailable = true;
142        }
143
144        /**
145         * @see org.apache.avalon.framework.configuration.Reconfigurable#reconfigure(org.apache.avalon.framework.configuration.Configuration)
146         */
147        public void reconfigure(Configuration configuration) throws ConfigurationException {
148                super.reconfigure(configuration);
149                this.configure(configuration);
150        }
151
152        /**
153         * @see org.apache.avalon.framework.activity.Disposable#dispose()
154         */
155        public void dispose() {
156                if (this.reportOnExit) {
157                        this.run();
158                }
159
160                this.reportFile = null;
161        }
162
163        /////////////////////////////////////////////////////////////////////////
164        // Service interface implementation
165        /////////////////////////////////////////////////////////////////////////
166
167        /**
168         * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onEntry(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext)
169         */
170        public void onEntry(AvalonInterceptorContext interceptorContext) {
171                if (this.isJamonAvailable()) {
172                        this.writeReport();
173
174                        String serviceShortHand = interceptorContext.getServiceShorthand();
175                        Method serviceMethod = interceptorContext.getMethod();
176                        boolean isEnabled = this.isServiceMonitored(interceptorContext);
177                        JamonPerformanceMonitor monitor = this.createJamonPerformanceMonitor(serviceShortHand, serviceMethod,
178                                        isEnabled);
179                        monitor.start();
180                        interceptorContext.getRequestContext().put(this.getServiceName(), monitor);
181                }
182        }
183
184        /**
185         * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onExit(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext,
186         *      java.lang.Object)
187         */
188        public void onExit(AvalonInterceptorContext interceptorContext, Object result) {
189                if (this.isJamonAvailable()) {
190                        JamonPerformanceMonitor monitor;
191                        monitor = (JamonPerformanceMonitor) interceptorContext.getRequestContext().remove(this.getServiceName());
192                        monitor.stop();
193                }
194        }
195
196        /**
197         * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onError(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext,
198         *      java.lang.Throwable)
199         */
200        public void onError(AvalonInterceptorContext interceptorContext, Throwable t) {
201                if (this.isJamonAvailable()) {
202                        JamonPerformanceMonitor monitor;
203                        monitor = (JamonPerformanceMonitor) interceptorContext.getRequestContext().remove(this.getServiceName());
204                        monitor.stop(t);
205                }
206        }
207
208        /**
209         * Writes the JAMON report to the file system.
210         *
211         * @see java.lang.Runnable#run()
212         */
213        public void run() {
214                this.writeReport(this.reportFile);
215        }
216
217        /////////////////////////////////////////////////////////////////////////
218        // Service Implementation
219        /////////////////////////////////////////////////////////////////////////
220
221        /**
222         * @return Returns true if JAMon is availble.
223         */
224        protected final boolean isJamonAvailable() {
225                return this.isJamonAvailable;
226        }
227
228        /**
229         * Factory method for creating an implementation of a JamonPerformanceMonitor.
230         *
231         * @param serviceName the service name
232         * @param method      the method
233         * @param isEnabled   is the monitor enabled
234         * @return the instance or <b>null</b> if the creation failed
235         */
236        @SuppressWarnings("rawtypes")
237        protected JamonPerformanceMonitor createJamonPerformanceMonitor(String serviceName, Method method,
238                        boolean isEnabled) {
239                JamonPerformanceMonitor result = null;
240
241                try {
242                        Class[] signature = { String.class, Method.class, Boolean.class };
243                        Object[] args = { serviceName, method, isEnabled ? Boolean.TRUE : Boolean.FALSE };
244                        result = (JamonPerformanceMonitor) Clazz.newInstance(this.performanceMonitorClass, signature, args);
245                        return result;
246                } catch (Exception e) {
247                        String msg = "Failed to create a performance monitor instance : " + this.performanceMonitorClassName;
248                        this.getLogger().error(msg, e);
249                        return result;
250                }
251        }
252
253        /**
254         * Write a report file
255         */
256        protected void writeReport() {
257                if (this.reportTimeout > 0) {
258                        long currTimestamp = System.currentTimeMillis();
259
260                        if (currTimestamp > this.nextReportTimestamp) {
261                                this.nextReportTimestamp = currTimestamp + this.reportTimeout;
262                                this.writeReport(this.reportFile);
263                        }
264                }
265        }
266
267        /**
268         * Write the HTML report to the given destination.
269         *
270         * @param reportFile the report destination
271         */
272        protected void writeReport(File reportFile) {
273                PrintWriter printWriter = null;
274
275                if (this.isJamonAvailable()) {
276                        try {
277                                if (this.getLogger().isDebugEnabled()) {
278                                        this.getLogger().debug("Writing JAMOM report to " + reportFile.getAbsolutePath());
279                                }
280
281                                // Update to eliminate reliance on default encoding (DM_DEFAULT_ENCODING)
282                                Writer w = new OutputStreamWriter(new FileOutputStream(reportFile), "UTF-8");
283                                printWriter = new PrintWriter(w);
284
285                                JamonPerformanceMonitor monitor = this.createJamonPerformanceMonitor(null, null, true);
286                                String report = monitor.createReport();
287                                printWriter.write(report);
288                                printWriter.close();
289                        } catch (Throwable t) {
290                                String msg = "Generating the JAMON report failed for " + reportFile.getAbsolutePath();
291                                this.getLogger().error(msg, t);
292                        } finally {
293                                if (printWriter != null) {
294                                        printWriter.close();
295                                }
296                        }
297                }
298        }
299}