/* * Copyright 2008 the original author or authors. * * 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 org.constretto.spring; import org.constretto.exception.ConstrettoException; import org.constretto.spring.annotation.Environment; import org.constretto.spring.internal.ConstrettoAutowireCandidateResolver; import org.constretto.spring.resolver.AssemblyContextResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static java.util.Arrays.asList; /** * A BeanFactoryBeanFactoryPostProcessor implementation that will if registered as a bean in a spring context, enable * the constretto autowiring capabilities in the container. * <p/> * <p/> * May be used on any existing configurations and in combination with all the standard context implementations from the * Spring framework. * * @author <a href="mailto:kaare.nilsen@gmail.com">Kaare Nilsen</a> */ public class EnvironmentAnnotationConfigurer implements BeanFactoryPostProcessor { private final Logger logger = LoggerFactory.getLogger(getClass()); private final AssemblyContextResolver assemblyContextResolver; public static final String INCLUDE_IN_COLLECTIONS = "includeInCollections"; public EnvironmentAnnotationConfigurer(AssemblyContextResolver assemblyContextResolver) { this.assemblyContextResolver = assemblyContextResolver; } @SuppressWarnings("unchecked") public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { if (!(configurableListableBeanFactory instanceof DefaultListableBeanFactory)) { throw new IllegalStateException( "EnvironmentAnnotationConfigurer needs to operate on a DefaultListableBeanFactory"); } DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory; defaultListableBeanFactory.setAutowireCandidateResolver(new ConstrettoAutowireCandidateResolver()); String[] beanNames = configurableListableBeanFactory.getBeanDefinitionNames(); int lowestDiscoveredPriority = Integer.MAX_VALUE; for (String beanName : beanNames) { BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanName); if (beanDefinition.getBeanClassName() != null) { try { Class beanClass = Class.forName(beanDefinition.getBeanClassName()); Environment environmentAnnotation = findEnvironmentAnnotation(beanClass); if (environmentAnnotation != null) { if (!assemblyContextResolver.getAssemblyContext().isEmpty()) { boolean autowireCandidate = decideIfAutowireCandiate(beanName, environmentAnnotation); beanDefinition.setAutowireCandidate(autowireCandidate); if (autowireCandidate) { removeNonAnnotatedBeansFromAutowireForType(beanClass, configurableListableBeanFactory); } } else { beanDefinition.setAutowireCandidate(false); } } } catch (ClassNotFoundException e) { beanDefinition.setAutowireCandidate(false); } } } } @SuppressWarnings("unchecked") public static Environment findEnvironmentAnnotation(Class beanClass) { if (beanClass.isAnnotationPresent(Environment.class)) { return (Environment) beanClass.getAnnotation(Environment.class); } else { return findEnvironmentMetaAnnotation(new HashSet<Annotation>(), beanClass.getAnnotations()); } } public static Environment findEnvironmentMetaAnnotation(Set<Annotation> visited, Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation instanceof Environment) { return (Environment) annotation; } else { if (!visited.contains(annotation)) { visited.add(annotation); Environment environment = findEnvironmentMetaAnnotation(visited, annotation.annotationType().getAnnotations()); if (environment != null) { return environment; } } } } return null; } @SuppressWarnings("unchecked") private void removeNonAnnotatedBeansFromAutowireForType(Class lookupClass, ConfigurableListableBeanFactory configurableListableBeanFactory) throws ClassNotFoundException { List<String> beanNames = new ArrayList<String>(); Class[] interfaces = lookupClass.getInterfaces(); for (Class anInterface : interfaces) { beanNames.addAll(asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(configurableListableBeanFactory, anInterface))); } List<BeanDefinition> potentialMatches = new ArrayList<BeanDefinition>(); for (String beanName : beanNames) { BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanName); Class beanClass = Class.forName(beanDefinition.getBeanClassName()); beanDefinition.setAttribute(INCLUDE_IN_COLLECTIONS, new Class[]{beanClass}); Environment environmentAnnotation = findEnvironmentAnnotation(beanClass); if (environmentAnnotation == null) { beanDefinition.setAutowireCandidate(false); } else { potentialMatches.add(beanDefinition); } } if (potentialMatches.size() == 1) { potentialMatches.get(0).setAutowireCandidate(true); } else { List<BeanDefinition> highestPriorityBeans = new ArrayList<BeanDefinition>(); for (BeanDefinition potentialMatch : potentialMatches) { if (potentialMatch.isAutowireCandidate()) { potentialMatch.setAutowireCandidate(false); highestPriorityBeans = prioritizeBeans(potentialMatch, highestPriorityBeans); } } if (highestPriorityBeans.size() == 1) { highestPriorityBeans.get(0).setAutowireCandidate(true); } else { List<String> equalPriorityBeans = new ArrayList<String>(); for (BeanDefinition highestPriorityBean : highestPriorityBeans) { equalPriorityBeans.add(highestPriorityBean.getBeanClassName()); } throw new ConstrettoException( "More than one bean with the class or interface + [" + lookupClass.getSimpleName() +"] registered with same tag. Could not resolve priority. To fix this, remove one of the following beans " + equalPriorityBeans.toString()); } } } private List<BeanDefinition> prioritizeBeans(BeanDefinition potentialMatch, List<BeanDefinition> highestPriorityBeans) throws ClassNotFoundException { List<BeanDefinition> result = new ArrayList<BeanDefinition>(); int matchPriority = getAutowirePriority(Class.forName(potentialMatch.getBeanClassName())); if (highestPriorityBeans.isEmpty()) { result.add(potentialMatch); } else { for (BeanDefinition highestPriorityBean : highestPriorityBeans) { int mostSpesificPriority = getAutowirePriority(Class.forName(highestPriorityBean.getBeanClassName())); if ((matchPriority - mostSpesificPriority) < 0) { result.clear(); result.add(potentialMatch); } else if (((matchPriority - mostSpesificPriority) == 0)) { result.add(potentialMatch); result.add(highestPriorityBean); } else { result.add(highestPriorityBean); } } } return result; } @SuppressWarnings("unchecked") private int getAutowirePriority(Class beanClass) { Environment environmentAnnotation = findEnvironmentAnnotation(beanClass); if (environmentAnnotation != null) { List<String> environments = asList(environmentAnnotation.value()); List<String> assemblyContext = assemblyContextResolver.getAssemblyContext(); for (int i = 0; i < assemblyContext.size(); i++) { if (environments.contains(assemblyContext.get(i))) { return i; } } } return Integer.MAX_VALUE; } private boolean decideIfAutowireCandiate(String beanName, final Environment environmentAnnotation) { List<String> targetEnvironments = new ArrayList<String>() {{ addAll(asList(environmentAnnotation.value())); }}; validateAnnotationValues(beanName, targetEnvironments); List<String> assemblyContext = assemblyContextResolver.getAssemblyContext(); targetEnvironments.retainAll(assemblyContext); boolean autowireCandidate = !targetEnvironments.isEmpty(); if (autowireCandidate) { logger.info("{} is annotated with environment '{}', and is selected for autowiring in the current environment '{}'", beanName, environmentAnnotation.value(), assemblyContextResolver.getAssemblyContext()); } else { logger.info("{} is annotated with environment '{}', and is discarded for autowiring in the current environment '{}'", beanName, environmentAnnotation.value(), assemblyContextResolver.getAssemblyContext()); } return autowireCandidate; } private void validateAnnotationValues(String beanName, List<String> beanEnvironments) { if (beanEnvironments.isEmpty()) { throw new ConstrettoException( "You must specify environment tags in @Environment. offending bean: " + beanName); } } }