/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.rpc.internal; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.common.InstanceNodeSessionId; import de.rcenvironment.core.communication.rpc.ServiceCallRequest; import de.rcenvironment.core.communication.rpc.api.RemotableCallbackService; import de.rcenvironment.core.communication.rpc.api.RemoteServiceCallSenderService; import de.rcenvironment.core.communication.spi.CallbackMethod; import de.rcenvironment.core.communication.spi.CallbackObject; /** * {@link InvocationHandler} implementation used to create proxy for objects which need to be called back. * * TODO review/rework this concept; it is odd that the {@link InvocationHandler} itself is serialized * * @author Doreen Seider * @author Robert Mischke */ public class CallbackInvocationHandler implements InvocationHandler, Serializable { private static final long serialVersionUID = 3758584730981030172L; /** * Holds the {@link RemoteServiceCallSenderService} for a node so it can be accessed by deserialized {@link CallbackInvocationHandler}s. * (Note that the whole callback concept should be reviewed/reworked.) * * @author Robert Mischke */ public static class RemoteServiceCallServiceHolder { private static RemoteServiceCallSenderService remoteServiceCallService; public RemoteServiceCallServiceHolder() {} /** * OSGi-DS bind method. * * @param newInstance the new service instance */ public static void bindRemoteServiceCallService(RemoteServiceCallSenderService newInstance) { RemoteServiceCallServiceHolder.remoteServiceCallService = newInstance; } private static RemoteServiceCallSenderService getRemoteServiceCallService() { return remoteServiceCallService; } } private static final transient Log LOGGER = LogFactory.getLog(CallbackInvocationHandler.class); private final CallbackObject callbackObject; private final String objectId; private final InstanceNodeSessionId objectNodeId; private final InstanceNodeSessionId proxyNodeId; private final Class<?> callbackInterface; public CallbackInvocationHandler(CallbackObject callbackObject, String objectIdentifier, InstanceNodeSessionId objectHome, InstanceNodeSessionId proxyHome) { this.callbackObject = callbackObject; this.callbackInterface = callbackObject.getInterface(); this.objectId = objectIdentifier; this.objectNodeId = objectHome; this.proxyNodeId = proxyHome; } @Override public Object invoke(Object proxy, Method method, Object[] parameters) throws Throwable { final String methodName = method.getName(); Object returnValue; // extracted to satisfy CheckStyle if (methodName.equals("getHomePlatform")) { returnValue = objectNodeId; } else if (methodName.equals("getObjectIdentifier")) { returnValue = objectId; } else { if (matchesCallbackMethod(method)) { returnValue = invokeRemoteMethod(methodName, parameters); } else { returnValue = method.invoke(callbackObject, parameters); } } return returnValue; } /** * Performs a remote method invocation on the wrapped {@link CallbackObject}. * * @param methodName the name of the method to invoke * @param parameters the parameters to pass to the method * * @return the return value of the remote method call * @throws Throwable any {@link Throwable} that was throws by the remote method call */ @SuppressWarnings({ "unchecked", "rawtypes" }) private Object invokeRemoteMethod(final String methodName, Object[] parameters) throws Throwable { List<Serializable> parameterList = new ArrayList<Serializable>(); parameterList.add(0, objectId); parameterList.add(1, methodName); if (parameters != null) { // TODO improve generics handling -- misc_ro final List<Object> parametersAsList = Arrays.asList(parameters); parameterList.add(2, new ArrayList(parametersAsList)); } else { parameterList.add(2, new ArrayList<Serializable>()); } ServiceCallRequest serviceCallRequest = new ServiceCallRequest(objectNodeId.convertToDefaultLogicalNodeSessionId(), proxyNodeId.convertToDefaultLogicalNodeSessionId(), RemotableCallbackService.class.getCanonicalName(), "callback", parameterList); RemoteServiceCallSenderService remoteServiceCallService = RemoteServiceCallServiceHolder.getRemoteServiceCallService(); return remoteServiceCallService.performRemoteServiceCallAsProxy(serviceCallRequest); } /** * Determines whether the given method is applicable for remote invocation. This is the case if and only if it implements a method in * the interface returned by {@link CallbackObject#getInterface()} of the wrapped {@link CallbackObject}, and if the implemented method * has a @{@link CallbackMethod} annotation in this interface. * * @param method the method to invoke * @return true if the method is applicable for remote invocation; see method description */ // TODO add caching by class and method to avoid repeated reflection? private boolean matchesCallbackMethod(Method method) { final String methodName = method.getName(); final Class<?>[] parameterTypes = method.getParameterTypes(); final int parameterCount = parameterTypes.length; // skip the interface check for common local methods to prevent needless generation of NoSuchMethodExceptions; // also check the parameter count to detect simple name clashes final boolean isCommonMethod = ("hashCode".equals(methodName) && parameterCount == 0) || ("toString".equals(methodName) && parameterCount == 0) || ("equals".equals(methodName) && parameterCount == 1); if (isCommonMethod) { // always dispatch these locally, and do not log them return false; } try { Method interfaceMethod = callbackInterface.getMethod(methodName, parameterTypes); // if it exists, check if it has a @Callback method annotation in the interface return interfaceMethod.isAnnotationPresent(CallbackMethod.class); } catch (NoSuchMethodException e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Non-interface method called on callback object of interface " + callbackInterface.getName() + ": " + method); } // not present in interface -> dispatch locally return false; } } }