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