Skip to content

DATACMNS-1255, DATACMNS-1233 - Add CDI support for repository fragments (mixins). #272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATACMNS-1255-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,67 @@

package org.springframework.data.repository.cdi;

import java.util.Optional;

import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;

/**
* Interface containing the configurable options for the Spring Data repository subsystem using CDI.
*
* @author Mark Paluch
* @author Fabian Henniges
*/
public interface CdiRepositoryConfiguration {

/**
* Return the {@link EvaluationContextProvider} to use. Can be {@link Optional#empty()} .
*
* @return the optional {@link EvaluationContextProvider} base to use, can be {@link Optional#empty()}, must not be
* {@literal null}.
* @since 2.1
*/
default Optional<EvaluationContextProvider> getEvaluationContextProvider() {
return Optional.empty();
}

/**
* Return the {@link NamedQueries} to use. Can be {@link Optional#empty()}.
*
* @return the optional named queries to use, can be {@link Optional#empty()}, must not be {@literal null}.
* @since 2.1
*/
default Optional<NamedQueries> getNamedQueries() {
return Optional.empty();
}

/**
* Return the {@link QueryLookupStrategy.Key} to lookup queries. Can be {@link Optional#empty()}.
*
* @return the lookup strategy to use, can be {@link Optional#empty()}, must not be {@literal null}.
* @since 2.1
*/
default Optional<QueryLookupStrategy.Key> getQueryLookupStrategy() {
return Optional.empty();
}

/**
* Return the {@link Class repository base class} to use. Can be {@link Optional#empty()} .
*
* @return the optional repository base to use, can be {@link Optional#empty()}, must not be {@literal null}.
* @since 2.1
*/
default Optional<Class<?>> getRepositoryBeanClass() {
return Optional.empty();
}

/**
* Returns the configured postfix to be used for looking up custom implementation classes.
*
* @return the postfix to use, must not be {@literal null}.
*/
String getRepositoryImplementationPostfix();
default String getRepositoryImplementationPostfix() {
return "Impl";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright 2018 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.cdi;

import lombok.RequiredArgsConstructor;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.stream.Stream;

import javax.enterprise.inject.CreationException;
import javax.enterprise.inject.UnsatisfiedResolutionException;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.config.CustomRepositoryImplementationDetector;
import org.springframework.data.repository.config.FragmentMetadata;
import org.springframework.data.repository.config.RepositoryFragmentConfiguration;
import org.springframework.data.repository.config.RepositoryFragmentDiscovery;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
* Context for CDI repositories. This class provides {@link ClassLoader} and
* {@link org.springframework.data.repository.core.support.RepositoryFragment detection} which are commonly used within
* CDI.
*
* @author Mark Paluch
* @since 2.1
*/
public class CdiRepositoryContext {

private final ClassLoader classLoader;
private final CustomRepositoryImplementationDetector detector;
private final MetadataReaderFactory metadataReaderFactory;

/**
* Create a new {@link CdiRepositoryContext} given {@link ClassLoader} and initialize
* {@link CachingMetadataReaderFactory}.
*
* @param classLoader must not be {@literal null}.
*/
public CdiRepositoryContext(ClassLoader classLoader) {

Assert.notNull(classLoader, "ClassLoader must not be null!");

this.classLoader = classLoader;

Environment environment = new StandardEnvironment();
ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(classLoader);

this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
this.detector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment, resourceLoader);
}

/**
* Create a new {@link CdiRepositoryContext} given {@link ClassLoader} and
* {@link CustomRepositoryImplementationDetector}.
*
* @param classLoader must not be {@literal null}.
* @param detector must not be {@literal null}.
*/
public CdiRepositoryContext(ClassLoader classLoader, CustomRepositoryImplementationDetector detector) {

Assert.notNull(classLoader, "ClassLoader must not be null!");
Assert.notNull(detector, "CustomRepositoryImplementationDetector must not be null!");

ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(classLoader);

this.classLoader = classLoader;
this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
this.detector = detector;
}

CustomRepositoryImplementationDetector getCustomRepositoryImplementationDetector() {
return detector;
}

/**
* Load a {@link Class} using the CDI {@link ClassLoader}.
*
* @param className
* @return
* @throws UnsatisfiedResolutionException if the class cannot be found.
*/
Class<?> loadClass(String className) {

try {
return ClassUtils.forName(className, classLoader);
} catch (ClassNotFoundException e) {
throw new UnsatisfiedResolutionException(String.format("Unable to resolve class for '%s'", className), e);
}
}

/**
* Discover {@link RepositoryFragmentConfiguration fragment configurations} for a {@link Class repository interface}.
*
* @param configuration must not be {@literal null}.
* @param repositoryInterface must not be {@literal null}.
* @return {@link Stream} of {@link RepositoryFragmentConfiguration fragment configurations}.
*/
Stream<RepositoryFragmentConfiguration> getRepositoryFragments(CdiRepositoryConfiguration configuration,
Class<?> repositoryInterface) {

ClassMetadata classMetadata = getClassMetadata(metadataReaderFactory, repositoryInterface.getName());

RepositoryFragmentDiscovery fragmentConfiguration = new CdiRepositoryFragmentDiscovery(configuration);

return Arrays.stream(classMetadata.getInterfaceNames()) //
.filter(it -> FragmentMetadata.isCandidate(it, metadataReaderFactory)) //
.map(it -> FragmentMetadata.of(it, fragmentConfiguration)) //
.map(this::detectRepositoryFragmentConfiguration) //
.flatMap(Optionals::toStream);
}

/**
* Retrieves a custom repository interfaces from a repository type. This works for the whole class hierarchy and can
* find also a custom repository which is inherited over many levels.
*
* @param repositoryType The class representing the repository.
* @param cdiRepositoryConfiguration The configuration for CDI usage.
* @return the interface class or {@literal null}.
*/
Optional<Class<?>> getCustomImplementationClass(Class<?> repositoryType,
CdiRepositoryConfiguration cdiRepositoryConfiguration) {

String className = getCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration);

Optional<AbstractBeanDefinition> beanDefinition = detector.detectCustomImplementation( //
className, //
className, Collections.singleton(repositoryType.getPackage().getName()), //
Collections.emptySet(), //
BeanDefinition::getBeanClassName);

return beanDefinition.map(it -> loadClass(it.getBeanClassName()));
}

private Optional<RepositoryFragmentConfiguration> detectRepositoryFragmentConfiguration(
FragmentMetadata configuration) {

String className = configuration.getFragmentImplementationClassName();

Optional<AbstractBeanDefinition> beanDefinition = detector.detectCustomImplementation(className, null,
configuration.getBasePackages(), configuration.getExclusions(), BeanDefinition::getBeanClassName);

return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(configuration.getFragmentInterfaceName(), bd));
}

private static ClassMetadata getClassMetadata(MetadataReaderFactory metadataReaderFactory, String className) {

try {
return metadataReaderFactory.getMetadataReader(className).getClassMetadata();
} catch (IOException e) {
throw new CreationException(String.format("Cannot parse %s metadata.", className), e);
}
}

private static String getCustomImplementationClassName(Class<?> repositoryType,
CdiRepositoryConfiguration cdiRepositoryConfiguration) {

String configuredPostfix = cdiRepositoryConfiguration.getRepositoryImplementationPostfix();
Assert.hasText(configuredPostfix, "Configured repository postfix must not be null or empty!");

return ClassUtils.getShortName(repositoryType) + configuredPostfix;
}

@RequiredArgsConstructor
private static class CdiRepositoryFragmentDiscovery implements RepositoryFragmentDiscovery {

private final CdiRepositoryConfiguration configuration;

/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryFragmentDiscovery#getExcludeFilters()
*/
@Override
public Streamable<TypeFilter> getExcludeFilters() {
return Streamable.of(new AnnotationTypeFilter(NoRepositoryBean.class));
}

/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryFragmentDiscovery#getRepositoryImplementationPostfix()
*/
@Override
public Optional<String> getRepositoryImplementationPostfix() {
return Optional.of(configuration.getRepositoryImplementationPostfix());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
Expand All @@ -61,16 +55,10 @@ public abstract class CdiRepositoryExtensionSupport implements Extension {

private final Map<Class<?>, Set<Annotation>> repositoryTypes = new HashMap<>();
private final Set<CdiRepositoryBean<?>> eagerRepositories = new HashSet<>();
private final CustomRepositoryImplementationDetector customImplementationDetector;
private final CdiRepositoryContext context;

protected CdiRepositoryExtensionSupport() {

Environment environment = new StandardEnvironment();
ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(getClass().getClassLoader());
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);

this.customImplementationDetector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment,
resourceLoader);
context = new CdiRepositoryContext(getClass().getClassLoader());
}

/**
Expand All @@ -91,8 +79,8 @@ protected <X> void processAnnotatedType(@Observes ProcessAnnotatedType<X> proces
Set<Annotation> qualifiers = getQualifiers(repositoryType);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Discovered repository type '%s' with qualifiers %s.", repositoryType.getName(),
qualifiers));
LOGGER.debug(
String.format("Discovered repository type '%s' with qualifiers %s.", repositoryType.getName(), qualifiers));
}
// Store the repository type using its qualifiers.
repositoryTypes.put(repositoryType, qualifiers);
Expand Down Expand Up @@ -184,7 +172,15 @@ protected void registerBean(CdiRepositoryBean<?> bean) {
* @return the {@link CustomRepositoryImplementationDetector} to scan for the custom implementation
*/
protected CustomRepositoryImplementationDetector getCustomImplementationDetector() {
return customImplementationDetector;
return context.getCustomRepositoryImplementationDetector();
}

/**
* @return the {@link CdiRepositoryContext} encapsulating the CDI-specific class loaders and fragment scanning.
* @since 2.1
*/
protected CdiRepositoryContext getRepositoryContext() {
return context;
}

@SuppressWarnings("all")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
implements RepositoryConfiguration<T> {

public static final String DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX = "Impl";
private static final Key DEFAULT_QUERY_LOOKUP_STRATEGY = Key.CREATE_IF_NOT_FOUND;
public static final Key DEFAULT_QUERY_LOOKUP_STRATEGY = Key.CREATE_IF_NOT_FOUND;

private final @NonNull T configurationSource;
private final @NonNull BeanDefinition definition;
Expand Down
Loading