Java tutorial
/* * Copyright 2015 Sharmarke Aden. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.fitbur.testify.di.spring; import static com.fitbur.guava.common.base.Preconditions.checkState; import com.fitbur.guava.common.reflect.TypeToken; import com.fitbur.testify.di.ServiceAnnotations; import com.fitbur.testify.di.ServiceDescriptor; import com.fitbur.testify.di.ServiceLocator; import com.fitbur.testify.di.ServiceProvider; import com.fitbur.testify.di.ServiceScope; import static com.fitbur.testify.di.ServiceScope.REQUEST; import static com.fitbur.testify.di.ServiceScope.SESSION; import static com.fitbur.testify.di.ServiceScope.SINGLETON; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.List; import java.util.Map; import java.util.Optional; import static java.util.Optional.empty; import static java.util.Optional.of; import java.util.Set; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import javax.inject.Named; import javax.inject.Provider; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.annotation.Qualifier; import static org.springframework.beans.factory.config.BeanDefinition.ROLE_APPLICATION; import org.springframework.beans.factory.config.ConstructorArgumentValues; import static org.springframework.beans.factory.support.AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR; import static org.springframework.beans.factory.support.AbstractBeanDefinition.AUTOWIRE_NO; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; /** * A spring implementation of test service locator. It provides the ability to * work with AnnotationConfigApplication context to create and load services * dynamically. * * @author saden */ public class SpringServiceLocator implements ServiceLocator { private final ConfigurableApplicationContext context; private final ServiceAnnotations serviceAnnotations; public SpringServiceLocator(ConfigurableApplicationContext context, ServiceAnnotations annotations) { this.context = context; this.serviceAnnotations = annotations; } @Override public void init() { } @Override public void destroy() { if (isActive() && context.isRunning()) { context.close(); } } @Override public void reload() { if (!isActive()) { context.refresh(); } } @Override public boolean isActive() { return context.isActive(); } @Override public ConfigurableApplicationContext getContext() { return context; } @Override public String getName() { return context.getId(); } @Override public <T> T getService(String name) { return (T) context.getBean(name); } @Override public <T> T getService(Type type) { TypeToken<?> token = TypeToken.of(type); Class rawType = token.getRawType(); Object instance; if (token.isSubtypeOf(Provider.class)) { TypeVariable<Class<Provider>> paramType = Provider.class.getTypeParameters()[0]; rawType = token.resolveType(paramType).getRawType(); instance = new ServiceProvider(this, rawType); } else if (token.isSubtypeOf(Optional.class)) { TypeVariable<Class<Optional>> paramType = Optional.class.getTypeParameters()[0]; rawType = token.resolveType(paramType).getRawType(); instance = Optional.ofNullable(context.getBean(rawType)); } else if (token.isSubtypeOf(Map.class)) { TypeVariable<Class<Map>> valueType = Map.class.getTypeParameters()[1]; rawType = token.resolveType(valueType).getRawType(); instance = context.getBeansOfType(rawType); } else if (token.isSubtypeOf(Set.class)) { TypeVariable<Class<Set>> valueType = Set.class.getTypeParameters()[0]; rawType = token.resolveType(valueType).getRawType(); instance = context.getBeansOfType(rawType).values().stream().collect(toSet()); } else if (token.isSubtypeOf(List.class)) { TypeVariable<Class<List>> valueType = List.class.getTypeParameters()[0]; rawType = token.resolveType(valueType).getRawType(); instance = context.getBeansOfType(rawType).values().stream().collect(toList()); } else { instance = context.getBean(rawType); } return (T) instance; } @Override public <T> T getService(Type type, String name) { TypeToken<?> token = TypeToken.of(type); return (T) context.getBean(name, token.getRawType()); } @Override public <T> T getService(Type type, Set<? extends Annotation> annotations) { TypeToken<?> token = TypeToken.of(type); Object instance; Optional<Class<? extends Annotation>> beanAnnotation = empty(); Class rawType = token.getRawType(); Optional<String> beanName = annotations.parallelStream() .filter(p -> p.annotationType().equals(Qualifier.class)).map(Qualifier.class::cast) .map(Qualifier::value).findFirst(); if (!beanName.isPresent()) { beanName = annotations.parallelStream().filter(p -> p.annotationType().equals(Named.class)) .map(Named.class::cast).map(Named::value).findFirst(); } if (!beanName.isPresent()) { Set<Class<? extends Annotation>> customQualfiers = serviceAnnotations.getCustomQualifiers(); for (Annotation annotation : annotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); for (Class<? extends Annotation> customQualfier : customQualfiers) { if (annotationType.isAnnotationPresent(customQualfier) || annotationType.equals(Qualifier.class)) { beanAnnotation = of(annotationType); break; } } if (beanAnnotation.isPresent()) { break; } } } if (beanName.isPresent()) { String name = beanName.get(); if (token.isSubtypeOf(Provider.class)) { TypeVariable<Class<Provider>> paramType = Provider.class.getTypeParameters()[0]; rawType = token.resolveType(paramType).getRawType(); instance = new ServiceProvider(this, name, rawType); } else if (token.isSubtypeOf(Optional.class)) { TypeVariable<Class<Optional>> paramType = Optional.class.getTypeParameters()[0]; rawType = token.resolveType(paramType).getRawType(); instance = Optional.ofNullable(context.getBean(name, rawType)); } else { instance = context.getBean(name, rawType); } } else if (beanAnnotation.isPresent()) { Map<String, Object> beans = context.getBeansWithAnnotation(beanAnnotation.get()); instance = beans.entrySet().parallelStream().map(p -> { Object value; if (token.isSubtypeOf(Provider.class)) { value = new ServiceProvider(this, p.getKey(), p.getValue().getClass()); } else if (token.isSubtypeOf(Optional.class)) { value = of(p.getValue()); } else { value = p.getValue(); } return value; }).findFirst().get(); } else { instance = getService(type); } return (T) instance; } @Override public <T> T getServiceWith(Class<T> type, Object... arguments) { return context.getBean(type, arguments); } @Override public <T> T getServiceWith(String name, Object... arguments) { return (T) context.getBean(name, arguments); } @Override public void addService(Class<?> type) { GenericBeanDefinition bean = new GenericBeanDefinition(); bean.setBeanClass(type); bean.setRole(ROLE_APPLICATION); DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); beanFactory.registerBeanDefinition(type.getSimpleName(), bean); } @Override public void addService(ServiceDescriptor descriptor) { checkState(!context.containsBeanDefinition(descriptor.getName()), "Service with the name '%s' already exists.", descriptor.getName()); GenericBeanDefinition bean = new GenericBeanDefinition(); bean.setBeanClass(descriptor.getType()); bean.setAutowireCandidate(descriptor.getDiscoverable()); bean.setPrimary(descriptor.getPrimary()); bean.setLazyInit(descriptor.getLazy()); bean.setRole(ROLE_APPLICATION); if (descriptor.getInjectable()) { bean.setAutowireMode(AUTOWIRE_CONSTRUCTOR); } else { bean.setAutowireMode(AUTOWIRE_NO); ConstructorArgumentValues values = new ConstructorArgumentValues(); Object[] arguments = descriptor.getArguments(); for (int i = 0; i < arguments.length; i++) { Object arg = arguments[i]; if (arg == null) { //TODO: warn user that the argument was not specified and there //for the real instance will be injected. continue; } values.addIndexedArgumentValue(i, arg); } bean.setConstructorArgumentValues(values); } ServiceScope scope = descriptor.getScope(); switch (scope) { case PROTOTYPE: bean.setScope("prototype"); break; case SINGLETON: bean.setScope("singleton"); break; case REQUEST: bean.setScope("request"); break; case SESSION: bean.setScope("session"); break; case APPLICATION: bean.setScope("application"); break; default: checkState(false, "Scope '{}' is not supported by Spring IoC.", scope.name()); } DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); beanFactory.registerBeanDefinition(descriptor.getName(), bean); } @Override public void addConstant(String name, Object instance) { DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); beanFactory.registerSingleton(name, instance); } @Override public void replaceWithConstant(Set<Class<?>> contracts, String name, Object instance) { DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); for (Class<?> contract : contracts) { String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, contract, true, false); for (String beanName : beanNames) { beanFactory.removeBeanDefinition(beanName); } } beanFactory.registerSingleton(name, instance); } @Override public void removeService(ServiceDescriptor descriptor) { BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) context; beanFactory.removeBeanDefinition(descriptor.getName()); } @Override public void removeService(String name) { BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) context; beanFactory.removeBeanDefinition(name); } @Override public void addModule(Class<?> type) { GenericBeanDefinition bean = new GenericBeanDefinition(); bean.setBeanClass(type); bean.setRole(ROLE_APPLICATION); DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); beanFactory.registerBeanDefinition(type.getSimpleName(), bean); } @Override public void scanPackages(String... packages) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) context; new ClassPathBeanDefinitionScanner(registry).scan(packages); } @Override public ServiceAnnotations getServiceAnnotations() { return serviceAnnotations; } }