/*
 * Copyright 2014-2020 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
 *
 *      https://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 java.util.Collection;
import java.util.Optional;
import java.util.Set;
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.data.util.Lazy;
import org.springframework.data.util.StreamUtils;
import org.springframework.util.Assert;

Detects the custom implementation for a Repository instance. If configured with a ImplementationDetectionConfiguration at construction time, the necessary component scan is executed on first access, cached and its result is the filtered on every further implementation lookup according to the given ImplementationDetectionConfiguration. If none is given initially, every invocation to detectCustomImplementation(ImplementationLookupConfiguration, String, ImplementationDetectionConfiguration) will issue a new component scan.
Author:Oliver Gierke, Mark Paluch, Christoph Strobl, Peter Rietzler, Jens Schauder, Mark Paluch
/** * Detects the custom implementation for a {@link org.springframework.data.repository.Repository} instance. If * configured with a {@link ImplementationDetectionConfiguration} at construction time, the necessary component scan is * executed on first access, cached and its result is the filtered on every further implementation lookup according to * the given {@link ImplementationDetectionConfiguration}. If none is given initially, every invocation to * {@link #detectCustomImplementation(String, String, ImplementationDetectionConfiguration)} will issue a new component * scan. * * @author Oliver Gierke * @author Mark Paluch * @author Christoph Strobl * @author Peter Rietzler * @author Jens Schauder * @author Mark Paluch */
public class CustomRepositoryImplementationDetector { private static final String CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN = "**/*%s.class"; private static final String AMBIGUOUS_CUSTOM_IMPLEMENTATIONS = "Ambiguous custom implementations detected! Found %s but expected a single implementation!"; private final Environment environment; private final ResourceLoader resourceLoader; private final Lazy<Set<BeanDefinition>> implementationCandidates;
Creates a new CustomRepositoryImplementationDetector with the given Environment, ResourceLoader and ImplementationDetectionConfiguration. The latter will be registered for a one-time component scan for implementation candidates that will the be used and filtered in all subsequent calls to detectCustomImplementation(ImplementationLookupConfiguration).
Params:
  • environment – must not be null.
  • resourceLoader – must not be null.
  • configuration – must not be null.
/** * Creates a new {@link CustomRepositoryImplementationDetector} with the given {@link Environment}, * {@link ResourceLoader} and {@link ImplementationDetectionConfiguration}. The latter will be registered for a * one-time component scan for implementation candidates that will the be used and filtered in all subsequent calls to * {@link #detectCustomImplementation(RepositoryConfiguration)}. * * @param environment must not be {@literal null}. * @param resourceLoader must not be {@literal null}. * @param configuration must not be {@literal null}. */
public CustomRepositoryImplementationDetector(Environment environment, ResourceLoader resourceLoader, ImplementationDetectionConfiguration configuration) { Assert.notNull(environment, "Environment must not be null!"); Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); Assert.notNull(configuration, "ImplementationDetectionConfiguration must not be null!"); this.environment = environment; this.resourceLoader = resourceLoader; this.implementationCandidates = Lazy.of(() -> findCandidateBeanDefinitions(configuration)); }
Params:
  • environment – must not be null.
  • resourceLoader – must not be null.
/** * Creates a new {@link CustomRepositoryImplementationDetector} with the given {@link Environment} and * {@link ResourceLoader}. Calls to {@link #detectCustomImplementation(ImplementationLookupConfiguration)} will issue * scans for * * @param environment must not be {@literal null}. * @param resourceLoader must not be {@literal null}. */
public CustomRepositoryImplementationDetector(Environment environment, ResourceLoader resourceLoader) { Assert.notNull(environment, "Environment must not be null!"); Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); this.environment = environment; this.resourceLoader = resourceLoader; this.implementationCandidates = Lazy.empty(); }
Tries to detect a custom implementation for a repository bean by classpath scanning.
Params:
  • lookup – must not be null.
Returns:the AbstractBeanDefinition of the custom implementation or null if none found.
/** * Tries to detect a custom implementation for a repository bean by classpath scanning. * * @param lookup must not be {@literal null}. * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found. */
public Optional<AbstractBeanDefinition> detectCustomImplementation(ImplementationLookupConfiguration lookup) { Assert.notNull(lookup, "ImplementationLookupConfiguration must not be null!"); Set<BeanDefinition> definitions = implementationCandidates.getOptional() .orElseGet(() -> findCandidateBeanDefinitions(lookup)).stream() // .filter(lookup::matches) // .collect(StreamUtils.toUnmodifiableSet()); return SelectionSet // .of(definitions, c -> c.isEmpty() ? Optional.empty() : throwAmbiguousCustomImplementationException(c)) // .filterIfNecessary(lookup::hasMatchingBeanName) // .uniqueResult() // .map(AbstractBeanDefinition.class::cast); } private Set<BeanDefinition> findCandidateBeanDefinitions(ImplementationDetectionConfiguration config) { String postfix = config.getImplementationPostfix(); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment); provider.setResourceLoader(resourceLoader); provider.setResourcePattern(String.format(CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN, postfix)); provider.setMetadataReaderFactory(config.getMetadataReaderFactory()); provider.addIncludeFilter((reader, factory) -> true); config.getExcludeFilters().forEach(it -> provider.addExcludeFilter(it)); return config.getBasePackages().stream()// .flatMap(it -> provider.findCandidateComponents(it).stream())// .collect(Collectors.toSet()); } private static Optional<BeanDefinition> throwAmbiguousCustomImplementationException( Collection<BeanDefinition> definitions) { String implementationNames = definitions.stream()// .map(BeanDefinition::getBeanClassName)// .collect(Collectors.joining(", ")); throw new IllegalStateException(String.format(AMBIGUOUS_CUSTOM_IMPLEMENTATIONS, implementationNames)); } }