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}