001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashSet;
024import java.util.LinkedHashMap;
025import java.util.LinkedHashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.TimeUnit;
030import java.util.stream.Collectors;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034
035import org.springframework.beans.BeansException;
036import org.springframework.beans.factory.Aware;
037import org.springframework.beans.factory.BeanClassLoaderAware;
038import org.springframework.beans.factory.BeanFactory;
039import org.springframework.beans.factory.BeanFactoryAware;
040import org.springframework.beans.factory.NoSuchBeanDefinitionException;
041import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
042import org.springframework.boot.context.properties.bind.Binder;
043import org.springframework.context.EnvironmentAware;
044import org.springframework.context.ResourceLoaderAware;
045import org.springframework.context.annotation.Configuration;
046import org.springframework.context.annotation.DeferredImportSelector;
047import org.springframework.core.Ordered;
048import org.springframework.core.annotation.AnnotationAttributes;
049import org.springframework.core.env.ConfigurableEnvironment;
050import org.springframework.core.env.Environment;
051import org.springframework.core.io.ResourceLoader;
052import org.springframework.core.io.support.SpringFactoriesLoader;
053import org.springframework.core.type.AnnotationMetadata;
054import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
055import org.springframework.core.type.classreading.MetadataReaderFactory;
056import org.springframework.util.Assert;
057import org.springframework.util.ClassUtils;
058import org.springframework.util.StringUtils;
059
060/**
061 * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
062 * auto-configuration}. This class can also be subclassed if a custom variant of
063 * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
064 *
065 * @author Phillip Webb
066 * @author Andy Wilkinson
067 * @author Stephane Nicoll
068 * @author Madhura Bhave
069 * @since 1.3.0
070 * @see EnableAutoConfiguration
071 */
072public class AutoConfigurationImportSelector
073                implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
074                BeanFactoryAware, EnvironmentAware, Ordered {
075
076        private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
077
078        private static final String[] NO_IMPORTS = {};
079
080        private static final Log logger = LogFactory
081                        .getLog(AutoConfigurationImportSelector.class);
082
083        private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
084
085        private ConfigurableListableBeanFactory beanFactory;
086
087        private Environment environment;
088
089        private ClassLoader beanClassLoader;
090
091        private ResourceLoader resourceLoader;
092
093        @Override
094        public String[] selectImports(AnnotationMetadata annotationMetadata) {
095                if (!isEnabled(annotationMetadata)) {
096                        return NO_IMPORTS;
097                }
098                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
099                                .loadMetadata(this.beanClassLoader);
100                AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
101                                autoConfigurationMetadata, annotationMetadata);
102                return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
103        }
104
105        /**
106         * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
107         * of the importing {@link Configuration @Configuration} class.
108         * @param autoConfigurationMetadata the auto-configuration metadata
109         * @param annotationMetadata the annotation metadata of the configuration class
110         * @return the auto-configurations that should be imported
111         */
112        protected AutoConfigurationEntry getAutoConfigurationEntry(
113                        AutoConfigurationMetadata autoConfigurationMetadata,
114                        AnnotationMetadata annotationMetadata) {
115                if (!isEnabled(annotationMetadata)) {
116                        return EMPTY_ENTRY;
117                }
118                AnnotationAttributes attributes = getAttributes(annotationMetadata);
119                List<String> configurations = getCandidateConfigurations(annotationMetadata,
120                                attributes);
121                configurations = removeDuplicates(configurations);
122                Set<String> exclusions = getExclusions(annotationMetadata, attributes);
123                checkExcludedClasses(configurations, exclusions);
124                configurations.removeAll(exclusions);
125                configurations = filter(configurations, autoConfigurationMetadata);
126                fireAutoConfigurationImportEvents(configurations, exclusions);
127                return new AutoConfigurationEntry(configurations, exclusions);
128        }
129
130        @Override
131        public Class<? extends Group> getImportGroup() {
132                return AutoConfigurationGroup.class;
133        }
134
135        protected boolean isEnabled(AnnotationMetadata metadata) {
136                if (getClass() == AutoConfigurationImportSelector.class) {
137                        return getEnvironment().getProperty(
138                                        EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
139                                        true);
140                }
141                return true;
142        }
143
144        /**
145         * Return the appropriate {@link AnnotationAttributes} from the
146         * {@link AnnotationMetadata}. By default this method will return attributes for
147         * {@link #getAnnotationClass()}.
148         * @param metadata the annotation metadata
149         * @return annotation attributes
150         */
151        protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
152                String name = getAnnotationClass().getName();
153                AnnotationAttributes attributes = AnnotationAttributes
154                                .fromMap(metadata.getAnnotationAttributes(name, true));
155                Assert.notNull(attributes,
156                                () -> "No auto-configuration attributes found. Is "
157                                                + metadata.getClassName() + " annotated with "
158                                                + ClassUtils.getShortName(name) + "?");
159                return attributes;
160        }
161
162        /**
163         * Return the source annotation class used by the selector.
164         * @return the annotation class
165         */
166        protected Class<?> getAnnotationClass() {
167                return EnableAutoConfiguration.class;
168        }
169
170        /**
171         * Return the auto-configuration class names that should be considered. By default
172         * this method will load candidates using {@link SpringFactoriesLoader} with
173         * {@link #getSpringFactoriesLoaderFactoryClass()}.
174         * @param metadata the source metadata
175         * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
176         * attributes}
177         * @return a list of candidate configurations
178         */
179        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
180                        AnnotationAttributes attributes) {
181                List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
182                                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
183                Assert.notEmpty(configurations,
184                                "No auto configuration classes found in META-INF/spring.factories. If you "
185                                                + "are using a custom packaging, make sure that file is correct.");
186                return configurations;
187        }
188
189        /**
190         * Return the class used by {@link SpringFactoriesLoader} to load configuration
191         * candidates.
192         * @return the factory class
193         */
194        protected Class<?> getSpringFactoriesLoaderFactoryClass() {
195                return EnableAutoConfiguration.class;
196        }
197
198        private void checkExcludedClasses(List<String> configurations,
199                        Set<String> exclusions) {
200                List<String> invalidExcludes = new ArrayList<>(exclusions.size());
201                for (String exclusion : exclusions) {
202                        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader())
203                                        && !configurations.contains(exclusion)) {
204                                invalidExcludes.add(exclusion);
205                        }
206                }
207                if (!invalidExcludes.isEmpty()) {
208                        handleInvalidExcludes(invalidExcludes);
209                }
210        }
211
212        /**
213         * Handle any invalid excludes that have been specified.
214         * @param invalidExcludes the list of invalid excludes (will always have at least one
215         * element)
216         */
217        protected void handleInvalidExcludes(List<String> invalidExcludes) {
218                StringBuilder message = new StringBuilder();
219                for (String exclude : invalidExcludes) {
220                        message.append("\t- ").append(exclude).append(String.format("%n"));
221                }
222                throw new IllegalStateException(String
223                                .format("The following classes could not be excluded because they are"
224                                                + " not auto-configuration classes:%n%s", message));
225        }
226
227        /**
228         * Return any exclusions that limit the candidate configurations.
229         * @param metadata the source metadata
230         * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
231         * attributes}
232         * @return exclusions or an empty set
233         */
234        protected Set<String> getExclusions(AnnotationMetadata metadata,
235                        AnnotationAttributes attributes) {
236                Set<String> excluded = new LinkedHashSet<>();
237                excluded.addAll(asList(attributes, "exclude"));
238                excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
239                excluded.addAll(getExcludeAutoConfigurationsProperty());
240                return excluded;
241        }
242
243        private List<String> getExcludeAutoConfigurationsProperty() {
244                if (getEnvironment() instanceof ConfigurableEnvironment) {
245                        Binder binder = Binder.get(getEnvironment());
246                        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
247                                        .map(Arrays::asList).orElse(Collections.emptyList());
248                }
249                String[] excludes = getEnvironment()
250                                .getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
251                return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
252        }
253
254        private List<String> filter(List<String> configurations,
255                        AutoConfigurationMetadata autoConfigurationMetadata) {
256                long startTime = System.nanoTime();
257                String[] candidates = StringUtils.toStringArray(configurations);
258                boolean[] skip = new boolean[candidates.length];
259                boolean skipped = false;
260                for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
261                        invokeAwareMethods(filter);
262                        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
263                        for (int i = 0; i < match.length; i++) {
264                                if (!match[i]) {
265                                        skip[i] = true;
266                                        candidates[i] = null;
267                                        skipped = true;
268                                }
269                        }
270                }
271                if (!skipped) {
272                        return configurations;
273                }
274                List<String> result = new ArrayList<>(candidates.length);
275                for (int i = 0; i < candidates.length; i++) {
276                        if (!skip[i]) {
277                                result.add(candidates[i]);
278                        }
279                }
280                if (logger.isTraceEnabled()) {
281                        int numberFiltered = configurations.size() - result.size();
282                        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
283                                        + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
284                                        + " ms");
285                }
286                return new ArrayList<>(result);
287        }
288
289        protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
290                return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
291                                this.beanClassLoader);
292        }
293
294        protected final <T> List<T> removeDuplicates(List<T> list) {
295                return new ArrayList<>(new LinkedHashSet<>(list));
296        }
297
298        protected final List<String> asList(AnnotationAttributes attributes, String name) {
299                String[] value = attributes.getStringArray(name);
300                return Arrays.asList((value != null) ? value : new String[0]);
301        }
302
303        private void fireAutoConfigurationImportEvents(List<String> configurations,
304                        Set<String> exclusions) {
305                List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
306                if (!listeners.isEmpty()) {
307                        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
308                                        configurations, exclusions);
309                        for (AutoConfigurationImportListener listener : listeners) {
310                                invokeAwareMethods(listener);
311                                listener.onAutoConfigurationImportEvent(event);
312                        }
313                }
314        }
315
316        protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
317                return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
318                                this.beanClassLoader);
319        }
320
321        private void invokeAwareMethods(Object instance) {
322                if (instance instanceof Aware) {
323                        if (instance instanceof BeanClassLoaderAware) {
324                                ((BeanClassLoaderAware) instance)
325                                                .setBeanClassLoader(this.beanClassLoader);
326                        }
327                        if (instance instanceof BeanFactoryAware) {
328                                ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
329                        }
330                        if (instance instanceof EnvironmentAware) {
331                                ((EnvironmentAware) instance).setEnvironment(this.environment);
332                        }
333                        if (instance instanceof ResourceLoaderAware) {
334                                ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
335                        }
336                }
337        }
338
339        @Override
340        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
341                Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
342                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
343        }
344
345        protected final ConfigurableListableBeanFactory getBeanFactory() {
346                return this.beanFactory;
347        }
348
349        @Override
350        public void setBeanClassLoader(ClassLoader classLoader) {
351                this.beanClassLoader = classLoader;
352        }
353
354        protected ClassLoader getBeanClassLoader() {
355                return this.beanClassLoader;
356        }
357
358        @Override
359        public void setEnvironment(Environment environment) {
360                this.environment = environment;
361        }
362
363        protected final Environment getEnvironment() {
364                return this.environment;
365        }
366
367        @Override
368        public void setResourceLoader(ResourceLoader resourceLoader) {
369                this.resourceLoader = resourceLoader;
370        }
371
372        protected final ResourceLoader getResourceLoader() {
373                return this.resourceLoader;
374        }
375
376        @Override
377        public int getOrder() {
378                return Ordered.LOWEST_PRECEDENCE - 1;
379        }
380
381        private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
382                        BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
383
384                private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
385
386                private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
387
388                private ClassLoader beanClassLoader;
389
390                private BeanFactory beanFactory;
391
392                private ResourceLoader resourceLoader;
393
394                private AutoConfigurationMetadata autoConfigurationMetadata;
395
396                @Override
397                public void setBeanClassLoader(ClassLoader classLoader) {
398                        this.beanClassLoader = classLoader;
399                }
400
401                @Override
402                public void setBeanFactory(BeanFactory beanFactory) {
403                        this.beanFactory = beanFactory;
404                }
405
406                @Override
407                public void setResourceLoader(ResourceLoader resourceLoader) {
408                        this.resourceLoader = resourceLoader;
409                }
410
411                @Override
412                public void process(AnnotationMetadata annotationMetadata,
413                                DeferredImportSelector deferredImportSelector) {
414                        Assert.state(
415                                        deferredImportSelector instanceof AutoConfigurationImportSelector,
416                                        () -> String.format("Only %s implementations are supported, got %s",
417                                                        AutoConfigurationImportSelector.class.getSimpleName(),
418                                                        deferredImportSelector.getClass().getName()));
419                        AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
420                                        .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
421                                                        annotationMetadata);
422                        this.autoConfigurationEntries.add(autoConfigurationEntry);
423                        for (String importClassName : autoConfigurationEntry.getConfigurations()) {
424                                this.entries.putIfAbsent(importClassName, annotationMetadata);
425                        }
426                }
427
428                @Override
429                public Iterable<Entry> selectImports() {
430                        if (this.autoConfigurationEntries.isEmpty()) {
431                                return Collections.emptyList();
432                        }
433                        Set<String> allExclusions = this.autoConfigurationEntries.stream()
434                                        .map(AutoConfigurationEntry::getExclusions)
435                                        .flatMap(Collection::stream).collect(Collectors.toSet());
436                        Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
437                                        .map(AutoConfigurationEntry::getConfigurations)
438                                        .flatMap(Collection::stream)
439                                        .collect(Collectors.toCollection(LinkedHashSet::new));
440                        processedConfigurations.removeAll(allExclusions);
441
442                        return sortAutoConfigurations(processedConfigurations,
443                                        getAutoConfigurationMetadata())
444                                                        .stream()
445                                                        .map((importClassName) -> new Entry(
446                                                                        this.entries.get(importClassName), importClassName))
447                                                        .collect(Collectors.toList());
448                }
449
450                private AutoConfigurationMetadata getAutoConfigurationMetadata() {
451                        if (this.autoConfigurationMetadata == null) {
452                                this.autoConfigurationMetadata = AutoConfigurationMetadataLoader
453                                                .loadMetadata(this.beanClassLoader);
454                        }
455                        return this.autoConfigurationMetadata;
456                }
457
458                private List<String> sortAutoConfigurations(Set<String> configurations,
459                                AutoConfigurationMetadata autoConfigurationMetadata) {
460                        return new AutoConfigurationSorter(getMetadataReaderFactory(),
461                                        autoConfigurationMetadata).getInPriorityOrder(configurations);
462                }
463
464                private MetadataReaderFactory getMetadataReaderFactory() {
465                        try {
466                                return this.beanFactory.getBean(
467                                                SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
468                                                MetadataReaderFactory.class);
469                        }
470                        catch (NoSuchBeanDefinitionException ex) {
471                                return new CachingMetadataReaderFactory(this.resourceLoader);
472                        }
473                }
474
475        }
476
477        protected static class AutoConfigurationEntry {
478
479                private final List<String> configurations;
480
481                private final Set<String> exclusions;
482
483                private AutoConfigurationEntry() {
484                        this.configurations = Collections.emptyList();
485                        this.exclusions = Collections.emptySet();
486                }
487
488                /**
489                 * Create an entry with the configurations that were contributed and their
490                 * exclusions.
491                 * @param configurations the configurations that should be imported
492                 * @param exclusions the exclusions that were applied to the original list
493                 */
494                AutoConfigurationEntry(Collection<String> configurations,
495                                Collection<String> exclusions) {
496                        this.configurations = new ArrayList<>(configurations);
497                        this.exclusions = new HashSet<>(exclusions);
498                }
499
500                public List<String> getConfigurations() {
501                        return this.configurations;
502                }
503
504                public Set<String> getExclusions() {
505                        return this.exclusions;
506                }
507
508        }
509
510}