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}