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.domain; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Set; 026 027import org.springframework.beans.factory.BeanFactory; 028import org.springframework.beans.factory.NoSuchBeanDefinitionException; 029import org.springframework.beans.factory.config.BeanDefinition; 030import org.springframework.beans.factory.config.ConstructorArgumentValues; 031import org.springframework.beans.factory.support.BeanDefinitionRegistry; 032import org.springframework.beans.factory.support.GenericBeanDefinition; 033import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 034import org.springframework.core.annotation.AnnotationAttributes; 035import org.springframework.core.type.AnnotationMetadata; 036import org.springframework.util.Assert; 037import org.springframework.util.ClassUtils; 038import org.springframework.util.StringUtils; 039 040/** 041 * Class for storing {@link EntityScan @EntityScan} specified packages for reference later 042 * (e.g. by JPA auto-configuration). 043 * 044 * @author Phillip Webb 045 * @since 1.4.0 046 * @see EntityScan 047 * @see EntityScanner 048 */ 049public class EntityScanPackages { 050 051 private static final String BEAN = EntityScanPackages.class.getName(); 052 053 private static final EntityScanPackages NONE = new EntityScanPackages(); 054 055 private final List<String> packageNames; 056 057 EntityScanPackages(String... packageNames) { 058 List<String> packages = new ArrayList<>(); 059 for (String name : packageNames) { 060 if (StringUtils.hasText(name)) { 061 packages.add(name); 062 } 063 } 064 this.packageNames = Collections.unmodifiableList(packages); 065 } 066 067 /** 068 * Return the package names specified from all {@link EntityScan @EntityScan} 069 * annotations. 070 * @return the entity scan package names 071 */ 072 public List<String> getPackageNames() { 073 return this.packageNames; 074 } 075 076 /** 077 * Return the {@link EntityScanPackages} for the given bean factory. 078 * @param beanFactory the source bean factory 079 * @return the {@link EntityScanPackages} for the bean factory (never {@code null}) 080 */ 081 public static EntityScanPackages get(BeanFactory beanFactory) { 082 // Currently we only store a single base package, but we return a list to 083 // allow this to change in the future if needed 084 try { 085 return beanFactory.getBean(BEAN, EntityScanPackages.class); 086 } 087 catch (NoSuchBeanDefinitionException ex) { 088 return NONE; 089 } 090 } 091 092 /** 093 * Register the specified entity scan packages with the system. 094 * @param registry the source registry 095 * @param packageNames the package names to register 096 */ 097 public static void register(BeanDefinitionRegistry registry, String... packageNames) { 098 Assert.notNull(registry, "Registry must not be null"); 099 Assert.notNull(packageNames, "PackageNames must not be null"); 100 register(registry, Arrays.asList(packageNames)); 101 } 102 103 /** 104 * Register the specified entity scan packages with the system. 105 * @param registry the source registry 106 * @param packageNames the package names to register 107 */ 108 public static void register(BeanDefinitionRegistry registry, 109 Collection<String> packageNames) { 110 Assert.notNull(registry, "Registry must not be null"); 111 Assert.notNull(packageNames, "PackageNames must not be null"); 112 if (registry.containsBeanDefinition(BEAN)) { 113 BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); 114 ConstructorArgumentValues constructorArguments = beanDefinition 115 .getConstructorArgumentValues(); 116 constructorArguments.addIndexedArgumentValue(0, 117 addPackageNames(constructorArguments, packageNames)); 118 } 119 else { 120 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 121 beanDefinition.setBeanClass(EntityScanPackages.class); 122 beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, 123 StringUtils.toStringArray(packageNames)); 124 beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 125 registry.registerBeanDefinition(BEAN, beanDefinition); 126 } 127 } 128 129 private static String[] addPackageNames( 130 ConstructorArgumentValues constructorArguments, 131 Collection<String> packageNames) { 132 String[] existing = (String[]) constructorArguments 133 .getIndexedArgumentValue(0, String[].class).getValue(); 134 Set<String> merged = new LinkedHashSet<>(); 135 merged.addAll(Arrays.asList(existing)); 136 merged.addAll(packageNames); 137 return StringUtils.toStringArray(merged); 138 } 139 140 /** 141 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing 142 * configuration. 143 */ 144 static class Registrar implements ImportBeanDefinitionRegistrar { 145 146 @Override 147 public void registerBeanDefinitions(AnnotationMetadata metadata, 148 BeanDefinitionRegistry registry) { 149 register(registry, getPackagesToScan(metadata)); 150 } 151 152 private Set<String> getPackagesToScan(AnnotationMetadata metadata) { 153 AnnotationAttributes attributes = AnnotationAttributes.fromMap( 154 metadata.getAnnotationAttributes(EntityScan.class.getName())); 155 String[] basePackages = attributes.getStringArray("basePackages"); 156 Class<?>[] basePackageClasses = attributes 157 .getClassArray("basePackageClasses"); 158 Set<String> packagesToScan = new LinkedHashSet<>(); 159 packagesToScan.addAll(Arrays.asList(basePackages)); 160 for (Class<?> basePackageClass : basePackageClasses) { 161 packagesToScan.add(ClassUtils.getPackageName(basePackageClass)); 162 } 163 if (packagesToScan.isEmpty()) { 164 String packageName = ClassUtils.getPackageName(metadata.getClassName()); 165 Assert.state(!StringUtils.isEmpty(packageName), 166 "@EntityScan cannot be used with the default package"); 167 return Collections.singleton(packageName); 168 } 169 return packagesToScan; 170 } 171 172 } 173 174}