LoggingInterceptorServiceImpl.java

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

/*
 * 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.lang.reflect.Method;

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.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext;
import org.apache.fulcrum.yaafi.framework.reflection.Clazz;
import org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl;
import org.apache.fulcrum.yaafi.interceptor.util.ArgumentToStringBuilderImpl;
import org.apache.fulcrum.yaafi.interceptor.util.DefaultToStringBuilderImpl;
import org.apache.fulcrum.yaafi.interceptor.util.InterceptorToStringBuilder;
import org.apache.fulcrum.yaafi.interceptor.util.MethodToStringBuilderImpl;
import org.apache.fulcrum.yaafi.interceptor.util.StopWatch;

/**
 * A service logging of service invocations. The service allows to monitor a
 * list of services defined in the configuration.
 *
 * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
 */

public class LoggingInterceptorServiceImpl extends BaseInterceptorServiceImpl
		implements LoggingInterceptorService, Reconfigurable, Initializable {
	/** the maximum length of a dumped argument */
	private static final int MAX_ARG_LENGTH = 2000;

	/** seperator for the arguments in the logfile */
	private static final String SEPERATOR = ";";

	/** maximum argument length for dumping arguments */
	private int maxArgLength;

	/** the class name of the string builder to use */
	private String toStringBuilderClassName;

	/** monitor all excpetions independent from the monitored services */
	private boolean monitorAllExceptions;

	/** the ReflectionToStringBuilder class */
	private Class<?> toStringBuilderClass;

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

	/**
	 * Constructor
	 */
	public LoggingInterceptorServiceImpl() {
		super();
		this.maxArgLength = MAX_ARG_LENGTH;
	}

	/**
	 * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
	 */
	public void configure(Configuration configuration) throws ConfigurationException {
		super.configure(configuration);

		this.maxArgLength = configuration.getChild("maxArgLength").getValueAsInteger(MAX_ARG_LENGTH);
		this.toStringBuilderClassName = configuration.getChild("toStringBuilderClass")
				.getValue(ArgumentToStringBuilderImpl.class.getName());
		this.monitorAllExceptions = configuration.getChild("monitorAllExceptions").getValueAsBoolean(true);
	}

	/**
	 * @see org.apache.avalon.framework.activity.Initializable#initialize()
	 */
	public void initialize() throws Exception {
		// load the string builder class

		ClassLoader classLoader = this.getClass().getClassLoader();

		if (Clazz.hasClazz(classLoader, this.getToStringBuilderClassName())) {
			this.toStringBuilderClass = Clazz.getClazz(classLoader, this.getToStringBuilderClassName());
		}

		// create an instance of the StringBuilder to see if everything works

		InterceptorToStringBuilder interceptorToStringBuilder = this.createArgumentToStringBuilder(this);

		interceptorToStringBuilder.toString();
	}

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

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

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl#
	 * onEntry(org.apache.fulcrum.yaafi.framework.interceptor.
	 * AvalonInterceptorContext)
	 */
	public void onEntry(AvalonInterceptorContext interceptorContext) {
		if (this.isServiceMonitored(interceptorContext) && this.getLogger().isInfoEnabled() == true) {
			String msg = this.toString(interceptorContext, null, ON_ENTRY);
			this.getLogger().info(msg);
			this.createStopWatch(interceptorContext);
		}
	}

	/* (non-Javadoc)
	 * @see org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl#onError(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext, java.lang.Throwable)
	 */
	public void onError(AvalonInterceptorContext interceptorContext, Throwable t) {
		if (this.getLogger().isErrorEnabled()
				&& (this.isMonitorAllExceptions() || this.isServiceMonitored(interceptorContext))) {
			StopWatch stopWatch = this.getStopWatch(interceptorContext);
			stopWatch.stop();
			String msg = this.toString(interceptorContext, stopWatch, t);
			this.getLogger().error(msg);
		}
	}

	/* (non-Javadoc)
	 * @see org.apache.fulcrum.yaafi.interceptor.baseservice.BaseInterceptorServiceImpl#onExit(org.apache.fulcrum.yaafi.framework.interceptor.AvalonInterceptorContext, java.lang.Object)
	 */
	public void onExit(AvalonInterceptorContext interceptorContext, Object result) {
		if (this.isServiceMonitored(interceptorContext) && this.getLogger().isDebugEnabled() == true) {
			StopWatch stopWatch = this.getStopWatch(interceptorContext);
			stopWatch.stop();
			String msg = this.toString(interceptorContext, stopWatch, result);
			this.getLogger().debug(msg);
		}
	}

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

	/**
	 * Creates a stop watch
	 *
	 * @param interceptorContext the current interceptor context
	 */
	protected void createStopWatch(AvalonInterceptorContext interceptorContext) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		interceptorContext.getRequestContext().put(this.getServiceName(), stopWatch);
	}

	/**
	 * Gets the stop watch. Even if none is defined we return one in a proper state.
	 *
	 * @param interceptorContext the current interceptor context
	 * @return the stop watch
	 */
	protected StopWatch getStopWatch(AvalonInterceptorContext interceptorContext) {
		StopWatch result = (StopWatch) interceptorContext.getRequestContext().remove(this.getServiceName());

		if (result == null) {
			result = new StopWatch();
			result.start();
		}

		return result;
	}

	/**
	 * @return Returns the maxLineLength.
	 */
	protected int getMaxArgLength() {
		return maxArgLength;
	}

	/**
	 * @return Returns the monitorAllExceptions.
	 */
	protected boolean isMonitorAllExceptions() {
		return monitorAllExceptions;
	}

	/**
	 * @return Returns the toStringBuilderClass.
	 */
	protected Class<?> getToStringBuilderClass() {
		return toStringBuilderClass;
	}

	/**
	 * @return Returns the toStringBuilderClassName.
	 */
	protected String getToStringBuilderClassName() {
		return toStringBuilderClassName;
	}

	/**
	 * Create an instance of an InterceptorToStringBuilder
	 *
	 * @param target the object to stringify
	 * @return the string builder
	 */
	protected InterceptorToStringBuilder createArgumentToStringBuilder(Object target) {
		InterceptorToStringBuilder result = null;

		try {
			result = (InterceptorToStringBuilder) this.getToStringBuilderClass().newInstance();
		} catch (Exception e) {
			String msg = "Unable to create an instance for " + this.getToStringBuilderClassName();
			this.getLogger().error(msg, e);
			result = new DefaultToStringBuilderImpl();
		}

		result.setTarget(target);
		result.setMaxArgLength(this.getMaxArgLength());
		result.setMode(1);

		return result;
	}

	/**
	 * Create a string representation of a service invocation returning a result.
	 *
	 * @param avalonInterceptorContext the interceptor context
	 * @param stopWatch                the stopwatch for the execution time
	 * @param result                   the result of the service invocation
	 * @return the string representation of the result
	 */
	protected String toString(AvalonInterceptorContext avalonInterceptorContext, StopWatch stopWatch, Object result) {
		StringBuilder methodSignature = new StringBuilder();
		InterceptorToStringBuilder toStringBuilder = this.createArgumentToStringBuilder(result);

		methodSignature.append(this.toString(avalonInterceptorContext, stopWatch, ON_EXIT));
		methodSignature.append(SEPERATOR);
		methodSignature.append("result={");
		methodSignature.append(toStringBuilder.toString());
		methodSignature.append("}");

		return methodSignature.toString();
	}

	/**
	 * Create a string representation of a service invocation throwing a Throwable
	 *
	 * @param avalonInterceptorContext the interceptor context
	 * @param stopWatch                the stopwatch for the execution time
	 * @param throwable                the result of the service invocation
	 * @return the string representation of the result
	 */
	protected String toString(AvalonInterceptorContext avalonInterceptorContext, StopWatch stopWatch,
			Throwable throwable) {
		StringBuilder methodSignature = new StringBuilder();
		InterceptorToStringBuilder toStringBuilder = this.createArgumentToStringBuilder(throwable);

		methodSignature.append(this.toString(avalonInterceptorContext, stopWatch, ON_ERROR));
		methodSignature.append(SEPERATOR);
		methodSignature.append(throwable.getClass().getName());
		methodSignature.append(SEPERATOR);
		methodSignature.append(toStringBuilder.toString());

		return methodSignature.toString();
	}

	/**
	 * Create a method signature.
	 *
	 * @param interceptorContext the avalonInterceptorContext
	 * @param stopWatch          the stopwatch for the execution time
	 * @param mode               the mode (onEntry, onExit, onError)
	 * @return the debug output
	 */
	protected String toString(AvalonInterceptorContext interceptorContext, StopWatch stopWatch, int mode) {
		StringBuilder result = new StringBuilder();
		Method method = interceptorContext.getMethod();
		Object[] args = interceptorContext.getArgs();
		InterceptorToStringBuilder toStringBuilder = null;
		MethodToStringBuilderImpl methodToStringBuilder = new MethodToStringBuilderImpl(method);

		if (args == null) {
			args = new Object[0];
		}

		result.append(interceptorContext.getTransactionId());
		result.append(SEPERATOR);
		result.append(interceptorContext.getInvocationId());
		result.append(SEPERATOR);
		result.append(interceptorContext.getInvocationDepth());
		result.append(SEPERATOR);
		result.append(mode);
		result.append(SEPERATOR);
		result.append(interceptorContext.getServiceShorthand());
		result.append(SEPERATOR);
		result.append(method.getName());
		result.append(SEPERATOR);

		if (stopWatch != null) {
			result.append(stopWatch.getTime());
		} else {
			result.append('0');
		}

		result.append(SEPERATOR);
		result.append(methodToStringBuilder.toString());

		if (ON_ENTRY == mode || ON_ERROR == mode) {
			for (int i = 0; i < args.length; i++) {
				toStringBuilder = this.createArgumentToStringBuilder(args[i]);
				result.append(SEPERATOR);
				result.append("arg[" + i + "]:={");
				result.append(toStringBuilder.toString());
				result.append("}");
			}
		}

		return result.toString();
	}
}