package fr.openwide.core.spring.config.spring.annotation; import java.util.Collections; import java.util.List; import java.util.Map; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextException; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.io.Resource; import org.springframework.util.ClassUtils; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * <p>Ce {@link BeanFactoryPostProcessor} permet d'extraire des métadonnées sur l'application et de les utiliser pour * constituer la liste des fichiers de configuration à utiliser pour sa configuration.</p> * * <p>L'application doit initialiser un {@link PropertySourcesPlaceholderConfigurer} sans indiquer de valeur pour le * champ {@link PropertySourcesPlaceholderConfigurer#setLocations(Resource[])}.</p> */ public class ApplicationConfigurerBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware, PriorityOrdered { private ApplicationContext applicationContext; /** * Parcourt les beans du contexte pour extraire les informations sur l'application et les emplacements de * configuration par la prise en compte des annotations {@link ApplicationDescription} et * {@link ConfigurationLocations} */ @Override public void postProcessBeanFactory( ConfigurableListableBeanFactory beanFactory) throws BeansException { PropertySourcesPlaceholderConfigurer configurer = beanFactory.getBean(PropertySourcesPlaceholderConfigurer.class); Map<Integer, List<Resource>> locationGroups = Maps.newHashMap(); String applicationName = null; // parcours des définitions de bean for (String beanName : beanFactory.getBeanDefinitionNames()) { // les beans @Configuration sont généralement wrappés par cglib ; on doit donc utiliser ClassUtils // pour récupérer le type du bean Class<?> beanType = ClassUtils.getUserClass(beanFactory.getType(beanName)); // récupération des informations sur l'application // on récupère le bean annoté à la fois @ApplicationDescription et @Configuration dans le contexte // si plusieurs beans respectant ces conditions sont trouvés, cela provoque une erreur if (beanType != null && beanType.isAnnotationPresent(Configuration.class) && beanType.isAnnotationPresent(ApplicationDescription.class)) { if (applicationName != null) { throw new ApplicationContextException("@" + ApplicationDescription.class + " ne doit être utilisé qu'une seule fois"); } applicationName = beanType.getAnnotation(ApplicationDescription.class).name(); } } // vérification qu'un nom a bien été déterminé pour l'application if (applicationName == null) { throw new ApplicationContextException("Au moins un bean doit fournir l'annotation @" + ApplicationDescription.class.getSimpleName() + " avec un nom pour l'application."); } // parcours des définitions de bean for (String beanName : beanFactory.getBeanDefinitionNames()) { // les beans @Configuration sont généralement wrappés par cglib ; on doit donc utiliser ClassUtils // pour récupérer le type du bean Class<?> beanType = ClassUtils.getUserClass(beanFactory.getType(beanName)); // récupération des locations pour la configuration de l'application ; tous les beans annotés // @Configuration et @ConfigurationLocations sont pris en compte. if (beanType != null && beanType.isAnnotationPresent(Configuration.class) && beanType.isAnnotationPresent(ConfigurationLocations.class)) { ConfigurationLocations annotation = beanType.getAnnotation(ConfigurationLocations.class); // récupération du provider et instanciation Class<? extends IConfigurationLocationProvider> configurationLocationProviderClass = annotation.configurationLocationProvider(); IConfigurationLocationProvider provider = BeanUtils.instantiateClass(configurationLocationProviderClass, IConfigurationLocationProvider.class); // récupération des locations et répartition par numéro d'ordre for (String location : provider.getLocations(applicationName, annotation.locations())) { List<Resource> locationGroup; if (locationGroups.containsKey(annotation.order())) { locationGroup = locationGroups.get(annotation.order()); } else { locationGroup = Lists.newArrayList(); locationGroups.put(annotation.order(), locationGroup); } locationGroup.add(applicationContext.getResource(location)); } } } // constitution de la liste ordonnée des locations et insertion dans le configurer List<Resource> locations = Lists.newArrayList(); List<Integer> orders = Lists.newArrayList(locationGroups.keySet()); Collections.sort(orders); for (Integer order : orders) { locations.addAll(locationGroups.get(order)); } // ce sont les fichiers qui doivent être pris en compte avant l'environnement configurer.setFileEncoding(Charsets.UTF_8.name()); configurer.setLocalOverride(true); configurer.setIgnoreResourceNotFound(true); configurer.setLocations(locations.toArray(new Resource[locations.size()])); } /** * Nécessaire pour la récupération des {@link Resource} */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * Ce {@link BeanFactoryPostProcessor} doit être exécuté avant celui du {@link PropertySourcesPlaceholderConfigurer} */ @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 1; } }