/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.internal; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.api.CommunicationService; import de.rcenvironment.core.communication.api.LiveNetworkIdResolutionService; import de.rcenvironment.core.communication.api.PlatformService; import de.rcenvironment.core.communication.api.ServiceCallContextUtils; import de.rcenvironment.core.communication.common.IdentifierException; import de.rcenvironment.core.communication.common.InstanceNodeSessionId; import de.rcenvironment.core.communication.common.LogicalNodeId; import de.rcenvironment.core.communication.common.LogicalNodeSessionId; import de.rcenvironment.core.communication.common.NetworkGraph; import de.rcenvironment.core.communication.common.ResolvableNodeId; import de.rcenvironment.core.communication.configuration.NodeConfigurationService; import de.rcenvironment.core.communication.management.CommunicationManagementService; import de.rcenvironment.core.communication.routing.NetworkRoutingService; import de.rcenvironment.core.communication.rpc.spi.LocalServiceResolver; import de.rcenvironment.core.communication.rpc.spi.ServiceProxyFactory; import de.rcenvironment.core.communication.spi.NetworkTopologyChangeListener; import de.rcenvironment.core.communication.spi.NetworkTopologyChangeListenerAdapter; import de.rcenvironment.core.toolkitbridge.api.StaticToolkitHolder; import de.rcenvironment.core.utils.common.rpc.RemotableService; import de.rcenvironment.core.utils.common.service.AdditionalServiceDeclaration; import de.rcenvironment.core.utils.common.service.AdditionalServicesProvider; import de.rcenvironment.core.utils.incubator.DebugSettings; import de.rcenvironment.toolkit.modules.concurrency.api.threadcontext.ThreadContextMemento; 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 CommunicationService}. * * @author Doreen Seider * @author Robert Mischke */ public class CommunicationServiceImpl implements CommunicationService, AdditionalServicesProvider { private static final String SERVICE_NOT_AVAILABLE_ERROR = "The requested service is not available: "; private Set<InstanceNodeSessionId> cachedReachableNodes; private Set<LogicalNodeId> cachedReachableLogicalNodes; private ServiceProxyFactory remoteServiceHandler; private PlatformService platformService; private CommunicationManagementService newManagementService; private NetworkRoutingService routingService; private LocalServiceResolver localServiceResolver; private InstanceNodeSessionId localInstanceNodeSessionId; private LogicalNodeSessionId localDefaultLogicalNodeSessionId; private LiveNetworkIdResolutionServiceImpl idResolutionService; // NOTE: used in several locations private final boolean forceLocalRPCSerialization = System .getProperty(NodeConfigurationService.SYSTEM_PROPERTY_FORCE_LOCAL_RPC_SERIALIZATION) != null; private final CounterCategory serviceRequestCounter; @SuppressWarnings("unused") private final boolean verboseLogging = DebugSettings.getVerboseLoggingEnabled(getClass()); private final Log log = LogFactory.getLog(getClass()); public CommunicationServiceImpl() { // 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); serviceRequestCounter = statisticsService.getCounterCategory("Remote services: service proxies fetched via getRemotableService()", StatisticsFilterLevel.DEVELOPMENT); } /** * OSGi-DS lifecycle method; also called by integration tests. */ public void activate() { this.localInstanceNodeSessionId = platformService.getLocalInstanceNodeSessionId(); this.localDefaultLogicalNodeSessionId = platformService.getLocalDefaultLogicalNodeSessionId(); idResolutionService.registerLocalInstanceNodeSessionId(localInstanceNodeSessionId); updateOnReachableNetworkChanged(routingService.getReachableNetworkGraph()); // TODO old code; rework // RemoteServiceCallServiceImpl.bindNetworkRoutingService(routingService); } /** * OSGi-DS lifecycle method. */ public void deactivate() { // TODO for now, triggered from here; move to management service? newManagementService.shutDownNetwork(); } @Override public Collection<AdditionalServiceDeclaration> defineAdditionalServices() { List<AdditionalServiceDeclaration> result = new ArrayList<AdditionalServiceDeclaration>(); result.add(new AdditionalServiceDeclaration(NetworkTopologyChangeListener.class, new NetworkTopologyChangeListenerAdapter() { @Override public void onReachableNodesChanged(Set<InstanceNodeSessionId> reachableNodes, Set<InstanceNodeSessionId> addedNodes, Set<InstanceNodeSessionId> removedNodes) { for (InstanceNodeSessionId node : removedNodes) { log.debug( "Topology change: Node " + node + " is not reachable anymore (local node: " + localInstanceNodeSessionId + ")"); idResolutionService.unregisterInstanceNodeSessionId(node); } for (InstanceNodeSessionId node : addedNodes) { log.debug("Topology change: Node " + node + " is now reachable (local node: " + localInstanceNodeSessionId + ")"); idResolutionService.registerInstanceNodeSessionId(node); } } @Override public void onReachableNetworkChanged(NetworkGraph networkGraph) { CommunicationServiceImpl.this.updateOnReachableNetworkChanged(networkGraph); } })); return result; } protected synchronized void updateOnReachableNetworkChanged(NetworkGraph networkGraph) { cachedReachableNodes = Collections.unmodifiableSet(new HashSet<InstanceNodeSessionId>(networkGraph.getNodeIds())); // FIXME >8.0.0 preliminary - this only supports the *default* logical node ids, not the ones published via other mechanisms; // however, as we decided against active LNId publishing in 8.0.0, there is nothing to fix right now final Set<LogicalNodeId> tempSet = new HashSet<LogicalNodeId>(); for (InstanceNodeSessionId instanceSessionId : cachedReachableNodes) { tempSet.add(instanceSessionId.convertToDefaultLogicalNodeId()); } cachedReachableLogicalNodes = Collections.unmodifiableSet(tempSet); } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindServiceProxyFactory(ServiceProxyFactory newInstance) { remoteServiceHandler = newInstance; } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindLocalServiceResolver(LocalServiceResolver newInstance) { localServiceResolver = newInstance; } /** * 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) { // cast required as this service uses non-interface methods of specific implementation this.idResolutionService = (LiveNetworkIdResolutionServiceImpl) newInstance; } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindCommunicationManagementService(CommunicationManagementService newInstance) { this.newManagementService = newInstance; } /** * OSGi-DS bind method; made public for integration testing. * * @param newInstance the new service instance */ public void bindNetworkRoutingService(NetworkRoutingService newInstance) { this.routingService = newInstance; } @Override public synchronized Set<InstanceNodeSessionId> getReachableInstanceNodes() { return cachedReachableNodes; } @Override public synchronized Set<LogicalNodeId> getReachableLogicalNodes() { return cachedReachableLogicalNodes; } @Override public <T> T getRemotableService(Class<T> iface, ResolvableNodeId nodeId) { if (nodeId == null) { throw new IllegalArgumentException("The service location can not be null"); } if (!iface.isAnnotationPresent(RemotableService.class)) { throw new IllegalArgumentException("The requested interface is not a " + RemotableService.class.getSimpleName() + ": " + iface.getName()); } serviceRequestCounter.count(iface.getName()); // not using countClass() as it would always register "java.lang.Class" here // TODO once the annotation check is passed, simply delegate return getServiceProxy(iface, nodeId); } private <T> T getServiceProxy(Class<T> iface, ResolvableNodeId nodeId) { Objects.requireNonNull(nodeId); // sanity check if (platformService.matchesLocalInstance(nodeId)) { if (forceLocalRPCSerialization) { log.debug("Creating service proxy for local service as the 'force RPC serialization' flag is set: " + iface.getName()); return createSerializingServiceProxy(iface, nodeId); // do not resolve the id yet; this is done at invocation time } else { final T localService = resolveLocalService(iface); if (localService == null) { throw new IllegalStateException("Unexpected state: There is no local instance of service " + iface.getName()); } LogicalNodeSessionId targetLogicalNodeInstanceId; try { targetLogicalNodeInstanceId = idResolutionService.resolveToLogicalNodeSessionId(nodeId); } catch (IdentifierException e) { // should never happen throw new RuntimeException("Internal error: resolution of instance-local node id failed", e); } return createDirectCallServiceProxy(localDefaultLogicalNodeSessionId, targetLogicalNodeInstanceId, iface, localService); } } else { return createSerializingServiceProxy(iface, nodeId); // do not resolve the id yet; this is done at invocation time } } @Override public String getFormattedNetworkInformation(String type) { return routingService.getFormattedNetworkInformation(type); } @SuppressWarnings("unchecked") private <T> T createSerializingServiceProxy(Class<T> iface, final ResolvableNodeId targetNodeId) { return (T) remoteServiceHandler.createServiceProxy(targetNodeId, iface, null); } @SuppressWarnings("unchecked") private <T> T createDirectCallServiceProxy(final LogicalNodeSessionId callerLogicalNodeSessionId, final LogicalNodeSessionId targetLogicalNodeSessionId, final Class<T> serviceIface, final T serviceImpl) { final InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // wrap service invocation into service call context set/restore final ThreadContextMemento previousThreadContext = ServiceCallContextUtils.attachServiceCallDataToThreadContext(callerLogicalNodeSessionId, targetLogicalNodeSessionId, serviceIface.getSimpleName(), method.getName()); try { return method.invoke(serviceImpl, args); } finally { previousThreadContext.restore(); } } }; return (T) Proxy.newProxyInstance(serviceIface.getClassLoader(), new Class[] { serviceIface }, handler); } private <T> T resolveLocalService(Class<? super T> iface) { @SuppressWarnings("unchecked") T service = (T) localServiceResolver.getLocalService(iface.getName()); if (service != null) { return service; } else { throw new IllegalStateException(SERVICE_NOT_AVAILABLE_ERROR + iface.getName()); } } }