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.lang.annotation.Annotation;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Set;
024
025import org.springframework.beans.factory.config.BeanDefinition;
026import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
027import org.springframework.context.ApplicationContext;
028import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
029import org.springframework.core.type.filter.AnnotationTypeFilter;
030import org.springframework.util.Assert;
031import org.springframework.util.ClassUtils;
032import org.springframework.util.StringUtils;
033
034/**
035 * An entity scanner that searches the classpath from an {@link EntityScan @EntityScan}
036 * specified packages.
037 *
038 * @author Phillip Webb
039 * @since 1.4.0
040 */
041public class EntityScanner {
042
043        private final ApplicationContext context;
044
045        /**
046         * Create a new {@link EntityScanner} instance.
047         * @param context the source application context
048         */
049        public EntityScanner(ApplicationContext context) {
050                Assert.notNull(context, "Context must not be null");
051                this.context = context;
052        }
053
054        /**
055         * Scan for entities with the specified annotations.
056         * @param annotationTypes the annotation types used on the entities
057         * @return a set of entity classes
058         * @throws ClassNotFoundException if an entity class cannot be loaded
059         */
060        @SafeVarargs
061        public final Set<Class<?>> scan(Class<? extends Annotation>... annotationTypes)
062                        throws ClassNotFoundException {
063                List<String> packages = getPackages();
064                if (packages.isEmpty()) {
065                        return Collections.emptySet();
066                }
067                ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
068                                false);
069                scanner.setEnvironment(this.context.getEnvironment());
070                scanner.setResourceLoader(this.context);
071                for (Class<? extends Annotation> annotationType : annotationTypes) {
072                        scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
073                }
074                Set<Class<?>> entitySet = new HashSet<>();
075                for (String basePackage : packages) {
076                        if (StringUtils.hasText(basePackage)) {
077                                for (BeanDefinition candidate : scanner
078                                                .findCandidateComponents(basePackage)) {
079                                        entitySet.add(ClassUtils.forName(candidate.getBeanClassName(),
080                                                        this.context.getClassLoader()));
081                                }
082                        }
083                }
084                return entitySet;
085        }
086
087        private List<String> getPackages() {
088                List<String> packages = EntityScanPackages.get(this.context).getPackageNames();
089                if (packages.isEmpty() && AutoConfigurationPackages.has(this.context)) {
090                        packages = AutoConfigurationPackages.get(this.context);
091                }
092                return packages;
093        }
094
095}