diff --git a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java index 60f3f6223..967f98a88 100644 --- a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -18,7 +18,6 @@ package org.springframework.data.repository.config; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -37,6 +36,7 @@ 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.util.Streamable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -113,7 +113,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBasePackages() */ - public Iterable getBasePackages() { + public Streamable getBasePackages() { String[] value = attributes.getStringArray("value"); String[] basePackages = attributes.getStringArray(BASE_PACKAGES); @@ -121,19 +121,20 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura // Default configuration - return package of annotated class if (value.length == 0 && basePackages.length == 0 && basePackageClasses.length == 0) { + String className = configMetadata.getClassName(); - return Collections.singleton(ClassUtils.getPackageName(className)); + return Streamable.of(ClassUtils.getPackageName(className)); } Set packages = new HashSet<>(); packages.addAll(Arrays.asList(value)); packages.addAll(Arrays.asList(basePackages)); - for (Class typeName : basePackageClasses) { - packages.add(ClassUtils.getPackageName(typeName)); - } + Arrays.stream(basePackageClasses)// + .map(ClassUtils::getPackageName)// + .forEach(it -> packages.add(it)); - return packages; + return Streamable.of(packages); } /* @@ -182,20 +183,15 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura * @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getExcludeFilters() */ @Override - public Iterable getExcludeFilters() { + public Streamable getExcludeFilters() { return parseFilters("excludeFilters"); } - private Set parseFilters(String attributeName) { + private Streamable parseFilters(String attributeName) { - Set result = new HashSet<>(); AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName); - for (AnnotationAttributes filter : filters) { - result.addAll(typeFiltersFor(filter)); - } - - return result; + return Streamable.of(() -> Arrays.stream(filters).flatMap(it -> typeFiltersFor(it).stream())); } /** diff --git a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java index df9925006..1ab8cadbf 100644 --- a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java +++ b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java @@ -13,16 +13,12 @@ * 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.ArrayList; import java.util.Collection; -import java.util.HashSet; -import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -37,6 +33,7 @@ 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.data.util.Streamable; import org.springframework.util.Assert; /** @@ -52,6 +49,7 @@ import org.springframework.util.Assert; 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 @NonNull MetadataReaderFactory metadataReaderFactory; private final @NonNull Environment environment; @@ -94,12 +92,10 @@ public class CustomRepositoryImplementationDetector { Set definitions = findCandidateBeanDefinitions(className, basePackages, excludeFilters); - SelectionSet selection = new SelectionSet<>( // - definitions, // - c -> c.isEmpty() ? null : throwAmbiguousCustomImplementationException(c) // - ).filterIfNecessary(bd -> beanName != null && beanName.equals(beanNameGenerator.apply(bd))); - - return Optional.ofNullable((AbstractBeanDefinition) selection.uniqueResult()); + return SelectionSet // + .of(definitions, c -> c.isEmpty() ? Optional.empty() : throwAmbiguousCustomImplementationException(c)) // + .filterIfNecessary(bd -> beanName != null && beanName.equals(beanNameGenerator.apply(bd)))// + .uniqueResult().map(it -> AbstractBeanDefinition.class.cast(it)); } Set findCandidateBeanDefinitions(String className, Iterable basePackages, @@ -116,31 +112,20 @@ public class CustomRepositoryImplementationDetector { provider.setMetadataReaderFactory(metadataReaderFactory); provider.addIncludeFilter(new RegexPatternTypeFilter(pattern)); - for (TypeFilter excludeFilter : excludeFilters) { - provider.addExcludeFilter(excludeFilter); - } + excludeFilters.forEach(it -> provider.addExcludeFilter(it)); - Set definitions = new HashSet<>(); - - for (String basePackage : basePackages) { - definitions.addAll(provider.findCandidateComponents(basePackage)); - } - - return definitions; + return Streamable.of(basePackages).stream()// + .flatMap(it -> provider.findCandidateComponents(it).stream())// + .collect(Collectors.toSet()); } - private static AbstractBeanDefinition throwAmbiguousCustomImplementationException( + private static Optional throwAmbiguousCustomImplementationException( Collection definitions) { - List implementationClassNames = new ArrayList(); - for (BeanDefinition bean : definitions) { - implementationClassNames.add(bean.getBeanClassName()); - } + String implementationNames = definitions.stream()// + .map(BeanDefinition::getBeanClassName)// + .collect(Collectors.joining(", ")); - throw new IllegalStateException( - String.format("Ambiguous custom implementations detected! Found %s but expected a single implementation!", // - definitions.stream()// - .map(BeanDefinition::getBeanClassName)// - .collect(Collectors.joining(", ")))); + throw new IllegalStateException(String.format(AMBIGUOUS_CUSTOM_IMPLEMENTATIONS, implementationNames)); } } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java index ccfb265ba..7058ee47f 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java @@ -15,10 +15,11 @@ */ package org.springframework.data.repository.config; +import lombok.RequiredArgsConstructor; + import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; -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.ScannedGenericBeanDefinition; @@ -32,16 +33,13 @@ import org.springframework.util.ClassUtils; * @author Oliver Gierke * @author Jens Schauder */ +@RequiredArgsConstructor public class RepositoryBeanNameGenerator { private static final BeanNameGenerator DELEGATE = new AnnotationBeanNameGenerator(); private final ClassLoader beanClassLoader; - public RepositoryBeanNameGenerator(ClassLoader beanClassLoader) { - this.beanClassLoader = beanClassLoader; - } - /** * Generate a bean name for the given bean definition. * @@ -66,11 +64,13 @@ public class RepositoryBeanNameGenerator { private Class getRepositoryInterfaceFrom(BeanDefinition beanDefinition) { if (beanDefinition instanceof ScannedGenericBeanDefinition) { + try { return ((ScannedGenericBeanDefinition) beanDefinition).resolveBeanClass(beanClassLoader); } catch (ClassNotFoundException e) { throw new IllegalStateException("Could not resolve bean class.", e); } + } else { return getRepositoryInterfaceFromFactory(beanDefinition); } @@ -81,8 +81,11 @@ public class RepositoryBeanNameGenerator { Object value = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, Class.class).getValue(); if (value instanceof Class) { + return (Class) value; + } else { + try { return ClassUtils.forName(value.toString(), beanClassLoader); } catch (Exception o_O) { diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java index 76af7c005..b34674755 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java @@ -15,13 +15,13 @@ */ package org.springframework.data.repository.config; -import java.util.Collection; import java.util.Optional; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.filter.TypeFilter; import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.util.Streamable; /** * Interface containing the configurable options for the Spring Data repository subsystem. @@ -46,7 +46,7 @@ public interface RepositoryConfigurationSource { * * @return must not be {@literal null}. */ - Iterable getBasePackages(); + Streamable getBasePackages(); /** * Returns the {@link QueryLookupStrategy.Key} to define how query methods shall be resolved. @@ -89,7 +89,7 @@ public interface RepositoryConfigurationSource { * @param loader * @return */ - Collection getCandidates(ResourceLoader loader); + Streamable getCandidates(ResourceLoader loader); /** * Returns the value for the {@link String} attribute with the given name. The name is expected to be handed in @@ -115,12 +115,12 @@ public interface RepositoryConfigurationSource { * * @return must not be {@literal null}. */ - Iterable getExcludeFilters(); + Streamable getExcludeFilters(); /** * Returns a name for the beanDefinition. * - * @param beanDefinition Must not be {@literal null}. + * @param beanDefinition must not be {@literal null}. * @return * @since 2.0 */ diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java index b96857367..2ea1798b1 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java @@ -15,15 +15,13 @@ */ package org.springframework.data.repository.config; -import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.util.Streamable; import org.springframework.util.Assert; /** @@ -58,27 +56,20 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository /* * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfiguration#getCandidates(org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getCandidates(org.springframework.core.io.ResourceLoader) */ - public Collection getCandidates(ResourceLoader loader) { + @Override + public Streamable getCandidates(ResourceLoader loader) { RepositoryComponentProvider scanner = new RepositoryComponentProvider(getIncludeFilters()); scanner.setConsiderNestedRepositoryInterfaces(shouldConsiderNestedRepositories()); scanner.setResourceLoader(loader); scanner.setEnvironment(environment); - for (TypeFilter filter : getExcludeFilters()) { - scanner.addExcludeFilter(filter); - } + getExcludeFilters().forEach(it -> scanner.addExcludeFilter(it)); - Set result = new HashSet<>(); - - for (String basePackage : getBasePackages()) { - Set candidate = scanner.findCandidateComponents(basePackage); - result.addAll(candidate); - } - - return result; + return Streamable.of(() -> getBasePackages().stream()// + .flatMap(it -> scanner.findCandidateComponents(it).stream())); } /** @@ -87,8 +78,18 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository * * @return must not be {@literal null}. */ - public Iterable getExcludeFilters() { - return Collections.emptySet(); + @Override + public Streamable getExcludeFilters() { + return Streamable.empty(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBeanNameGenerator() + */ + @Override + public String generateBeanName(BeanDefinition beanDefinition) { + return beanNameGenerator.generateBeanName(beanDefinition); } /** @@ -110,13 +111,4 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository public boolean shouldConsiderNestedRepositories() { return false; } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBeanNameGenerator() - */ - @Override - public String generateBeanName(BeanDefinition beanDefinition) { - return beanNameGenerator.generateBeanName(beanDefinition); - } } diff --git a/src/main/java/org/springframework/data/repository/config/SelectionSet.java b/src/main/java/org/springframework/data/repository/config/SelectionSet.java index 08faaf4ca..5feba655e 100644 --- a/src/main/java/org/springframework/data/repository/config/SelectionSet.java +++ b/src/main/java/org/springframework/data/repository/config/SelectionSet.java @@ -15,8 +15,10 @@ */ package org.springframework.data.repository.config; +import lombok.RequiredArgsConstructor; + import java.util.Collection; -import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -26,41 +28,35 @@ import java.util.stream.Collectors; * filters are ignored. * * @author Jens Schauder + * @author Oliver Gierke + * @since 2.0 */ +@RequiredArgsConstructor(staticName = "of") class SelectionSet { private final Collection collection; - private final Function, T> fallback; + private final Function, Optional> fallback; /** * creates a {@link SelectionSet} with a default fallback of {@literal null}, when no element is found and an * {@link IllegalStateException} when no element is found. */ - SelectionSet(Collection collection) { - this(collection, defaultFallback()); + static SelectionSet of(Collection collection) { + return new SelectionSet<>(collection, defaultFallback()); } /** - * @param collection The collection from which a unique element will get picked. - * @param fallback Will get called once {@literal #uniqueResult} gets called if no unique result can be determined. - */ - SelectionSet(Collection collection, Function, T> fallback) { - - this.collection = collection; - this.fallback = fallback; - } - - /** - * If this SelectionSet contains exactly one element it gets returned. If no unique result can - * be identified the fallback function passed in at the constructor gets called and its return value becomes - * the return value of this method. + * If this {@code SelectionSet} contains exactly one element it gets returned. If no unique result can be identified + * the fallback function passed in at the constructor gets called and its return value becomes the return value of + * this method. * * @return a unique result, or the result of the callback provided in the constructor. */ - T uniqueResult() { - T uniqueResult = findUniqueResult(); + Optional uniqueResult() { - return uniqueResult != null ? uniqueResult : fallback.apply(collection); + Optional uniqueResult = findUniqueResult(); + + return uniqueResult.isPresent() ? uniqueResult : fallback.apply(collection); } /** @@ -70,26 +66,22 @@ class SelectionSet { */ SelectionSet filterIfNecessary(Predicate predicate) { - if (findUniqueResult() != null) { - return this; - } - - List fillteredList = collection.stream().filter(predicate).collect(Collectors.toList()); - return new SelectionSet(fillteredList, fallback); + return findUniqueResult().map(it -> this).orElseGet( + () -> new SelectionSet(collection.stream().filter(predicate).collect(Collectors.toList()), fallback)); } - private static Function, S> defaultFallback() { + private static Function, Optional> defaultFallback() { return c -> { if (c.isEmpty()) { - return null; + return Optional.empty(); } else { throw new IllegalStateException("More then one element in collection."); } }; } - private T findUniqueResult() { - return collection.size() == 1 ? collection.iterator().next() : null; + private Optional findUniqueResult() { + return Optional.ofNullable(collection.size() == 1 ? collection.iterator().next() : null); } } diff --git a/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java index 031c98320..3dd1646dc 100644 --- a/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java @@ -15,7 +15,6 @@ */ package org.springframework.data.repository.config; -import java.util.Arrays; import java.util.Collection; import java.util.Optional; @@ -26,6 +25,7 @@ import org.springframework.data.config.TypeFilterParser; import org.springframework.data.config.TypeFilterParser.Type; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.util.ParsingUtils; +import org.springframework.data.util.Streamable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -89,10 +89,11 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBasePackages() */ - public Iterable getBasePackages() { + public Streamable getBasePackages() { String attribute = element.getAttribute(BASE_PACKAGE); - return Arrays.asList(StringUtils.delimitedListToStringArray(attribute, ",", " ")); + + return Streamable.of(StringUtils.delimitedListToStringArray(attribute, ",", " ")); } /* @@ -125,8 +126,8 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou * @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getExcludeFilters() */ @Override - public Iterable getExcludeFilters() { - return excludeFilters; + public Streamable getExcludeFilters() { + return Streamable.of(excludeFilters); } /* diff --git a/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java b/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java index 46b9483f8..58841314c 100755 --- a/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java @@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.*; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Collection; import org.junit.Before; import org.junit.Test; @@ -32,6 +31,7 @@ import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.data.util.Streamable; /** * Unit tests for {@link AnnotationRepositoryConfigurationSource}. @@ -65,7 +65,7 @@ public class AnnotationRepositoryConfigurationSourceUnitTests { @Test // DATACMNS-47 public void evaluatesExcludeFiltersCorrectly() { - Collection candidates = source.getCandidates(new DefaultResourceLoader()); + Streamable candidates = source.getCandidates(new DefaultResourceLoader()); assertThat(candidates).hasSize(1); BeanDefinition candidate = candidates.iterator().next(); diff --git a/src/test/java/org/springframework/data/repository/config/SelectionSetUnitTests.java b/src/test/java/org/springframework/data/repository/config/SelectionSetUnitTests.java index fabd37589..7e94b7fd4 100644 --- a/src/test/java/org/springframework/data/repository/config/SelectionSetUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/SelectionSetUnitTests.java @@ -1,8 +1,25 @@ +/* + * Copyright 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 static java.util.Arrays.*; import static java.util.Collections.*; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.Optional; import org.junit.Test; @@ -10,53 +27,53 @@ import org.junit.Test; * Unit tests for {@link SelectionSet} * * @author Jens Schauder + * @author Oliver Gierke */ public class SelectionSetUnitTests { @Test // DATACMNS-764 public void returnsUniqueResult() { - assertEquals("single value", new SelectionSet<>(singleton("single value")).uniqueResult()); + assertThat(SelectionSet.of(singleton("single value")).uniqueResult()).hasValue("single value"); } @Test // DATACMNS-764 public void emptyCollectionReturnsNull() { - assertNull(new SelectionSet(emptySet()).uniqueResult()); + assertThat(SelectionSet.of(emptySet()).uniqueResult()).isEmpty(); } @Test(expected = IllegalStateException.class) // DATACMNS-764 public void multipleElementsThrowException() { - new SelectionSet<>(asList("one", "two")).uniqueResult(); + SelectionSet.of(asList("one", "two")).uniqueResult(); } @Test(expected = NullPointerException.class) // DATACMNS-764 public void throwsCustomExceptionWhenConfigured() { - new SelectionSet<>(asList("one", "two"), c -> { + SelectionSet.of(asList("one", "two"), c -> { throw new NullPointerException(); }).uniqueResult(); } @Test // DATACMNS-764 public void usesFallbackWhenConfigured() { - - String value = new SelectionSet<>(asList("one", "two"), c -> String.valueOf(c.size())).uniqueResult(); - - assertEquals("2", value); + assertThat(SelectionSet.of(asList("one", "two"), c -> Optional.of(String.valueOf(c.size()))).uniqueResult()) + .hasValue("2"); } @Test // DATACMNS-764 public void returnsUniqueResultAfterFilter() { - SelectionSet selection = new SelectionSet<>(asList("one", "two", "three")).filterIfNecessary(s -> s.contains("w")); + SelectionSet selection = SelectionSet.of(asList("one", "two", "three")) + .filterIfNecessary(s -> s.contains("w")); - assertEquals("two", selection.uniqueResult()); + assertThat(selection.uniqueResult()).hasValue("two"); } @Test // DATACMNS-764 public void ignoresFilterWhenResultIsAlreadyUnique() { - SelectionSet selection = new SelectionSet<>(asList("one")).filterIfNecessary(s -> s.contains("w")); + SelectionSet selection = SelectionSet.of(asList("one")).filterIfNecessary(s -> s.contains("w")); - assertEquals("one", selection.uniqueResult()); + assertThat(selection.uniqueResult()).hasValue("one"); } }