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.Collections;
022import java.util.LinkedHashSet;
023import java.util.List;
024import java.util.Set;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.beans.factory.BeanFactory;
030import org.springframework.beans.factory.NoSuchBeanDefinitionException;
031import org.springframework.beans.factory.config.BeanDefinition;
032import org.springframework.beans.factory.config.ConstructorArgumentValues;
033import org.springframework.beans.factory.support.BeanDefinitionRegistry;
034import org.springframework.beans.factory.support.GenericBeanDefinition;
035import org.springframework.boot.context.annotation.DeterminableImports;
036import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
037import org.springframework.core.type.AnnotationMetadata;
038import org.springframework.util.ClassUtils;
039import org.springframework.util.StringUtils;
040
041/**
042 * Class for storing auto-configuration packages for reference later (e.g. by JPA entity
043 * scanner).
044 *
045 * @author Phillip Webb
046 * @author Dave Syer
047 * @author Oliver Gierke
048 */
049public abstract class AutoConfigurationPackages {
050
051        private static final Log logger = LogFactory.getLog(AutoConfigurationPackages.class);
052
053        private static final String BEAN = AutoConfigurationPackages.class.getName();
054
055        /**
056         * Determine if the auto-configuration base packages for the given bean factory are
057         * available.
058         * @param beanFactory the source bean factory
059         * @return true if there are auto-config packages available
060         */
061        public static boolean has(BeanFactory beanFactory) {
062                return beanFactory.containsBean(BEAN) && !get(beanFactory).isEmpty();
063        }
064
065        /**
066         * Return the auto-configuration base packages for the given bean factory.
067         * @param beanFactory the source bean factory
068         * @return a list of auto-configuration packages
069         * @throws IllegalStateException if auto-configuration is not enabled
070         */
071        public static List<String> get(BeanFactory beanFactory) {
072                try {
073                        return beanFactory.getBean(BEAN, BasePackages.class).get();
074                }
075                catch (NoSuchBeanDefinitionException ex) {
076                        throw new IllegalStateException(
077                                        "Unable to retrieve @EnableAutoConfiguration base packages");
078                }
079        }
080
081        /**
082         * Programmatically registers the auto-configuration package names. Subsequent
083         * invocations will add the given package names to those that have already been
084         * registered. You can use this method to manually define the base packages that will
085         * be used for a given {@link BeanDefinitionRegistry}. Generally it's recommended that
086         * you don't call this method directly, but instead rely on the default convention
087         * where the package name is set from your {@code @EnableAutoConfiguration}
088         * configuration class or classes.
089         * @param registry the bean definition registry
090         * @param packageNames the package names to set
091         */
092        public static void register(BeanDefinitionRegistry registry, String... packageNames) {
093                if (registry.containsBeanDefinition(BEAN)) {
094                        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
095                        ConstructorArgumentValues constructorArguments = beanDefinition
096                                        .getConstructorArgumentValues();
097                        constructorArguments.addIndexedArgumentValue(0,
098                                        addBasePackages(constructorArguments, packageNames));
099                }
100                else {
101                        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
102                        beanDefinition.setBeanClass(BasePackages.class);
103                        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
104                                        packageNames);
105                        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
106                        registry.registerBeanDefinition(BEAN, beanDefinition);
107                }
108        }
109
110        private static String[] addBasePackages(
111                        ConstructorArgumentValues constructorArguments, String[] packageNames) {
112                String[] existing = (String[]) constructorArguments
113                                .getIndexedArgumentValue(0, String[].class).getValue();
114                Set<String> merged = new LinkedHashSet<>();
115                merged.addAll(Arrays.asList(existing));
116                merged.addAll(Arrays.asList(packageNames));
117                return StringUtils.toStringArray(merged);
118        }
119
120        /**
121         * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
122         * configuration.
123         */
124        static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
125
126                @Override
127                public void registerBeanDefinitions(AnnotationMetadata metadata,
128                                BeanDefinitionRegistry registry) {
129                        register(registry, new PackageImport(metadata).getPackageName());
130                }
131
132                @Override
133                public Set<Object> determineImports(AnnotationMetadata metadata) {
134                        return Collections.singleton(new PackageImport(metadata));
135                }
136
137        }
138
139        /**
140         * Wrapper for a package import.
141         */
142        private static final class PackageImport {
143
144                private final String packageName;
145
146                PackageImport(AnnotationMetadata metadata) {
147                        this.packageName = ClassUtils.getPackageName(metadata.getClassName());
148                }
149
150                public String getPackageName() {
151                        return this.packageName;
152                }
153
154                @Override
155                public boolean equals(Object obj) {
156                        if (obj == null || getClass() != obj.getClass()) {
157                                return false;
158                        }
159                        return this.packageName.equals(((PackageImport) obj).packageName);
160                }
161
162                @Override
163                public int hashCode() {
164                        return this.packageName.hashCode();
165                }
166
167                @Override
168                public String toString() {
169                        return "Package Import " + this.packageName;
170                }
171
172        }
173
174        /**
175         * Holder for the base package (name may be null to indicate no scanning).
176         */
177        static final class BasePackages {
178
179                private final List<String> packages;
180
181                private boolean loggedBasePackageInfo;
182
183                BasePackages(String... names) {
184                        List<String> packages = new ArrayList<>();
185                        for (String name : names) {
186                                if (StringUtils.hasText(name)) {
187                                        packages.add(name);
188                                }
189                        }
190                        this.packages = packages;
191                }
192
193                public List<String> get() {
194                        if (!this.loggedBasePackageInfo) {
195                                if (this.packages.isEmpty()) {
196                                        if (logger.isWarnEnabled()) {
197                                                logger.warn("@EnableAutoConfiguration was declared on a class "
198                                                                + "in the default package. Automatic @Repository and "
199                                                                + "@Entity scanning is not enabled.");
200                                        }
201                                }
202                                else {
203                                        if (logger.isDebugEnabled()) {
204                                                String packageNames = StringUtils
205                                                                .collectionToCommaDelimitedString(this.packages);
206                                                logger.debug("@EnableAutoConfiguration was declared on a class "
207                                                                + "in the package '" + packageNames
208                                                                + "'. Automatic @Repository and @Entity scanning is "
209                                                                + "enabled.");
210                                        }
211                                }
212                                this.loggedBasePackageInfo = true;
213                        }
214                        return this.packages;
215                }
216
217        }
218
219}