package org.springframework.data.repository.config;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.config.ConfigurationUtils;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
public class AnnotationRepositoryConfigurationSource extends RepositoryConfigurationSourceSupport {
private static final String REPOSITORY_IMPLEMENTATION_POSTFIX = "repositoryImplementationPostfix";
private static final String BASE_PACKAGES = "basePackages";
private static final String BASE_PACKAGE_CLASSES = "basePackageClasses";
private static final String NAMED_QUERIES_LOCATION = "namedQueriesLocation";
private static final String QUERY_LOOKUP_STRATEGY = "queryLookupStrategy";
private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass";
private static final String REPOSITORY_BASE_CLASS = "repositoryBaseClass";
private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories";
private static final String BOOTSTRAP_MODE = "bootstrapMode";
private final AnnotationMetadata configMetadata;
private final AnnotationMetadata enableAnnotationMetadata;
private final AnnotationAttributes attributes;
private final ResourceLoader resourceLoader;
private final boolean hasExplicitFilters;
@Deprecated
public AnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class<? extends Annotation> annotation,
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
this(metadata, annotation, resourceLoader, environment, registry, null);
}
public AnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class<? extends Annotation> annotation,
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry,
@Nullable BeanNameGenerator generator) {
super(environment, ConfigurationUtils.getRequiredClassLoader(resourceLoader), registry,
defaultBeanNameGenerator(generator));
Assert.notNull(metadata, "Metadata must not be null!");
Assert.notNull(annotation, "Annotation must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(annotation.getName());
if (annotationAttributes == null) {
throw new IllegalStateException(String.format("Unable to obtain annotation attributes for %s!", annotation));
}
this.attributes = new AnnotationAttributes(annotationAttributes);
this.enableAnnotationMetadata = new StandardAnnotationMetadata(annotation);
this.configMetadata = metadata;
this.resourceLoader = resourceLoader;
this.hasExplicitFilters = hasExplicitFilters(attributes);
}
public Streamable<String> getBasePackages() {
String[] value = attributes.getStringArray("value");
String[] basePackages = attributes.getStringArray(BASE_PACKAGES);
Class<?>[] basePackageClasses = attributes.getClassArray(BASE_PACKAGE_CLASSES);
if (value.length == 0 && basePackages.length == 0 && basePackageClasses.length == 0) {
String className = configMetadata.getClassName();
return Streamable.of(ClassUtils.getPackageName(className));
}
Set<String> packages = new HashSet<>();
packages.addAll(Arrays.asList(value));
packages.addAll(Arrays.asList(basePackages));
Arrays.stream(basePackageClasses)
.map(ClassUtils::getPackageName)
.forEach(it -> packages.add(it));
return Streamable.of(packages);
}
public Optional<Object> getQueryLookupStrategyKey() {
return Optional.ofNullable(attributes.get(QUERY_LOOKUP_STRATEGY));
}
public Optional<String> getNamedQueryLocation() {
return getNullDefaultedAttribute(NAMED_QUERIES_LOCATION);
}
public Optional<String> getRepositoryImplementationPostfix() {
return getNullDefaultedAttribute(REPOSITORY_IMPLEMENTATION_POSTFIX);
}
@Nonnull
public Object getSource() {
return configMetadata;
}
@Override
protected Iterable<TypeFilter> getIncludeFilters() {
return parseFilters("includeFilters");
}
@Override
public Streamable<TypeFilter> getExcludeFilters() {
return parseFilters("excludeFilters");
}
@Override
public Optional<String> getRepositoryFactoryBeanClassName() {
return Optional.of(attributes.getClass(REPOSITORY_FACTORY_BEAN_CLASS).getName());
}
@Override
public Optional<String> getRepositoryBaseClassName() {
if (!attributes.containsKey(REPOSITORY_BASE_CLASS)) {
return Optional.empty();
}
Class<? extends Object> repositoryBaseClass = attributes.getClass(REPOSITORY_BASE_CLASS);
return DefaultRepositoryBaseClass.class.equals(repositoryBaseClass) ? Optional.empty()
: Optional.of(repositoryBaseClass.getName());
}
public AnnotationAttributes getAttributes() {
return attributes;
}
public AnnotationMetadata getEnableAnnotationMetadata() {
return enableAnnotationMetadata;
}
@Override
public boolean shouldConsiderNestedRepositories() {
return attributes.containsKey(CONSIDER_NESTED_REPOSITORIES) && attributes.getBoolean(CONSIDER_NESTED_REPOSITORIES);
}
@Override
public Optional<String> getAttribute(String name) {
return getAttribute(name, String.class);
}
@Override
public <T> Optional<T> getAttribute(String name, Class<T> type) {
if (!attributes.containsKey(name)) {
throw new IllegalArgumentException(String.format("No attribute named %s found!", name));
}
Object value = attributes.get(name);
if (value == null) {
return Optional.empty();
}
Assert.isInstanceOf(type, value,
() -> String.format("Attribute value for %s is of type %s but was expected to be of type %s!", name,
value.getClass(), type));
Object result = String.class.isInstance(value)
? StringUtils.hasText((String) value) ? value : null
: value;
return Optional.ofNullable(type.cast(result));
}
@Override
public boolean usesExplicitFilters() {
return hasExplicitFilters;
}
@Override
public BootstrapMode getBootstrapMode() {
try {
return attributes.getEnum(BOOTSTRAP_MODE);
} catch (IllegalArgumentException o_O) {
return BootstrapMode.DEFAULT;
}
}
private Streamable<TypeFilter> parseFilters(String attributeName) {
AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName);
return Streamable.of(() -> Arrays.stream(filters).flatMap(it -> typeFiltersFor(it).stream()));
}
private Optional<String> getNullDefaultedAttribute(String attributeName) {
String attribute = attributes.getString(attributeName);
return StringUtils.hasText(attribute) ? Optional.of(attribute) : Optional.empty();
}
private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) {
List<TypeFilter> typeFilters = new ArrayList<>();
FilterType filterType = filterAttributes.getEnum("type");
for (Class<?> filterClass : filterAttributes.getClassArray("value")) {
switch (filterType) {
case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass,
"An error occured when processing a @ComponentScan " + "ANNOTATION type filter: ");
@SuppressWarnings("unchecked")
Class<Annotation> annoClass = (Class<Annotation>) filterClass;
typeFilters.add(new AnnotationTypeFilter(annoClass));
break;
case ASSIGNABLE_TYPE:
typeFilters.add(new AssignableTypeFilter(filterClass));
break;
case CUSTOM:
Assert.isAssignable(TypeFilter.class, filterClass,
"An error occured when processing a @ComponentScan " + "CUSTOM type filter: ");
typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
break;
default:
throw new IllegalArgumentException("Unknown filter type " + filterType);
}
}
for (String expression : getPatterns(filterAttributes)) {
String rawName = filterType.toString();
if ("REGEX".equals(rawName)) {
typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression)));
} else if ("ASPECTJ".equals(rawName)) {
typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader()));
} else {
throw new IllegalArgumentException("Unknown filter type " + filterType);
}
}
return typeFilters;
}
private String[] getPatterns(AnnotationAttributes filterAttributes) {
try {
return filterAttributes.getStringArray("pattern");
} catch (IllegalArgumentException o_O) {
return new String[0];
}
}
private static boolean hasExplicitFilters(AnnotationAttributes attributes) {
return Stream.of("includeFilters", "excludeFilters")
.anyMatch(it -> attributes.getAnnotationArray(it).length > 0);
}
private static BeanNameGenerator defaultBeanNameGenerator(@Nullable BeanNameGenerator generator) {
return generator == null || ConfigurationClassPostProcessor.IMPORT_BEAN_NAME_GENERATOR.equals(generator)
? new AnnotationBeanNameGenerator()
: generator;
}
}