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}