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}