From 2d62c149bb7527a64baa7554e43724a41a5ba98c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 24 Feb 2015 21:08:23 +0100 Subject: [PATCH] DATACMNS-542 - Simplified way to customize repository base class. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RepositoryFactorySupport now exposes a setRepositoryBaseClass(…) to take a custom type that instances will be created reflectively for. This removes the need for a boilerplate repository factory and FactoryBean implementation to hook custom repository base class implementations into the infrastructure. RepositoryFactorySupport now exposes a getTargetRepositoryViaReflection(…) method to allow sub-classes to create repository instances and consider customized repository base classes nonetheless. getTargetRepository(…) needs a tiny signature change which unfortunately requires imple Fixed schema registration for version 1.8 schema. Added new XSD containing a base-class attribute on the element to customize the repository base class via XML. Removed outdated section of how to create a custom base repository class from the reference documentation and replaced it with new simplified instructions. Deprecated usage of factory-class attribute in configuration. Point users to newly introduced way of configuring a base class directly to avoid the need for a boilerplate repository factory and factory bean implementation. DefaultRepositoryInformation now makes target class methods assignable before caching and returning them, so that they can always be invoked reflectively. Made this aspect part of the contract of RepositoryInformation.getTargetClassMethod(…). --- src/main/asciidoc/repositories.adoc | 63 +---- ...notationRepositoryConfigurationSource.java | 14 +- .../config/DefaultRepositoryBaseClass.java | 30 +++ .../DefaultRepositoryConfiguration.java | 11 +- .../RepositoryBeanDefinitionBuilder.java | 8 +- .../config/RepositoryConfiguration.java | 13 +- .../config/RepositoryConfigurationSource.java | 14 +- .../XmlRepositoryConfigurationSource.java | 12 +- .../core/RepositoryInformation.java | 5 +- .../support/DefaultRepositoryInformation.java | 18 +- .../support/RepositoryFactoryBeanSupport.java | 16 +- .../support/RepositoryFactorySupport.java | 50 +++- .../data/util/ReflectionUtils.java | 53 ++++ src/main/resources/META-INF/spring.schemas | 4 +- .../config/spring-repository-1.11.xsd | 240 ++++++++++++++++++ .../repository/config/EnableRepositories.java | 3 + .../core/support/DummyRepositoryFactory.java | 3 +- .../data/util/ReflectionUtilsUnitTests.java | 44 +++- 18 files changed, 522 insertions(+), 79 deletions(-) create mode 100644 src/main/java/org/springframework/data/repository/config/DefaultRepositoryBaseClass.java create mode 100644 src/main/resources/org/springframework/data/repository/config/spring-repository-1.11.xsd diff --git a/src/main/asciidoc/repositories.adoc b/src/main/asciidoc/repositories.adoc index 0a811cb8a..94a184e53 100644 --- a/src/main/asciidoc/repositories.adoc +++ b/src/main/asciidoc/repositories.adoc @@ -528,10 +528,8 @@ The approach just shown works well if your custom implementation uses annotation [[repositories.custom-behaviour-for-all-repositories]] === Adding custom behavior to all repositories -The preceding approach is not feasible when you want to add a single method to all your repository interfaces. +The preceding approach is not feasible when you want to add a single method to all your repository interfaces. To add custom behavior to all repositories, you first add an intermediate interface to declare the shared behavior. -. To add custom behavior to all repositories, you first add an intermediate interface to declare the shared behavior. -+ .An interface declaring custom shared behavior ==== [source, java] @@ -545,10 +543,8 @@ public interface MyRepository ---- ==== -. Now your individual repository interfaces will extend this intermediate interface instead of the `Repository` interface to include the functionality declared. +Now your individual repository interfaces will extend this intermediate interface instead of the `Repository` interface to include the functionality declared. Next, create an implementation of the intermediate interface that extends the persistence technology-specific repository base class. This class will then act as a custom base class for the repository proxies. -. Next, create an implementation of the intermediate interface that extends the persistence technology-specific repository base class. This class will then act as a custom base class for the repository proxies. -+ .Custom repository base class ==== [source, java] @@ -571,62 +567,29 @@ public class MyRepositoryImpl } ---- ==== -+ + The default behavior of the Spring `` namespace is to provide an implementation for all interfaces that fall under the `base-package`. This means that if left in its current state, an implementation instance of `MyRepository` will be created by Spring. This is of course not desired as it is just supposed to act as an intermediary between `Repository` and the actual repository interfaces you want to define for each entity. To exclude an interface that extends `Repository` from being instantiated as a repository instance, you can either annotate it with `@NoRepositoryBean` (as seen above) or move it outside of the configured `base-package`. -. Then create a custom repository factory to replace the default `RepositoryFactoryBean` that will in turn produce a custom `RepositoryFactory`. The new repository factory will then provide your `MyRepositoryImpl` as the implementation of any interfaces that extend the `Repository` interface, replacing the `SimpleJpaRepository` implementation you just extended. -+ -.Custom repository factory bean +The final step is to make the Spring Data infrastructure aware of the customized repository base class. In JavaConfig this is achieved by using the `repositoryBaseClass` attribute of the `@Enable…Repositories` annotation: + +.Configuring a custom repository base class using JavaConfig ==== [source, java] ---- -public class MyRepositoryFactoryBean, T, - I extends Serializable> extends JpaRepositoryFactoryBean { - - protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) { - return new MyRepositoryFactory(em); - } - - private static class MyRepositoryFactory - extends JpaRepositoryFactory { - - private final EntityManager em; - - public MyRepositoryFactory(EntityManager em) { - - super(em); - this.em = em; - } - - protected Object getTargetRepository(RepositoryMetadata metadata) { - return new MyRepositoryImpl((Class) metadata.getDomainClass(), em); - } - - protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { - return MyRepositoryImpl.class; - } - } -} +@Configuration +@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class) +class ApplicationConfiguration { … } ---- ==== -. Finally, either declare beans of the custom factory directly or use the `factory-class` attribute of the Spring namespace or `@Enable…` annotation to instruct the repository infrastructure to use your custom factory implementation. -+ -.Using the custom factory with the namespace +A corresponding attribute is available in the XML namespace. + +.Configuring a custom repository base class using XML ==== [source, xml] ---- ----- -==== -+ -.Using the custom factory with the `@Enable…` annotation -==== -[source, java] ----- -@EnableJpaRepositories(factoryClass = "com.acme.MyRepositoryFactoryBean") -class Config {} + repository-base-class="….MyRepositoryImpl" /> ---- ==== 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 1c96d6beb..4702bd0d3 100644 --- a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -54,6 +54,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura 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 final AnnotationMetadata configMetadata; @@ -213,6 +214,17 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura return attributes.getClass(REPOSITORY_FACTORY_BEAN_CLASS).getName(); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getRepositoryBaseClassName() + */ + @Override + public String getRepositoryBaseClassName() { + + Class repositoryBaseClass = attributes.getClass(REPOSITORY_BASE_CLASS); + return DefaultRepositoryBaseClass.class.equals(repositoryBaseClass) ? null : repositoryBaseClass.getName(); + } + /** * Returns the {@link AnnotationAttributes} of the annotation configured. * diff --git a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryBaseClass.java b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryBaseClass.java new file mode 100644 index 000000000..7e06c9725 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryBaseClass.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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; + +/** + * Placeholder class to be used in {@code @Enable} annotation's {@code repositoryBaseClass} attribute. The configuration + * evaluation infrastructure can use this type to find out no special repository base class was configured and apply + * defaults. + * + * @author Oliver Gierke + * @soundtrack Elen - Sink like a stone (Elen) + * @since 1.11 + */ +public final class DefaultRepositoryBaseClass { + + private DefaultRepositoryBaseClass() {} +} diff --git a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java index f71c7f8ce..bbf858a6e 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 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. @@ -150,6 +150,15 @@ public class DefaultRepositoryConfiguration, private Key queryLookupStrategyKey; private Class repositoryInterface; + private Class repositoryBaseClass; private Object customImplementation; private NamedQueries namedQueries; private MappingContext mappingContext; @@ -74,6 +75,16 @@ public abstract class RepositoryFactoryBeanSupport, this.repositoryInterface = repositoryInterface; } + /** + * Configures the repository base class to be used. + * + * @param repositoryBaseClass the repositoryBaseClass to set, can be {@literal null}. + * @since 1.11 + */ + public void setRepositoryBaseClass(Class repositoryBaseClass) { + this.repositoryBaseClass = repositoryBaseClass; + } + /** * Set the {@link QueryLookupStrategy.Key} to be used. * @@ -218,6 +229,7 @@ public abstract class RepositoryFactoryBeanSupport, this.factory.setNamedQueries(namedQueries); this.factory.setBeanClassLoader(classLoader); this.factory.setEvaluationContextProvider(evaluationContextProvider); + this.factory.setRepositoryBaseClass(repositoryBaseClass); this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface); diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 2de6b3175..99f76d34b 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -15,9 +15,8 @@ */ package org.springframework.data.repository.core.support; -import static org.springframework.util.ReflectionUtils.*; - import java.io.Serializable; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -28,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; @@ -45,6 +45,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.util.ClassUtils; +import org.springframework.data.util.ReflectionUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -63,6 +64,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { private final Map repositoryInformationCache = new HashMap(); private final List postProcessors = new ArrayList(); + private Class repositoryBaseClass; private QueryLookupStrategy.Key queryLookupStrategyKey; private List> queryPostProcessors = new ArrayList>(); private NamedQueries namedQueries = PropertiesBasedNamedQueries.EMPTY; @@ -113,6 +115,17 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { : evaluationContextProvider; } + /** + * Configures the repository base class to use when creating the repository proxy. If not set, the factory will use + * the type returned by {@link #getRepositoryBaseClass(RepositoryMetadata)} by default. + * + * @param repositoryBaseClass the repository base class to back the repository proxy, can be {@literal null}. + * @since 1.11 + */ + public void setRepositoryBaseClass(Class repositoryBaseClass) { + this.repositoryBaseClass = repositoryBaseClass; + } + /** * Adds a {@link QueryCreationListener} to the factory to plug in functionality triggered right after creation of * {@link RepositoryQuery} instances. @@ -214,8 +227,10 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { return repositoryInformation; } - repositoryInformation = new DefaultRepositoryInformation(metadata, getRepositoryBaseClass(metadata), - customImplementationClass); + Class repositoryBaseClass = this.repositoryBaseClass == null ? getRepositoryBaseClass(metadata) + : this.repositoryBaseClass; + + repositoryInformation = new DefaultRepositoryInformation(metadata, repositoryBaseClass, customImplementationClass); repositoryInformationCache.put(cacheKey, repositoryInformation); return repositoryInformation; } @@ -240,7 +255,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { * @param metadata * @return */ - protected abstract Object getTargetRepository(RepositoryMetadata metadata); + protected abstract Object getTargetRepository(RepositoryInformation metadata); /** * Returns the base class backing the actual repository instance. Make sure @@ -296,6 +311,29 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { } + /** + * Creates a repository of the repository base class defined in the given {@link RepositoryInformation} using + * reflection. + * + * @param information + * @param constructorArguments + * @return + */ + @SuppressWarnings("unchecked") + protected final R getTargetRepositoryViaReflection(RepositoryInformation information, + Object... constructorArguments) { + + Class baseClass = information.getRepositoryBaseClass(); + Constructor constructor = ReflectionUtils.findConstructor(baseClass, constructorArguments); + + if (constructor == null) { + throw new IllegalStateException(String.format( + "No suitable constructor found on %s to match the given arguments: %s", baseClass, constructorArguments)); + } + + return (R) BeanUtils.instantiateClass(constructor, constructorArguments); + } + /** * This {@code MethodInterceptor} intercepts calls to methods of the custom implementation and delegates the to it if * configured. Furthermore it resolves method calls to finders and triggers execution of them. You can rely on having @@ -384,8 +422,8 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { Object[] arguments = invocation.getArguments(); if (isCustomMethodInvocation(invocation)) { + Method actualMethod = repositoryInformation.getTargetClassMethod(method); - makeAccessible(actualMethod); return executeMethodOn(customImplementation, actualMethod, arguments); } diff --git a/src/main/java/org/springframework/data/util/ReflectionUtils.java b/src/main/java/org/springframework/data/util/ReflectionUtils.java index 5790d5107..5a28940f0 100644 --- a/src/main/java/org/springframework/data/util/ReflectionUtils.java +++ b/src/main/java/org/springframework/data/util/ReflectionUtils.java @@ -16,6 +16,7 @@ package org.springframework.data.util; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -238,4 +239,56 @@ public abstract class ReflectionUtils { return JAVA8_STREAM_TYPE.isAssignableFrom(type); } + + /** + * Finds a constructoron the given type that matches the given constructor arguments. + * + * @param type must not be {@literal null}. + * @param constructorArguments must not be {@literal null}. + * @return a {@link Constructor} that is compatible with the given arguments or {@literal null} if none found. + */ + public static Constructor findConstructor(Class type, Object... constructorArguments) { + + Assert.notNull(type, "Target type must not be null!"); + Assert.notNull(constructorArguments, "Constructor arguments must not be null!"); + + for (Constructor candidate : type.getDeclaredConstructors()) { + + Class[] parameterTypes = candidate.getParameterTypes(); + + if (argumentsMatch(parameterTypes, constructorArguments)) { + return candidate; + } + } + + return null; + } + + private static final boolean argumentsMatch(Class[] parameterTypes, Object[] arguments) { + + if (parameterTypes.length != arguments.length) { + return false; + } + + int index = 0; + + for (Class argumentType : parameterTypes) { + + Object argument = arguments[index]; + + // Reject nulls for primitives + if (argumentType.isPrimitive() && argument == null) { + return false; + } + + // Type check if argument is not null + if (argument != null && !ClassUtils.isAssignableValue(argumentType, argument)) { + return false; + } + + index++; + } + + return true; + } } diff --git a/src/main/resources/META-INF/spring.schemas b/src/main/resources/META-INF/spring.schemas index 620dd602d..d3797779e 100644 --- a/src/main/resources/META-INF/spring.schemas +++ b/src/main/resources/META-INF/spring.schemas @@ -3,4 +3,6 @@ http\://www.springframework.org/schema/data/repository/spring-repository-1.4.xsd http\://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd=org/springframework/data/repository/config/spring-repository-1.5.xsd http\://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd=org/springframework/data/repository/config/spring-repository-1.6.xsd http\://www.springframework.org/schema/data/repository/spring-repository-1.7.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd -http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd +http\://www.springframework.org/schema/data/repository/spring-repository-1.8.xsd=org/springframework/data/repository/config/spring-repository-1.8.xsd +http\://www.springframework.org/schema/data/repository/spring-repository-1.11.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd +http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd diff --git a/src/main/resources/org/springframework/data/repository/config/spring-repository-1.11.xsd b/src/main/resources/org/springframework/data/repository/config/spring-repository-1.11.xsd new file mode 100644 index 000000000..da183f7c9 --- /dev/null +++ b/src/main/resources/org/springframework/data/repository/config/spring-repository-1.11.xsd @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Where to find the files to read the objects from the repository shall be populated with. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deprecated as of version 1.11. Configure a dedicated base-class instead. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/springframework/data/repository/config/EnableRepositories.java b/src/test/java/org/springframework/data/repository/config/EnableRepositories.java index 88214f621..41798ad26 100644 --- a/src/test/java/org/springframework/data/repository/config/EnableRepositories.java +++ b/src/test/java/org/springframework/data/repository/config/EnableRepositories.java @@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Import; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupportUnitTests.DummyRegistrar; import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean; @@ -41,6 +42,8 @@ public @interface EnableRepositories { Class repositoryFactoryBeanClass() default DummyRepositoryFactoryBean.class; + Class repositoryBaseClass() default PagingAndSortingRepository.class; + String namedQueriesLocation() default ""; String repositoryImplementationPostfix() default "Impl"; diff --git a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java index ee67e46cd..8279df80e 100644 --- a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java +++ b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import org.mockito.Mockito; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupportUnitTests.MyRepositoryQuery; import org.springframework.data.repository.query.QueryLookupStrategy; @@ -67,7 +68,7 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport { * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata) */ @Override - protected Object getTargetRepository(RepositoryMetadata metadata) { + protected Object getTargetRepository(RepositoryInformation information) { return repository; } diff --git a/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java b/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java index b39ef1288..a0cbc89e5 100644 --- a/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2015 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. @@ -18,6 +18,7 @@ package org.springframework.data.util; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import org.junit.Before; @@ -31,11 +32,13 @@ import org.springframework.util.ReflectionUtils.FieldFilter; */ public class ReflectionUtilsUnitTests { + @SuppressWarnings("rawtypes") Constructor constructor; Field reference; @Before public void setUp() throws Exception { this.reference = Sample.class.getField("field"); + this.constructor = ConstructorDetection.class.getConstructor(int.class, String.class); } @Test @@ -84,12 +87,43 @@ public class ReflectionUtilsUnitTests { assertThat(sample.first, is("foo")); } + /** + * @see DATACMNS-542 + */ + @Test + public void detectsConstructorForCompleteMatch() throws Exception { + assertThat(ReflectionUtils.findConstructor(ConstructorDetection.class, 2, "test"), is(constructor)); + } + + /** + * @see DATACMNS-542 + */ + @Test + public void detectsConstructorForMatchWithNulls() throws Exception { + assertThat(ReflectionUtils.findConstructor(ConstructorDetection.class, 2, null), is(constructor)); + } + + /** + * @see DATACMNS-542 + */ + @Test + public void rejectsConstructorIfNumberOfArgumentsDontMatch() throws Exception { + assertThat(ReflectionUtils.findConstructor(ConstructorDetection.class, 2, "test", "test"), is(nullValue())); + } + + /** + * @see DATACMNS-542 + */ + @Test + public void rejectsConstructorForNullForPrimitiveArgument() throws Exception { + assertThat(ReflectionUtils.findConstructor(ConstructorDetection.class, null, "test"), is(nullValue())); + } + static class Sample { public String field; - @Autowired - String first, second; + @Autowired String first, second; } static class FieldNameFieldFilter implements DescribedFieldFilter { @@ -108,4 +142,8 @@ public class ReflectionUtilsUnitTests { return String.format("Filter for fields named %s", name); } } + + static class ConstructorDetection { + public ConstructorDetection(int i, String string) {} + } }