/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration), * All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package alma.acs.monitoring; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.util.logging.Logger; /** * Dynamic interceptor, to be used for example to log or modify calls to a corba object like the CDB or a Java component. * <p> * This class has been extracted and generalized from <code>alma.acs.container.ContainerSealant</code> in module jcont, * to allow reusing the code for intercepting other calls as well. * * @author hsommer * @since ACS 9.0 */ public class DynamicInterceptor implements InvocationHandler { private final Object delegate; private final Logger logger; private final ClassLoader contextCL; private final InterceptionHandlerFactory interceptionHandlerFactory; /** * To be called only from the proxy factory method * {@link #createDynamicInterceptor(Class, Object, Logger, ClassLoader, InterceptionHandlerFactory)} */ protected DynamicInterceptor(Object delegate, Logger logger, ClassLoader contextCL, InterceptionHandlerFactory interceptionHandlerFactory) { this.delegate = delegate; this.logger = logger; this.contextCL = contextCL; this.interceptionHandlerFactory = interceptionHandlerFactory; } /** * Creates a <code>DynamicInterceptor</code> instance and uses it as the invocation handler for the returned dynamic proxy * which implements <code>dynInterface</code>. * <p> * Note that if we want to allow a chain of handlers for each call, e.g. to more cleanly separate logging from permission checks, * then we should change this method to accept a <code>List<InterceptionHandlerFactory></code>, to create many * <code>InterceptionHandler</code> objects for each call. These handlers must then be chained together in a way that the call aborts after the first * handler's <code>callReceived</code> has returned <code>false</code>, and the return value of the first handler's <code>callFinished</code> * is fed to the second handler as the <code>retVal</code> argument. * * @param dynInterface * @param delegate The delegation object. * <br> * Note about usage of java generics: Ideally this delegate would be declared "T" instead of "Object", * but did not get it to work with that... * @param logger The Logger to be used by this class. * @param contextCL The class loader to be associated with the current thread during the forwarding of the call to the delegate object, * or <code>null</code> if the interceptor should not swap the classloader. * @param interceptionHandlerFactory Will be used to create the callback object (one per invocation). */ public static <T> T createDynamicInterceptor(Class<T> dynInterface, Object delegate, Logger logger, ClassLoader contextCL, InterceptionHandlerFactory interceptionHandlerFactory) { DynamicInterceptor invHandler = new DynamicInterceptor(delegate, logger, contextCL, interceptionHandlerFactory); T proxy = (T) Proxy.newProxyInstance(dynInterface.getClassLoader(), new Class<?>[] { dynInterface }, invHandler); return proxy; } /** * Receives the intercepted calls, and forwards them to the handler and delegate object. * Also sets the class loader passed to {@link #createDynamicInterceptor(Class, Object, Logger, ClassLoader, InterceptionHandlerFactory)} * as the current thread's context class loader (see <code>Thread.currentThread().getContextClassLoader()</code>). * @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 { InterceptionHandler handler = interceptionHandlerFactory.createInterceptionHandler(); boolean allowCall = handler.callReceived(method, args); Object retObj = null; Throwable realThr = null; if (allowCall) { try { ClassLoader oldContCL = Thread.currentThread().getContextClassLoader(); if (contextCL != null) { Thread.currentThread().setContextClassLoader(contextCL); } try { // forward the call to the delegate object retObj = method.invoke(delegate, args); } finally { Thread.currentThread().setContextClassLoader(oldContCL); } } catch (Throwable thr) { realThr = unwindThrowableHierarchy(thr); } } else { logger.fine("Rejected call to method '" + method.getName() + "' as advised by handler " + handler.getClass().getName()); } Object retObj2 = handler.callFinished(retObj, args, realThr); return retObj2; } /** * Pops <code>InvocationTargetException</code>s and <code>UndeclaredThrowableException</code>s * off the exception cause stack, until a "real" exception is found. * @param thr * @return */ private Throwable unwindThrowableHierarchy(Throwable thr) { Throwable realThr = thr; if (thr instanceof InvocationTargetException || thr instanceof UndeclaredThrowableException) { if (thr.getCause() != null) { // should always be true... realThr = thr.getCause(); return unwindThrowableHierarchy(realThr); } } return realThr; } /** * Users of the {@link DynamicInterceptor} class must provide a {@link InterceptionHandlerFactory} * which will be used to create an instance of this {@link InterceptionHandler} for every intercepted call. * This instance is then used to notify the user before and after the actual call to the delegate object. * <p> * (If we make this interface into an abstract base class, we should move the arguments of "callReceived" * to the constructor, since there is only one method invoked during the lifetime of an InterceptionHandler). */ public static interface InterceptionHandler { /** * Notification that a call to the {@link DynamicInterceptor#delegate} has been intercepted, * but the call has not yet been forwarded. * @param method The name of the interface method that has been called. * @param args The call arguments, see {@link InvocationHandler#invoke(Object, Method, Object[])}. * @return <code>true</code> to continue with forwarding the call to the delegate object; * <code>false</code> to prohibit the call to the delegate object * (in which case {@link #callFinished(Object, Throwable)} must provide the return value or exception). */ public boolean callReceived(Method method, Object[] args); /** * @param retVal The return value received from the delegate object. * @param args The call arguments (same as in <code>callReceived</code>, * but possibly with values modified during the call, e.g. for Corba out or inout parameters). * @param thr The Throwable that was thrown by the delegate's method implementation, if any. * Note that wrapper exceptions {@link InvocationTargetException} and {@link UndeclaredThrowableException} * get removed automatically, so that <code>thr</code> is the original exception thrown, * or <code>null</code> if no exception/error was thrown. * @return The value that should be returned by the interceptor (should be <code>retVal</code> * unless the interceptor wants to modify the return value). * @throws Throwable The throwable that should be forwarded to the calling client. Normally this should be the same * as the argument <code>thr</code> (which then <b>must</b> be thrown by the implementation of this method!), * but the handler is free to throw an exception even if the * delegate object did not throw any, or to throw a different exception, or to suppress the exception. */ public Object callFinished(Object retVal, Object[] args, Throwable thr) throws Throwable; } /** * We use a factory to allow for the convenient (though slightly less performant) design * of grouping the methods for notification before and after the intercepted call is performed * into a single dedicated {@link InterceptionHandler} object. * Then even in a multithreaded environment the "after" can be matched to the "before" easily. */ public static interface InterceptionHandlerFactory { /** * Creates an InterceptionHandler instance to be used for a single call. */ public InterceptionHandler createInterceptionHandler(); } }