/* * 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.component.dynwrapper; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import alma.ACS.OffShoot; import alma.xmlentity.XmlEntityStruct; /** * @author heiko */ @SuppressWarnings("unchecked") public class ComponentInvocationHandler implements InvocationHandler { private final Logger m_logger; private final Object m_delegate; private final Map<String, Method> m_delegateMethodMap; private final List<TypeMapper> m_typeMappers; // key=String(fromClassNameToClassName), value=Mapper that can do it private final Map<String, TypeMapper> m_mapperMap; /** * Constructor for ComponentInvocationHandler. * <p> * <FONT SIZE="-1">Explanation of why <code>delegateIF</code> must be passed * in addition to <code>delegate</code>: * the method <code>findDelegateMethod</code> has to resolve the corresponding * method from the delegation object. * In the server-side case, the delegation object is the * component implementation, and the passed method belongs to the corba operations * interface of that component. CORBA interfaces are not allowed to use two methods * with the same name but different parameters, and the "internal" functional * interface of the component is foreseen to be derived such that method names * don't change. * <p> * One might thus believe that there would be a unique correspondence * between method names from the corba operations interface and from the functional * interface of the component implementation; this is not necessarily the case * though, because the component implementation might inherit arbitrary * methods for which the Corba IDL naming restrictions don't apply. Suppose the * Corba operations IF contains a method <code>SchedBlock getSchedBlock()</code>. * Thanks to IDL conventions, there can not be any other name-overloaded method like * <code>SchedBlock getSchedBlock(String name)</code>. However, the component * implementation could contain this method, either directly or through inheritance, * outside of the functional interface. This would cause an ambiguity when trying * to dispatch to the correct method. To avoid this conflict, <code>delegateIF</code> * is passed as well, so that the ComponentInvocationHandler knows which * subset of methods found in * <code>delegate</code> can be considered for method dispatching. * </FONT> */ ComponentInvocationHandler(java.lang.Object delegate, Class delegateIF, Logger logger) { m_delegate = delegate; m_logger = logger; m_typeMappers = new ArrayList<TypeMapper>(); m_mapperMap = new HashMap<String, TypeMapper>(); m_delegateMethodMap = new HashMap<String, Method>(); // store delegate methods by name in a Map Method[] delegateMethods = delegateIF.getMethods(); for (int i = 0; i < delegateMethods.length; i++) { m_delegateMethodMap.put(delegateMethods[i].getName(), delegateMethods[i]); } // add more type mappers here as they become available addTypeMapper(new OffShootMapper(delegate, m_logger)); addTypeMapper(new IdentityMapper(delegate, m_logger)); addTypeMapper(new CastorMarshalMapper(delegate, m_logger)); addTypeMapper(new CastorUnmarshalMapper(delegate, m_logger)); addTypeMapper(new HolderMapper(delegate, m_logger)); addTypeMapper(new ArrayMapper(delegate, m_logger)); addTypeMapper(new CompositionMapper(delegate, m_logger)); } void addTypeMapper(TypeMapper typeMapper) { m_typeMappers.add(typeMapper); } public void addOffshoot(Object offshootImpl, OffShoot shoot) { for(TypeMapper mapper: m_typeMappers) { if( mapper instanceof OffShootMapper ) { ((OffShootMapper)mapper).addOffshoot(offshootImpl, shoot); break; } } } /** * Processes a method invocation on a proxy instance and returns * the result. This method will be invoked on an invocation handler * when a method is invoked on a proxy instance that it is * associated with. * * @param proxy the proxy instance that the method was invoked on * * @param method the <code>Method</code> instance corresponding to * the interface method invoked on the proxy instance. The declaring * class of the <code>Method</code> object will be the interface that * the method was declared in, which may be a superinterface of the * proxy interface that the proxy class inherits the method through. * * @param args an array of objects containing the values of the * arguments passed in the method invocation on the proxy instance, * or <code>null</code> if interface method takes no arguments. * Arguments of primitive types are wrapped in instances of the * appropriate primitive wrapper class, such as * <code>java.lang.Integer</code> or <code>java.lang.Boolean</code>. * * @return the value to return from the method invocation on the * proxy instance. If the declared return type of the interface * method is a primitive type, then the value returned by * this method must be an instance of the corresponding primitive * wrapper class; otherwise, it must be a type assignable to the * declared return type. If the value returned by this method is * <code>null</code> and the interface method's return type is * primitive, then a <code>NullPointerException</code> will be * thrown by the method invocation on the proxy instance. If the * value returned by this method is otherwise not compatible with * the interface method's declared return type as described above, * a <code>ClassCastException</code> will be thrown by the method * invocation on the proxy instance. * * @throws Throwable the exception to throw from the method * invocation on the proxy instance. The exception's type must be * assignable either to any of the exception types declared in the * <code>throws</code> clause of the interface method or to the * unchecked exception types <code>java.lang.RuntimeException</code> * or <code>java.lang.Error</code>. If a checked exception is * thrown by this method that is not assignable to any of the * exception types declared in the <code>throws</code> clause of * the interface method, then an * {@link UndeclaredThrowableException} containing the * exception that was thrown by this method will be thrown by the * method invocation on the proxy instance. * * @see UndeclaredThrowableException */ /** * @see java.lang.reflect.InvocationHandler#invoke(Object, Method, Object[]) */ public Object invoke(Object proxy, Method proxyMethod, Object[] proxyArgs) throws Throwable { Method delegateMethod = findDelegateMethod(proxyMethod); // translate arguments (todo: only IN and INOUT) Object[] delegateArgs = null; if (proxyArgs != null) { delegateArgs = new Object[proxyArgs.length]; for (int argIndex = 0; argIndex < proxyArgs.length; argIndex++) { Class delegateArgType = delegateMethod.getParameterTypes()[argIndex]; delegateArgs[argIndex] = translate(proxyArgs[argIndex], null, delegateArgType); } } // call the delegate method Object delegateReturn = null; try { delegateReturn = delegateMethod.invoke(m_delegate, delegateArgs); } catch (InvocationTargetException ex) { throw ex.getCause(); } // translate arguments (todo: only OUT and INOUT arguments) int argIndex = 0; try { if (proxyArgs != null) { for (argIndex = 0; argIndex < proxyArgs.length; argIndex++) { Class facadeArgType = proxyMethod.getParameterTypes()[argIndex]; proxyArgs[argIndex] = translate(delegateArgs[argIndex], proxyArgs[argIndex], facadeArgType); } } } catch (Exception e) { String methodName = m_delegate.getClass().getName() + "#" + delegateMethod.getName(); throw new DynWrapperException("failed to translate parameter in method " + methodName, e); // System.err.println("exception in invoke--after delegate call: "); // e.printStackTrace(System.err); } // translate return value if necessary Object proxyRet = null; try { proxyRet = translate(delegateReturn, null, proxyMethod.getReturnType()); } catch (Exception e) { throw new DynWrapperException("failed to translate return value.", e); // System.err.println("exception in invoke--after delegate call: "); // e.printStackTrace(System.err); } return proxyRet; } /** * Finds the <code>Method</code> in the functional interface of the delegate object * that maps to the given argument <code>method</code>. * <p> * Note that the restriction to methods from the functional interface (passed to the ctor) * reduces the task to finding a method with the same name, because the interfaces * considered here are derived from CORBA IDL definitions and therefore cannot have * method names overloaded. * * @param method the method from the outside interface * @return the corresponding delegate method. * @throws DynWrapperException if no such method can be found. */ Method findDelegateMethod(Method method) throws DynWrapperException { Method matchingDelegateMethod = m_delegateMethodMap.get(method.getName()); if (matchingDelegateMethod == null) { throw new DynWrapperException("no method '" + method.getName() + "' found in delegate object's functional interface."); } return matchingDelegateMethod; } boolean canTranslate(Class<?> oldObjClass, Class<?> newObjClass) { String mapKey = oldObjClass.getName() + newObjClass.getName(); if (m_mapperMap.containsKey(mapKey)) { // checked this already return true; } // now try all our mappers and see who can do it for (TypeMapper typeMapper : m_typeMappers) { if (typeMapper.canTranslate(oldObjClass, newObjClass, this)) { m_mapperMap.put(mapKey, typeMapper); return true; } } return false; } Object translate(Object oldObject, Object newObjectTemplate, Class newObjectClass) throws DynWrapperException { if (oldObject == null) { // Normally a null stays a null, even if that would be illegal for a corba return value. // For xml entities though, as a special service we translate a null to an empty XmlEntityStruct, // see http://jira.alma.cl/browse/COMP-1336 if (newObjectClass == XmlEntityStruct.class) { return new XmlEntityStruct(); } else { return null; } } // must convert since oldObject is of wrapper type even if the method signature uses the primitive type newObjectClass = primitiveToWrapper(newObjectClass); if (newObjectClass.isInstance(oldObject)) { // nothing to do... TODO: or do we leave this nothing to our IdentityMapper? return oldObject; } // get the right Mapper String mapKey = oldObject.getClass().getName() + newObjectClass.getName(); TypeMapper typeMapper = m_mapperMap.get(mapKey); // if not found, check them out again; perhaps canTranslate hadn't been called if (typeMapper == null) { canTranslate(oldObject.getClass(), newObjectClass); typeMapper = m_mapperMap.get(mapKey); } // now we know that we can't deal with it if (typeMapper == null) { String msg = "no TypeMapper found to translate " + oldObject.getClass().getName() + " to " + newObjectClass.getName(); throw new DynWrapperException(msg); } Object newObject = typeMapper.translate(oldObject, newObjectTemplate, newObjectClass, this); return newObject; } /** * Converts the Class of primitive types to the Class of the corresponding wrappers. */ private Class<?> primitiveToWrapper(Class<?> inClass) { // todo: check if the JDK provides this method as public (must have it internally somewhere...) // remember: Boolean.TYPE == boolean.class if (inClass == Boolean.TYPE) return Boolean.class; if (inClass == Character.TYPE) return Character.class; if (inClass == Byte.TYPE) return Byte.class; if (inClass == Short.TYPE) return Short.class; if (inClass == Integer.TYPE) return Integer.class; if (inClass == Long.TYPE) return Long.class; if (inClass == Float.TYPE) return Float.class; if (inClass == Double.TYPE) return Double.class; if (inClass == Void.TYPE) return Void.class; return inClass; } }