View Javadoc
1   package org.apache.fulcrum.yaafi.interceptor.jamon;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.OutputStreamWriter;
25  import java.io.PrintWriter;
26  import java.io.Writer;
27  import java.lang.reflect.Method;
28  
29  import org.apache.avalon.framework.activity.Disposable;
30  import org.apache.avalon.framework.activity.Initializable;
31  import org.apache.avalon.framework.configuration.Configuration;
32  import org.apache.avalon.framework.configuration.ConfigurationException;
33  import org.apache.avalon.framework.configuration.Reconfigurable;
34  import org.apache.avalon.framework.thread.ThreadSafe;
35  import org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext;
36  import org.apache.fulcrum.yaafi.framework.reflection.Clazz;
37  import org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl;
38  
39  /**
40   * A service using JAMon for performance monitoring. The implementation relies
41   * on reflection to invoke JAMON to avoid compile-time coupling.
42   *
43   * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
44   */
45  
46  public class JamonInterceptorServiceImpl extends BaseInterceptorServiceImpl
47  		implements JamonInterceptorService, Reconfigurable, ThreadSafe, Disposable, Initializable {
48  	/** are the JAMon classes in the classpath */
49  	private boolean isJamonAvailable;
50  
51  	/** the file to hold the report */
52  	private File reportFile;
53  
54  	/** the time in ms between two reports */
55  	private long reportTimeout;
56  
57  	/** do we create a report during disposal of the service */
58  	private boolean reportOnExit;
59  
60  	/** the time when the next report is due */
61  	private long nextReportTimestamp;
62  
63  	/** the implementation class name for the performance monitor */
64  	private String performanceMonitorClassName;
65  
66  	/** the implementation class name for the performance monitor */
67  	private Class<?> performanceMonitorClass;
68  
69  	/** the class name of the JAMon MonitorFactory */
70  	private static final String MONITORFACTORY_CLASSNAME = "com.jamonapi.MonitorFactory";
71  
72  	/** the class name of the JAMon MonitorFactory */
73  	private static final String DEFAULT_PERFORMANCEMONITOR_CLASSNAME = "org.apache.fulcrum.yaafi.interceptor.jamon.Jamon2PerformanceMonitorImpl";
74  
75  	/////////////////////////////////////////////////////////////////////////
76  	// Avalon Service Lifecycle Implementation
77  	/////////////////////////////////////////////////////////////////////////
78  
79  	/**
80  	 * Constructor
81  	 */
82  	public JamonInterceptorServiceImpl() {
83  		super();
84  	}
85  
86  	/**
87  	 * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
88  	 */
89  	public void configure(Configuration configuration) throws ConfigurationException {
90  		super.configure(configuration);
91  		this.reportTimeout = configuration.getChild("reportTimeout").getValueAsLong(0);
92  
93  		// parse the performance monitor class name
94  		this.performanceMonitorClassName = configuration.getChild("performanceMonitorClassName")
95  				.getValue(DEFAULT_PERFORMANCEMONITOR_CLASSNAME);
96  
97  		// parse the report file name
98  		String reportFileName = configuration.getChild("reportFile").getValue("./jamon.html");
99  		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 }