package com.mongodb.hvdf; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.dataformat.yaml.snakeyaml.Yaml; import com.google.common.collect.Lists; import com.mongodb.hvdf.api.FrameworkError; import com.mongodb.hvdf.api.ServiceException; import com.mongodb.hvdf.services.Service; import com.mongodb.hvdf.services.ServiceImplementation; class ServiceSpecification{ public ServiceSpecification(Class<?> serviceClazz, ServiceImplementation metadata) { super(); this.serviceClazz = serviceClazz; this.metadata = metadata; } public Class<?> serviceClazz; ServiceImplementation metadata; @Override public String toString(){ return String.format("name : %s, class : %s, config : %s", metadata.name(), serviceClazz.getName(), metadata.configClass().getName()); } } public class ServiceFactory { private static Logger logger = LoggerFactory.getLogger(ServiceFactory.class); private static final String PLUGIN_SEARCH_SCOPE = "com.mongodb.hvdf"; private Map<Class<?>, Object> serviceInstances = new LinkedHashMap<Class<?>, Object>(); public <T> T createAndRegisterService(Class<T> serviceType, Map<String, Object> serviceConfig, Object... params){ T service = createService(serviceType, serviceConfig, params); registerService(serviceType, service); return service; } public <T> T createService(Class<T> serviceType, Map<String, Object> serviceConfig, Object... params){ // Copy the config, then retrieve and remove model Map<String, Object> localConfig = new LinkedHashMap<String, Object>(serviceConfig); String serviceImpl = (String) localConfig.remove(ServiceManager.MODEL_KEY); T serviceInstance = null; try{ // Find the service impl class ServiceSpecification spec = getServiceImplByName(serviceImpl, serviceType); Constructor<?> implCtor = null; // Build the implementation constructor param list Class<?>[] paramTypeArray = buildParamTypes(params, spec); // Find the constructor that matches the provided args try{ implCtor = spec.serviceClazz.getConstructor(paramTypeArray); } catch(NoSuchMethodException nsmex) { // could not find exact constructor, search for compatible one implCtor = findCompatibleConstructor(spec.serviceClazz, paramTypeArray); // could not find anything compatible, rethrow if(implCtor == null) throw nsmex; } // Create and add the config object to params params = buildParams(params, spec, localConfig); // Construct a service instance and cast it out serviceInstance = serviceType.cast(implCtor.newInstance(params)); } catch (Exception e) { throw ServiceException.wrap(e, FrameworkError.FAILED_TO_LOAD_SERVICE). set("ServiceClass", serviceImpl); } return serviceInstance; } private static Constructor<?> findCompatibleConstructor( Class<?> serviceClazz, Class<?>[] paramTypeArray) { for(Constructor<?> candidate : serviceClazz.getConstructors()){ Class<?>[] candidateTypes = candidate.getParameterTypes(); if(paramsAreCompatible(paramTypeArray, candidateTypes)){ return candidate; } } return null; } private static Class<?>[] buildParamTypes( Object[] params, ServiceSpecification spec) { // Take the parameters passed in and find their types List<Class<?>> paramTypes = new ArrayList<Class<?>>(); for(int i = 0; i < params.length; ++i) paramTypes.add(params[i].getClass()); // Add any dependency services Class<?>[] dependencies = spec.metadata.dependencies(); for(int i = 0; i < dependencies.length; ++i) paramTypes.add(dependencies[i]); // If the service requires a configuration, add it Class<?> configClass = spec.metadata.configClass(); if(configClass != Void.class){ paramTypes.add(configClass); } return paramTypes.toArray(new Class<?>[paramTypes.size()]); } private Object[] buildParams(Object[] params, ServiceSpecification spec, Map<String, Object> serviceConfig) { // Add existing params List<Object> paramList = new ArrayList<Object>(); for(int i = 0; i < params.length; ++i) paramList.add(params[i]); // Add any dependency services Class<?>[] dependencies = spec.metadata.dependencies(); for(int i = 0; i < dependencies.length; ++i) paramList.add(getService(dependencies[i])); // If the service requires a configuration, add it Class<?> configClass = spec.metadata.configClass(); if(configClass != Void.class){ Yaml yaml = new Yaml(); String configString = yaml.dump(serviceConfig); Object configObject = yaml.loadAs(configString, configClass); paramList.add(configObject); } return paramList.toArray(); } public synchronized <T> T getService(Class<T> serviceType) { Object instance = this.serviceInstances.get(serviceType); if(instance == null) throw new ServiceException(FrameworkError.FAILED_TO_LOAD_DEPENDENCY). set("dependencyType", serviceType.getName()); return serviceType.cast(instance); } private synchronized void registerService(Class<?> serviceType, Object serviceInstance){ this.serviceInstances.put(serviceType, serviceInstance); } public synchronized List<Service> getServiceList() { // Get all the services from the map List<Service> services = new ArrayList<Service>(this.serviceInstances.size()); for(Object service : this.serviceInstances.values()) services.add((Service)service); // This will deliver them in reverse order to registration return Lists.reverse(services); } private static ServiceSpecification getServiceImplByName(String serviceImpl, Class<?> serviceType) { logger.debug("SEARCHING for {}:{} under {}", serviceType.getSimpleName(), serviceImpl, PLUGIN_SEARCH_SCOPE); Reflections reflections = new Reflections(PLUGIN_SEARCH_SCOPE); Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(ServiceImplementation.class); for(Class<?> candidate : annotated){ if(serviceType.isAssignableFrom(candidate)){ ServiceImplementation spec = candidate.getAnnotation(ServiceImplementation.class); logger.trace("FOUND candidate {} : {}", serviceType.getSimpleName(), spec.name()); if(spec.name().equals(serviceImpl)){ ServiceSpecification match = new ServiceSpecification(candidate, spec); logger.debug("MATCHED service of type - {}", serviceType.getName()); logger.debug("\t{}", match); return match; } } } throw new ServiceException(FrameworkError.FAILED_TO_LOAD_SERVICE).set("serviceName", serviceImpl); } private static boolean paramsAreCompatible( Class<?>[] requested, Class<?>[] offered){ // Checking one by one that the supplied arguments are // assignable to the arguments supported by this constructor if(requested.length == offered.length){ for(int i=0; i < requested.length; ++i){ if(false == offered[i].isAssignableFrom(requested[i])){ return false; } } } else { return false; } return true; } }