/* * 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.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.api.LiveNetworkIdResolutionService; import de.rcenvironment.core.communication.api.PlatformService; import de.rcenvironment.core.communication.common.IdentifierException; import de.rcenvironment.core.communication.common.LogicalNodeSessionId; import de.rcenvironment.core.communication.common.ResolvableNodeId; import de.rcenvironment.core.communication.protocol.ProtocolConstants; import de.rcenvironment.core.communication.rpc.ServiceCallRequest; import de.rcenvironment.core.communication.rpc.api.CallbackProxyService; import de.rcenvironment.core.communication.rpc.api.CallbackService; import de.rcenvironment.core.communication.rpc.api.RemoteServiceCallSenderService; import de.rcenvironment.core.communication.rpc.spi.ServiceProxyFactory; import de.rcenvironment.core.toolkitbridge.api.StaticToolkitHolder; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.rpc.RemoteOperationException; import de.rcenvironment.core.utils.incubator.Assertions; import de.rcenvironment.toolkit.modules.concurrency.api.ThreadGuard; import de.rcenvironment.toolkit.modules.statistics.api.CounterCategory; import de.rcenvironment.toolkit.modules.statistics.api.StatisticsFilterLevel; import de.rcenvironment.toolkit.modules.statistics.api.StatisticsTrackerService; /** * Implementation of the {@link ServiceProxyFactory}. * * @author Dirk Rossow * @author Heinrich Wendel * @author Doreen Seider * @author Robert Mischke */ public final class ServiceProxyFactoryImpl implements ServiceProxyFactory { private static final String ASSERT_MUST_NOT_BE_NULL = " must not be null!"; private static final long serialVersionUID = -4239349616520603192L; private static final Log sharedLogInstance = LogFactory.getLog(ServiceProxyFactoryImpl.class); private static final long ID_RESOLUTION_MAX_RETRIES = 5; private static final long ID_RESOLUTION_RETRY_WAIT_MSEC = 100; private PlatformService platformService; private CallbackService callbackService; private CallbackProxyService callbackProxyService; private RemoteServiceCallSenderService remoteServiceCallService; private CounterCategory parameterTypesCounter; private CounterCategory methodCallCounter; private LiveNetworkIdResolutionService idResolutionService; public ServiceProxyFactoryImpl() { // not injecting this via OSGi-DS as this service is planned to move to the toolkit layer anyway - misc_ro final StatisticsTrackerService statisticsService = StaticToolkitHolder.getServiceWithUnitTestFallback(StatisticsTrackerService.class); methodCallCounter = statisticsService.getCounterCategory("Remote service calls (sent): service methods", StatisticsFilterLevel.RELEASE); parameterTypesCounter = statisticsService.getCounterCategory("Remote service calls (sent): parameter types", StatisticsFilterLevel.DEVELOPMENT); } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindPlatformService(PlatformService newInstance) { platformService = newInstance; } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindLiveNetworkIdResolutionService(LiveNetworkIdResolutionService newInstance) { this.idResolutionService = newInstance; } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindCallbackService(CallbackService newInstance) { callbackService = newInstance; } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindCallbackProxyService(CallbackProxyService newInstance) { callbackProxyService = newInstance; } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindRemoteServiceCallService(RemoteServiceCallSenderService newInstance) { remoteServiceCallService = newInstance; } @Override public Object createServiceProxy(final ResolvableNodeId nodeId, final Class<?> serviceIface, Class<?>[] ifaces) { Assertions.isDefined(nodeId, "The identifier of the requested platform" + ASSERT_MUST_NOT_BE_NULL); Assertions.isDefined(serviceIface, "The interface of the requested service" + ASSERT_MUST_NOT_BE_NULL); final InvocationHandler handler = new InvocationHandler() { private final PlatformService ps = platformService; private final CallbackService cs = callbackService; private final CallbackProxyService cps = callbackProxyService; private final Class<?> myService = serviceIface; private final ResolvableNodeId destinationNodeId = nodeId; // original destination id private LogicalNodeSessionId destinationLogicalNodeSessionId; // resolved destination id @Override public Object invoke(Object proxy, Method method, Object[] parameters) throws Throwable { // this should usually not be called from the GUI thread ThreadGuard.checkForForbiddenThread(); synchronized (this) { if (destinationLogicalNodeSessionId == null) { // lazy resolution at first actual service call time resolveDestinationNodeIdOrFail(); } } if (remoteServiceCallService == null) { // internal error throw new RemoteOperationException("RemoteServiceCallService was null"); } // check to avoid string concatenation if stats are disabled if (methodCallCounter.isEnabled()) { methodCallCounter.count(StringUtils.format("%s#%s(...)", myService.getName(), method.getName())); } List<Serializable> parameterList = new ArrayList<>(); if (parameters != null) { for (Object parameter : parameters) { if (parameterTypesCounter.isEnabled()) { parameterTypesCounter.countClass(parameter); } parameterList.add((Serializable) CallbackUtils.handleCallbackObject(parameter, destinationLogicalNodeSessionId.convertToInstanceNodeSessionId(), cs)); } } ServiceCallRequest serviceCallRequest = new ServiceCallRequest(destinationLogicalNodeSessionId, ps.getLocalDefaultLogicalNodeSessionId(), myService.getCanonicalName(), method.getName(), parameterList); Object returnValue = remoteServiceCallService.performRemoteServiceCallAsProxy(serviceCallRequest); if (returnValue != null) { returnValue = CallbackUtils.handleCallbackProxy(returnValue, cs, cps); } return returnValue; } private void resolveDestinationNodeIdOrFail() throws RemoteOperationException { int retries = 0; // TODO this retry loop is mostly needed for unit tests, as waiting for node visibility does not guarantee complete // propagation of ids to the resolution service; check if there is a more elegant solution to this - misc_ro, 8.0.0 while (true) { try { destinationLogicalNodeSessionId = idResolutionService.resolveToLogicalNodeSessionId(destinationNodeId); if (retries > 0) { sharedLogInstance.debug("Resolved service call destination id " + destinationNodeId + " after " + retries + " failed attempts"); } return; } catch (IdentifierException e) { if (retries < ID_RESOLUTION_MAX_RETRIES) { try { Thread.sleep(ID_RESOLUTION_RETRY_WAIT_MSEC); } catch (InterruptedException e1) { throw new RemoteOperationException( "Interrupted while waiting to retry id resolution for " + destinationNodeId); } retries++; } else { sharedLogInstance.debug( "Converting id resolution exception to a " + RemoteOperationException.class.getSimpleName() + " of type " + ProtocolConstants.ResultCode.NO_ROUTE_TO_DESTINATION_AT_SENDER + ": " + e.toString()); // intended to show similar behavior as NetworkResponseFactory.generateResponseForNoRouteAtSender() throw new RemoteOperationException(StringUtils.format("%s; the destination instance was %s", ProtocolConstants.ResultCode.NO_ROUTE_TO_DESTINATION_AT_SENDER.toString(), destinationNodeId)); } } } } }; if (ifaces == null) { return Proxy.newProxyInstance(serviceIface.getClassLoader(), new Class[] { serviceIface }, handler); } else { Class<?>[] allIfaces = new Class<?>[ifaces.length + 1]; allIfaces[0] = serviceIface; System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); return Proxy.newProxyInstance(serviceIface.getClassLoader(), allIfaces, handler); } } }