/*
* 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;
}
}
}