/* * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.repository.config; import lombok.NonNull; import lombok.RequiredArgsConstructor; import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.RegexPatternTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.Assert; /** * Detects the custom implementation for a {@link org.springframework.data.repository.Repository} * * @author Oliver Gierke * @author Mark Paluch * @author Christoph Strobl * @author Peter Rietzler */ @RequiredArgsConstructor public class CustomRepositoryImplementationDetector { private static final String CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN = "**/*%s.class"; private final @NonNull MetadataReaderFactory metadataReaderFactory; private final @NonNull Environment environment; private final @NonNull ResourceLoader resourceLoader; /** * Tries to detect a custom implementation for a repository bean by classpath scanning. * * @param configuration the {@link RepositoryConfiguration} to consider. * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found. */ public Optional<AbstractBeanDefinition> detectCustomImplementation(RepositoryConfiguration<?> configuration) { // TODO 2.0: Extract into dedicated interface for custom implementation lookup configuration. return detectCustomImplementation(configuration.getImplementationClassName(), // configuration.getBasePackages(), // configuration.getExcludeFilters()); } /** * Tries to detect a custom implementation for a repository bean by classpath scanning. * * @param className must not be {@literal null}. * @param basePackages must not be {@literal null}. * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found. */ public Optional<AbstractBeanDefinition> detectCustomImplementation(String className, Iterable<String> basePackages, Iterable<TypeFilter> excludeFilters) { Assert.notNull(className, "ClassName must not be null!"); Assert.notNull(basePackages, "BasePackages must not be null!"); // Build pattern to lookup implementation class Pattern pattern = Pattern.compile(".*\\." + className); // Build classpath scanner and lookup bean definition ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.setEnvironment(environment); provider.setResourceLoader(resourceLoader); provider.setResourcePattern(String.format(CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN, className)); provider.setMetadataReaderFactory(metadataReaderFactory); provider.addIncludeFilter(new RegexPatternTypeFilter(pattern)); for (TypeFilter excludeFilter : excludeFilters) { provider.addExcludeFilter(excludeFilter); } Set<BeanDefinition> definitions = new HashSet<>(); for (String basePackage : basePackages) { definitions.addAll(provider.findCandidateComponents(basePackage)); } if (definitions.isEmpty()) { return Optional.empty(); } if (definitions.size() == 1) { return Optional.of((AbstractBeanDefinition) definitions.iterator().next()); } throw new IllegalStateException( String.format("Ambiguous custom implementations detected! Found %s but expected a single implementation!", // definitions.stream()// .map(BeanDefinition::getBeanClassName)// .collect(Collectors.joining(", ")))); } }