/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.testutils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.utils.incubator.DebugSettings; import de.rcenvironment.toolkit.utils.common.AutoCreationMap; /** * An helper class to set up, bind and activate OSGi-DS service instances for integration testing. Note that only dependency binding and * service activation is supported; unbinding and deactivation is not. * * @author Robert Mischke */ public class VirtualServiceRegistry { private List<VirtualService> unboundServices = new ArrayList<VirtualServiceRegistry.VirtualService>(); private AutoCreationMap<Class<?>, List<VirtualService>> activatedServices = new AutoCreationMap<Class<?>, List<VirtualService>>() { @Override protected List<VirtualService> createNewEntry(Class<?> key) { return new ArrayList<VirtualService>(); } }; private final boolean verboseLogging = DebugSettings.getVerboseLoggingEnabled(getClass()); private final Log log = LogFactory.getLog(getClass()); /** * Encapsulation of a registered service instance. * * @author Robert Mischke */ private final class VirtualService { private final Object implementation; private final boolean expectActivator; private final Class<?>[] serviceClasses; private final Map<Class<?>, Method> bindMethods = new HashMap<Class<?>, Method>(); VirtualService(Object implementation, boolean expectActivator, Class<?>[] serviceClasses) { this.implementation = implementation; this.expectActivator = expectActivator; this.serviceClasses = serviceClasses; final Method[] methods = implementation.getClass().getMethods(); for (Method method : methods) { if (method.getName().startsWith("bind") && method.getParameterTypes().length == 1) { Class<?> type = method.getParameterTypes()[0]; if (verboseLogging) { log.debug("Found bind method: " + implementation.getClass().getName() + "." + method.getName() + "() -> " + type.getName()); } bindMethods.put(type, method); } } } public Class<?>[] getServiceClasses() { return serviceClasses; } public Object getImplementation() { return implementation; } public Map<Class<?>, Method> getBindMethods() { return bindMethods; } } /** * Registers an already-initialized (activated) service instance. Used to provide external service instances to managed instances. * * @param implementation the service implementation * @param serviceClasses the service interfaces to register this implementation for */ public void registerProvidedService(Object implementation, Class<?>... serviceClasses) { final VirtualService service = new VirtualService(implementation, false, serviceClasses); for (Class<?> clazz : serviceClasses) { activatedServices.get(clazz).add(service); } } /** * Registers an unbound service instance. This is the main method to register services by. * * The given service instance is expected to have an activator method; if it does not, use the method variant with a boolean parameter * to prevent a runtime exception. * * This is a convenience shortcut for calling {@link #registerManagedService(Object, true, Class...)}. * * @param implementation the service implementation * @param serviceClasses the service interfaces to register this implementation for */ public void registerManagedService(Object implementation, Class<?>... serviceClasses) { registerManagedService(implementation, true, serviceClasses); } /** * Registers an unbound service instance. This is the main method to register services by. * * @param implementation the service implementation * @param expectActivator true if this service is expected to have an activator; if this expectation is not met either way, an runtime * exception is thron. logged * @param serviceClasses the service interfaces to register this implementation for */ public void registerManagedService(Object implementation, boolean expectActivator, Class<?>... serviceClasses) { final VirtualService service = new VirtualService(implementation, expectActivator, serviceClasses); unboundServices.add(service); } /** * Returns a service that was registered for the given interface class, if it exists. * * @param <T> the service interface class (for generics) * @param clazz the service interface class * @return a service instance that was registered for the given interface, or null if none exists */ @SuppressWarnings("unchecked") public <T> T getService(Class<T> clazz) { final List<VirtualService> list = activatedServices.get(clazz); if (list.isEmpty()) { return null; } // TODO check for uniqueness? return (T) list.get(0).getImplementation(); } /** * Returns all services that were registered for the given interface class. * * TODO apply generics on return value? * * @param clazz the service interface class * @return all service instances that were registered for the given interface; may be empty */ public Collection<?> getServices(Class<?> clazz) { return new ArrayList<Object>(); } /** * Attempts to bind dependencies of managed services with already-activated or externally provided services, and then activate them, * until no more change is possible. * * TODO add "all services activated" return value? */ public void bindAndActivateServices() { VirtualService activatedService; do { activatedService = null; for (VirtualService service : unboundServices) { boolean satisfied = checkDependencies(service); if (satisfied) { bindDependencies(service); activate(service); activatedService = service; break; } } if (activatedService != null) { unboundServices.remove(activatedService); for (Class<?> serviceClass : activatedService.getServiceClasses()) { activatedServices.get(serviceClass).add(activatedService); } } } while (activatedService != null); if (!unboundServices.isEmpty()) { log.warn("After bindAndActivateServices(), there are still unbound services left:"); for (VirtualService service : unboundServices) { log.warn(" " + service.getImplementation().getClass()); for (Class<?> depClass : service.getBindMethods().keySet()) { String warningSuffix = ""; if (getService(depClass) == null) { warningSuffix = " [unsatisfied]"; } log.warn(" -> depends on " + depClass.getName() + warningSuffix); } for (Class<?> serviceClass : service.getServiceClasses()) { log.warn(" <- provides " + serviceClass.getName()); } } throw new RuntimeException("Failed to bind and activate all services; see log output for details"); } } private boolean checkDependencies(VirtualService service) { boolean satisfied = true; for (Map.Entry<Class<?>, Method> bindMethod : service.getBindMethods().entrySet()) { final Class<?> type = bindMethod.getKey(); if (getService(type) == null) { satisfied = false; // log.debug("Service " + service.getImplementation().getClass().getName() // + " has unsatisfied dependency, waiting for " + type.getName()); break; } } return satisfied; } private void bindDependencies(VirtualService service) { if (verboseLogging) { log.debug("Dependencies of service " + service.getImplementation().getClass().getName() + " are satisfied, starting to bind"); } for (Map.Entry<Class<?>, Method> bindMethod : service.getBindMethods().entrySet()) { final Class<?> type = bindMethod.getKey(); final Object dependency = getService(type); if (verboseLogging) { log.debug("Binding dependency of service " + service.getImplementation().getClass().getName() + " with instance of type " + dependency.getClass().getName()); } Exception error = null; try { bindMethod.getValue().invoke(service.getImplementation(), dependency); } catch (IllegalArgumentException e) { error = e; } catch (IllegalAccessException e) { error = e; } catch (InvocationTargetException e) { error = e; } if (error != null) { throw new RuntimeException("Error calling bind method", error); } } } private void activate(VirtualService service) { Exception error = null; final Object implementation = service.getImplementation(); Class<? extends Object> implementationClass = implementation.getClass(); try { try { // TODO support activate(BundleContext) as well; requires definition of a virtual BundleContext first, though - misc_ro final Method noArgs = implementationClass.getMethod("activate", new Class<?>[0]); if (!service.expectActivator) { throw new IllegalArgumentException("Activator found in " + implementationClass.getName() + ", but it was specified to not have one"); } if (verboseLogging) { log.debug("Activating service " + implementationClass.getName()); } noArgs.invoke(implementation); } catch (NoSuchMethodException e) { if (service.expectActivator) { throw new IllegalArgumentException("No activator method found in " + implementationClass.getName() + ", but it was specified to have one"); } return; } } catch (IllegalArgumentException e) { error = e; } catch (SecurityException e) { error = e; } catch (IllegalAccessException e) { error = e; } catch (InvocationTargetException e) { error = e; } if (error != null) { throw new RuntimeException("Error activating service", error); } } }