JavaSimonInterceptorServiceImpl.java

package org.apache.fulcrum.yaafi.interceptor.javasimon;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;

import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.Reconfigurable;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext;
import org.apache.fulcrum.yaafi.framework.reflection.Clazz;
import org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl;

/**
 * A service using JavaSimon for performance monitoring. The implementation
 * relies on reflection to invoke JavaSimon to avoid compile-time coupling.
 *
 * @since 1.0.7
 * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
 */

public class JavaSimonInterceptorServiceImpl extends BaseInterceptorServiceImpl
		implements JavaSimonInterceptorService, Reconfigurable, ThreadSafe, Disposable, Initializable {
	/** are the JavaSimon classes in the classpath */
	private boolean isJavaSimonAvailable;

	/** the file to hold the report */
	private File reportFile;

	/** the time in ms between two reports */
	private long reportTimeout;

	/** do we create a report during disposal of the service */
	private boolean reportOnExit;

	/** the time when the next report is due */
	private long nextReportTimestamp;

	/** the implementation class name for the performance monitor */
	private String performanceMonitorClassName;

	/** the implementation class name for the performance monitor */
	private Class<?> performanceMonitorClass;

	/** the class name of the JavaSimon factory */
	private static final String MONITORFACTORY_CLASSNAME = "org.javasimon.SimonManager";

	/** the class name of the JavaSimon MonitorFactory */
	private static final String DEFAULT_PERFORMANCEMONITOR_CLASSNAME = "org.apache.fulcrum.yaafi.interceptor.javasimon.JavaSimon4PerformanceMonitorImpl";

	/////////////////////////////////////////////////////////////////////////
	// Avalon Service Lifecycle Implementation
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Constructor
	 */
	public JavaSimonInterceptorServiceImpl() {
		super();
	}

	/**
	 * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
	 */
	public void configure(Configuration configuration) throws ConfigurationException {
		super.configure(configuration);
		this.reportTimeout = configuration.getChild("reportTimeout").getValueAsLong(0);

		// parse the performance monitor class name
		this.performanceMonitorClassName = configuration.getChild("performanceMonitorClassName")
				.getValue(DEFAULT_PERFORMANCEMONITOR_CLASSNAME);

		// parse the report file name
		String reportFileName = configuration.getChild("reportFile").getValue("./javasimon.html");
		this.reportFile = this.makeAbsoluteFile(reportFileName);

		// determine when to create the next report
		this.nextReportTimestamp = System.currentTimeMillis() + this.reportTimeout;

		// do we create a report on disposal
		this.reportOnExit = configuration.getChild("reportOnExit").getValueAsBoolean(false);
	}

	/**
	 * @see Initializable#initialize()
	 */
	public void initialize() throws Exception {
		ClassLoader classLoader = this.getClassLoader();

		if (!Clazz.hasClazz(classLoader, MONITORFACTORY_CLASSNAME)) {
			String msg = "The JavaSimonInterceptorService is disabled since the JavaSimon classes are not found in the classpath";
			this.getLogger().warn(msg);
			this.isJavaSimonAvailable = false;
			return;
		}

		if (!Clazz.hasClazz(classLoader, this.performanceMonitorClassName)) {
			String msg = "The JavaSimonInterceptorService is disabled since the performance monitor class is not found in the classpath";
			this.getLogger().warn(msg);
			this.isJavaSimonAvailable = false;
			return;
		}

		// load the performance monitor class
		this.performanceMonitorClass = Clazz.getClazz(this.getClassLoader(), this.performanceMonitorClassName);

		// check if we can create an instance of the performance monitor class
		JavaSimonPerformanceMonitor testMonitor = this.createJavaSimonPerformanceMonitor(null, null, true);
		if (testMonitor == null) {
			String msg = "The JavaSimonInterceptorService is disabled since the performance monitor can't be instantiated";
			this.getLogger().warn(msg);
			this.isJavaSimonAvailable = false;
			return;
		}

		this.getLogger().debug("The JavaSimonInterceptorService is enabled");
		this.isJavaSimonAvailable = true;
	}

	/**
	 * @see Reconfigurable#reconfigure(Configuration)
	 */
	public void reconfigure(Configuration configuration) throws ConfigurationException {
		super.reconfigure(configuration);
		this.configure(configuration);
	}

	/**
	 * @see Disposable#dispose()
	 */
	public void dispose() {
		if (this.reportOnExit) {
			this.run();
		}

		this.reportFile = null;
	}

	/////////////////////////////////////////////////////////////////////////
	// Service interface implementation
	/////////////////////////////////////////////////////////////////////////

	/**
	 * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onEntry(AvalonInterceptorContext)
	 */
	public void onEntry(AvalonInterceptorContext interceptorContext) {
		if (this.isJavaSimonAvailable()) {
			this.writeReport();

			String serviceShortHand = interceptorContext.getServiceShorthand();
			Method serviceMethod = interceptorContext.getMethod();
			boolean isEnabled = this.isServiceMonitored(interceptorContext);
			JavaSimonPerformanceMonitor monitor = this.createJavaSimonPerformanceMonitor(serviceShortHand,
					serviceMethod, isEnabled);
			monitor.start();
			interceptorContext.getRequestContext().put(this.getServiceName(), monitor);
		}
	}

	/**
	 * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onExit(AvalonInterceptorContext,
	 *      Object)
	 */
	public void onExit(AvalonInterceptorContext interceptorContext, Object result) {
		if (this.isJavaSimonAvailable()) {
			JavaSimonPerformanceMonitor monitor;
			monitor = (JavaSimonPerformanceMonitor) interceptorContext.getRequestContext()
					.remove(this.getServiceName());
			monitor.stop();
		}
	}

	/**
	 * @see org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorService#onError(AvalonInterceptorContext,
	 *      Throwable)
	 */
	public void onError(AvalonInterceptorContext interceptorContext, Throwable t) {
		if (this.isJavaSimonAvailable()) {
			JavaSimonPerformanceMonitor monitor;
			monitor = (JavaSimonPerformanceMonitor) interceptorContext.getRequestContext()
					.remove(this.getServiceName());
			monitor.stop(t);
		}
	}

	/**
	 * Writes the JavaSimon report to the file system.
	 *
	 * @see Runnable#run()
	 */
	public void run() {
		this.writeReport(this.reportFile);
	}

	/////////////////////////////////////////////////////////////////////////
	// Service Implementation
	/////////////////////////////////////////////////////////////////////////

	/**
	 * @return Returns the isJavaSimonAvailable.
	 */
	protected final boolean isJavaSimonAvailable() {
		return this.isJavaSimonAvailable;
	}

	/**
	 * Factory method for creating an implementation of a
	 * JavaSimonPerformanceMonitor.
	 *
	 * @param serviceName the service name
	 * @param method      the method
	 * @param isEnabled   is the monitor enabled
	 * @return the instance or <b>null</b> if the creation failed
	 */
	@SuppressWarnings("rawtypes")
	protected JavaSimonPerformanceMonitor createJavaSimonPerformanceMonitor(String serviceName, Method method,
			boolean isEnabled) {
		JavaSimonPerformanceMonitor result = null;

		try {
			Class[] signature = { String.class, Method.class, Boolean.class };
			Object[] args = { serviceName, method, isEnabled ? Boolean.TRUE : Boolean.FALSE };
			result = (JavaSimonPerformanceMonitor) Clazz.newInstance(this.performanceMonitorClass, signature, args);
		} catch (Exception e) {
			String msg = "Failed to create a performance monitor instance : " + this.performanceMonitorClassName;
			this.getLogger().error(msg, e);
		}

		return result;
	}

	/**
	 * Write a report file
	 */
	protected void writeReport() {
		if (this.reportTimeout > 0) {
			long currTimestamp = System.currentTimeMillis();

			if (currTimestamp > this.nextReportTimestamp) {
				this.nextReportTimestamp = currTimestamp + this.reportTimeout;
				this.writeReport(this.reportFile);
			}
		}
	}

	/**
	 * Write the HTML report to the given destination.
	 *
	 * @param reportFile the report destination
	 */
	protected void writeReport(File reportFile) {
		PrintWriter printWriter = null;

		if (this.isJavaSimonAvailable()) {
			try {
				if (this.getLogger().isDebugEnabled()) {
					this.getLogger().debug("Writing JavaSimon report to " + reportFile.getAbsolutePath());
				}

				// Update to eliminate reliance on default encoding (DM_DEFAULT_ENCODING)
				Writer w = new OutputStreamWriter(new FileOutputStream(reportFile), "UTF-8");
				printWriter = new PrintWriter(w);

				// JavaSimonPerformanceMonitor monitor =
				// this.createJavaSimonPerformanceMonitor(null, null, true);
				String report = "Not implemented yet ...";
				printWriter.write(report);
				printWriter.close();
			} catch (Throwable t) {
				String msg = "Generating the JavaSimon report failed for " + reportFile.getAbsolutePath();
				this.getLogger().error(msg, t);
			} finally {
				if (printWriter != null) {
					printWriter.close();
				}
			}
		}
	}
}