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}