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