/*
* $Id: LoggingProxy.java 1011 2008-06-16 17:57:36Z amandel $
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons.tracing;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.jcoderz.commons.ArgumentMalformedException;
import org.jcoderz.commons.tracing.Tracer.TracingToken;
/**
* <p>
* This class can be used to proxy any object, providing tracing for
* all <i>interfaces</i> of the object.
* </p>
* <p>
* <b>Note:</b> Java Dynamic Proxies only work on <i>interfaces</i>.
* The object returned by the {@link #getProxy(Object)} can be cast to
* any interface implemented by the argument or one of its ancestors. It
* can't, however, be cast to an implementation class.
* </p>
*
* @author Andreas Mandel
*/
public final class TracingProxy
implements InvocationHandler
{
private final Object mRealObject;
private final String mRealObjectClassName;
private final Tracer mObjectTracer;
/**
* Create a proxy that directs all calls to the real object and logs all
* method calls with entering/exiting/throwing, using the given logger.
*
* @param realObject the object for which a proxy is created
* @param logger the logger to which calls are logged
*/
private TracingProxy (Object realObject, Tracer tracer)
{
mRealObject = realObject;
mRealObjectClassName = mRealObject.getClass().getName();
mObjectTracer = tracer;
}
/**
* Static factory that wraps an object into a proxy depending on the
* log level for that object.
*
* @param obj an object for which a proxy should be created
* @return a logging proxy for the obj, if the log level for that
* object is FINER or finest, the object itself otherwise
*/
public static Object getProxy (Object obj, Class tracerClass)
{
final String classname = obj.getClass().getName();
final Tracer tracer = getTracer(tracerClass, classname);
final Object proxy;
if (tracer.isTracing())
{
// collect all interfaces implemented by this objects class and
// its super classes
// Note: We do not add super-interfaces here....
final Set interfaces = new HashSet();
Class currentClass = obj.getClass();
while (currentClass != null)
{
interfaces.addAll(Arrays.asList(currentClass.getInterfaces()));
currentClass = currentClass.getSuperclass();
}
proxy = Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
(Class[]) interfaces.toArray(new Class[interfaces.size()]),
new TracingProxy(obj, tracer));
}
else
{
proxy = obj;
}
return proxy;
}
/**
* Log the entering, exiting and throwing events of the proxied object.
*
* @see java.lang.reflect.InvocationHandler#invoke(
* java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
public Object invoke (Object proxy, Method method, Object[] args)
throws Throwable
{
final TracingToken tt;
if (mObjectTracer.isTracing())
{
if (args == null || !mObjectTracer.isTracingArguments())
{
tt = mObjectTracer.entering(
mRealObjectClassName, method.getName());
}
else
{
tt = mObjectTracer.entering(
mRealObjectClassName, method.getName(), args);
}
}
else
{
tt = null;
}
final Object result = invokeMethod(method, args, tt);
if (tt != null)
{
if (result != null
|| method.getReturnType() != Void.TYPE
|| !mObjectTracer.isTracingArguments())
{
mObjectTracer.exiting(tt, result);
}
else
{
mObjectTracer.exiting(tt);
}
}
return result;
}
private static Tracer getTracer(Class tracer, String className)
{
final Tracer result;
try
{
final Method method
= tracer.getMethod("getTracer", new Class[] {String.class});
if (!Modifier.isStatic(method.getModifiers()))
{
throw new ArgumentMalformedException(
"tracer", tracer,
"Factory method 'getTracer' must be static.");
}
if (!Modifier.isPublic(method.getModifiers()))
{
throw new ArgumentMalformedException(
"tracer", tracer,
"Factory method 'getTracer' must be public.");
}
result = (Tracer) method.invoke(null, new Object[] {className});
}
catch (IllegalArgumentException e)
{
throw new ArgumentMalformedException(
"tracer", tracer,
"The static tracer factory did not accept the String argument.",
e);
}
catch (IllegalAccessException e)
{
throw new ArgumentMalformedException(
"tracer", tracer,
"The static tracer factory did deny access.",
e);
}
catch (InvocationTargetException e)
{
throw new ArgumentMalformedException(
"tracer", tracer,
"The static tracer factory threw an exception with detail: "
+ e.getMessage() + ".",
e);
}
catch (SecurityException e)
{
throw new ArgumentMalformedException(
"tracer", tracer,
"Failed to look up static factory method.",
e);
}
catch (NoSuchMethodException e)
{
throw new ArgumentMalformedException(
"tracer", tracer,
"The Tracer implementation must implement a static factory "
+ "method 'public Tracer getTracer(String)'.",
e);
}
return result;
}
private Object invokeMethod (
Method method, Object[] args, TracingToken tt)
throws Throwable
{
final Object result;
try
{
result = method.invoke(mRealObject, args);
}
catch (InvocationTargetException x)
{
if (tt != null)
{
mObjectTracer.throwing(tt, x.getCause());
}
throw x.getCause();
}
catch (Exception x)
{
if (tt != null)
{
mObjectTracer.throwing(tt, x);
}
throw x;
}
return result;
}
}