From 049970874dca2b648c9d2f3bfdd01f0e8a11de51 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 27 Jun 2017 08:41:16 +0200 Subject: [PATCH] DATACMNS-1114 - Introduced usage of nullable annotations for API validation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marked all packages with Spring Frameworks @NonNullApi. Added Spring's @Nullable to methods, parameters and fields that take or produce null values. Adapted using code to make sure the IDE can evaluate the null flow properly. Fixed Javadoc in places where an invalid null handling policy was advertised. Strengthened null requirements for types that expose null-instances. Removed null handling from converters for JodaTime and ThreeTenBP. Introduced factory methods Page.empty() and Page.empty(Pageable). Introduced default methods getRequiredGetter(), …Setter() and …Field() on PersistentProperty to allow non-nullable lookups of members. The same for TypeInformation.getrequiredActualType(), …SuperTypeInformation(). Tweaked PersistentPropertyCreator.addPropertiesForRemainingDescriptors() to filter unsuitable PropertyDescriptors before actually trying to create a Property instance from them as the new stronger nullability requirements would cause exceptions downstream. Lazy.get() now expects a non-null return value. Clients being able to cope with null need to call ….orElse(…). Original pull request: #232. --- pom.xml | 1 - .../data/annotation/package-info.java | 3 +- .../data/auditing/AuditingHandler.java | 2 +- .../DefaultAuditableBeanWrapperFactory.java | 7 +- .../AnnotationAuditingConfiguration.java | 11 +- .../AuditingHandlerBeanDefinitionParser.java | 4 + .../data/auditing/config/package-info.java | 5 + .../data/auditing/package-info.java | 3 +- .../data/authentication/UserCredentials.java | 15 ++- .../data/authentication/package-info.java | 3 +- .../data/config/ConfigurationUtils.java | 104 +++++++++++++++++ .../data/config/ParsingUtils.java | 3 +- .../data/config/TypeFilterParser.java | 17 +-- .../data/config/package-info.java | 3 +- .../ClassGeneratingEntityInstantiator.java | 5 +- .../ConfigurableTypeInformationMapper.java | 3 + .../data/convert/CustomConversions.java | 15 ++- .../data/convert/DefaultConverterBuilder.java | 7 +- .../data/convert/DefaultTypeMapper.java | 9 +- .../data/convert/JodaTimeConverters.java | 47 ++++---- .../data/convert/Jsr310Converters.java | 32 ++++-- .../MappingContextTypeInformationMapper.java | 2 + .../convert/SimpleTypeInformationMapper.java | 2 + .../convert/ThreeTenBackPortConverters.java | 32 ++++-- .../data/convert/TypeInformationMapper.java | 7 +- .../data/convert/TypeMapper.java | 4 +- .../data/convert/package-info.java | 3 +- .../data/crossstore/ChangeSet.java | 4 + .../data/crossstore/HashMapChangeSet.java | 12 +- .../data/crossstore/package-info.java | 3 +- .../data/domain/AbstractPageRequest.java | 4 +- .../springframework/data/domain/Chunk.java | 3 +- .../data/domain/ExampleMatcher.java | 16 +-- .../org/springframework/data/domain/Page.java | 22 ++++ .../springframework/data/domain/PageImpl.java | 4 +- .../data/domain/PageRequest.java | 3 +- .../data/domain/SliceImpl.java | 4 +- .../org/springframework/data/domain/Sort.java | 23 ++-- .../data/domain/jaxb/OrderAdapter.java | 24 +++- .../data/domain/jaxb/PageAdapter.java | 7 +- .../data/domain/jaxb/PageableAdapter.java | 19 +++- .../data/domain/jaxb/SortAdapter.java | 8 +- .../data/domain/jaxb/SpringDataJaxb.java | 9 +- .../data/domain/jaxb/package-info.java | 8 +- .../data/domain/package-info.java | 5 +- .../org/springframework/data/geo/Box.java | 3 +- .../org/springframework/data/geo/Circle.java | 38 +------ .../springframework/data/geo/Distance.java | 79 ++++--------- .../org/springframework/data/geo/GeoPage.java | 20 ++-- .../springframework/data/geo/GeoResult.java | 78 +------------ .../springframework/data/geo/GeoResults.java | 56 +++------ .../org/springframework/data/geo/Point.java | 3 +- .../org/springframework/data/geo/Polygon.java | 32 +----- .../data/geo/format/DistanceFormatter.java | 4 +- .../data/geo/format/PointFormatter.java | 6 +- .../data/geo/format/package-info.java | 8 ++ .../data/geo/package-info.java | 3 +- .../data/history/Revision.java | 9 +- .../data/history/RevisionSort.java | 7 +- .../data/history/package-info.java | 4 +- .../springframework/data/mapping/Alias.java | 8 +- .../data/mapping/IdentifierAccessor.java | 3 + .../data/mapping/MappingException.java | 9 +- .../data/mapping/PersistentEntity.java | 7 ++ .../data/mapping/PersistentProperty.java | 45 +++++++- .../mapping/PersistentPropertyAccessor.java | 7 +- .../data/mapping/PreferredConstructor.java | 17 ++- .../data/mapping/PropertyPath.java | 50 +++++++-- .../mapping/PropertyReferenceException.java | 2 + .../context/AbstractMappingContext.java | 35 +++--- .../DefaultPersistentPropertyPath.java | 86 +++++++------- .../InvalidPersistentPropertyPath.java | 5 +- .../data/mapping/context/MappingContext.java | 6 +- .../MappingContextIsNewStrategyFactory.java | 6 +- .../context/PersistentPropertyPath.java | 19 +++- .../data/mapping/context/package-info.java | 3 +- .../model/AbstractPersistentProperty.java | 52 +++------ .../AnnotationBasedPersistentProperty.java | 6 +- .../mapping/model/BasicPersistentEntity.java | 50 ++++++--- .../data/mapping/model/BeanWrapper.java | 31 +++-- ...lassGeneratingPropertyAccessorFactory.java | 19 ++-- .../model/ConvertingPropertyAccessor.java | 8 +- .../model/DefaultSpELExpressionEvaluator.java | 22 ++-- .../model/IdPropertyIdentifierAccessor.java | 2 + .../model/MappingInstantiationException.java | 9 +- .../mapping/model/ParameterValueProvider.java | 2 + ...ersistentEntityParameterValueProvider.java | 46 ++++---- .../model/PreferredConstructorDiscoverer.java | 8 +- .../data/mapping/model/Property.java | 101 ++++++++++++----- .../data/mapping/model/SpELContext.java | 16 +-- .../model/SpELExpressionEvaluator.java | 5 +- .../SpELExpressionParameterValueProvider.java | 37 ++---- .../data/mapping/model/package-info.java | 3 +- .../data/mapping/package-info.java | 3 +- .../data/projection/Accessor.java | 10 +- ...efaultMethodInvokingMethodInterceptor.java | 5 +- .../MapAccessingMethodInterceptor.java | 7 +- .../ProjectingMethodInterceptor.java | 7 +- .../data/projection/ProjectionFactory.java | 16 ++- .../PropertyAccessingMethodInterceptor.java | 4 +- .../projection/ProxyProjectionFactory.java | 9 +- .../SpelAwareProxyProjectionFactory.java | 3 +- .../SpelEvaluatingMethodInterceptor.java | 6 +- .../data/projection/TargetAware.java | 4 +- .../data/projection/package-info.java | 5 + .../springframework/data/querydsl/QSort.java | 1 + .../data/querydsl/QuerydslUtils.java | 3 +- .../querydsl/binding/PathInformation.java | 2 + .../binding/PropertyPathInformation.java | 34 +++--- .../querydsl/binding/QuerydslBindings.java | 20 +++- .../binding/QuerydslPathInformation.java | 11 +- .../binding/QuerydslPredicateBuilder.java | 19 +++- .../data/querydsl/binding/package-info.java | 5 + .../data/querydsl/package-info.java | 3 +- .../repository/cdi/CdiRepositoryBean.java | 32 +++--- .../data/repository/cdi/package-info.java | 2 +- ...notationRepositoryConfigurationSource.java | 18 ++- ...ustomRepositoryImplementationDetector.java | 5 +- .../DefaultRepositoryConfiguration.java | 8 +- .../NamedQueriesBeanDefinitionBuilder.java | 5 +- .../NamedQueriesBeanDefinitionParser.java | 3 + .../RepositoryBeanDefinitionParser.java | 5 +- ...ositoryBeanDefinitionRegistrarSupport.java | 8 +- .../config/RepositoryBeanNameGenerator.java | 28 +++-- .../config/RepositoryComponentProvider.java | 3 + .../config/RepositoryConfiguration.java | 2 + .../RepositoryConfigurationDelegate.java | 5 +- ...positoryConfigurationExtensionSupport.java | 5 +- .../config/RepositoryConfigurationSource.java | 6 +- .../RepositoryConfigurationSourceSupport.java | 1 + .../config/RepositoryConfigurationUtils.java | 8 +- .../RepositoryFragmentConfiguration.java | 3 +- ...positoryPopulatorBeanDefinitionParser.java | 3 + ...SpringDataAnnotationBeanNameGenerator.java | 46 ++++++++ .../XmlRepositoryConfigurationSource.java | 5 +- .../data/repository/config/package-info.java | 2 +- .../data/repository/core/CrudMethods.java | 8 +- .../repository/core/EntityInformation.java | 2 + .../data/repository/core/NamedQueries.java | 12 +- .../core/RepositoryInformation.java | 4 +- .../repository/core/RepositoryMetadata.java | 4 +- .../data/repository/core/package-info.java | 4 +- .../support/DefaultRepositoryMetadata.java | 33 +++--- .../support/DelegatingEntityInformation.java | 6 + ...ublishingRepositoryProxyPostProcessor.java | 47 ++++---- .../repository/core/support/MethodLookup.java | 1 + .../support/PersistableEntityInformation.java | 11 +- .../support/PersistentEntityInformation.java | 2 + .../support/PropertiesBasedNamedQueries.java | 40 ++++--- .../support/QueryExecutionResultHandler.java | 18 +-- .../support/ReflectionEntityInformation.java | 26 +++-- .../support/RepositoryFactoryBeanSupport.java | 5 + .../support/RepositoryFactorySupport.java | 15 ++- .../RepositoryFragmentsFactoryBean.java | 8 +- ...gTransactionDetectorMethodInterceptor.java | 5 +- ...sactionalRepositoryFactoryBeanSupport.java | 19 +++- ...sactionalRepositoryProxyPostProcessor.java | 8 +- .../repository/core/support/package-info.java | 2 +- .../history/RevisionRepository.java | 2 +- .../data/repository/history/package-info.java | 2 +- .../history/support/package-info.java | 2 +- .../init/Jackson2ResourceReader.java | 6 +- .../init/RepositoriesPopulatedEvent.java | 4 +- .../data/repository/package-info.java | 2 +- ...EvaluationContextExtensionInformation.java | 13 +-- ...tensionAwareEvaluationContextProvider.java | 47 ++++---- .../data/repository/query/Parameter.java | 6 +- .../repository/query/QueryLookupStrategy.java | 2 + .../repository/query/ResultProcessor.java | 16 ++- .../data/repository/query/ReturnedType.java | 16 ++- .../data/repository/query/package-info.java | 2 +- .../support/DomainClassConverter.java | 49 +++++--- .../repository/support/MethodParameters.java | 4 +- .../support/ReflectionRepositoryInvoker.java | 53 +++++++-- .../data/repository/support/Repositories.java | 6 +- .../data/repository/support/package-info.java | 3 +- .../data/repository/util/ClassUtils.java | 8 +- .../repository/util/JavaslangCollections.java | 16 ++- .../data/repository/util/NullableWrapper.java | 9 +- .../util/QueryExecutionConverters.java | 23 +++- .../util/ReactiveWrapperConverters.java | 106 ++++++++++++++---- .../repository/util/ReactiveWrappers.java | 1 - .../data/repository/util/VavrCollections.java | 8 +- .../data/repository/util/package-info.java | 2 +- .../data/support/ExampleMatcherAccessor.java | 9 +- .../support/IsNewStrategyFactorySupport.java | 10 +- .../data/support/package-info.java | 3 +- .../data/util/AnnotatedTypeScanner.java | 19 +++- .../AnnotationDetectionFieldCallback.java | 60 ++++++++-- .../AnnotationDetectionMethodCallback.java | 28 ++++- .../DirectFieldAccessFallbackBeanWrapper.java | 4 +- .../util/GenericArrayTypeInformation.java | 3 + .../org/springframework/data/util/Lazy.java | 59 +++++++++- .../util/ParameterizedTypeInformation.java | 39 +++---- .../util/ParentTypeAwareTypeInformation.java | 8 +- .../data/util/ReflectionUtils.java | 53 ++++++++- .../data/util/TypeDiscoverer.java | 23 +++- .../data/util/TypeInformation.java | 59 +++++++++- .../util/TypeVariableTypeInformation.java | 3 +- .../springframework/data/util/Version.java | 12 +- .../data/util/package-info.java | 3 +- ...PageableHandlerMethodArgumentResolver.java | 16 +-- ...eoasSortHandlerMethodArgumentResolver.java | 5 +- ...sonProjectingMethodInterceptorFactory.java | 9 +- .../data/web/MapDataBinder.java | 42 +++++-- ...ParameterAwarePagedResourcesAssembler.java | 6 +- .../data/web/PageableArgumentResolver.java | 10 +- ...PageableHandlerMethodArgumentResolver.java | 29 ++--- .../data/web/PagedResourcesAssembler.java | 10 +- ...gedResourcesAssemblerArgumentResolver.java | 16 ++- ...rojectingJackson2HttpMessageConverter.java | 10 +- .../data/web/SortArgumentResolver.java | 8 +- .../SortHandlerMethodArgumentResolver.java | 16 ++- .../data/web/SpringDataAnnotationUtils.java | 19 +++- .../data/web/XmlBeamHttpMessageConverter.java | 6 +- .../config/SpringDataWebConfiguration.java | 38 ++++++- .../data/web/config/package-info.java | 21 +--- .../data/web/package-info.java | 3 +- .../QuerydslPredicateArgumentResolver.java | 16 ++- .../data/web/querydsl/package-info.java | 4 + .../data/domain/SortUnitTests.java | 25 +++-- .../data/geo/DistanceUnitTests.java | 2 - .../context/PropertyMatchUnitTests.java | 7 -- ...ationBasedPersistentPropertyUnitTests.java | 2 +- .../ProxyProjectionFactoryUnitTests.java | 4 +- ...PersistableEntityInformationUnitTests.java | 8 +- ...eReaderRepositoryInitializerUnitTests.java | 3 + .../CrudRepositoryInvokerUnitTests.java | 5 + .../DomainClassConverterUnitTests.java | 6 +- ...nAndSortingRepositoryInvokerUnitTests.java | 3 + .../ReflectionRepositoryInvokerUnitTests.java | 12 +- ...tationDetectionFieldCallbackUnitTests.java | 8 +- .../data/util/VersionUnitTests.java | 1 - ...andlerMethodArgumentResolverUnitTests.java | 14 ++- 234 files changed, 2272 insertions(+), 1177 deletions(-) create mode 100644 src/main/java/org/springframework/data/auditing/config/package-info.java create mode 100644 src/main/java/org/springframework/data/config/ConfigurationUtils.java create mode 100644 src/main/java/org/springframework/data/geo/format/package-info.java create mode 100644 src/main/java/org/springframework/data/projection/package-info.java create mode 100644 src/main/java/org/springframework/data/querydsl/binding/package-info.java create mode 100644 src/main/java/org/springframework/data/repository/config/SpringDataAnnotationBeanNameGenerator.java create mode 100644 src/main/java/org/springframework/data/web/querydsl/package-info.java diff --git a/pom.xml b/pom.xml index 9841087b1..032112212 100644 --- a/pom.xml +++ b/pom.xml @@ -293,7 +293,6 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.3.1 enforce-rules diff --git a/src/main/java/org/springframework/data/annotation/package-info.java b/src/main/java/org/springframework/data/annotation/package-info.java index 01d4d0313..b22ea5403 100644 --- a/src/main/java/org/springframework/data/annotation/package-info.java +++ b/src/main/java/org/springframework/data/annotation/package-info.java @@ -1,4 +1,5 @@ /** * Core annotations being used by Spring Data. */ -package org.springframework.data.annotation; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.annotation; diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandler.java b/src/main/java/org/springframework/data/auditing/AuditingHandler.java index 8315a3a63..011472794 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandler.java @@ -226,7 +226,7 @@ public class AuditingHandler implements InitializingBean { */ public void afterPropertiesSet() { - if (auditorAware == null) { + if (!auditorAware.isPresent()) { LOGGER.debug("No AuditorAware set! Auditing will not be applied!"); } } diff --git a/src/main/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactory.java b/src/main/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactory.java index a0f50250d..2035f569d 100644 --- a/src/main/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactory.java +++ b/src/main/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactory.java @@ -34,6 +34,7 @@ import org.springframework.data.convert.ThreeTenBackPortConverters; import org.springframework.data.domain.Auditable; import org.springframework.data.util.ReflectionUtils; import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -88,7 +89,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory this.auditable = auditable; this.type = (Class) ResolvableType.forClass(Auditable.class, auditable.getClass()) - .getGeneric(2).getRawClass(); + .getGeneric(2).resolve(TemporalAccessor.class); } /* @@ -183,6 +184,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory * @param source must not be {@literal null}. * @return */ + @Nullable protected Object getDateValueToSet(TemporalAccessor value, Class targetType, Object source) { if (TemporalAccessor.class.equals(targetType)) { @@ -213,6 +215,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory * Returns the given object as {@link Calendar}. * * @param source can be {@literal null}. + * @param target must not be {@literal null}. * @return */ @SuppressWarnings("unchecked") @@ -299,7 +302,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory return getAsTemporalAccessor(metadata.getLastModifiedDateField().map(field -> { Object value = org.springframework.util.ReflectionUtils.getField(field, target); - return Optional.class.isInstance(value) ? ((Optional) value).orElse(null) : value; + return value instanceof Optional ? ((Optional) value).orElse(null) : value; }), TemporalAccessor.class); } diff --git a/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java b/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java index 07f085072..9e4ac109c 100644 --- a/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java +++ b/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.data.auditing.config; import java.lang.annotation.Annotation; +import java.util.Map; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; @@ -30,6 +31,8 @@ import org.springframework.util.Assert; */ public class AnnotationAuditingConfiguration implements AuditingConfiguration { + private static final String MISSING_ANNOTATION_ATTRIBUTES = "Couldn't find annotation attributes for %s in %s!"; + private final AnnotationAttributes attributes; /** @@ -44,7 +47,13 @@ public class AnnotationAuditingConfiguration implements AuditingConfiguration { Assert.notNull(metadata, "AnnotationMetadata must not be null!"); Assert.notNull(annotation, "Annotation must not be null!"); - this.attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName())); + Map attributesSource = metadata.getAnnotationAttributes(annotation.getName()); + + if (attributesSource == null) { + throw new IllegalArgumentException(String.format(MISSING_ANNOTATION_ATTRIBUTES, annotation, metadata)); + } + + this.attributes = new AnnotationAttributes(attributesSource); } /* diff --git a/src/main/java/org/springframework/data/auditing/config/AuditingHandlerBeanDefinitionParser.java b/src/main/java/org/springframework/data/auditing/config/AuditingHandlerBeanDefinitionParser.java index dfdcad44a..e39cffd86 100644 --- a/src/main/java/org/springframework/data/auditing/config/AuditingHandlerBeanDefinitionParser.java +++ b/src/main/java/org/springframework/data/auditing/config/AuditingHandlerBeanDefinitionParser.java @@ -17,6 +17,8 @@ package org.springframework.data.auditing.config; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; +import javax.annotation.Nonnull; + import org.springframework.aop.framework.ProxyFactoryBean; import org.springframework.aop.target.LazyInitTargetSource; import org.springframework.beans.factory.config.BeanDefinition; @@ -51,6 +53,7 @@ public class AuditingHandlerBeanDefinitionParser extends AbstractSingleBeanDefin * * @param mappingContextBeanName must not be {@literal null} or empty. */ + @SuppressWarnings("null") public AuditingHandlerBeanDefinitionParser(String mappingContextBeanName) { Assert.hasText(mappingContextBeanName, "MappingContext bean name must not be null!"); @@ -70,6 +73,7 @@ public class AuditingHandlerBeanDefinitionParser extends AbstractSingleBeanDefin * (non-Javadoc) * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClass(org.w3c.dom.Element) */ + @Nonnull @Override protected Class getBeanClass(Element element) { return AuditingHandler.class; diff --git a/src/main/java/org/springframework/data/auditing/config/package-info.java b/src/main/java/org/springframework/data/auditing/config/package-info.java new file mode 100644 index 000000000..96a220882 --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/config/package-info.java @@ -0,0 +1,5 @@ +/** + * Types to abstract authentication concepts. + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.auditing.config; diff --git a/src/main/java/org/springframework/data/auditing/package-info.java b/src/main/java/org/springframework/data/auditing/package-info.java index bc958496c..07a883192 100644 --- a/src/main/java/org/springframework/data/auditing/package-info.java +++ b/src/main/java/org/springframework/data/auditing/package-info.java @@ -1,4 +1,5 @@ /** * General support for entity auditing. */ -package org.springframework.data.auditing; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.auditing; diff --git a/src/main/java/org/springframework/data/authentication/UserCredentials.java b/src/main/java/org/springframework/data/authentication/UserCredentials.java index 63ab1a674..9d70f7f39 100644 --- a/src/main/java/org/springframework/data/authentication/UserCredentials.java +++ b/src/main/java/org/springframework/data/authentication/UserCredentials.java @@ -15,6 +15,7 @@ */ package org.springframework.data.authentication; +import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -28,8 +29,7 @@ public class UserCredentials { public static final UserCredentials NO_CREDENTIALS = new UserCredentials(null, null); - private final String username; - private final String password; + private final @Nullable String username, password; /** * Creates a new {@link UserCredentials} instance from the given username and password. Empty {@link String}s provided @@ -38,7 +38,7 @@ public class UserCredentials { * @param username * @param password */ - public UserCredentials(String username, String password) { + public UserCredentials(@Nullable String username, @Nullable String password) { this.username = StringUtils.hasText(username) ? username : null; this.password = StringUtils.hasText(password) ? password : null; } @@ -48,6 +48,7 @@ public class UserCredentials { * * @return the username */ + @Nullable public String getUsername() { return username; } @@ -57,6 +58,7 @@ public class UserCredentials { * * @return the password */ + @Nullable public String getPassword() { return password; } @@ -85,9 +87,12 @@ public class UserCredentials { * * @return the obfuscated password */ + @Nullable public String getObfuscatedPassword() { - if (!hasPassword()) { + String password = this.password; + + if (password == null) { return null; } @@ -125,7 +130,7 @@ public class UserCredentials { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; diff --git a/src/main/java/org/springframework/data/authentication/package-info.java b/src/main/java/org/springframework/data/authentication/package-info.java index 46a1ee551..32ec325ef 100644 --- a/src/main/java/org/springframework/data/authentication/package-info.java +++ b/src/main/java/org/springframework/data/authentication/package-info.java @@ -1,4 +1,5 @@ /** * Types to abstract authentication concepts. */ -package org.springframework.data.authentication; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.authentication; diff --git a/src/main/java/org/springframework/data/config/ConfigurationUtils.java b/src/main/java/org/springframework/data/config/ConfigurationUtils.java new file mode 100644 index 000000000..5aac1d538 --- /dev/null +++ b/src/main/java/org/springframework/data/config/ConfigurationUtils.java @@ -0,0 +1,104 @@ +/* + * 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.config; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.xml.XmlReaderContext; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; + +/** + * Helper class to centralize common functionality that needs to be used in various places of the configuration + * implementation. + * + * @author Oliver Gierke + * @since 2.0 + * @soundtrack Richard Spaven - The Self (feat. Jordan Rakei) + */ +public interface ConfigurationUtils { + + /** + * Returns the {@link ResourceLoader} from the given {@link XmlReaderContext}. + * + * @param context must not be {@literal null}. + * @return + * @throws IllegalArgumentException if no {@link ResourceLoader} can be obtained from the {@link XmlReaderContext}. + */ + public static ResourceLoader getRequiredResourceLoader(XmlReaderContext context) { + + Assert.notNull(context, "XmlReaderContext must not be null!"); + + ResourceLoader resourceLoader = context.getResourceLoader(); + + if (resourceLoader == null) { + throw new IllegalArgumentException("Could not obtain ResourceLoader from XmlReaderContext!"); + } + + return resourceLoader; + } + + /** + * Returns the {@link ClassLoader} used by the given {@link XmlReaderContext}. + * + * @param context must not be {@literal null}. + * @return + * @throws IllegalArgumentException if no {@link ClassLoader} can be obtained from the given {@link XmlReaderContext}. + */ + public static ClassLoader getRequiredClassLoader(XmlReaderContext context) { + return getRequiredClassLoader(getRequiredResourceLoader(context)); + } + + /** + * Returns the {@link ClassLoader} used by the given {@link ResourceLoader}. + * + * @param resourceLoader must not be {@literal null}. + * @return + * @throws IllegalArgumentException if the given {@link ResourceLoader} does not expose a {@link ClassLoader}. + */ + public static ClassLoader getRequiredClassLoader(ResourceLoader resourceLoader) { + + Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); + + ClassLoader classLoader = resourceLoader.getClassLoader(); + + if (classLoader == null) { + throw new IllegalArgumentException("Could not obtain ClassLoader from ResourceLoader!"); + } + + return classLoader; + } + + /** + * Returns the bean class name of the given {@link BeanDefinition}. + * + * @param beanDefinition must not be {@literal null}. + * @return + * @throws IllegalArgumentException if the given {@link BeanDefinition} does not contain a bean class name. + */ + public static String getRequiredBeanClassName(BeanDefinition beanDefinition) { + + Assert.notNull(beanDefinition, "BeanDefinition must not be null!"); + + String result = beanDefinition.getBeanClassName(); + + if (result == null) { + throw new IllegalArgumentException( + String.format("Could not obtain required bean class name from BeanDefinition!", beanDefinition)); + } + + return result; + } +} diff --git a/src/main/java/org/springframework/data/config/ParsingUtils.java b/src/main/java/org/springframework/data/config/ParsingUtils.java index ac729de4a..66cd6a99b 100644 --- a/src/main/java/org/springframework/data/config/ParsingUtils.java +++ b/src/main/java/org/springframework/data/config/ParsingUtils.java @@ -21,6 +21,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -120,7 +121,7 @@ public abstract class ParsingUtils { * @param source * @return */ - public static AbstractBeanDefinition getSourceBeanDefinition(BeanDefinitionBuilder builder, Object source) { + public static AbstractBeanDefinition getSourceBeanDefinition(BeanDefinitionBuilder builder, @Nullable Object source) { Assert.notNull(builder, "Builder must not be null!"); diff --git a/src/main/java/org/springframework/data/config/TypeFilterParser.java b/src/main/java/org/springframework/data/config/TypeFilterParser.java index dde63897d..c1c765a40 100644 --- a/src/main/java/org/springframework/data/config/TypeFilterParser.java +++ b/src/main/java/org/springframework/data/config/TypeFilterParser.java @@ -30,6 +30,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.lang.Nullable; import org.springframework.util.Assert; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -55,7 +56,7 @@ public class TypeFilterParser { * @param readerContext must not be {@literal null}. */ public TypeFilterParser(XmlReaderContext readerContext) { - this(readerContext, readerContext.getResourceLoader().getClassLoader()); + this(readerContext, ConfigurationUtils.getRequiredClassLoader(readerContext)); } /** @@ -92,13 +93,14 @@ public class TypeFilterParser { Node node = nodeList.item(i); Element childElement = type.getElement(node); - if (childElement != null) { + if (childElement == null) { + continue; + } - try { - filters.add(createTypeFilter(childElement, classLoader)); - } catch (RuntimeException e) { - readerContext.error(e.getMessage(), readerContext.extractSource(element), e.getCause()); - } + try { + filters.add(createTypeFilter(childElement, classLoader)); + } catch (RuntimeException e) { + readerContext.error(e.getMessage(), readerContext.extractSource(element), e.getCause()); } } @@ -224,6 +226,7 @@ public class TypeFilterParser { * @param node * @return */ + @Nullable Element getElement(Node node) { if (node.getNodeType() == Node.ELEMENT_NODE) { diff --git a/src/main/java/org/springframework/data/config/package-info.java b/src/main/java/org/springframework/data/config/package-info.java index efe985649..1db339d5b 100644 --- a/src/main/java/org/springframework/data/config/package-info.java +++ b/src/main/java/org/springframework/data/config/package-info.java @@ -1,4 +1,5 @@ /** * Basic support for creating custom Spring namespaces and JavaConfig. */ -package org.springframework.data.config; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.config; diff --git a/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java index 4f6ed2e95..deed67e84 100644 --- a/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java @@ -38,6 +38,7 @@ import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.model.MappingInstantiationException; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -213,7 +214,7 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator { * @return */ private

, T> Object[] extractInvocationArguments( - PreferredConstructor constructor, ParameterValueProvider

provider) { + @Nullable PreferredConstructor constructor, ParameterValueProvider

provider) { if (provider == null || constructor == null || !constructor.hasParameters()) { return EMPTY_ARRAY; @@ -484,7 +485,7 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator { */ private class ByteArrayClassLoader extends ClassLoader { - public ByteArrayClassLoader(ClassLoader parent) { + public ByteArrayClassLoader(@Nullable ClassLoader parent) { super(parent); } diff --git a/src/main/java/org/springframework/data/convert/ConfigurableTypeInformationMapper.java b/src/main/java/org/springframework/data/convert/ConfigurableTypeInformationMapper.java index c8270115b..860d07dc5 100644 --- a/src/main/java/org/springframework/data/convert/ConfigurableTypeInformationMapper.java +++ b/src/main/java/org/springframework/data/convert/ConfigurableTypeInformationMapper.java @@ -19,6 +19,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import javax.annotation.Nullable; + import org.springframework.data.mapping.Alias; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; @@ -77,6 +79,7 @@ public class ConfigurableTypeInformationMapper implements TypeInformationMapper * (non-Javadoc) * @see org.springframework.data.convert.TypeInformationMapper#resolveTypeFrom(org.springframework.data.mapping.Alias) */ + @Nullable @Override public TypeInformation resolveTypeFrom(Alias alias) { return aliasToType.get(alias); diff --git a/src/main/java/org/springframework/data/convert/CustomConversions.java b/src/main/java/org/springframework/data/convert/CustomConversions.java index 652dce516..d2edadd4b 100644 --- a/src/main/java/org/springframework/data/convert/CustomConversions.java +++ b/src/main/java/org/springframework/data/convert/CustomConversions.java @@ -561,8 +561,11 @@ public class CustomConversions { } else if (converter instanceof GenericConverter) { - return Streamable.of(GenericConverter.class.cast(converter).getConvertibleTypes())// - .map(it -> register(it, isReading, isWriting)); + Set convertibleTypes = GenericConverter.class.cast(converter).getConvertibleTypes(); + + return convertibleTypes == null // + ? Streamable.empty() // + : Streamable.of(convertibleTypes).map(it -> register(it, isReading, isWriting)); } else if (converter instanceof ConverterFactory) { @@ -580,7 +583,13 @@ public class CustomConversions { private Streamable getRegistrationFor(Object converter, Class type, boolean isReading, boolean isWriting) { - Class[] arguments = GenericTypeResolver.resolveTypeArguments(converter.getClass(), type); + Class converterType = converter.getClass(); + Class[] arguments = GenericTypeResolver.resolveTypeArguments(converterType, type); + + if (arguments == null) { + throw new IllegalStateException(String.format("Couldn't resolve type arguments for %s!", converterType)); + } + return Streamable.of(register(arguments[0], arguments[1], isReading, isWriting)); } diff --git a/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java b/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java index ddbfbd3ce..88a1e8611 100644 --- a/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java +++ b/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java @@ -27,6 +27,8 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nonnull; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -35,6 +37,7 @@ import org.springframework.data.convert.ConverterBuilder.ConverterAware; import org.springframework.data.convert.ConverterBuilder.ReadingConverterBuilder; import org.springframework.data.convert.ConverterBuilder.WritingConverterBuilder; import org.springframework.data.util.Optionals; +import org.springframework.lang.Nullable; /** * Builder to easily set up (bi-directional) {@link Converter} instances for Spring Data type mapping using Lambdas. Use @@ -128,9 +131,10 @@ class DefaultConverterBuilder * (non-Javadoc) * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) */ + @Nullable @Override @SuppressWarnings("unchecked") - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return function.apply((S) source); } @@ -138,6 +142,7 @@ class DefaultConverterBuilder * (non-Javadoc) * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() */ + @Nonnull @Override public Set getConvertibleTypes() { return Collections.singleton(convertiblePair); diff --git a/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java b/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java index b10e62035..c4c9ef7d3 100644 --- a/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java +++ b/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java @@ -28,6 +28,7 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -77,7 +78,7 @@ public class DefaultTypeMapper implements TypeMapper { * @param additionalMappers must not be {@literal null}. */ public DefaultTypeMapper(TypeAliasAccessor accessor, - MappingContext, ?> mappingContext, + @Nullable MappingContext, ?> mappingContext, List additionalMappers) { Assert.notNull(accessor, "Accessor must not be null!"); @@ -109,6 +110,7 @@ public class DefaultTypeMapper implements TypeMapper { * (non-Javadoc) * @see org.springframework.data.convert.TypeMapper#readType(java.lang.Object) */ + @Nullable public TypeInformation readType(S source) { Assert.notNull(source, "Source object must not be null!"); @@ -123,6 +125,7 @@ public class DefaultTypeMapper implements TypeMapper { * @param alias * @return */ + @Nullable private TypeInformation getFromCacheOrCreate(Alias alias) { return typeCache.computeIfAbsent(alias, getAlias).orElse(null); } @@ -153,7 +156,7 @@ public class DefaultTypeMapper implements TypeMapper { ClassTypeInformation targetType = ClassTypeInformation.from(documentsTargetType); - return (TypeInformation) (basicType != null ? basicType.specialize(targetType) : targetType); + return (TypeInformation) basicType.specialize(targetType); } /** @@ -163,6 +166,7 @@ public class DefaultTypeMapper implements TypeMapper { * @param source * @return */ + @Nullable private Class getDefaultedTypeToBeUsed(S source) { TypeInformation documentsTargetTypeInformation = readType(source); @@ -177,6 +181,7 @@ public class DefaultTypeMapper implements TypeMapper { * @param source will never be {@literal null}. * @return */ + @Nullable protected TypeInformation getFallbackTypeFor(S source) { return null; } diff --git a/src/main/java/org/springframework/data/convert/JodaTimeConverters.java b/src/main/java/org/springframework/data/convert/JodaTimeConverters.java index a973f9a45..e9b1e4de6 100644 --- a/src/main/java/org/springframework/data/convert/JodaTimeConverters.java +++ b/src/main/java/org/springframework/data/convert/JodaTimeConverters.java @@ -22,6 +22,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import javax.annotation.Nonnull; + import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; @@ -72,9 +74,10 @@ public abstract class JodaTimeConverters { INSTANCE; + @Nonnull @Override public java.time.LocalDateTime convert(LocalDateTime source) { - return source == null ? null : java.time.LocalDateTime.ofInstant(source.toDate().toInstant(), ZoneId.of("UTC")); + return java.time.LocalDateTime.ofInstant(source.toDate().toInstant(), ZoneId.of("UTC")); } } @@ -82,8 +85,10 @@ public abstract class JodaTimeConverters { INSTANCE; + @Nonnull + @Override public Date convert(LocalDate source) { - return source == null ? null : source.toDate(); + return source.toDate(); } } @@ -91,8 +96,10 @@ public abstract class JodaTimeConverters { INSTANCE; + @Nonnull + @Override public Date convert(LocalDateTime source) { - return source == null ? null : source.toDate(); + return source.toDate(); } } @@ -100,8 +107,10 @@ public abstract class JodaTimeConverters { INSTANCE; + @Nonnull + @Override public Date convert(DateTime source) { - return source == null ? null : source.toDate(); + return source.toDate(); } } @@ -109,8 +118,10 @@ public abstract class JodaTimeConverters { INSTANCE; + @Nonnull + @Override public LocalDate convert(Date source) { - return source == null ? null : new LocalDate(source.getTime()); + return new LocalDate(source.getTime()); } } @@ -118,8 +129,10 @@ public abstract class JodaTimeConverters { INSTANCE; + @Nonnull + @Override public LocalDateTime convert(Date source) { - return source == null ? null : new LocalDateTime(source.getTime()); + return new LocalDateTime(source.getTime()); } } @@ -127,8 +140,10 @@ public abstract class JodaTimeConverters { INSTANCE; + @Nonnull + @Override public DateTime convert(Date source) { - return source == null ? null : new DateTime(source.getTime()); + return new DateTime(source.getTime()); } } @@ -136,15 +151,10 @@ public abstract class JodaTimeConverters { INSTANCE; - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) - */ + @Nonnull @Override public LocalDateTime convert(java.time.LocalDateTime source) { - return source == null ? null - : LocalDateTime.fromDateFields( - org.springframework.data.convert.Jsr310Converters.LocalDateTimeToDateConverter.INSTANCE.convert(source)); + return LocalDateTime.fromDateFields(Jsr310Converters.LocalDateTimeToDateConverter.INSTANCE.convert(source)); } } @@ -152,15 +162,10 @@ public abstract class JodaTimeConverters { INSTANCE; - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) - */ + @Nonnull @Override public DateTime convert(java.time.LocalDateTime source) { - return source == null ? null - : new DateTime( - org.springframework.data.convert.Jsr310Converters.LocalDateTimeToDateConverter.INSTANCE.convert(source)); + return new DateTime(Jsr310Converters.LocalDateTimeToDateConverter.INSTANCE.convert(source)); } } } diff --git a/src/main/java/org/springframework/data/convert/Jsr310Converters.java b/src/main/java/org/springframework/data/convert/Jsr310Converters.java index 53e3989a7..46f30845c 100644 --- a/src/main/java/org/springframework/data/convert/Jsr310Converters.java +++ b/src/main/java/org/springframework/data/convert/Jsr310Converters.java @@ -33,6 +33,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import javax.annotation.Nonnull; + import org.springframework.core.convert.converter.Converter; import org.springframework.util.ClassUtils; @@ -92,9 +94,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public LocalDateTime convert(Date source) { - return source == null ? null : ofInstant(source.toInstant(), systemDefault()); + return ofInstant(source.toInstant(), systemDefault()); } } @@ -102,9 +105,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public Date convert(LocalDateTime source) { - return source == null ? null : Date.from(source.atZone(systemDefault()).toInstant()); + return Date.from(source.atZone(systemDefault()).toInstant()); } } @@ -112,9 +116,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public LocalDate convert(Date source) { - return source == null ? null : ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalDate(); + return ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalDate(); } } @@ -122,9 +127,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public Date convert(LocalDate source) { - return source == null ? null : Date.from(source.atStartOfDay(systemDefault()).toInstant()); + return Date.from(source.atStartOfDay(systemDefault()).toInstant()); } } @@ -132,9 +138,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public LocalTime convert(Date source) { - return source == null ? null : ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalTime(); + return ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalTime(); } } @@ -142,9 +149,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public Date convert(LocalTime source) { - return source == null ? null : Date.from(source.atDate(LocalDate.now()).atZone(systemDefault()).toInstant()); + return Date.from(source.atDate(LocalDate.now()).atZone(systemDefault()).toInstant()); } } @@ -152,9 +160,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public Instant convert(Date source) { - return source == null ? null : source.toInstant(); + return source.toInstant(); } } @@ -162,9 +171,10 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public Date convert(Instant source) { - return source == null ? null : Date.from(source.atZone(systemDefault()).toInstant()); + return Date.from(source.atZone(systemDefault()).toInstant()); } } @@ -173,6 +183,7 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public String convert(ZoneId source) { return source.toString(); @@ -184,6 +195,7 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public ZoneId convert(String source) { return ZoneId.of(source); @@ -195,6 +207,7 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public String convert(Duration duration) { return duration.toString(); @@ -206,6 +219,7 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public Duration convert(String s) { return Duration.parse(s); @@ -217,6 +231,7 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public String convert(Period period) { return period.toString(); @@ -228,6 +243,7 @@ public abstract class Jsr310Converters { INSTANCE; + @Nonnull @Override public Period convert(String s) { return Period.parse(s); diff --git a/src/main/java/org/springframework/data/convert/MappingContextTypeInformationMapper.java b/src/main/java/org/springframework/data/convert/MappingContextTypeInformationMapper.java index ca6a6ff70..6f0436def 100644 --- a/src/main/java/org/springframework/data/convert/MappingContextTypeInformationMapper.java +++ b/src/main/java/org/springframework/data/convert/MappingContextTypeInformationMapper.java @@ -24,6 +24,7 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -115,6 +116,7 @@ public class MappingContextTypeInformationMapper implements TypeInformationMappe * (non-Javadoc) * @see org.springframework.data.convert.TypeInformationMapper#resolveTypeFrom(java.util.Optional) */ + @Nullable @Override public TypeInformation resolveTypeFrom(Alias alias) { diff --git a/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java b/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java index f7504f96c..0d0bcf065 100644 --- a/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java +++ b/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java @@ -22,6 +22,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.springframework.data.mapping.Alias; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** @@ -45,6 +46,7 @@ public class SimpleTypeInformationMapper implements TypeInformationMapper { * @return the type to be used for the given {@link String} representation or {@literal null} if nothing found or the * class cannot be loaded. */ + @Nullable @Override public TypeInformation resolveTypeFrom(Alias alias) { diff --git a/src/main/java/org/springframework/data/convert/ThreeTenBackPortConverters.java b/src/main/java/org/springframework/data/convert/ThreeTenBackPortConverters.java index 1bceef7e8..8deb684c8 100644 --- a/src/main/java/org/springframework/data/convert/ThreeTenBackPortConverters.java +++ b/src/main/java/org/springframework/data/convert/ThreeTenBackPortConverters.java @@ -27,6 +27,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import javax.annotation.Nonnull; + import org.springframework.core.convert.converter.Converter; import org.springframework.util.ClassUtils; import org.threeten.bp.Instant; @@ -95,12 +97,13 @@ public abstract class ThreeTenBackPortConverters { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nonnull @Override public java.time.LocalDateTime convert(LocalDateTime source) { Date date = toDate(source.atZone(ZoneId.systemDefault()).toInstant()); - return source == null ? null : Jsr310Converters.DateToLocalDateTimeConverter.INSTANCE.convert(date); + return Jsr310Converters.DateToLocalDateTimeConverter.INSTANCE.convert(date); } } @@ -108,10 +111,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public LocalDateTime convert(Date source) { - - return source == null ? null : ofInstant(toInstant(source), systemDefault()); + return ofInstant(toInstant(source), systemDefault()); } } @@ -119,9 +122,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public Date convert(LocalDateTime source) { - return source == null ? null : toDate(source.atZone(systemDefault()).toInstant()); + return toDate(source.atZone(systemDefault()).toInstant()); } } @@ -129,9 +133,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public LocalDate convert(Date source) { - return source == null ? null : ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalDate(); + return ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalDate(); } } @@ -139,9 +144,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public Date convert(LocalDate source) { - return source == null ? null : toDate(source.atStartOfDay(systemDefault()).toInstant()); + return toDate(source.atStartOfDay(systemDefault()).toInstant()); } } @@ -149,9 +155,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public LocalTime convert(Date source) { - return source == null ? null : ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalTime(); + return ofInstant(ofEpochMilli(source.getTime()), systemDefault()).toLocalTime(); } } @@ -159,9 +166,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public Date convert(LocalTime source) { - return source == null ? null : toDate(source.atDate(LocalDate.now()).atZone(systemDefault()).toInstant()); + return toDate(source.atDate(LocalDate.now()).atZone(systemDefault()).toInstant()); } } @@ -169,9 +177,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public Instant convert(Date source) { - return source == null ? null : toInstant(source); + return toInstant(source); } } @@ -179,9 +188,10 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public Date convert(Instant source) { - return source == null ? null : toDate(source.atZone(systemDefault()).toInstant()); + return toDate(source.atZone(systemDefault()).toInstant()); } } @@ -190,6 +200,7 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public String convert(ZoneId source) { return source.toString(); @@ -201,6 +212,7 @@ public abstract class ThreeTenBackPortConverters { INSTANCE; + @Nonnull @Override public ZoneId convert(String source) { return ZoneId.of(source); diff --git a/src/main/java/org/springframework/data/convert/TypeInformationMapper.java b/src/main/java/org/springframework/data/convert/TypeInformationMapper.java index fd8bcdbe4..28a86fbea 100644 --- a/src/main/java/org/springframework/data/convert/TypeInformationMapper.java +++ b/src/main/java/org/springframework/data/convert/TypeInformationMapper.java @@ -15,6 +15,8 @@ */ package org.springframework.data.convert; +import javax.annotation.Nullable; + import org.springframework.data.mapping.Alias; import org.springframework.data.util.TypeInformation; @@ -28,15 +30,16 @@ public interface TypeInformationMapper { /** * Returns the actual {@link TypeInformation} to be used for the given alias. * - * @param alias can be {@literal null}. + * @param alias must not be {@literal null}. * @return */ + @Nullable TypeInformation resolveTypeFrom(Alias alias); /** * Returns the alias to be used for the given {@link TypeInformation}. * - * @param type + * @param type must not be {@literal null}. * @return */ Alias createAliasFor(TypeInformation type); diff --git a/src/main/java/org/springframework/data/convert/TypeMapper.java b/src/main/java/org/springframework/data/convert/TypeMapper.java index 16b15dc8e..6a53327bd 100644 --- a/src/main/java/org/springframework/data/convert/TypeMapper.java +++ b/src/main/java/org/springframework/data/convert/TypeMapper.java @@ -16,6 +16,7 @@ package org.springframework.data.convert; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** * Interface to define strategies how to store type information in a store specific sink or source. @@ -30,13 +31,14 @@ public interface TypeMapper { * @param source must not be {@literal null}. * @return */ + @Nullable TypeInformation readType(S source); /** * Returns the {@link TypeInformation} from the given source if it is a more concrete type than the given default one. * * @param source must not be {@literal null}. - * @param defaultType + * @param defaultType must not be {@literal null}. * @return */ TypeInformation readType(S source, TypeInformation defaultType); diff --git a/src/main/java/org/springframework/data/convert/package-info.java b/src/main/java/org/springframework/data/convert/package-info.java index 9868f2b3f..52f5d4b16 100644 --- a/src/main/java/org/springframework/data/convert/package-info.java +++ b/src/main/java/org/springframework/data/convert/package-info.java @@ -3,4 +3,5 @@ * * @see org.springframework.data.convert.EntityConverter */ -package org.springframework.data.convert; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.convert; diff --git a/src/main/java/org/springframework/data/crossstore/ChangeSet.java b/src/main/java/org/springframework/data/crossstore/ChangeSet.java index c67d1c66d..1e55459fa 100644 --- a/src/main/java/org/springframework/data/crossstore/ChangeSet.java +++ b/src/main/java/org/springframework/data/crossstore/ChangeSet.java @@ -17,6 +17,8 @@ package org.springframework.data.crossstore; import java.util.Map; +import javax.annotation.Nullable; + import org.springframework.core.convert.ConversionService; /** @@ -27,12 +29,14 @@ import org.springframework.core.convert.ConversionService; */ public interface ChangeSet { + @Nullable T get(String key, Class requiredClass, ConversionService cs); void set(String key, Object o); Map getValues(); + @Nullable Object removeProperty(String k); } diff --git a/src/main/java/org/springframework/data/crossstore/HashMapChangeSet.java b/src/main/java/org/springframework/data/crossstore/HashMapChangeSet.java index 5f294de8c..3b22141d7 100644 --- a/src/main/java/org/springframework/data/crossstore/HashMapChangeSet.java +++ b/src/main/java/org/springframework/data/crossstore/HashMapChangeSet.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; import org.springframework.core.convert.ConversionService; +import org.springframework.lang.Nullable; /** * Simple ChangeSet implementation backed by a HashMap. @@ -52,12 +53,21 @@ public class HashMapChangeSet implements ChangeSet { return Collections.unmodifiableMap(values); } + @Nullable public Object removeProperty(String k) { return this.values.remove(k); } + @Nullable public T get(String key, Class requiredClass, ConversionService conversionService) { - return conversionService.convert(values.get(key), requiredClass); + + Object value = values.get(key); + + if (value == null) { + return null; + } + + return conversionService.convert(value, requiredClass); } } diff --git a/src/main/java/org/springframework/data/crossstore/package-info.java b/src/main/java/org/springframework/data/crossstore/package-info.java index f874dbaeb..824d8bb8f 100644 --- a/src/main/java/org/springframework/data/crossstore/package-info.java +++ b/src/main/java/org/springframework/data/crossstore/package-info.java @@ -1,4 +1,5 @@ /** * Support for cross-store persistence. */ -package org.springframework.data.crossstore; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.crossstore; diff --git a/src/main/java/org/springframework/data/domain/AbstractPageRequest.java b/src/main/java/org/springframework/data/domain/AbstractPageRequest.java index 5cba51c0d..fdd37b0fb 100644 --- a/src/main/java/org/springframework/data/domain/AbstractPageRequest.java +++ b/src/main/java/org/springframework/data/domain/AbstractPageRequest.java @@ -17,6 +17,8 @@ package org.springframework.data.domain; import java.io.Serializable; +import org.springframework.lang.Nullable; + /** * Abstract Java Bean implementation of {@code Pageable}. * @@ -131,7 +133,7 @@ public abstract class AbstractPageRequest implements Pageable, Serializable { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/domain/Chunk.java b/src/main/java/org/springframework/data/domain/Chunk.java index 83436ea99..42d9ce5a4 100644 --- a/src/main/java/org/springframework/data/domain/Chunk.java +++ b/src/main/java/org/springframework/data/domain/Chunk.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -171,7 +172,7 @@ abstract class Chunk implements Slice, Serializable { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/domain/ExampleMatcher.java b/src/main/java/org/springframework/data/domain/ExampleMatcher.java index f61e96a14..609d252c9 100644 --- a/src/main/java/org/springframework/data/domain/ExampleMatcher.java +++ b/src/main/java/org/springframework/data/domain/ExampleMatcher.java @@ -32,6 +32,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -201,9 +202,7 @@ public class ExampleMatcher { propertySpecifier = propertySpecifier.withStringMatcher(genericPropertyMatcher.stringMatcher); } - if (genericPropertyMatcher.valueTransformer != null) { - propertySpecifier = propertySpecifier.withValueTransformer(genericPropertyMatcher.valueTransformer); - } + propertySpecifier = propertySpecifier.withValueTransformer(genericPropertyMatcher.valueTransformer); propertySpecifiers.add(propertySpecifier); @@ -394,8 +393,8 @@ public class ExampleMatcher { @EqualsAndHashCode public static class GenericPropertyMatcher { - StringMatcher stringMatcher = null; - Boolean ignoreCase = null; + @Nullable StringMatcher stringMatcher = null; + @Nullable Boolean ignoreCase = null; PropertyValueTransformer valueTransformer = NoOpPropertyValueTransformer.INSTANCE; /** @@ -697,6 +696,7 @@ public class ExampleMatcher { * @see java.util.function.Function#apply(java.lang.Object) */ @Override + @SuppressWarnings("null") public Optional apply(Optional source) { return source; } @@ -715,8 +715,8 @@ public class ExampleMatcher { public static class PropertySpecifier { String path; - StringMatcher stringMatcher; - Boolean ignoreCase; + @Nullable StringMatcher stringMatcher; + @Nullable Boolean ignoreCase; PropertyValueTransformer valueTransformer; /** @@ -785,6 +785,7 @@ public class ExampleMatcher { * * @return can be {@literal null}. */ + @Nullable public StringMatcher getStringMatcher() { return stringMatcher; } @@ -792,6 +793,7 @@ public class ExampleMatcher { /** * @return {@literal null} if not set. */ + @Nullable public Boolean getIgnoreCase() { return ignoreCase; } diff --git a/src/main/java/org/springframework/data/domain/Page.java b/src/main/java/org/springframework/data/domain/Page.java index 589a0ca15..cb5faa707 100644 --- a/src/main/java/org/springframework/data/domain/Page.java +++ b/src/main/java/org/springframework/data/domain/Page.java @@ -15,6 +15,7 @@ */ package org.springframework.data.domain; +import java.util.Collections; import java.util.function.Function; /** @@ -26,6 +27,27 @@ import java.util.function.Function; */ public interface Page extends Slice { + /** + * Creates a new empty {@link Page}. + * + * @return + * @since 2.0 + */ + static Page empty() { + return empty(Pageable.unpaged()); + } + + /** + * Creates a new empty {@link Page} for the given {@link Pageable}. + * + * @param pageable must not be {@literal null}. + * @return + * @since 2.0 + */ + static Page empty(Pageable pageable) { + return new PageImpl<>(Collections.emptyList(), pageable, 0); + } + /** * Returns the number of total pages. * diff --git a/src/main/java/org/springframework/data/domain/PageImpl.java b/src/main/java/org/springframework/data/domain/PageImpl.java index 9c1970df9..b57b21ce1 100644 --- a/src/main/java/org/springframework/data/domain/PageImpl.java +++ b/src/main/java/org/springframework/data/domain/PageImpl.java @@ -18,6 +18,8 @@ package org.springframework.data.domain; import java.util.List; import java.util.function.Function; +import org.springframework.lang.Nullable; + /** * Basic {@code Page} implementation. * @@ -127,7 +129,7 @@ public class PageImpl extends Chunk implements Page { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/domain/PageRequest.java b/src/main/java/org/springframework/data/domain/PageRequest.java index 3d3b42973..f3ce4775b 100644 --- a/src/main/java/org/springframework/data/domain/PageRequest.java +++ b/src/main/java/org/springframework/data/domain/PageRequest.java @@ -16,6 +16,7 @@ package org.springframework.data.domain; import org.springframework.data.domain.Sort.Direction; +import org.springframework.lang.Nullable; /** * Basic Java Bean implementation of {@code Pageable}. @@ -145,7 +146,7 @@ public class PageRequest extends AbstractPageRequest { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(final Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/domain/SliceImpl.java b/src/main/java/org/springframework/data/domain/SliceImpl.java index 1811ad887..ad67d3191 100644 --- a/src/main/java/org/springframework/data/domain/SliceImpl.java +++ b/src/main/java/org/springframework/data/domain/SliceImpl.java @@ -18,6 +18,8 @@ package org.springframework.data.domain; import java.util.List; import java.util.function.Function; +import org.springframework.lang.Nullable; + /** * Default implementation of {@link Slice}. * @@ -95,7 +97,7 @@ public class SliceImpl extends Chunk { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/domain/Sort.java b/src/main/java/org/springframework/data/domain/Sort.java index 332d429d0..f270c8b7e 100644 --- a/src/main/java/org/springframework/data/domain/Sort.java +++ b/src/main/java/org/springframework/data/domain/Sort.java @@ -26,6 +26,7 @@ import java.util.Optional; import java.util.stream.Collectors; import org.springframework.data.util.Streamable; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -212,9 +213,7 @@ public class Sort implements Streamable these = new ArrayList<>(this.orders); @@ -231,6 +230,7 @@ public class Sort implements Streamable { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object) */ + @Nullable @Override - public OrderDto marshal(Order order) { + public OrderDto marshal(@Nullable Order order) { if (order == null) { return null; @@ -50,8 +53,21 @@ public class OrderAdapter extends XmlAdapter { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object) */ + @Nullable @Override - public Order unmarshal(OrderDto source) { - return source == null ? null : new Order(source.direction, source.property); + public Order unmarshal(@Nullable OrderDto source) { + + if (source == null) { + return null; + } + + Direction direction = source.direction; + String property = source.property; + + if (direction == null || property == null) { + return null; + } + + return new Order(direction, property); } } diff --git a/src/main/java/org/springframework/data/domain/jaxb/PageAdapter.java b/src/main/java/org/springframework/data/domain/jaxb/PageAdapter.java index 6b770befd..b0ad9a895 100644 --- a/src/main/java/org/springframework/data/domain/jaxb/PageAdapter.java +++ b/src/main/java/org/springframework/data/domain/jaxb/PageAdapter.java @@ -23,6 +23,7 @@ import javax.xml.bind.annotation.adapters.XmlAdapter; import org.springframework.data.domain.Page; import org.springframework.data.domain.jaxb.SpringDataJaxb.PageDto; import org.springframework.hateoas.Link; +import org.springframework.lang.Nullable; /** * {@link XmlAdapter} to convert {@link Page} instances into {@link PageDto} instances and vice versa. @@ -35,8 +36,9 @@ public class PageAdapter extends XmlAdapter> { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object) */ + @Nullable @Override - public PageDto marshal(Page source) { + public PageDto marshal(@Nullable Page source) { if (source == null) { return null; @@ -53,8 +55,9 @@ public class PageAdapter extends XmlAdapter> { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object) */ + @Nullable @Override - public Page unmarshal(PageDto v) { + public Page unmarshal(@Nullable PageDto v) { return null; } diff --git a/src/main/java/org/springframework/data/domain/jaxb/PageableAdapter.java b/src/main/java/org/springframework/data/domain/jaxb/PageableAdapter.java index b57ce5c83..fb590fc32 100644 --- a/src/main/java/org/springframework/data/domain/jaxb/PageableAdapter.java +++ b/src/main/java/org/springframework/data/domain/jaxb/PageableAdapter.java @@ -17,13 +17,14 @@ package org.springframework.data.domain.jaxb; import java.util.Collections; +import javax.annotation.Nonnull; import javax.xml.bind.annotation.adapters.XmlAdapter; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.jaxb.SpringDataJaxb.OrderDto; import org.springframework.data.domain.jaxb.SpringDataJaxb.PageRequestDto; import org.springframework.data.domain.jaxb.SpringDataJaxb.SortDto; +import org.springframework.lang.Nullable; /** * {@link XmlAdapter} to convert {@link Pageable} instances int a {@link PageRequestDto} and vice versa. @@ -37,12 +38,17 @@ class PageableAdapter extends XmlAdapter { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object) */ + @Nullable @Override - public PageRequestDto marshal(Pageable request) { + public PageRequestDto marshal(@Nullable Pageable request) { - SortDto sortDto = SortAdapter.INSTANCE.marshal(request.getSort()); + if (request == null) { + return null; + } PageRequestDto dto = new PageRequestDto(); + + SortDto sortDto = SortAdapter.INSTANCE.marshal(request.getSort()); dto.orders = sortDto == null ? Collections.emptyList() : sortDto.orders; dto.page = request.getPageNumber(); dto.size = request.getPageSize(); @@ -54,8 +60,13 @@ class PageableAdapter extends XmlAdapter { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object) */ + @Nonnull @Override - public Pageable unmarshal(PageRequestDto v) { + public Pageable unmarshal(@Nullable PageRequestDto v) { + + if (v == null) { + return Pageable.unpaged(); + } if (v.orders.isEmpty()) { return PageRequest.of(v.page, v.size); diff --git a/src/main/java/org/springframework/data/domain/jaxb/SortAdapter.java b/src/main/java/org/springframework/data/domain/jaxb/SortAdapter.java index b3ed72218..f4ae390f2 100644 --- a/src/main/java/org/springframework/data/domain/jaxb/SortAdapter.java +++ b/src/main/java/org/springframework/data/domain/jaxb/SortAdapter.java @@ -15,10 +15,12 @@ */ package org.springframework.data.domain.jaxb; +import javax.annotation.Nonnull; import javax.xml.bind.annotation.adapters.XmlAdapter; import org.springframework.data.domain.Sort; import org.springframework.data.domain.jaxb.SpringDataJaxb.SortDto; +import org.springframework.lang.Nullable; /** * {@link XmlAdapter} to convert {@link Sort} instances into {@link SortDto} instances and vice versa. @@ -33,8 +35,9 @@ public class SortAdapter extends XmlAdapter { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object) */ + @Nullable @Override - public SortDto marshal(Sort source) { + public SortDto marshal(@Nullable Sort source) { if (source == null) { return null; @@ -50,8 +53,9 @@ public class SortAdapter extends XmlAdapter { * (non-Javadoc) * @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object) */ + @Nonnull @Override - public Sort unmarshal(SortDto source) { + public Sort unmarshal(@Nullable SortDto source) { return source == null ? Sort.unsorted() : Sort.by(SpringDataJaxb.unmarshal(source.orders, OrderAdapter.INSTANCE)); } } diff --git a/src/main/java/org/springframework/data/domain/jaxb/SpringDataJaxb.java b/src/main/java/org/springframework/data/domain/jaxb/SpringDataJaxb.java index cb07360c6..fc0f7bdf4 100644 --- a/src/main/java/org/springframework/data/domain/jaxb/SpringDataJaxb.java +++ b/src/main/java/org/springframework/data/domain/jaxb/SpringDataJaxb.java @@ -36,6 +36,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; import org.springframework.hateoas.ResourceSupport; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -84,8 +85,8 @@ public abstract class SpringDataJaxb { @XmlAccessorType(XmlAccessType.FIELD) public static class OrderDto { - @XmlAttribute String property; - @XmlAttribute Direction direction; + @Nullable @XmlAttribute String property; + @Nullable @XmlAttribute Direction direction; } /** @@ -97,7 +98,7 @@ public abstract class SpringDataJaxb { @XmlAccessorType(XmlAccessType.FIELD) public static class PageDto extends ResourceSupport { - @XmlAnyElement @XmlElementWrapper(name = "content") List content; + @Nullable @XmlAnyElement @XmlElementWrapper(name = "content") List content; } /** @@ -136,7 +137,7 @@ public abstract class SpringDataJaxb { * @return * @throws Exception */ - public static List marshal(Iterable source, XmlAdapter adapter) { + public static List marshal(@Nullable Iterable source, XmlAdapter adapter) { Assert.notNull(adapter, "Adapter must not be null!"); diff --git a/src/main/java/org/springframework/data/domain/jaxb/package-info.java b/src/main/java/org/springframework/data/domain/jaxb/package-info.java index 2a74e51bc..90c5cdf25 100644 --- a/src/main/java/org/springframework/data/domain/jaxb/package-info.java +++ b/src/main/java/org/springframework/data/domain/jaxb/package-info.java @@ -1,13 +1,16 @@ /** - * Central domain abstractions especially to be used in combination with the {@link org.springframework.data.repository.Repository} abstraction. + * Central domain abstractions especially to be used in combination with the + * {@link org.springframework.data.repository.Repository} abstraction. * * @see org.springframework.data.repository.Repository */ -@XmlSchema(xmlns = { @XmlNs(prefix = "sd", namespaceURI = org.springframework.data.domain.jaxb.SpringDataJaxb.NAMESPACE) }) +@XmlSchema( + xmlns = { @XmlNs(prefix = "sd", namespaceURI = org.springframework.data.domain.jaxb.SpringDataJaxb.NAMESPACE) }) @XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(value = org.springframework.data.domain.jaxb.PageableAdapter.class, type = Pageable.class), @XmlJavaTypeAdapter(value = org.springframework.data.domain.jaxb.SortAdapter.class, type = Sort.class), @XmlJavaTypeAdapter(value = org.springframework.data.domain.jaxb.PageAdapter.class, type = Page.class) }) +@org.springframework.lang.NonNullApi package org.springframework.data.domain.jaxb; import javax.xml.bind.annotation.XmlNs; @@ -18,4 +21,3 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; - diff --git a/src/main/java/org/springframework/data/domain/package-info.java b/src/main/java/org/springframework/data/domain/package-info.java index e4ecf3168..1dc24ce77 100644 --- a/src/main/java/org/springframework/data/domain/package-info.java +++ b/src/main/java/org/springframework/data/domain/package-info.java @@ -1,7 +1,8 @@ /** - * Central domain abstractions especially to be used in combination with the {@link org.springframework.data.repository.Repository} abstraction. + * Central domain abstractions especially to be used in combination with the + * {@link org.springframework.data.repository.Repository} abstraction. * * @see org.springframework.data.repository.Repository */ +@org.springframework.lang.NonNullApi package org.springframework.data.domain; - diff --git a/src/main/java/org/springframework/data/geo/Box.java b/src/main/java/org/springframework/data/geo/Box.java index 21b7565a3..7794d73a7 100644 --- a/src/main/java/org/springframework/data/geo/Box.java +++ b/src/main/java/org/springframework/data/geo/Box.java @@ -15,6 +15,7 @@ */ package org.springframework.data.geo; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -109,7 +110,7 @@ public class Box implements Shape { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/geo/Circle.java b/src/main/java/org/springframework/data/geo/Circle.java index e075c1b0f..6ef02c064 100644 --- a/src/main/java/org/springframework/data/geo/Circle.java +++ b/src/main/java/org/springframework/data/geo/Circle.java @@ -15,6 +15,8 @@ */ package org.springframework.data.geo; +import lombok.EqualsAndHashCode; + import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.util.Assert; @@ -26,6 +28,7 @@ import org.springframework.util.Assert; * @author Thomas Darimont * @since 1.8 */ +@EqualsAndHashCode public class Circle implements Shape { private static final long serialVersionUID = 5215611530535947924L; @@ -98,39 +101,4 @@ public class Circle implements Shape { public String toString() { return String.format("Circle: [center=%s, radius=%s]", center, radius); } - - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (this == obj) { - return true; - } - - if (!(obj instanceof Circle)) { - return false; - } - - Circle that = (Circle) obj; - - return this.center.equals(that.center) && this.radius.equals(that.radius); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - - int result = 17; - - result += 31 * center.hashCode(); - result += 31 * radius.hashCode(); - - return result; - } } diff --git a/src/main/java/org/springframework/data/geo/Distance.java b/src/main/java/org/springframework/data/geo/Distance.java index b0d7dbc53..2eaa38c22 100644 --- a/src/main/java/org/springframework/data/geo/Distance.java +++ b/src/main/java/org/springframework/data/geo/Distance.java @@ -15,9 +15,13 @@ */ package org.springframework.data.geo; +import lombok.Value; + import java.io.Serializable; import org.springframework.data.domain.Range; +import org.springframework.data.domain.Range.Bound; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -27,11 +31,19 @@ import org.springframework.util.Assert; * @author Thomas Darimont * @since 1.8 */ +@Value public class Distance implements Serializable, Comparable { private static final long serialVersionUID = 2460886201934027744L; + /** + * The distance value in the current {@link Metric}. + */ private final double value; + + /** + * The {@link Metric} of the {@link Distance}. + */ private final Metric metric; /** @@ -47,12 +59,14 @@ public class Distance implements Serializable, Comparable { * Creates a new {@link Distance} with the given {@link Metric}. * * @param value - * @param metric can be {@literal null}. + * @param metric must not be {@literal null}. */ public Distance(double value, Metric metric) { + Assert.notNull(metric, "Metric must not be null!"); + this.value = value; - this.metric = metric == null ? Metrics.NEUTRAL : metric; + this.metric = metric; } /** @@ -63,7 +77,7 @@ public class Distance implements Serializable, Comparable { * @return will never be {@literal null}. */ public static Range between(Distance min, Distance max) { - return new Range<>(min, max); + return Range.from(Bound.inclusive(min)).to(Bound.inclusive(max)); } /** @@ -79,15 +93,6 @@ public class Distance implements Serializable, Comparable { return between(new Distance(minValue, minMetric), new Distance(maxValue, maxMetric)); } - /** - * Returns the distance value in the current {@link Metric}. - * - * @return the value - */ - public double getValue() { - return value; - } - /** * Returns the normalized value regarding the underlying {@link Metric}. * @@ -97,15 +102,6 @@ public class Distance implements Serializable, Comparable { return value / metric.getMultiplier(); } - /** - * Returns the {@link Metric} of the {@link Distance}. - * - * @return the metric - */ - public Metric getMetric() { - return metric; - } - /** * Returns a {@link String} representation of the unit the distance is in. * @@ -160,6 +156,7 @@ public class Distance implements Serializable, Comparable { public Distance in(Metric metric) { Assert.notNull(metric, "Metric must not be null!"); + return this.metric.equals(metric) ? this : new Distance(getNormalizedValue() * metric.getMultiplier(), metric); } @@ -168,49 +165,19 @@ public class Distance implements Serializable, Comparable { * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override - public int compareTo(Distance o) { + public int compareTo(@Nullable Distance that) { - double difference = this.getNormalizedValue() - o.getNormalizedValue(); - - return difference == 0 ? 0 : difference > 0 ? 1 : -1; - } - - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (this == obj) { - return true; + if (that == null) { + return 1; } - if (!(obj instanceof Distance)) { - return false; - } + double difference = this.getNormalizedValue() - that.getNormalizedValue(); - Distance that = (Distance) obj; - - return this.value == that.value && this.metric.equals(that.metric); + return difference == 0 ? 0 : difference > 0 ? 1 : -1; } /* * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - - int result = 17; - - result += 31 * Double.doubleToLongBits(value); - result += 31 * metric.hashCode(); - - return result; - } - - /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override diff --git a/src/main/java/org/springframework/data/geo/GeoPage.java b/src/main/java/org/springframework/data/geo/GeoPage.java index f0d25489b..144adae30 100644 --- a/src/main/java/org/springframework/data/geo/GeoPage.java +++ b/src/main/java/org/springframework/data/geo/GeoPage.java @@ -15,9 +15,12 @@ */ package org.springframework.data.geo; +import lombok.Getter; + import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** @@ -31,7 +34,11 @@ import org.springframework.util.ObjectUtils; public class GeoPage extends PageImpl> { private static final long serialVersionUID = -5655267379242128600L; - private final Distance averageDistance; + + /** + * The average distance of the underlying results. + */ + private final @Getter Distance averageDistance; /** * Creates a new {@link GeoPage} from the given {@link GeoResults}. @@ -59,21 +66,12 @@ public class GeoPage extends PageImpl> { this.averageDistance = results.getAverageDistance(); } - /** - * Returns the average distance of the underlying results. - * - * @return the averageDistance - */ - public Distance getAverageDistance() { - return averageDistance; - } - /* * (non-Javadoc) * @see org.springframework.data.domain.PageImpl#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/geo/GeoResult.java b/src/main/java/org/springframework/data/geo/GeoResult.java index 5be1e2eb6..794d63f1a 100644 --- a/src/main/java/org/springframework/data/geo/GeoResult.java +++ b/src/main/java/org/springframework/data/geo/GeoResult.java @@ -15,9 +15,10 @@ */ package org.springframework.data.geo; -import java.io.Serializable; +import lombok.NonNull; +import lombok.Value; -import org.springframework.util.Assert; +import java.io.Serializable; /** * Value object capturing some arbitrary object plus a distance. @@ -26,80 +27,13 @@ import org.springframework.util.Assert; * @author Thomas Darimont * @since 1.8 */ +@Value public class GeoResult implements Serializable { private static final long serialVersionUID = 1637452570977581370L; - private final T content; - private final Distance distance; - - /** - * Creates a new {@link GeoResult} for the given content and distance. - * - * @param content must not be {@literal null}. - * @param distance must not be {@literal null}. - */ - public GeoResult(T content, Distance distance) { - - Assert.notNull(content, "Content must not be null!"); - Assert.notNull(distance, "Distance must not be null!"); - - this.content = content; - this.distance = distance; - } - - /** - * Returns the actual content object. - * - * @return the content - */ - public T getContent() { - return content; - } - - /** - * Returns the distance the actual content object has from the origin. - * - * @return the distance - */ - public Distance getDistance() { - return distance; - } - - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (this == obj) { - return true; - } - - if (!(obj instanceof GeoResult)) { - return false; - } - - GeoResult that = (GeoResult) obj; - - return this.content.equals(that.content) && this.distance.equals(that.distance); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - - int result = 17; - - result += 31 * distance.hashCode(); - result += 31 * content.hashCode(); - - return result; - } + @NonNull T content; + @NonNull Distance distance; /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/geo/GeoResults.java b/src/main/java/org/springframework/data/geo/GeoResults.java index cac941c36..1cb58943d 100644 --- a/src/main/java/org/springframework/data/geo/GeoResults.java +++ b/src/main/java/org/springframework/data/geo/GeoResults.java @@ -15,6 +15,8 @@ */ package org.springframework.data.geo; +import lombok.EqualsAndHashCode; + import java.io.Serializable; import java.util.Collections; import java.util.Iterator; @@ -31,6 +33,7 @@ import org.springframework.util.StringUtils; * @author Thomas Darimont * @since 1.8 */ +@EqualsAndHashCode public class GeoResults implements Iterable>, Serializable { private static final long serialVersionUID = 8347363491300219485L; @@ -45,7 +48,7 @@ public class GeoResults implements Iterable>, Serializable { * @param results must not be {@literal null}. */ public GeoResults(List> results) { - this(results, (Metric) null); + this(results, Metrics.NEUTRAL); } /** @@ -63,12 +66,13 @@ public class GeoResults implements Iterable>, Serializable { * Creates a new {@link GeoResults} instance from the given {@link GeoResult}s and average distance. * * @param results must not be {@literal null}. - * @param averageDistance can be {@literal null}. + * @param averageDistance must not be {@literal null}. */ @PersistenceConstructor public GeoResults(List> results, Distance averageDistance) { Assert.notNull(results, "Results must not be null!"); + Assert.notNull(averageDistance, "Average Distance must not be null!"); this.results = results; this.averageDistance = averageDistance; @@ -101,41 +105,6 @@ public class GeoResults implements Iterable>, Serializable { return (Iterator>) results.iterator(); } - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (this == obj) { - return true; - } - - if (!(obj instanceof GeoResults)) { - return false; - } - - GeoResults that = (GeoResults) obj; - - return this.results.equals(that.results) && this.averageDistance.equals(that.averageDistance); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - - int result = 17; - - result += 31 * results.hashCode(); - result += 31 * averageDistance.hashCode(); - - return result; - } - /* * (non-Javadoc) * @see java.lang.Object#toString() @@ -148,16 +117,17 @@ public class GeoResults implements Iterable>, Serializable { private static Distance calculateAverageDistance(List> results, Metric metric) { + Assert.notNull(results, "Results must not be null!"); + Assert.notNull(metric, "Metric must not be null!"); + if (results.isEmpty()) { return new Distance(0, metric); } - double averageDistance = 0; - - for (GeoResult result : results) { - averageDistance += result.getDistance().getValue(); - } + double averageDistance = results.stream()// + .mapToDouble(it -> it.getDistance().getValue())// + .average().orElse(0); - return new Distance(averageDistance / results.size(), metric); + return new Distance(averageDistance, metric); } } diff --git a/src/main/java/org/springframework/data/geo/Point.java b/src/main/java/org/springframework/data/geo/Point.java index 74b4de125..e5bf6a865 100644 --- a/src/main/java/org/springframework/data/geo/Point.java +++ b/src/main/java/org/springframework/data/geo/Point.java @@ -19,6 +19,7 @@ import java.io.Serializable; import java.util.Locale; import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -102,7 +103,7 @@ public class Point implements Serializable { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/geo/Polygon.java b/src/main/java/org/springframework/data/geo/Polygon.java index 65244d003..20a858831 100644 --- a/src/main/java/org/springframework/data/geo/Polygon.java +++ b/src/main/java/org/springframework/data/geo/Polygon.java @@ -15,6 +15,8 @@ */ package org.springframework.data.geo; +import lombok.EqualsAndHashCode; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -32,6 +34,7 @@ import org.springframework.util.StringUtils; * @author Thomas Darimont * @since 1.8 */ +@EqualsAndHashCode public class Polygon implements Iterable, Shape { private static final long serialVersionUID = -2705040068154648988L; @@ -98,35 +101,6 @@ public class Polygon implements Iterable, Shape { return this.points.iterator(); } - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (this == obj) { - return true; - } - - if (!(obj instanceof Polygon)) { - return false; - } - - Polygon that = (Polygon) obj; - - return this.points.equals(that.points); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return points.hashCode(); - } - /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/src/main/java/org/springframework/data/geo/format/DistanceFormatter.java b/src/main/java/org/springframework/data/geo/format/DistanceFormatter.java index 63f486dda..4eef81db0 100644 --- a/src/main/java/org/springframework/data/geo/format/DistanceFormatter.java +++ b/src/main/java/org/springframework/data/geo/format/DistanceFormatter.java @@ -27,6 +27,7 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metric; import org.springframework.data.geo.Metrics; import org.springframework.format.Formatter; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -60,6 +61,7 @@ public enum DistanceFormatter implements Converter, Formatter< * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override public final Distance convert(String source) { return source == null ? null : doConvert(source.trim().toLowerCase(Locale.US)); @@ -80,7 +82,7 @@ public enum DistanceFormatter implements Converter, Formatter< */ @Override public Distance parse(String text, Locale locale) throws ParseException { - return !StringUtils.hasText(text) ? null : doConvert(text.trim().toLowerCase(locale)); + return doConvert(text.trim().toLowerCase(locale)); } /** diff --git a/src/main/java/org/springframework/data/geo/format/PointFormatter.java b/src/main/java/org/springframework/data/geo/format/PointFormatter.java index 14650fb02..4fdc14b31 100644 --- a/src/main/java/org/springframework/data/geo/format/PointFormatter.java +++ b/src/main/java/org/springframework/data/geo/format/PointFormatter.java @@ -18,11 +18,12 @@ package org.springframework.data.geo.format; import java.text.ParseException; import java.util.Locale; +import javax.annotation.Nonnull; + import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.data.geo.Point; import org.springframework.format.Formatter; -import org.springframework.util.StringUtils; /** * Converter to parse two comma-separated doubles into a {@link Point}. @@ -41,6 +42,7 @@ public enum PointFormatter implements Converter, Formatter * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nonnull @Override public Point convert(String source) { @@ -77,6 +79,6 @@ public enum PointFormatter implements Converter, Formatter */ @Override public Point parse(String text, Locale locale) throws ParseException { - return !StringUtils.hasText(text) ? null : convert(text); + return convert(text); } } diff --git a/src/main/java/org/springframework/data/geo/format/package-info.java b/src/main/java/org/springframework/data/geo/format/package-info.java new file mode 100644 index 000000000..3b0a6ba70 --- /dev/null +++ b/src/main/java/org/springframework/data/geo/format/package-info.java @@ -0,0 +1,8 @@ +/** + * Formatters for geo-spatial types. + * + * @author Oliver Gierke + * @since 1.8 + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.geo.format; diff --git a/src/main/java/org/springframework/data/geo/package-info.java b/src/main/java/org/springframework/data/geo/package-info.java index f7ca48c33..f2b340c26 100644 --- a/src/main/java/org/springframework/data/geo/package-info.java +++ b/src/main/java/org/springframework/data/geo/package-info.java @@ -5,4 +5,5 @@ * @author Oliver Gierke * @since 1.8 */ -package org.springframework.data.geo; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.geo; diff --git a/src/main/java/org/springframework/data/history/Revision.java b/src/main/java/org/springframework/data/history/Revision.java index da515e41e..c219c0402 100755 --- a/src/main/java/org/springframework/data/history/Revision.java +++ b/src/main/java/org/springframework/data/history/Revision.java @@ -25,6 +25,8 @@ import lombok.Value; import java.time.LocalDateTime; import java.util.Optional; +import org.springframework.lang.Nullable; + /** * Wrapper to contain {@link RevisionMetadata} as well as the revisioned entity. * @@ -97,7 +99,12 @@ public final class Revision, T> implements Comp * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ - public int compareTo(Revision that) { + public int compareTo(@Nullable Revision that) { + + if (that == null) { + return 1; + } + return mapIfAllPresent(getRevisionNumber(), that.getRevisionNumber(), // (left, right) -> left.compareTo(right)).orElse(-1); } diff --git a/src/main/java/org/springframework/data/history/RevisionSort.java b/src/main/java/org/springframework/data/history/RevisionSort.java index 84e773d2c..010b6d25a 100644 --- a/src/main/java/org/springframework/data/history/RevisionSort.java +++ b/src/main/java/org/springframework/data/history/RevisionSort.java @@ -16,6 +16,7 @@ package org.springframework.data.history; import org.springframework.data.domain.Sort; +import org.springframework.util.Assert; /** * A dedicated {@link Sort} implementation that allows the definition of the ordering of revisions independently of the @@ -66,14 +67,12 @@ public class RevisionSort extends Sort { * Returns in which direction to sort revisions for the given {@link Sort} instance. Defaults to * {@link Direction#ASC}. * - * @param sort can be {@literal null}. + * @param sort must not be {@literal null}. * @return */ public static Direction getRevisionDirection(Sort sort) { - if (sort == null) { - return Direction.ASC; - } + Assert.notNull(sort, "Sort must not be null!"); Order order = sort.getOrderFor(PROPERTY); return order == null ? Direction.ASC : order.getDirection(); diff --git a/src/main/java/org/springframework/data/history/package-info.java b/src/main/java/org/springframework/data/history/package-info.java index 6d0af2eac..03ba13327 100644 --- a/src/main/java/org/springframework/data/history/package-info.java +++ b/src/main/java/org/springframework/data/history/package-info.java @@ -1,5 +1,5 @@ /** - * Basic interfaces and value objects for historiography API. + * Basic interfaces and value objects for histography API. */ +@org.springframework.lang.NonNullApi package org.springframework.data.history; - diff --git a/src/main/java/org/springframework/data/mapping/Alias.java b/src/main/java/org/springframework/data/mapping/Alias.java index b1ddd672e..b8dbfae23 100644 --- a/src/main/java/org/springframework/data/mapping/Alias.java +++ b/src/main/java/org/springframework/data/mapping/Alias.java @@ -19,6 +19,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.Value; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -41,6 +42,7 @@ public class Alias { /** * Common instance for {@code empty()}. */ + @SuppressWarnings("null") // public static final Alias NONE = new Alias(null); private final Object value; @@ -65,7 +67,7 @@ public class Alias { * @param alias may be {@literal null}. * @return the {@link Alias} for {@code alias} or {@link #empty()} if the given alias was {@literal null}. */ - public static Alias ofNullable(Object alias) { + public static Alias ofNullable(@Nullable Object alias) { return alias == null ? NONE : new Alias(alias); } @@ -124,6 +126,7 @@ public class Alias { * @param type must not be {@literal null}. * @return */ + @Nullable @SuppressWarnings("unchecked") public T mapTyped(Class type) { @@ -132,7 +135,8 @@ public class Alias { return isPresent() && type.isInstance(value) ? (T) value : null; } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see java.lang.Object#toString() */ @Override diff --git a/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java b/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java index b89b46904..520afe0a0 100644 --- a/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java +++ b/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mapping; +import org.springframework.lang.Nullable; + /** * Interface for a component allowing the access of identifier values. * @@ -29,6 +31,7 @@ public interface IdentifierAccessor { * * @return the identifier of the underlying instance. */ + @Nullable Object getIdentifier(); /** diff --git a/src/main/java/org/springframework/data/mapping/MappingException.java b/src/main/java/org/springframework/data/mapping/MappingException.java index e9296202f..37b90b352 100644 --- a/src/main/java/org/springframework/data/mapping/MappingException.java +++ b/src/main/java/org/springframework/data/mapping/MappingException.java @@ -15,21 +15,20 @@ */ package org.springframework.data.mapping; +import org.springframework.lang.Nullable; + /** * @author Jon Brisbin */ public class MappingException extends RuntimeException { - /** - * - */ private static final long serialVersionUID = 1L; - public MappingException(String s) { + public MappingException(@Nullable String s) { super(s); } - public MappingException(String s, Throwable throwable) { + public MappingException(@Nullable String s, @Nullable Throwable throwable) { super(s, throwable); } } diff --git a/src/main/java/org/springframework/data/mapping/PersistentEntity.java b/src/main/java/org/springframework/data/mapping/PersistentEntity.java index 44444628c..81417d8c5 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/PersistentEntity.java @@ -20,6 +20,7 @@ import java.util.Iterator; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** * Represents a persistent entity. @@ -47,6 +48,7 @@ public interface PersistentEntity> extends It * indicates that the instantiation of the object of that persistent entity is done through either a customer * {@link EntityInstantiator} or handled by custom conversion mechanisms entirely. */ + @Nullable PreferredConstructor getPersistenceConstructor(); /** @@ -81,6 +83,7 @@ public interface PersistentEntity> extends It * * @return the id property of the {@link PersistentEntity}. */ + @Nullable P getIdProperty(); /** @@ -107,6 +110,7 @@ public interface PersistentEntity> extends It * * @return the version property of the {@link PersistentEntity}. */ + @Nullable P getVersionProperty(); /** @@ -134,6 +138,7 @@ public interface PersistentEntity> extends It * @param name The name of the property. Can be {@literal null}. * @return the {@link PersistentProperty} or {@literal null} if it doesn't exist. */ + @Nullable P getPersistentProperty(String name); /** @@ -161,6 +166,7 @@ public interface PersistentEntity> extends It * @return {@literal null} if no property found with given annotation type. * @since 1.8 */ + @Nullable default P getPersistentProperty(Class annotationType) { Iterator

it = getPersistentProperties(annotationType).iterator(); @@ -240,6 +246,7 @@ public interface PersistentEntity> extends It * @return {@literal null} if not found. * @since 1.8 */ + @Nullable A findAnnotation(Class annotationType); /** diff --git a/src/main/java/org/springframework/data/mapping/PersistentProperty.java b/src/main/java/org/springframework/data/mapping/PersistentProperty.java index ecb224d56..f71af1542 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/PersistentProperty.java @@ -23,6 +23,7 @@ import java.util.Map; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** * @author Graeme Rocher @@ -75,26 +76,64 @@ public interface PersistentProperty

> { * * @return the getter method to access the property value if available, otherwise {@literal null}. */ + @Nullable Method getGetter(); + default Method getRequiredGetter() { + + Method getter = getGetter(); + + if (getter == null) { + throw new IllegalArgumentException("No getter available for this persistent property!"); + } + + return getter; + } + /** * Returns the setter method to set a property value. Might return {@literal null} in case there is no setter * available. * * @return the setter method to set a property value if available, otherwise {@literal null}. */ + @Nullable Method getSetter(); + default Method getRequiredSetter() { + + Method setter = getSetter(); + + if (setter == null) { + throw new IllegalArgumentException("No setter available for this persistent property!"); + } + + return setter; + } + + @Nullable Field getField(); + default Field getRequiredField() { + + Field field = getField(); + + if (field == null) { + throw new IllegalArgumentException("No field backing this persistent property!"); + } + + return field; + } + /** * @return {@literal null} if no expression defined. */ + @Nullable String getSpelExpression(); /** * @return {@literal null} if property is not part of an {@link Association}. */ + @Nullable Association

getAssociation(); /** @@ -193,6 +232,7 @@ public interface PersistentProperty

> { * @return the component type, the map's key type or {@literal null} if neither {@link java.util.Collection} nor * {@link java.util.Map}. */ + @Nullable Class getComponentType(); /** @@ -207,6 +247,7 @@ public interface PersistentProperty

> { * * @return the map's value type or {@literal null} if no {@link java.util.Map} */ + @Nullable Class getMapValueType(); /** @@ -225,15 +266,17 @@ public interface PersistentProperty

> { * @return the annotation of the given type. Can be {@literal null}. * @see AnnotationUtils#findAnnotation(Method, Class) */ + @Nullable A findAnnotation(Class annotationType); /** * Looks up the annotation of the given type on the property and the owning type if no annotation can be found on it. - * Usefull to lookup annotations that can be configured on the type but overridden on an individual property. + * Useful to lookup annotations that can be configured on the type but overridden on an individual property. * * @param annotationType must not be {@literal null}. * @return the annotation of the given type. Can be {@literal null}. */ + @Nullable A findPropertyOrOwnerAnnotation(Class annotationType); /** diff --git a/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java b/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java index ce9d46d80..971ffc3bb 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java +++ b/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java @@ -16,6 +16,7 @@ package org.springframework.data.mapping; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.lang.Nullable; /** * Domain service to allow accessing and setting {@link PersistentProperty}s of an entity. Usually obtained through @@ -35,10 +36,9 @@ public interface PersistentPropertyAccessor { * * @param property must not be {@literal null}. * @param value can be {@literal null}. - * @throws MappingException in case an exception occurred when setting the - * property value. + * @throws MappingException in case an exception occurred when setting the property value. */ - void setProperty(PersistentProperty property, Object value); + void setProperty(PersistentProperty property, @Nullable Object value); /** * Returns the value of the given {@link PersistentProperty} of the underlying bean instance. @@ -46,6 +46,7 @@ public interface PersistentPropertyAccessor { * @param property must not be {@literal null}. * @return can be {@literal null}. */ + @Nullable Object getProperty(PersistentProperty property); /** diff --git a/src/main/java/org/springframework/data/mapping/PreferredConstructor.java b/src/main/java/org/springframework/data/mapping/PreferredConstructor.java index e1d5eee72..053975bba 100644 --- a/src/main/java/org/springframework/data/mapping/PreferredConstructor.java +++ b/src/main/java/org/springframework/data/mapping/PreferredConstructor.java @@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -188,17 +189,17 @@ public class PreferredConstructor> { @EqualsAndHashCode(exclude = { "enclosingClassCache", "hasSpelExpression" }) public static class Parameter> { - private final String name; + private final @Nullable String name; private final TypeInformation type; private final String key; - private final PersistentEntity entity; + private final @Nullable PersistentEntity entity; private final Lazy enclosingClassCache; private final Lazy hasSpelExpression; /** * Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of - * {@link Annotation}s. Will insprect the annotations for an {@link Value} annotation to lookup a key or an SpEL + * {@link Annotation}s. Will inspect the annotations for an {@link Value} annotation to lookup a key or an SpEL * expression to be evaluated. * * @param name the name of the parameter, can be {@literal null} @@ -206,7 +207,8 @@ public class PreferredConstructor> { * @param annotations must not be {@literal null} but can be empty * @param entity must not be {@literal null}. */ - public Parameter(String name, TypeInformation type, Annotation[] annotations, PersistentEntity entity) { + public Parameter(@Nullable String name, TypeInformation type, Annotation[] annotations, + @Nullable PersistentEntity entity) { Assert.notNull(type, "Type must not be null!"); Assert.notNull(annotations, "Annotations must not be null!"); @@ -242,6 +244,7 @@ public class PreferredConstructor> { * * @return */ + @Nullable public String getName() { return name; } @@ -290,7 +293,11 @@ public class PreferredConstructor> { */ boolean maps(PersistentProperty property) { - P referencedProperty = entity == null ? null : entity.getPersistentProperty(name); + PersistentEntity entity = this.entity; + String name = this.name; + + P referencedProperty = entity == null ? null : name == null ? null : entity.getPersistentProperty(name); + return property != null && property.equals(referencedProperty); } diff --git a/src/main/java/org/springframework/data/mapping/PropertyPath.java b/src/main/java/org/springframework/data/mapping/PropertyPath.java index ee709665f..920488699 100644 --- a/src/main/java/org/springframework/data/mapping/PropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/PropertyPath.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.Streamable; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.StringUtils; @@ -57,7 +58,7 @@ public class PropertyPath implements Streamable { private final TypeInformation actualTypeInformation; private final boolean isCollection; - private PropertyPath next; + private @Nullable PropertyPath next; /** * Creates a leaf {@link PropertyPath} (no nested ones) with the given name inside the given owning type. @@ -92,8 +93,9 @@ public class PropertyPath implements Streamable { this.owningType = owningType; this.typeInformation = propertyType; this.isCollection = propertyType.isCollectionLike(); - this.actualTypeInformation = propertyType.getActualType(); this.name = propertyName; + this.actualTypeInformation = propertyType.getActualType() == null ? propertyType + : propertyType.getRequiredActualType(); } /** @@ -124,7 +126,7 @@ public class PropertyPath implements Streamable { PropertyPath result = this; while (result.hasNext()) { - result = result.next(); + result = result.requiredNext(); } return result; @@ -146,6 +148,7 @@ public class PropertyPath implements Streamable { * @return the next nested {@link PropertyPath} or {@literal null} if no nested {@link PropertyPath} available. * @see #hasNext() */ + @Nullable public PropertyPath next() { return next; } @@ -168,7 +171,7 @@ public class PropertyPath implements Streamable { public String toDotPath() { if (hasNext()) { - return getSegment() + "." + next().toDotPath(); + return getSegment() + "." + requiredNext().toDotPath(); } return getSegment(); @@ -188,17 +191,25 @@ public class PropertyPath implements Streamable { * @see java.lang.Iterable#iterator() */ public Iterator iterator() { + return new Iterator() { - private PropertyPath current = PropertyPath.this; + private @Nullable PropertyPath current = PropertyPath.this; public boolean hasNext() { return current != null; } + @Nullable public PropertyPath next() { + PropertyPath result = current; - this.current = current.next(); + + if (result == null) { + return null; + } + + this.current = result.next(); return result; } @@ -208,6 +219,24 @@ public class PropertyPath implements Streamable { }; } + /** + * Returns the next {@link PropertyPath}. + * + * @return + * @throws IllegalStateException it there's no next one. + */ + private PropertyPath requiredNext() { + + PropertyPath result = next; + + if (result == null) { + throw new IllegalStateException( + "No next path available! Clients should call hasNext() before invoking this method!"); + } + + return result; + } + /** * Extracts the {@link PropertyPath} chain from the given source {@link String} and type. * @@ -258,6 +287,11 @@ public class PropertyPath implements Streamable { } } + if (result == null) { + throw new IllegalStateException( + String.format("Expected parsing to yield a PropertyPath from %s but got null!", source)); + } + return result; }); } @@ -277,7 +311,7 @@ public class PropertyPath implements Streamable { PropertyPath previous = base.peek(); - PropertyPath propertyPath = create(source, previous.typeInformation.getActualType(), base); + PropertyPath propertyPath = create(source, previous.typeInformation.getRequiredActualType(), base); previous.next = propertyPath; return propertyPath; } @@ -297,7 +331,7 @@ public class PropertyPath implements Streamable { } /** - * Tries to look up a chain of {@link PropertyPath}s by trying the givne source first. If that fails it will split the + * Tries to look up a chain of {@link PropertyPath}s by trying the given source first. If that fails it will split the * source apart at camel case borders (starting from the right side) and try to look up a {@link PropertyPath} from * the calculated head and recombined new tail and additional tail. * diff --git a/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java b/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java index 7970fc3f7..7891c080f 100644 --- a/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java +++ b/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java @@ -23,6 +23,7 @@ import java.util.Set; import org.springframework.beans.PropertyMatches; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -118,6 +119,7 @@ public class PropertyReferenceException extends RuntimeException { * * @return */ + @Nullable public PropertyPath getBaseProperty() { return alreadyResolvedPath.isEmpty() ? null : alreadyResolvedPath.get(alreadyResolvedPath.size() - 1); } diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index d8a90203c..569984c1c 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -56,6 +56,7 @@ import org.springframework.data.util.Optionals; import org.springframework.data.util.Pair; import org.springframework.data.util.Streamable; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ReflectionUtils; @@ -89,7 +90,7 @@ public abstract class AbstractMappingContext> propertyPaths = new ConcurrentReferenceHashMap<>(); private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory(); - private ApplicationEventPublisher applicationEventPublisher; + private @Nullable ApplicationEventPublisher applicationEventPublisher; private Set> initialEntitySet = new HashSet<>(); private boolean strict = false; @@ -131,7 +132,7 @@ public abstract class AbstractMappingContext type) { return getPersistentEntity(ClassTypeInformation.from(type)); } @@ -186,6 +188,7 @@ public abstract class AbstractMappingContext type) { @@ -228,13 +231,14 @@ public abstract class AbstractMappingContext typeInfo = persistentProperty.getTypeInformation(); - return getPersistentEntity(typeInfo.getActualType()); + return getPersistentEntity(typeInfo.getRequiredActualType()); } /* @@ -315,6 +319,7 @@ public abstract class AbstractMappingContext, E> getPair(DefaultPersistentPropertyPath

path, Iterator iterator, String segment, E entity) { @@ -324,7 +329,7 @@ public abstract class AbstractMappingContext type = property.getTypeInformation().getActualType(); + TypeInformation type = property.getTypeInformation().getRequiredActualType(); return Pair.of(path.append(property), iterator.hasNext() ? getRequiredPersistentEntity(type) : entity); } @@ -520,9 +525,10 @@ public abstract class AbstractMappingContext type) { + Assert.notNull(name, "Name must not be null!"); + Assert.notNull(type, "Type must not be null!"); + if (namePattern != null && !name.matches(namePattern)) { return false; } diff --git a/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java b/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java index ef474de74..ed635fa72 100644 --- a/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java @@ -16,13 +16,13 @@ package org.springframework.data.mapping.context; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.springframework.core.convert.converter.Converter; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -32,25 +32,19 @@ import org.springframework.util.StringUtils; * @author Oliver Gierke * @author Christoph Strobl */ -class DefaultPersistentPropertyPath> implements PersistentPropertyPath { +class DefaultPersistentPropertyPath

> implements PersistentPropertyPath

{ - private enum PropertyNameConverter implements Converter, String> { + private static final Converter, String> DEFAULT_CONVERTER = (source) -> source.getName(); + private static final String DEFAULT_DELIMITER = "."; - INSTANCE; - - public String convert(PersistentProperty source) { - return source.getName(); - } - } - - private final List properties; + private final List

properties; /** * Creates a new {@link DefaultPersistentPropertyPath} for the given {@link PersistentProperty}s. * * @param properties must not be {@literal null}. */ - public DefaultPersistentPropertyPath(List properties) { + public DefaultPersistentPropertyPath(List

properties) { Assert.notNull(properties, "Properties must not be null!"); @@ -63,7 +57,7 @@ class DefaultPersistentPropertyPath> implements * @return */ public static > DefaultPersistentPropertyPath empty() { - return new DefaultPersistentPropertyPath<>(Collections. emptyList()); + return new DefaultPersistentPropertyPath(Collections.emptyList()); } /** @@ -73,7 +67,7 @@ class DefaultPersistentPropertyPath> implements * @return a new {@link DefaultPersistentPropertyPath} with the given property appended to the current one. * @throws IllegalArgumentException in case the property is not a property of the type of the current leaf property. */ - public DefaultPersistentPropertyPath append(T property) { + public DefaultPersistentPropertyPath

append(P property) { Assert.notNull(property, "Property must not be null!"); @@ -81,11 +75,13 @@ class DefaultPersistentPropertyPath> implements return new DefaultPersistentPropertyPath<>(Collections.singletonList(property)); } + @SuppressWarnings("null") Class leafPropertyType = getLeafProperty().getActualType(); + Assert.isTrue(property.getOwner().getType().equals(leafPropertyType), String.format("Cannot append property %s to type %s!", property.getName(), leafPropertyType.getName())); - List properties = new ArrayList<>(this.properties); + List

properties = new ArrayList<>(this.properties); properties.add(property); return new DefaultPersistentPropertyPath<>(properties); @@ -95,56 +91,59 @@ class DefaultPersistentPropertyPath> implements * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath() */ + @Nullable public String toDotPath() { - return toPath(null, null); + return toPath(DEFAULT_DELIMITER, DEFAULT_CONVERTER); } /* * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath(org.springframework.core.convert.converter.Converter) */ - public String toDotPath(Converter converter) { - return toPath(null, converter); + @Nullable + public String toDotPath(Converter converter) { + return toPath(DEFAULT_DELIMITER, converter); } /* * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String) */ + @Nullable public String toPath(String delimiter) { - return toPath(delimiter, null); + return toPath(delimiter, DEFAULT_CONVERTER); } /* * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String, org.springframework.core.convert.converter.Converter) */ - public String toPath(String delimiter, Converter converter) { + @Nullable + public String toPath(String delimiter, Converter converter) { - @SuppressWarnings("unchecked") - Converter converterToUse = converter == null - ? PropertyNameConverter.INSTANCE : converter; - String delimiterToUse = delimiter == null ? "." : delimiter; + Assert.hasText(delimiter, "Delimiter must not be null or empty!"); + Assert.notNull(converter, "Converter must not be null!"); List result = new ArrayList<>(); - for (T property : properties) { + for (P property : properties) { - String convert = converterToUse.convert(property); + String convert = converter.convert(property); if (StringUtils.hasText(convert)) { result.add(convert); } } - return result.isEmpty() ? null : StringUtils.collectionToDelimitedString(result, delimiterToUse); + return result.isEmpty() ? null : StringUtils.collectionToDelimitedString(result, delimiter); } /* * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#getLeafProperty() */ - public T getLeafProperty() { + @Nullable + public P getLeafProperty() { return properties.get(properties.size() - 1); } @@ -152,7 +151,8 @@ class DefaultPersistentPropertyPath> implements * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#getBaseProperty() */ - public T getBaseProperty() { + @Nullable + public P getBaseProperty() { return properties.get(0); } @@ -160,21 +160,19 @@ class DefaultPersistentPropertyPath> implements * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#isBasePathOf(org.springframework.data.mapping.context.PersistentPropertyPath) */ - public boolean isBasePathOf(PersistentPropertyPath path) { + public boolean isBasePathOf(PersistentPropertyPath

path) { - if (path == null) { - return false; - } + Assert.notNull(path, "PersistentPropertyPath must not be null!"); - Iterator iterator = path.iterator(); + Iterator

iterator = path.iterator(); - for (T property : this) { + for (P property : this) { if (!iterator.hasNext()) { return false; } - T reference = iterator.next(); + P reference = iterator.next(); if (!property.equals(reference)) { return false; @@ -188,14 +186,14 @@ class DefaultPersistentPropertyPath> implements * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#getExtensionForBaseOf(org.springframework.data.mapping.context.PersistentPropertyPath) */ - public PersistentPropertyPath getExtensionForBaseOf(PersistentPropertyPath base) { + public PersistentPropertyPath

getExtensionForBaseOf(PersistentPropertyPath

base) { if (!base.isBasePathOf(this)) { return this; } - List result = new ArrayList<>(); - Iterator iterator = iterator(); + List

result = new ArrayList<>(); + Iterator

iterator = iterator(); for (int i = 0; i < base.getLength(); i++) { iterator.next(); @@ -212,11 +210,14 @@ class DefaultPersistentPropertyPath> implements * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#getParentPath() */ - public PersistentPropertyPath getParentPath() { + public PersistentPropertyPath

getParentPath() { + int size = properties.size(); + if (size <= 1) { return this; } + return new DefaultPersistentPropertyPath<>(properties.subList(0, size - 1)); } @@ -232,7 +233,7 @@ class DefaultPersistentPropertyPath> implements * (non-Javadoc) * @see java.lang.Iterable#iterator() */ - public Iterator iterator() { + public Iterator

iterator() { return properties.iterator(); } @@ -249,7 +250,7 @@ class DefaultPersistentPropertyPath> implements * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; @@ -278,6 +279,7 @@ class DefaultPersistentPropertyPath> implements * @see java.lang.Object#toString() */ @Override + @Nullable public String toString() { return toDotPath(); } diff --git a/src/main/java/org/springframework/data/mapping/context/InvalidPersistentPropertyPath.java b/src/main/java/org/springframework/data/mapping/context/InvalidPersistentPropertyPath.java index 9091862ca..e6023c561 100644 --- a/src/main/java/org/springframework/data/mapping/context/InvalidPersistentPropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/context/InvalidPersistentPropertyPath.java @@ -17,6 +17,7 @@ package org.springframework.data.mapping.context; import org.springframework.data.mapping.MappingException; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -40,8 +41,8 @@ public class InvalidPersistentPropertyPath extends MappingException { * @param resolvedPath * @param message must not be {@literal null} or empty. */ - InvalidPersistentPropertyPath(String source, TypeInformation type, String unresolvableSegment, String resolvedPath, - String message) { + InvalidPersistentPropertyPath(String source, TypeInformation type, String unresolvableSegment, + @Nullable String resolvedPath, String message) { super(message); diff --git a/src/main/java/org/springframework/data/mapping/context/MappingContext.java b/src/main/java/org/springframework/data/mapping/context/MappingContext.java index 772074b93..35bd8fa2a 100644 --- a/src/main/java/org/springframework/data/mapping/context/MappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/MappingContext.java @@ -22,6 +22,7 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** * This interface defines the overall context including all known PersistentEntity instances and methods to obtain @@ -51,6 +52,7 @@ public interface MappingContext, P extends Pers * @param type must not be {@literal null}. * @return {@literal null} if no {@link PersistentEntity} found for {@literal type}. */ + @Nullable E getPersistentEntity(Class type); /** @@ -91,6 +93,7 @@ public interface MappingContext, P extends Pers * @param type must not be {@literal null}. * @return {@literal null} if no {@link PersistentEntity} found for {@link TypeInformation}. */ + @Nullable E getPersistentEntity(TypeInformation type); /** @@ -117,11 +120,12 @@ public interface MappingContext, P extends Pers * Returns the {@link PersistentEntity} mapped by the given {@link PersistentProperty}. * * @param persistentProperty must not be {@literal null}. - * @return the {@link PersistentEntity} mapped by the given {@link PersistentProperty} or null if no + * @return the {@link PersistentEntity} mapped by the given {@link PersistentProperty} or {@literal null} if no * {@link PersistentEntity} exists for it or the {@link PersistentProperty} does not refer to an entity (the * type of the property is considered simple see * {@link org.springframework.data.mapping.model.SimpleTypeHolder#isSimpleType(Class)}). */ + @Nullable E getPersistentEntity(P persistentProperty); /** diff --git a/src/main/java/org/springframework/data/mapping/context/MappingContextIsNewStrategyFactory.java b/src/main/java/org/springframework/data/mapping/context/MappingContextIsNewStrategyFactory.java index ab558e5f5..edab600b0 100644 --- a/src/main/java/org/springframework/data/mapping/context/MappingContextIsNewStrategyFactory.java +++ b/src/main/java/org/springframework/data/mapping/context/MappingContextIsNewStrategyFactory.java @@ -26,6 +26,7 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.support.IsNewStrategy; import org.springframework.data.support.IsNewStrategyFactory; import org.springframework.data.support.IsNewStrategyFactorySupport; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -68,12 +69,11 @@ public class MappingContextIsNewStrategyFactory extends IsNewStrategyFactorySupp * (non-Javadoc) * @see org.springframework.data.support.IsNewStrategyFactorySupport#getFallBackStrategy(java.lang.Class) */ + @Nullable @Override protected IsNewStrategy doGetIsNewStrategy(Class type) { - return MappingContextIsNewStrategyFactory.getIsNewStrategy(context.getRequiredPersistentEntity(type)); - } - private static IsNewStrategy getIsNewStrategy(PersistentEntity entity) { + PersistentEntity> entity = context.getRequiredPersistentEntity(type); if (entity.hasVersionProperty()) { diff --git a/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java b/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java index c9e75ebcc..89c3b24a6 100644 --- a/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java @@ -17,6 +17,7 @@ package org.springframework.data.mapping.context; import org.springframework.core.convert.converter.Converter; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.lang.Nullable; /** * Abstraction of a path of {@link PersistentProperty}s. @@ -30,33 +31,37 @@ public interface PersistentPropertyPath> extends * * @return */ + @Nullable String toDotPath(); /** * Returns the dot based path notation using the given {@link Converter} to translate individual * {@link PersistentProperty}s to path segments. * - * @param converter + * @param converter must not be {@literal null}. * @return */ + @Nullable String toDotPath(Converter converter); /** * Returns a {@link String} path with the given delimiter based on the {@link PersistentProperty#getName()}. * - * @param delimiter will default to {@code .} if {@literal null} is given. + * @param delimiter must not be {@literal null}. * @return */ + @Nullable String toPath(String delimiter); /** * Returns a {@link String} path with the given delimiter using the given {@link Converter} for * {@link PersistentProperty} to String conversion. * - * @param delimiter will default to {@code .} if {@literal null} is given. - * @param converter will default to use {@link PersistentProperty#getName()}. + * @param delimiter must not be {@literal null}. + * @param converter must not be {@literal null}. * @return */ + @Nullable String toPath(String delimiter, Converter converter); /** @@ -66,6 +71,7 @@ public interface PersistentPropertyPath> extends * * @return */ + @Nullable T getLeafProperty(); /** @@ -75,13 +81,14 @@ public interface PersistentPropertyPath> extends * * @return */ + @Nullable T getBaseProperty(); /** * Returns whether the given {@link PersistentPropertyPath} is a base path of the current one. This means that the * current {@link PersistentPropertyPath} is basically an extension of the given one. * - * @param path + * @param path must not be {@literal null}. * @return */ boolean isBasePathOf(PersistentPropertyPath path); @@ -91,7 +98,7 @@ public interface PersistentPropertyPath> extends * {@code foo.bar} and a given base {@code foo} it would return {@code bar}. If the given path is not a base of the * the current one the current {@link PersistentPropertyPath} will be returned as is. * - * @param base + * @param base must not be {@literal null}. * @return */ PersistentPropertyPath getExtensionForBaseOf(PersistentPropertyPath base); diff --git a/src/main/java/org/springframework/data/mapping/context/package-info.java b/src/main/java/org/springframework/data/mapping/context/package-info.java index 8d05431bc..6ffe039ff 100644 --- a/src/main/java/org/springframework/data/mapping/context/package-info.java +++ b/src/main/java/org/springframework/data/mapping/context/package-info.java @@ -1,4 +1,5 @@ /** * Mapping context API and implementation base classes. */ -package org.springframework.data.mapping.context; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.mapping.context; diff --git a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java index 65f52e22a..ea4c3f971 100644 --- a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java @@ -31,9 +31,10 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.util.Lazy; +import org.springframework.data.util.ReflectionUtils; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * Simple implementation of {@link PersistentProperty}. @@ -48,7 +49,7 @@ public abstract class AbstractPersistentProperty

private static final Field CAUSE_FIELD; static { - CAUSE_FIELD = ReflectionUtils.findField(Throwable.class, "cause"); + CAUSE_FIELD = ReflectionUtils.findRequiredField(Throwable.class, "cause"); } private final String name; @@ -56,14 +57,15 @@ public abstract class AbstractPersistentProperty

private final Class rawType; private final Lazy> association; private final @Getter PersistentEntity owner; - private final @Getter(AccessLevel.PROTECTED) Property property; + + private final @Getter(value = AccessLevel.PROTECTED, onMethod = @__(@SuppressWarnings("null"))) Property property; private final Lazy hashCode; private final Lazy usePropertyAccess; private final Lazy>> entityTypeInformation; - private final Method getter; - private final Method setter; - private final Field field; + private final @Getter(onMethod = @__(@Nullable)) Method getter; + private final @Getter(onMethod = @__(@Nullable)) Method setter; + private final @Getter(onMethod = @__(@Nullable)) Field field; public AbstractPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { @@ -81,6 +83,7 @@ public abstract class AbstractPersistentProperty

this.hashCode = Lazy.of(property::hashCode); this.usePropertyAccess = Lazy.of(() -> owner.getType().isInterface() || CAUSE_FIELD.equals(getField())); + this.entityTypeInformation = Lazy.of(() -> Optional.ofNullable(information.getActualType())// .filter(it -> !simpleTypeHolder.isSimpleType(it.getType()))// .filter(it -> !it.isCollectionLike())// @@ -145,38 +148,12 @@ public abstract class AbstractPersistentProperty

.orElseGet(Collections::emptySet); } - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.PersistentProperty#getGetter() - */ - @Override - public Method getGetter() { - return getter; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.PersistentProperty#getSetter() - */ - @Override - public Method getSetter() { - return setter; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.PersistentProperty#getField() - */ - @Override - public Field getField() { - return field; - } - /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getSpelExpression() */ @Override + @Nullable public String getSpelExpression() { return null; } @@ -212,9 +189,10 @@ public abstract class AbstractPersistentProperty

* (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getAssociation() */ + @Nullable @Override public Association

getAssociation() { - return association.get(); + return association.orElse(null); } /* @@ -257,6 +235,7 @@ public abstract class AbstractPersistentProperty

* (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getComponentType() */ + @Nullable @Override public Class getComponentType() { return isMap() || isCollectionLike() ? information.getRequiredComponentType().getType() : null; @@ -266,6 +245,7 @@ public abstract class AbstractPersistentProperty

* (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getMapValueType() */ + @Nullable @Override public Class getMapValueType() { @@ -286,7 +266,7 @@ public abstract class AbstractPersistentProperty

*/ @Override public Class getActualType() { - return information.getActualType().getType(); + return information.getRequiredActualType().getType(); } /* @@ -302,7 +282,7 @@ public abstract class AbstractPersistentProperty

* @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java index 9866741b3..ff04e3976 100644 --- a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java @@ -39,6 +39,7 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.util.Lazy; import org.springframework.data.util.Optionals; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -53,7 +54,7 @@ public abstract class AnnotationBasedPersistentProperty

, Optional> annotationCache = new HashMap<>(); private final Lazy usePropertyAccess = Lazy.of(() -> { @@ -159,6 +160,7 @@ public abstract class AnnotationBasedPersistentProperty

A findAnnotation(Class annotationType) { Assert.notNull(annotationType, "Annotation type must not be null!"); @@ -248,6 +251,7 @@ public abstract class AnnotationBasedPersistentProperty

A findPropertyOrOwnerAnnotation(Class annotationType) { diff --git a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java index 9571bea48..d0d828eed 100644 --- a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java @@ -50,6 +50,7 @@ import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.mapping.TargetAwareIdentifierAccessor; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -69,19 +70,19 @@ public class BasicPersistentEntity> implement private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!"; - private final PreferredConstructor constructor; + private final @Nullable PreferredConstructor constructor; private final TypeInformation information; private final List

properties; private final List

persistentPropertiesCache; - private final Comparator

comparator; + private final @Nullable Comparator

comparator; private final Set> associations; private final Map propertyCache; private final Map, Optional> annotationCache; private final MultiValueMap, P> propertyAnnotationCache; - private P idProperty; - private P versionProperty; + private @Nullable P idProperty; + private @Nullable P versionProperty; private PersistentPropertyAccessorFactory propertyAccessorFactory; private final Lazy typeAlias; @@ -103,7 +104,7 @@ public class BasicPersistentEntity> implement * @param information must not be {@literal null}. * @param comparator can be {@literal null}. */ - public BasicPersistentEntity(TypeInformation information, Comparator

comparator) { + public BasicPersistentEntity(TypeInformation information, @Nullable Comparator

comparator) { Assert.notNull(information, "Information must not be null!"); @@ -125,6 +126,7 @@ public class BasicPersistentEntity> implement * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getPersistenceConstructor() */ + @Nullable public PreferredConstructor getPersistenceConstructor() { return constructor; } @@ -165,6 +167,7 @@ public class BasicPersistentEntity> implement * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getIdProperty() */ + @Nullable public P getIdProperty() { return idProperty; } @@ -173,6 +176,7 @@ public class BasicPersistentEntity> implement * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getVersionProperty() */ + @Nullable public P getVersionProperty() { return versionProperty; } @@ -223,12 +227,15 @@ public class BasicPersistentEntity> implement if (property.isVersionProperty()) { - if (this.versionProperty != null) { + P versionProperty = this.versionProperty; - throw new MappingException(String.format( - "Attempt to add version property %s but already have property %s registered " - + "as version. Check your mapping configuration!", - property.getField(), this.versionProperty.getField())); + if (versionProperty != null) { + + throw new MappingException( + String.format( + "Attempt to add version property %s but already have property %s registered " + + "as version. Check your mapping configuration!", + property.getField(), versionProperty.getField())); } this.versionProperty = property; @@ -241,15 +248,18 @@ public class BasicPersistentEntity> implement * @param property the new id property candidate, will never be {@literal null}. * @return the given id property or {@literal null} if the given property is not an id property. */ + @Nullable protected P returnPropertyIfBetterIdPropertyCandidateOrNull(P property) { if (!property.isIdProperty()) { return null; } - if (this.idProperty != null) { + P idProperty = this.idProperty; + + if (idProperty != null) { throw new MappingException(String.format("Attempt to add id property %s but already have property %s registered " - + "as id. Check your mapping configuration!", property.getField(), this.idProperty.getField())); + + "as id. Check your mapping configuration!", property.getField(), idProperty.getField())); } return property; @@ -273,6 +283,7 @@ public class BasicPersistentEntity> implement * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.String) */ @Override + @Nullable public P getPersistentProperty(String name) { return propertyCache.get(name); } @@ -384,8 +395,8 @@ public class BasicPersistentEntity> implement * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#findAnnotation(java.lang.Class) */ + @Nullable @Override - @SuppressWarnings("unchecked") public A findAnnotation(Class annotationType) { return doFindAnnotation(annotationType).orElse(null); } @@ -399,6 +410,7 @@ public class BasicPersistentEntity> implement return doFindAnnotation(annotationType).isPresent(); } + @SuppressWarnings("unchecked") private Optional doFindAnnotation(Class annotationType) { return (Optional) annotationCache.computeIfAbsent(annotationType, @@ -492,6 +504,7 @@ public class BasicPersistentEntity> implement * @see org.springframework.data.mapping.IdentifierAccessor#getIdentifier() */ @Override + @Nullable public Object getIdentifier() { return null; } @@ -513,7 +526,16 @@ public class BasicPersistentEntity> implement * (non-Javadoc) * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ - public int compare(Association

left, Association

right) { + public int compare(@Nullable Association

left, @Nullable Association

right) { + + if (left == null) { + throw new IllegalArgumentException("Left argument must not be null!"); + } + + if (right == null) { + throw new IllegalArgumentException("Right argument must not be null!"); + } + return delegate.compare(left.getInverse(), right.getInverse()); } } diff --git a/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java b/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java index 63bb11906..35177c5e0 100644 --- a/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java +++ b/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -49,7 +50,7 @@ class BeanWrapper implements PersistentPropertyAccessor { * (non-Javadoc) * @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.util.Optional) */ - public void setProperty(PersistentProperty property, Object value) { + public void setProperty(PersistentProperty property, @Nullable Object value) { Assert.notNull(property, "PersistentProperty must not be null!"); @@ -57,20 +58,17 @@ class BeanWrapper implements PersistentPropertyAccessor { if (!property.usePropertyAccess()) { - Field field = property.getField(); + Field field = property.getRequiredField(); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, bean, value); return; } - Method setter = property.getSetter(); + Method setter = property.getRequiredSetter(); - if (setter != null) { - - ReflectionUtils.makeAccessible(setter); - ReflectionUtils.invokeMethod(setter, bean, value); - } + ReflectionUtils.makeAccessible(setter); + ReflectionUtils.invokeMethod(setter, bean, value); } catch (IllegalStateException e) { throw new MappingException("Could not set object property!", e); @@ -81,6 +79,7 @@ class BeanWrapper implements PersistentPropertyAccessor { * (non-Javadoc) * @see org.springframework.data.mapping.PersistentPropertyAccessor#getProperty(org.springframework.data.mapping.PersistentProperty) */ + @Nullable public Object getProperty(PersistentProperty property) { return getProperty(property, property.getType()); } @@ -94,7 +93,7 @@ class BeanWrapper implements PersistentPropertyAccessor { * @return * @throws MappingException in case an exception occured when accessing the property. */ - @SuppressWarnings("unchecked") + @Nullable public Object getProperty(PersistentProperty property, Class type) { Assert.notNull(property, "PersistentProperty must not be null!"); @@ -103,20 +102,16 @@ class BeanWrapper implements PersistentPropertyAccessor { if (!property.usePropertyAccess()) { - Field field = property.getField(); + Field field = property.getRequiredField(); + ReflectionUtils.makeAccessible(field); return ReflectionUtils.getField(field, bean); } - Method getter = property.getGetter(); - - if (getter != null) { - - ReflectionUtils.makeAccessible(getter); - return ReflectionUtils.invokeMethod(getter, bean); - } + Method getter = property.getRequiredGetter(); - return null; + ReflectionUtils.makeAccessible(getter); + return ReflectionUtils.invokeMethod(getter, bean); } catch (IllegalStateException e) { throw new MappingException( diff --git a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java index e588910dd..00323bb0c 100644 --- a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java +++ b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java @@ -50,8 +50,8 @@ import org.springframework.data.mapping.SimpleAssociationHandler; import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.util.Optionals; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * A factory that can generate byte code to speed-up dynamic property access. Uses the {@link PersistentEntity}'s @@ -530,6 +530,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert * Retrieve all classes which are involved in property/getter/setter declarations as these elements may be * distributed across the type hierarchy. */ + @SuppressWarnings("null") private static List> getPropertyDeclaratingClasses(List> persistentProperties) { return persistentProperties.stream().flatMap(property -> { @@ -854,7 +855,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert } } else { - Field field = property.getField(); + Field field = property.getRequiredField(); + if (generateMethodHandle(entity, field)) { // $fieldGetter.invoke(bean) mv.visitFieldInsn(GETSTATIC, internalClassName, fieldGetterName(property), @@ -1111,7 +1113,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert return !(Modifier.isPrivate(modifiers) || Modifier.isProtected(modifiers) || Modifier.isPublic(modifiers)); } - private static boolean generateSetterMethodHandle(PersistentEntity entity, Field field) { + private static boolean generateSetterMethodHandle(PersistentEntity entity, @Nullable Field field) { if (field == null) { return false; @@ -1125,7 +1127,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert * its declaring class. Use also {@link java.lang.invoke.MethodHandle} if visibility is protected/package-default * and packages of the declaring types are different. */ - private static boolean generateMethodHandle(PersistentEntity entity, Member member) { + private static boolean generateMethodHandle(PersistentEntity entity, @Nullable Member member) { if (member == null) { return false; @@ -1143,6 +1145,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert return false; } } + return true; } @@ -1358,8 +1361,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override - public int compareTo(PropertyStackAddress o) { - return (hash < o.hash) ? -1 : ((hash == o.hash) ? 0 : 1); + public int compareTo(@SuppressWarnings("null") PropertyStackAddress o) { + return hash < o.hash ? -1 : hash == o.hash ? 0 : 1; } } @@ -1398,8 +1401,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert ClassLoader classLoader = entity.getType().getClassLoader(); Class classLoaderClass = classLoader.getClass(); - Method defineClass = ReflectionUtils.findMethod(classLoaderClass, "defineClass", String.class, byte[].class, - Integer.TYPE, Integer.TYPE, ProtectionDomain.class); + Method defineClass = org.springframework.data.util.ReflectionUtils.findRequiredMethod(classLoaderClass, + "defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class); defineClass.setAccessible(true); diff --git a/src/main/java/org/springframework/data/mapping/model/ConvertingPropertyAccessor.java b/src/main/java/org/springframework/data/mapping/model/ConvertingPropertyAccessor.java index 0f2a976e1..edb7613c3 100644 --- a/src/main/java/org/springframework/data/mapping/model/ConvertingPropertyAccessor.java +++ b/src/main/java/org/springframework/data/mapping/model/ConvertingPropertyAccessor.java @@ -18,6 +18,7 @@ package org.springframework.data.mapping.model; import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -55,7 +56,7 @@ public class ConvertingPropertyAccessor implements PersistentPropertyAccessor { * @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.lang.Object) */ @Override - public void setProperty(PersistentProperty property, Object value) { + public void setProperty(PersistentProperty property, @Nullable Object value) { accessor.setProperty(property, convertIfNecessary(value, property.getType())); } @@ -63,6 +64,7 @@ public class ConvertingPropertyAccessor implements PersistentPropertyAccessor { * (non-Javadoc) * @see org.springframework.data.mapping.PersistentPropertyAccessor#getProperty(org.springframework.data.mapping.PersistentProperty) */ + @Nullable @Override public Object getProperty(PersistentProperty property) { return accessor.getProperty(property); @@ -75,6 +77,7 @@ public class ConvertingPropertyAccessor implements PersistentPropertyAccessor { * @param targetType must not be {@literal null}. * @return */ + @Nullable public T getProperty(PersistentProperty property, Class targetType) { Assert.notNull(property, "PersistentProperty must not be null!"); @@ -100,8 +103,9 @@ public class ConvertingPropertyAccessor implements PersistentPropertyAccessor { * @param type must not be {@literal null}. * @return */ + @Nullable @SuppressWarnings("unchecked") - private T convertIfNecessary(Object source, Class type) { + private T convertIfNecessary(@Nullable Object source, Class type) { return (T) (source == null ? null : type.isAssignableFrom(source.getClass()) ? source : conversionService.convert(source, type)); } diff --git a/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java b/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java index 26d287b16..20c9e1afc 100644 --- a/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java +++ b/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java @@ -16,10 +16,14 @@ package org.springframework.data.mapping.model; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.Nullable; /** * {@link ParameterValueProvider} implementation that evaluates the {@link Parameter}s key against @@ -27,23 +31,17 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; * * @author Oliver Gierke */ +@RequiredArgsConstructor public class DefaultSpELExpressionEvaluator implements SpELExpressionEvaluator { - private final Object source; - private final SpELContext factory; - - /** - * @param source - * @param factory - */ - public DefaultSpELExpressionEvaluator(Object source, SpELContext factory) { - this.source = source; - this.factory = factory; - } + private final @NonNull Object source; + private final @NonNull SpELContext factory; - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.mapping.model.SpELExpressionEvaluator#evaluate(java.lang.String) */ + @Nullable @SuppressWarnings("unchecked") public T evaluate(String expression) { diff --git a/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java b/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java index 2cdce0b01..a63ee3ce7 100644 --- a/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java +++ b/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java @@ -20,6 +20,7 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.TargetAwareIdentifierAccessor; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -57,6 +58,7 @@ public class IdPropertyIdentifierAccessor extends TargetAwareIdentifierAccessor * (non-Javadoc) * @see org.springframework.data.keyvalue.core.IdentifierAccessor#getIdentifier() */ + @Nullable public Object getIdentifier() { return accessor.getProperty(idProperty); } diff --git a/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java b/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java index 9a9d3a57c..68e7a8299 100644 --- a/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java +++ b/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java @@ -22,6 +22,7 @@ import java.util.Optional; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PreferredConstructor; +import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** @@ -63,8 +64,8 @@ public class MappingInstantiationException extends RuntimeException { this(Optional.empty(), arguments, null, cause); } - private MappingInstantiationException(Optional> entity, List arguments, String message, - Exception cause) { + private MappingInstantiationException(Optional> entity, List arguments, + @Nullable String message, Exception cause) { super(buildExceptionMessage(entity, arguments, message), cause); @@ -75,13 +76,13 @@ public class MappingInstantiationException extends RuntimeException { } private static String buildExceptionMessage(Optional> entity, List arguments, - String defaultMessage) { + @Nullable String defaultMessage) { return entity.map(it -> { Optional> constructor = Optional.ofNullable(it.getPersistenceConstructor()); - List toStringArgs = new ArrayList<>(arguments.size()); + for (Object o : arguments) { toStringArgs.add(ObjectUtils.nullSafeToString(o)); } diff --git a/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java index 324061990..333ce15fd 100644 --- a/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java +++ b/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java @@ -17,6 +17,7 @@ package org.springframework.data.mapping.model; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.lang.Nullable; /** * Callback interface to lookup values for a given {@link Parameter}. @@ -31,5 +32,6 @@ public interface ParameterValueProvider

> { * @param parameter must not be {@literal null}. * @return */ + @Nullable T getParameterValue(Parameter parameter); } diff --git a/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java index 420903d75..c38e8d587 100644 --- a/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java +++ b/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java @@ -15,12 +15,15 @@ */ package org.springframework.data.mapping.model; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PreferredConstructor.Parameter; -import org.springframework.util.Assert; +import org.springframework.lang.Nullable; /** * {@link ParameterValueProvider} based on a {@link PersistentEntity} to use a {@link PropertyValueProvider} to lookup @@ -30,50 +33,39 @@ import org.springframework.util.Assert; * * @author Oliver Gierke */ +@RequiredArgsConstructor public class PersistentEntityParameterValueProvider

> implements ParameterValueProvider

{ - private final PersistentEntity entity; - private final PropertyValueProvider

provider; - private final Object parent; - - /** - * Creates a new {@link PersistentEntityParameterValueProvider} for the given {@link PersistentEntity} and - * {@link PropertyValueProvider}. - * - * @param entity must not be {@literal null}. - * @param provider must not be {@literal null}. - * @param parent the parent object being created currently, can be {@literal null}. - */ - public PersistentEntityParameterValueProvider(PersistentEntity entity, PropertyValueProvider

provider, - Object parent) { - - Assert.notNull(entity, "Entity must not be null!"); - Assert.notNull(provider, "Provider must not be null!"); - - this.entity = entity; - this.provider = provider; - this.parent = parent; - } + private final @NonNull PersistentEntity entity; + private final @NonNull PropertyValueProvider

provider; + private final @Nullable Object parent; /* * (non-Javadoc) * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) */ + @Nullable @SuppressWarnings("unchecked") public T getParameterValue(Parameter parameter) { PreferredConstructor constructor = entity.getPersistenceConstructor(); - if (constructor.isEnclosingClassParameter(parameter)) { + if (constructor != null && constructor.isEnclosingClassParameter(parameter)) { return (T) parent; } - P property = entity.getPersistentProperty(parameter.getName()); + String name = parameter.getName(); + + if (name == null) { + throw new MappingException(String.format("Parameter %s does not have a name!", parameter)); + } + + P property = entity.getPersistentProperty(name); if (property == null) { - throw new MappingException(String.format("No property %s found on entity %s to bind constructor parameter to!", - parameter.getName(), entity.getType())); + throw new MappingException( + String.format("No property %s found on entity %s to bind constructor parameter to!", name, entity.getType())); } return provider.getPropertyValue(property); diff --git a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java index 694a884cf..eb8ed3fcf 100644 --- a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java +++ b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java @@ -27,6 +27,7 @@ import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** * Helper class to find a {@link PreferredConstructor}. @@ -40,7 +41,7 @@ public class PreferredConstructorDiscoverer> private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); - private PreferredConstructor constructor; + private @Nullable PreferredConstructor constructor; /** * Creates a new {@link PreferredConstructorDiscoverer} for the given type. @@ -66,7 +67,7 @@ public class PreferredConstructorDiscoverer> * @param type must not be {@literal null}. * @param entity */ - protected PreferredConstructorDiscoverer(TypeInformation type, PersistentEntity entity) { + protected PreferredConstructorDiscoverer(TypeInformation type, @Nullable PersistentEntity entity) { boolean noArgConstructorFound = false; int numberOfArgConstructors = 0; @@ -106,7 +107,7 @@ public class PreferredConstructorDiscoverer> @SuppressWarnings({ "unchecked", "rawtypes" }) private PreferredConstructor buildPreferredConstructor(Constructor constructor, - TypeInformation typeInformation, PersistentEntity entity) { + TypeInformation typeInformation, @Nullable PersistentEntity entity) { List> parameterTypes = typeInformation.getParameterTypes(constructor); @@ -136,6 +137,7 @@ public class PreferredConstructorDiscoverer> * * @return */ + @Nullable public PreferredConstructor getConstructor() { return constructor; } diff --git a/src/main/java/org/springframework/data/mapping/model/Property.java b/src/main/java/org/springframework/data/mapping/model/Property.java index 47d9acbb4..125c028e3 100644 --- a/src/main/java/org/springframework/data/mapping/model/Property.java +++ b/src/main/java/org/springframework/data/mapping/model/Property.java @@ -22,8 +22,11 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Optional; +import java.util.function.Function; import org.springframework.data.util.Lazy; +import org.springframework.data.util.Optionals; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -43,20 +46,28 @@ public class Property { private final Optional getter; private final Optional setter; + private final Lazy name; + private final Lazy toString; + private Property(Optional field, Optional descriptor) { + Assert.isTrue(Optionals.isAnyPresent(field, descriptor), "Either field or descriptor has to be given!"); + this.field = field; this.descriptor = descriptor; - this.hashCode = Lazy.of(this::computeHashCode); - this.rawType = field.> map(Field::getType) - .orElseGet(() -> descriptor.map(PropertyDescriptor::getPropertyType)// - .orElse(null)); + + this.rawType = withFieldOrDescriptor(Field::getType, PropertyDescriptor::getPropertyType); + this.hashCode = Lazy.of(() -> withFieldOrDescriptor(Object::hashCode)); + this.name = Lazy.of(() -> withFieldOrDescriptor(Field::getName, FeatureDescriptor::getName)); + this.toString = Lazy.of(() -> withFieldOrDescriptor(Object::toString)); this.getter = descriptor.map(PropertyDescriptor::getReadMethod)// - .filter(it -> getType() != null).filter(it -> getType().isAssignableFrom(it.getReturnType())); + .filter(it -> getType() != null)// + .filter(it -> getType().isAssignableFrom(it.getReturnType())); this.setter = descriptor.map(PropertyDescriptor::getWriteMethod)// - .filter(it -> getType() != null).filter(it -> it.getParameterTypes()[0].isAssignableFrom(getType())); + .filter(it -> getType() != null)// + .filter(it -> it.getParameterTypes()[0].isAssignableFrom(getType())); } /** @@ -88,10 +99,12 @@ public class Property { } /** - * Creates a new {@link Property} for the given {@link PropertyDescriptor}. + * Creates a new {@link Property} for the given {@link PropertyDescriptor}. The creation might fail if the given + * property is not representing a proper property. * * @param descriptor must not be {@literal null}. * @return + * @see #supportsStandalone(PropertyDescriptor) */ public static Property of(PropertyDescriptor descriptor) { @@ -100,6 +113,20 @@ public class Property { return new Property(Optional.empty(), Optional.of(descriptor)); } + /** + * Returns whether the given {@link PropertyDescriptor} is supported in for standalone creation of a {@link Property} + * instance. + * + * @param descriptor + * @return + */ + public static boolean supportsStandalone(PropertyDescriptor descriptor) { + + Assert.notNull(descriptor, "PropertDescriptor must not be null!"); + + return descriptor.getPropertyType() != null; + } + /** * Returns whether the property is backed by a field. * @@ -142,10 +169,7 @@ public class Property { * @return will never be {@literal null}. */ public String getName() { - - return field.map(Field::getName)// - .orElseGet(() -> descriptor.map(FeatureDescriptor::getName)// - .orElseThrow(IllegalStateException::new)); + return this.name.get(); } /** @@ -157,27 +181,12 @@ public class Property { return rawType; } - private int computeHashCode() { - - return this.field.map(Field::hashCode) - .orElseGet(() -> this.descriptor.map(PropertyDescriptor::hashCode).orElseThrow(IllegalStateException::new)); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return hashCode.get(); - } - /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; @@ -192,13 +201,47 @@ public class Property { return this.field.isPresent() ? this.field.equals(that.field) : this.descriptor.equals(that.descriptor); } + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return hashCode.get(); + } + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - return field.map(Object::toString) - .orElseGet(() -> descriptor.map(Object::toString).orElseThrow(IllegalStateException::new)); + return toString.get(); + } + + /** + * Maps the backing {@link Field} or {@link PropertyDescriptor} using the given {@link Function}. + * + * @param function must not be {@literal null}. + * @return + */ + private T withFieldOrDescriptor(Function function) { + return withFieldOrDescriptor(function, function); + } + + /** + * Maps the backing {@link Field} or {@link PropertyDescriptor} using the given functions. + * + * @param field must not be {@literal null}. + * @param descriptor must not be {@literal null}. + * @return + */ + private T withFieldOrDescriptor(Function field, + Function descriptor) { + + return Optionals.firstNonEmpty(// + () -> this.field.map(field), // + () -> this.descriptor.map(descriptor))// + .orElseThrow(() -> new IllegalStateException("Should not occur! Either field or descriptor has to be given")); } } diff --git a/src/main/java/org/springframework/data/mapping/model/SpELContext.java b/src/main/java/org/springframework/data/mapping/model/SpELContext.java index 9773bf2fa..51e365510 100644 --- a/src/main/java/org/springframework/data/mapping/model/SpELContext.java +++ b/src/main/java/org/springframework/data/mapping/model/SpELContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-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. @@ -22,6 +22,8 @@ import org.springframework.expression.ExpressionParser; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Simple factory to create {@link SpelExpressionParser} and {@link EvaluationContext} instances. @@ -32,7 +34,7 @@ public class SpELContext { private final SpelExpressionParser parser; private final PropertyAccessor accessor; - private final BeanFactory factory; + private final @Nullable BeanFactory factory; /** * Creates a new {@link SpELContext} with the given {@link PropertyAccessor}. Defaults the @@ -75,7 +77,9 @@ public class SpELContext { * @param parser * @param factory */ - private SpELContext(PropertyAccessor accessor, SpelExpressionParser parser, BeanFactory factory) { + private SpELContext(PropertyAccessor accessor, @Nullable SpelExpressionParser parser, @Nullable BeanFactory factory) { + + Assert.notNull(accessor, "PropertyAccessor must not be null!"); this.parser = parser == null ? new SpelExpressionParser() : parser; this.accessor = accessor; @@ -97,10 +101,7 @@ public class SpELContext { public EvaluationContext getEvaluationContext(Object source) { StandardEvaluationContext evaluationContext = new StandardEvaluationContext(source); - - if (accessor != null) { - evaluationContext.addPropertyAccessor(accessor); - } + evaluationContext.addPropertyAccessor(accessor); if (factory != null) { evaluationContext.setBeanResolver(new BeanFactoryResolver(factory)); @@ -108,5 +109,4 @@ public class SpELContext { return evaluationContext; } - } diff --git a/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java b/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java index bbad60cd1..71e23636f 100644 --- a/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java +++ b/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mapping.model; +import org.springframework.lang.Nullable; + /** * SPI for components that can evaluate Spring EL expressions. * @@ -28,5 +30,6 @@ public interface SpELExpressionEvaluator { * @param expression * @return */ + @Nullable T evaluate(String expression); -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java index 67306d287..3fe3b4685 100644 --- a/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java +++ b/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java @@ -15,50 +15,34 @@ */ package org.springframework.data.mapping.model; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor.Parameter; -import org.springframework.util.Assert; +import org.springframework.lang.Nullable; /** - * {@link ParameterValueProvider} that can be used to front a {@link ParameterValueProvider} delegate to prefer a Spel + * {@link ParameterValueProvider} that can be used to front a {@link ParameterValueProvider} delegate to prefer a SpEL * expression evaluation over directly resolving the parameter value with the delegate. * * @author Oliver Gierke * @author Mark Paluch */ +@RequiredArgsConstructor public class SpELExpressionParameterValueProvider

> implements ParameterValueProvider

{ - private final SpELExpressionEvaluator evaluator; - private final ParameterValueProvider

delegate; - private final ConversionService conversionService; - - /** - * Creates a new {@link SpELExpressionParameterValueProvider} using the given {@link SpELExpressionEvaluator}, - * {@link ConversionService} and {@link ParameterValueProvider} delegate to forward calls to, that resolve parameters - * that do not have a Spel expression configured with them. - * - * @param evaluator must not be {@literal null}. - * @param conversionService must not be {@literal null}. - * @param delegate must not be {@literal null}. - */ - public SpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, ConversionService conversionService, - ParameterValueProvider

delegate) { - - Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!"); - Assert.notNull(conversionService, "ConversionService must not be null!"); - Assert.notNull(delegate, "ParameterValueProvider delegate must not be null!"); - - this.evaluator = evaluator; - this.conversionService = conversionService; - this.delegate = delegate; - } + private final @NonNull SpELExpressionEvaluator evaluator; + private final @NonNull ConversionService conversionService; + private final @NonNull ParameterValueProvider

delegate; /* * (non-Javadoc) * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) */ + @Nullable public T getParameterValue(Parameter parameter) { if (!parameter.hasSpelExpression()) { @@ -77,6 +61,7 @@ public class SpELExpressionParameterValueProvider

T potentiallyConvertSpelValue(Object object, Parameter parameter) { return conversionService.convert(object, parameter.getRawType()); } diff --git a/src/main/java/org/springframework/data/mapping/model/package-info.java b/src/main/java/org/springframework/data/mapping/model/package-info.java index 255afe5b6..d45487b53 100644 --- a/src/main/java/org/springframework/data/mapping/model/package-info.java +++ b/src/main/java/org/springframework/data/mapping/model/package-info.java @@ -1,4 +1,5 @@ /** * Core implementation of the mapping subsystem's model. */ -package org.springframework.data.mapping.model; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.mapping.model; diff --git a/src/main/java/org/springframework/data/mapping/package-info.java b/src/main/java/org/springframework/data/mapping/package-info.java index 471d0e38d..74503fe6e 100644 --- a/src/main/java/org/springframework/data/mapping/package-info.java +++ b/src/main/java/org/springframework/data/mapping/package-info.java @@ -1,4 +1,5 @@ /** * Base package for the mapping subsystem. */ -package org.springframework.data.mapping; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.mapping; diff --git a/src/main/java/org/springframework/data/projection/Accessor.java b/src/main/java/org/springframework/data/projection/Accessor.java index 04ab154b9..29f2a15d7 100644 --- a/src/main/java/org/springframework/data/projection/Accessor.java +++ b/src/main/java/org/springframework/data/projection/Accessor.java @@ -44,10 +44,14 @@ public final class Accessor { Assert.notNull(method, "Method must not be null!"); - this.descriptor = BeanUtils.findPropertyForMethod(method); - this.method = method; + PropertyDescriptor descriptor = BeanUtils.findPropertyForMethod(method); + + if (descriptor == null) { + throw new IllegalArgumentException(String.format("Invoked method %s is no accessor method!", method)); + } - Assert.notNull(descriptor, () -> String.format("Invoked method %s is no accessor method!", method)); + this.descriptor = descriptor; + this.method = method; } /** diff --git a/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java index 3ab79a493..24e6b4002 100644 --- a/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java @@ -25,6 +25,8 @@ import java.util.Arrays; import java.util.Map; import java.util.Optional; +import javax.annotation.Nullable; + import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.ProxyMethodInvocation; @@ -48,8 +50,9 @@ public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); diff --git a/src/main/java/org/springframework/data/projection/MapAccessingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/MapAccessingMethodInterceptor.java index bf856c38a..fb8d6deb4 100644 --- a/src/main/java/org/springframework/data/projection/MapAccessingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/MapAccessingMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -21,6 +21,8 @@ import lombok.RequiredArgsConstructor; import java.lang.reflect.Method; import java.util.Map; +import javax.annotation.Nullable; + import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.util.ReflectionUtils; @@ -40,8 +42,9 @@ class MapAccessingMethodInterceptor implements MethodInterceptor { * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); diff --git a/src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java index cbf48824b..88914eb2e 100644 --- a/src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java @@ -26,12 +26,15 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import javax.annotation.Nonnull; + import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.core.CollectionFactory; import org.springframework.core.convert.ConversionService; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -54,8 +57,9 @@ class ProjectingMethodInterceptor implements MethodInterceptor { * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") @Nonnull MethodInvocation invocation) throws Throwable { Object result = delegate.invoke(invocation); @@ -121,6 +125,7 @@ class ProjectingMethodInterceptor implements MethodInterceptor { return result; } + @Nullable private Object getProjection(Object result, Class returnType) { return result == null || ClassUtils.isAssignable(returnType, result.getClass()) ? result : factory.createProjection(returnType, result); diff --git a/src/main/java/org/springframework/data/projection/ProjectionFactory.java b/src/main/java/org/springframework/data/projection/ProjectionFactory.java index ecd2e2904..c03ea2612 100644 --- a/src/main/java/org/springframework/data/projection/ProjectionFactory.java +++ b/src/main/java/org/springframework/data/projection/ProjectionFactory.java @@ -15,6 +15,8 @@ */ package org.springframework.data.projection; +import org.springframework.lang.Nullable; + /** * A factory to create projecting instances for other objects usually used to allow easy creation of representation * projections to define which properties of a domain objects shall be exported in which way. @@ -29,11 +31,23 @@ public interface ProjectionFactory { * the implementations. * * @param projectionType the type to create, must not be {@literal null}. - * @param source the object to create a projection for, can be {@literal null} + * @param source the object to create a projection for, must not be {@literal null}. * @return */ T createProjection(Class projectionType, Object source); + /** + * Creates a projection to the given type for the given nullable source. + * + * @param projectionType must not be {@literal null}. + * @param source can be {@literal null}. + * @return + */ + @Nullable + default T createNullableProjection(Class projectionType, @Nullable Object source) { + return source == null ? null : createProjection(projectionType, source); + } + /** * Creates a projection instance for the given type. * diff --git a/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java index f1e9cd8ee..11abdca17 100644 --- a/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java @@ -23,6 +23,7 @@ import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -52,8 +53,9 @@ class PropertyAccessingMethodInterceptor implements MethodInterceptor { * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); diff --git a/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java b/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java index 4dac7cbd3..cd78e1253 100644 --- a/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java +++ b/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java @@ -28,6 +28,7 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; @@ -48,7 +49,7 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware private final List factories; private final ConversionService conversionService; private final Map, ProjectionInformation> projectionInformationCache = new ConcurrentReferenceHashMap<>(); - private ClassLoader classLoader; + private @Nullable ClassLoader classLoader; /** * Creates a new {@link ProxyProjectionFactory}. @@ -94,9 +95,10 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware public T createProjection(Class projectionType, Object source) { Assert.notNull(projectionType, "Projection type must not be null!"); + Assert.notNull(source, "Source must not be null!"); Assert.isTrue(projectionType.isInterface(), "Projection type must be an interface!"); - if (source == null || projectionType.isInstance(source)) { + if (projectionType.isInstance(source)) { return (T) source; } @@ -229,8 +231,9 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { if (invocation.getMethod().equals(GET_TARGET_CLASS_METHOD)) { return targetType; diff --git a/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java b/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java index fa58dcefd..f3b60a9c6 100644 --- a/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java +++ b/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.util.AnnotationDetectionMethodCallback; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -45,7 +46,7 @@ public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory impl private final Map, Boolean> typeCache = new HashMap<>(); private final SpelExpressionParser parser = new SpelExpressionParser(); - private BeanFactory beanFactory; + private @Nullable BeanFactory beanFactory; /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/projection/SpelEvaluatingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/SpelEvaluatingMethodInterceptor.java index b4f8084e0..fb2dd2407 100644 --- a/src/main/java/org/springframework/data/projection/SpelEvaluatingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/SpelEvaluatingMethodInterceptor.java @@ -32,6 +32,7 @@ import org.springframework.expression.ParserContext; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -63,7 +64,7 @@ class SpelEvaluatingMethodInterceptor implements MethodInterceptor { * @param parser must not be {@literal null}. * @param targetInterface must not be {@literal null}. */ - public SpelEvaluatingMethodInterceptor(MethodInterceptor delegate, Object target, BeanFactory beanFactory, + public SpelEvaluatingMethodInterceptor(MethodInterceptor delegate, Object target, @Nullable BeanFactory beanFactory, SpelExpressionParser parser, Class targetInterface) { Assert.notNull(delegate, "Delegate MethodInterceptor must not be null!"); @@ -123,8 +124,9 @@ class SpelEvaluatingMethodInterceptor implements MethodInterceptor { * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Expression expression = expressions.get(invocation.getMethod().hashCode()); diff --git a/src/main/java/org/springframework/data/projection/TargetAware.java b/src/main/java/org/springframework/data/projection/TargetAware.java index a67b6a645..4318ff9d4 100644 --- a/src/main/java/org/springframework/data/projection/TargetAware.java +++ b/src/main/java/org/springframework/data/projection/TargetAware.java @@ -17,6 +17,7 @@ package org.springframework.data.projection; import org.springframework.aop.RawTargetAccess; import org.springframework.core.DecoratingProxy; +import org.springframework.lang.Nullable; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -30,8 +31,9 @@ public interface TargetAware extends org.springframework.aop.TargetClassAware, R /** * Returns the type of the proxy target. * - * @return will never be {@literal null}. + * @return can be {@literal null}. */ + @Nullable @JsonIgnore Class getTargetClass(); diff --git a/src/main/java/org/springframework/data/projection/package-info.java b/src/main/java/org/springframework/data/projection/package-info.java new file mode 100644 index 000000000..75a3a75fa --- /dev/null +++ b/src/main/java/org/springframework/data/projection/package-info.java @@ -0,0 +1,5 @@ +/** + * Projection subsystem. + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.projection; diff --git a/src/main/java/org/springframework/data/querydsl/QSort.java b/src/main/java/org/springframework/data/querydsl/QSort.java index 354cbe1bd..d49758be7 100644 --- a/src/main/java/org/springframework/data/querydsl/QSort.java +++ b/src/main/java/org/springframework/data/querydsl/QSort.java @@ -56,6 +56,7 @@ public class QSort extends Sort implements Serializable { * * @param orderSpecifiers must not be {@literal null}. */ + @SuppressWarnings("deprecation") public QSort(List> orderSpecifiers) { super(toOrders(orderSpecifiers)); diff --git a/src/main/java/org/springframework/data/querydsl/QuerydslUtils.java b/src/main/java/org/springframework/data/querydsl/QuerydslUtils.java index fde96c39b..41a533ac5 100644 --- a/src/main/java/org/springframework/data/querydsl/QuerydslUtils.java +++ b/src/main/java/org/springframework/data/querydsl/QuerydslUtils.java @@ -17,6 +17,7 @@ package org.springframework.data.querydsl; import lombok.experimental.UtilityClass; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import com.querydsl.core.types.Path; @@ -52,7 +53,7 @@ public class QuerydslUtils { * @param tail must not be {@literal null}. * @return */ - private static String toDotPath(Path path, String tail) { + private static String toDotPath(@Nullable Path path, String tail) { if (path == null) { return tail; diff --git a/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java index 071bf97dd..64ba175db 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java +++ b/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java @@ -19,6 +19,7 @@ import java.beans.PropertyDescriptor; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.querydsl.EntityPathResolver; +import org.springframework.lang.Nullable; import com.querydsl.core.types.Path; @@ -56,6 +57,7 @@ interface PathInformation { * * @return */ + @Nullable PropertyDescriptor getLeafPropertyDescriptor(); /** diff --git a/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java index 43aa18849..f1410f22c 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java +++ b/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java @@ -28,6 +28,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; import com.querydsl.core.types.Path; @@ -69,45 +70,46 @@ class PropertyPathInformation implements PathInformation { return PropertyPathInformation.of(PropertyPath.from(path, type)); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.querydsl.binding.MappedPath#getLeafType() + * @see org.springframework.data.querydsl.binding.PathInformation#getLeafType() */ @Override public Class getLeafType() { return path.getLeafProperty().getType(); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.querydsl.binding.MappedPath#getLeafParentType() + * @see org.springframework.data.querydsl.binding.PathInformation#getLeafParentType() */ @Override public Class getLeafParentType() { return path.getLeafProperty().getOwningType().getType(); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.querydsl.binding.MappedPath#getLeafProperty() + * @see org.springframework.data.querydsl.binding.PathInformation#getLeafProperty() */ @Override public String getLeafProperty() { return path.getLeafProperty().getSegment(); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.querydsl.binding.MappedPath#getLeafPropertyDescriptor() + * @see org.springframework.data.querydsl.binding.PathInformation#getLeafPropertyDescriptor() */ + @Nullable @Override public PropertyDescriptor getLeafPropertyDescriptor() { return BeanUtils.getPropertyDescriptor(getLeafParentType(), getLeafProperty()); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.querydsl.binding.MappedPath#toDotPath() + * @see org.springframework.data.querydsl.binding.PathInformation#toDotPath() */ @Override public String toDotPath() { @@ -125,8 +127,7 @@ class PropertyPathInformation implements PathInformation { private static Path reifyPath(EntityPathResolver resolver, PropertyPath path, Optional> base) { - Optional> map = base.filter(it -> it instanceof CollectionPathBase) - .map(CollectionPathBase.class::cast)// + Optional> map = base.filter(it -> it instanceof CollectionPathBase).map(CollectionPathBase.class::cast)// .map(CollectionPathBase::any)// .map(Path.class::cast)// .map(it -> reifyPath(resolver, path, Optional.of(it))); @@ -135,11 +136,14 @@ class PropertyPathInformation implements PathInformation { Path entityPath = base.orElseGet(() -> resolver.createPath(path.getOwningType().getType())); - Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment()); + Field field = org.springframework.data.util.ReflectionUtils.findRequiredField(entityPath.getClass(), + path.getSegment()); Object value = ReflectionUtils.getField(field, entityPath); - if (path.hasNext()) { - return reifyPath(resolver, path.next(), Optional.of((Path) value)); + PropertyPath next = path.next(); + + if (next != null) { + return reifyPath(resolver, next, Optional.of((Path) value)); } return (Path) value; diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java index 186ab43ef..69ce6d8b9 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java @@ -32,6 +32,7 @@ import org.springframework.data.mapping.PropertyReferenceException; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.Optionals; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -239,6 +240,7 @@ public class QuerydslBindings { * @param type must not be {@literal null}. * @return */ + @Nullable PathInformation getPropertyPath(String path, TypeInformation type) { Assert.notNull(path, "Path must not be null!"); @@ -316,8 +318,18 @@ public class QuerydslBindings { * @return */ private static String toDotPath(Optional> path) { - return path.map(it -> it.toString().substring(it.getMetadata().getRootPath().getMetadata().getName().length() + 1)) - .orElse(""); + return path.map(QuerydslBindings::fromRootPath).orElse(""); + } + + private static String fromRootPath(Path path) { + + Path rootPath = path.getMetadata().getRootPath(); + + if (rootPath == null) { + throw new IllegalStateException(String.format("Couldn't find root path on path %s!", path)); + } + + return path.toString().substring(rootPath.getMetadata().getName().length() + 1); } /** @@ -386,7 +398,7 @@ public class QuerydslBindings { */ public class AliasingPathBinder

, T> extends PathBinder { - private final String alias; + private final @Nullable String alias; private final P path; /** @@ -404,7 +416,7 @@ public class QuerydslBindings { * @param alias can be {@literal null}. * @param path must not be {@literal null}. */ - private AliasingPathBinder(String alias, P path) { + private AliasingPathBinder(@Nullable String alias, P path) { super(path); diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java index 91153ff9b..165706c3b 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java @@ -24,6 +24,7 @@ import java.beans.PropertyDescriptor; import org.springframework.beans.BeanUtils; import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.QuerydslUtils; +import org.springframework.lang.Nullable; import com.querydsl.core.types.Path; @@ -55,7 +56,14 @@ class QuerydslPathInformation implements PathInformation { */ @Override public Class getLeafParentType() { - return path.getMetadata().getParent().getType(); + + Path parent = path.getMetadata().getParent(); + + if (parent == null) { + throw new IllegalStateException(String.format("Could not obtain metadata for parent node of %s!", path)); + } + + return parent.getType(); } /* @@ -71,6 +79,7 @@ class QuerydslPathInformation implements PathInformation { * (non-Javadoc) * @see org.springframework.data.querydsl.binding.MappedPath#getLeafPropertyDescriptor() */ + @Nullable @Override public PropertyDescriptor getLeafPropertyDescriptor() { return BeanUtils.getPropertyDescriptor(getLeafParentType(), getLeafProperty()); diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java index 14f4718ef..bfc39f2e7 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java @@ -32,9 +32,9 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import com.querydsl.core.BooleanBuilder; @@ -81,6 +81,7 @@ public class QuerydslPredicateBuilder { * @param bindings the {@link QuerydslBindings} for the predicate. * @return */ + @Nullable public Predicate getPredicate(TypeInformation type, MultiValueMap values, QuerydslBindings bindings) { @@ -173,7 +174,8 @@ public class QuerydslPredicateBuilder { for (String value : source) { target.add(conversionService.canConvert(String.class, targetType) - ? conversionService.convert(value, TypeDescriptor.forObject(value), getTargetTypeDescriptor(path)) : value); + ? conversionService.convert(value, TypeDescriptor.forObject(value), getTargetTypeDescriptor(path)) + : value); } return target; @@ -193,12 +195,17 @@ public class QuerydslPredicateBuilder { Class owningType = path.getLeafParentType(); String leafProperty = path.getLeafProperty(); - if (descriptor == null) { - return TypeDescriptor.nested(ReflectionUtils.findField(owningType, leafProperty), 0); + TypeDescriptor result = descriptor == null // + ? TypeDescriptor + .nested(org.springframework.data.util.ReflectionUtils.findRequiredField(owningType, leafProperty), 0) + : TypeDescriptor + .nested(new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty), 0); + + if (result == null) { + throw new IllegalStateException(String.format("Could not obtain TypeDesciptor for PathInformation %s!", path)); } - return TypeDescriptor - .nested(new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty), 0); + return result; } /** diff --git a/src/main/java/org/springframework/data/querydsl/binding/package-info.java b/src/main/java/org/springframework/data/querydsl/binding/package-info.java new file mode 100644 index 000000000..414d2a8b9 --- /dev/null +++ b/src/main/java/org/springframework/data/querydsl/binding/package-info.java @@ -0,0 +1,5 @@ +/** + * Base classes to implement CDI support for repositories. + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.querydsl.binding; diff --git a/src/main/java/org/springframework/data/querydsl/package-info.java b/src/main/java/org/springframework/data/querydsl/package-info.java index eb3ec65b4..308315177 100644 --- a/src/main/java/org/springframework/data/querydsl/package-info.java +++ b/src/main/java/org/springframework/data/querydsl/package-info.java @@ -3,4 +3,5 @@ * * @see http://www.querydsl.com */ -package org.springframework.data.querydsl; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.querydsl; diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java index 2aaafef30..c52546f26 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java @@ -40,10 +40,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; import org.springframework.data.repository.config.DefaultRepositoryConfiguration; import org.springframework.data.repository.config.RepositoryBeanNameGenerator; +import org.springframework.data.repository.config.SpringDataAnnotationBeanNameGenerator; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -69,11 +70,11 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl private final BeanManager beanManager; private final String passivationId; - private transient T repoInstance; + private transient @Nullable T repoInstance; - private final AnnotationBeanNameGenerator annotationBeanNameGenerator = new AnnotationBeanNameGenerator(); - private final RepositoryBeanNameGenerator beanNameGenerator = - new RepositoryBeanNameGenerator(getClass().getClassLoader()); + private final SpringDataAnnotationBeanNameGenerator annotationBeanNameGenerator = new SpringDataAnnotationBeanNameGenerator(); + private final RepositoryBeanNameGenerator beanNameGenerator = new RepositoryBeanNameGenerator( + getClass().getClassLoader()); /** * Creates a new {@link CdiRepositoryBean}. @@ -183,15 +184,19 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl * (non-Javadoc) * @see javax.enterprise.context.spi.Contextual#create(javax.enterprise.context.spi.CreationalContext) */ - public final T create(CreationalContext creationalContext) { + public final T create(@SuppressWarnings("null") CreationalContext creationalContext) { - if (this.repoInstance != null) { + T repoInstance = this.repoInstance; + + if (repoInstance != null) { LOGGER.debug("Returning eagerly created CDI repository instance for {}.", repositoryType.getName()); - return this.repoInstance; + return repoInstance; } LOGGER.debug("Creating CDI repository bean instance for {}.", repositoryType.getName()); - this.repoInstance = create(creationalContext, repositoryType); + repoInstance = create(creationalContext, repositoryType); + this.repoInstance = repoInstance; + return repoInstance; } @@ -199,7 +204,8 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl * (non-Javadoc) * @see javax.enterprise.context.spi.Contextual#destroy(java.lang.Object, javax.enterprise.context.spi.CreationalContext) */ - public void destroy(T instance, CreationalContext creationalContext) { + public void destroy(@SuppressWarnings("null") T instance, + @SuppressWarnings("null") CreationalContext creationalContext) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("Destroying bean instance %s for repository type '%s'.", instance.toString(), @@ -255,7 +261,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl CdiRepositoryConfiguration cdiRepositoryConfiguration, CustomRepositoryImplementationDetector detector) { String className = getCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration); - Optional beanDefinition = detector.detectCustomImplementation( // + Optional beanDefinition = detector.detectCustomImplementation( // className, // getCustomImplementationBeanName(repositoryType), // Collections.singleton(repositoryType.getPackage().getName()), // @@ -275,7 +281,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl } private String getCustomImplementationBeanName(Class repositoryType) { - return annotationBeanNameGenerator.generateBeanName(new AnnotatedGenericBeanDefinition(repositoryType), null) + return annotationBeanNameGenerator.generateBeanName(new AnnotatedGenericBeanDefinition(repositoryType)) + DEFAULT_CONFIGURATION.getRepositoryImplementationPostfix(); } @@ -374,7 +380,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl * @param creationalContext will never be {@literal null}. * @param repositoryType will never be {@literal null}. * @return - * @deprecated overide {@link #create(CreationalContext, Class, Object)} instead. + * @deprecated override {@link #create(CreationalContext, Class, Object)} instead. */ @Deprecated protected T create(CreationalContext creationalContext, Class repositoryType) { diff --git a/src/main/java/org/springframework/data/repository/cdi/package-info.java b/src/main/java/org/springframework/data/repository/cdi/package-info.java index d8f695547..69bebbc53 100644 --- a/src/main/java/org/springframework/data/repository/cdi/package-info.java +++ b/src/main/java/org/springframework/data/repository/cdi/package-info.java @@ -1,5 +1,5 @@ /** * Base classes to implement CDI support for repositories. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.cdi; - 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 675824750..1db850228 100644 --- a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -20,10 +20,13 @@ 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 javax.annotation.Nonnull; + import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.FilterType; @@ -37,6 +40,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.config.ConfigurationUtils; import org.springframework.data.util.Streamable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -74,18 +78,25 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura * @param metadata must not be {@literal null}. * @param annotation must not be {@literal null}. * @param resourceLoader must not be {@literal null}. - * @param environment + * @param environment must not be {@literal null}. + * @param registry must not be {@literal null}. */ public AnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class annotation, ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) { - super(environment, resourceLoader.getClassLoader(), registry); + super(environment, ConfigurationUtils.getRequiredClassLoader(resourceLoader), registry); Assert.notNull(metadata, "Metadata must not be null!"); Assert.notNull(annotation, "Annotation must not be null!"); Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); - this.attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName())); + Map 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; @@ -166,6 +177,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getSource() */ + @Nonnull public Object getSource() { return configMetadata; } 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 1ab8cadbf..593c110b7 100644 --- a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java +++ b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java @@ -25,6 +25,8 @@ import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.Nullable; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; @@ -61,6 +63,7 @@ public class CustomRepositoryImplementationDetector { * @param configuration the {@link RepositoryConfiguration} to consider. * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found. */ + @SuppressWarnings("deprecation") public Optional detectCustomImplementation(RepositoryConfiguration configuration) { // TODO 2.0: Extract into dedicated interface for custom implementation lookup configuration. @@ -83,7 +86,7 @@ public class CustomRepositoryImplementationDetector { * @param beanNameGenerator must not be {@literal null}. * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found. */ - public Optional detectCustomImplementation(String className, String beanName, + public Optional detectCustomImplementation(String className, @Nullable String beanName, Iterable basePackages, Iterable excludeFilters, Function beanNameGenerator) { 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 58b541002..7bb577749 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java @@ -22,8 +22,10 @@ import java.util.Optional; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.config.ConfigurationUtils; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.util.Streamable; +import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -74,7 +76,7 @@ public class DefaultRepositoryConfiguration getNamedQueriesLocation() { @@ -114,6 +117,7 @@ public class DefaultRepositoryConfiguration getRepositoryInterfaceFrom(BeanDefinition beanDefinition) { - return getRepositoryInterfaceFromFactory(beanDefinition); - } - private Class getRepositoryInterfaceFromFactory(BeanDefinition beanDefinition) { + ValueHolder argumentValue = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, Class.class); + + if (argumentValue == null) { + throw new IllegalStateException( + String.format("Failed to obtain first constructor parameter value of BeanDefinition %s!", beanDefinition)); + } + + Object value = argumentValue.getValue(); + + if (value == null) { - Object value = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, Class.class).getValue(); + throw new IllegalStateException( + String.format("Value of first constructor parameter value of BeanDefinition %s is null!", beanDefinition)); - if (value instanceof Class) { + } else if (value instanceof Class) { return (Class) value; diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java b/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java index 7d2899674..58ab7a8bb 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import javax.annotation.Nonnull; + import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -134,6 +136,7 @@ class RepositoryComponentProvider extends ClassPathScanningCandidateComponentPro * (non-Javadoc) * @see org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#getRegistry() */ + @Nonnull @Override protected BeanDefinitionRegistry getRegistry() { return registry; diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java index 58162881b..ccd418315 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java @@ -20,6 +20,7 @@ import java.util.Optional; import org.springframework.core.type.filter.TypeFilter; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.util.Streamable; +import org.springframework.lang.Nullable; /** * Configuration information for a single repository instance. @@ -97,6 +98,7 @@ public interface RepositoryConfiguration loadRepositoryInterface(RepositoryConfiguration configuration, ResourceLoader loader) { String repositoryInterface = configuration.getRepositoryInterface(); 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 b34674755..3e4113e94 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java @@ -22,6 +22,7 @@ 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; +import org.springframework.lang.Nullable; /** * Interface containing the configurable options for the Spring Data repository subsystem. @@ -35,10 +36,9 @@ public interface RepositoryConfigurationSource { /** * Returns the actual source object that the configuration originated from. Will be used by the tooling to give visual - * feedback on where the repository instances actually come from. - * - * @return must not be {@literal null}. + * feedback on where the repository instances actually come from. @return. */ + @Nullable Object getSource(); /** 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 eebfdb2a7..2014a7558 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java @@ -46,6 +46,7 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository * * @param environment must not be {@literal null}. * @param classLoader must not be {@literal null}. + * @param registry must not be {@literal null}. */ public RepositoryConfigurationSourceSupport(Environment environment, ClassLoader classLoader, BeanDefinitionRegistry registry) { diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationUtils.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationUtils.java index cdbce7d3f..f38cc0a8e 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationUtils.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-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. @@ -28,12 +28,10 @@ import org.springframework.util.Assert; * * @author Oliver Gierke */ -public abstract class RepositoryConfigurationUtils { - - private RepositoryConfigurationUtils() {} +public interface RepositoryConfigurationUtils { /** - * Registeres the given {@link RepositoryConfigurationExtension} to indicate the repository configuration for a + * Registers the given {@link RepositoryConfigurationExtension} to indicate the repository configuration for a * particular store (expressed through the extension's concrete type) has appened. Useful for downstream components * that need to detect exactly that case. The bean definition is marked as lazy-init so that it doesn't get * instantiated if no one really cares. diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryFragmentConfiguration.java b/src/main/java/org/springframework/data/repository/config/RepositoryFragmentConfiguration.java index 6ae0aa6e8..7ef998c04 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryFragmentConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryFragmentConfiguration.java @@ -20,6 +20,7 @@ import lombok.Value; import java.util.Optional; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.data.config.ConfigurationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -67,7 +68,7 @@ public class RepositoryFragmentConfiguration { Assert.notNull(beanDefinition, "Bean definition must not be null!"); this.interfaceName = interfaceName; - this.className = beanDefinition.getBeanClassName(); + this.className = ConfigurationUtils.getRequiredBeanClassName(beanDefinition); this.beanDefinition = Optional.of(beanDefinition); } diff --git a/src/main/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParser.java b/src/main/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParser.java index c75de2154..0fbdff950 100644 --- a/src/main/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParser.java +++ b/src/main/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParser.java @@ -17,6 +17,8 @@ package org.springframework.data.repository.config; import java.util.Arrays; +import javax.annotation.Nonnull; + import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser; @@ -36,6 +38,7 @@ public class ResourceReaderRepositoryPopulatorBeanDefinitionParser extends Abstr * (non-Javadoc) * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClassName(org.w3c.dom.Element) */ + @Nonnull @Override protected String getBeanClassName(Element element) { diff --git a/src/main/java/org/springframework/data/repository/config/SpringDataAnnotationBeanNameGenerator.java b/src/main/java/org/springframework/data/repository/config/SpringDataAnnotationBeanNameGenerator.java new file mode 100644 index 000000000..cd25be9cb --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/SpringDataAnnotationBeanNameGenerator.java @@ -0,0 +1,46 @@ +/* + * 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 org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; + +/** + * Simple extension to Spring's {@link AnnotationBeanNameGenerator} to work without a {@link BeanDefinitionRegistry}. + * Although he API of the extended class requires a non-{@literal null} registry it can actually work without one unless + * {@link AnnotationBeanNameGenerator#buildDefaultBeanName} is overridden and expecting a non-{@literal null} value + * here. + * + * @author Oliver Gierke + * @since 2.0 + * @soundtrack Nils Wülker - Never Left At All (feat. Rob Summerfield) + */ +public class SpringDataAnnotationBeanNameGenerator { + + private final AnnotationBeanNameGenerator delegate = new AnnotationBeanNameGenerator(); + + /** + * Generates a bean name for the given {@link BeanDefinition}. + * + * @param definition must not be {@literal null}. + * @return + */ + @SuppressWarnings("null") + public String generateBeanName(BeanDefinition definition) { + return delegate.generateBeanName(definition, 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 0713d1ee8..c583a08a8 100644 --- a/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java @@ -21,11 +21,13 @@ import java.util.Optional; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.env.Environment; import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.config.ConfigurationUtils; 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.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -64,7 +66,7 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou */ public XmlRepositoryConfigurationSource(Element element, ParserContext context, Environment environment) { - super(environment, context.getReaderContext().getResourceLoader().getClassLoader(), context.getRegistry()); + super(environment, ConfigurationUtils.getRequiredClassLoader(context.getReaderContext()), context.getRegistry()); Assert.notNull(element, "Element must not be null!"); Assert.notNull(context, "Context must not be null!"); @@ -81,6 +83,7 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getSource() */ + @Nullable public Object getSource() { return context.extractSource(element); } diff --git a/src/main/java/org/springframework/data/repository/config/package-info.java b/src/main/java/org/springframework/data/repository/config/package-info.java index fc9cf00b4..2e133ab5e 100644 --- a/src/main/java/org/springframework/data/repository/config/package-info.java +++ b/src/main/java/org/springframework/data/repository/config/package-info.java @@ -1,5 +1,5 @@ /** * Support classes for repository namespace and JavaConfig integration. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.config; - diff --git a/src/main/java/org/springframework/data/repository/core/CrudMethods.java b/src/main/java/org/springframework/data/repository/core/CrudMethods.java index 2b0dfcd62..057208c99 100644 --- a/src/main/java/org/springframework/data/repository/core/CrudMethods.java +++ b/src/main/java/org/springframework/data/repository/core/CrudMethods.java @@ -35,7 +35,7 @@ public interface CrudMethods { * Returns the method to be used for saving entities. Usually signature compatible to * {@link CrudRepository#save(Object)}. * - * @return the method to save entities or {@literal null} if none exposed. + * @return the method to save entities or {@link Optional#empty()} if none exposed. * @see #hasSaveMethod() */ Optional getSaveMethod(); @@ -51,7 +51,7 @@ public interface CrudMethods { * Returns the find all method of the repository. Implementations should prefer more detailed methods like * {@link PagingAndSortingRepository}'s taking a {@link Pageable} or {@link Sort} instance. * - * @return the find all method of the repository or {@literal null} if not available. + * @return the find all method of the repository or {@link Optional#empty()} if not available. * @see #hasFindAllMethod() */ Optional getFindAllMethod(); @@ -67,7 +67,7 @@ public interface CrudMethods { * Returns the find one method of the repository. Usually signature compatible to * {@link CrudRepository#findById(Object)} * - * @return the find one method of the repository or {@literal null} if not available. + * @return the find one method of the repository or {@link Optional#empty()} if not available. * @see #hasFindOneMethod() */ Optional getFindOneMethod(); @@ -82,7 +82,7 @@ public interface CrudMethods { /** * Returns the delete method of the repository. Will prefer a delete-by-entity method over a delete-by-id method. * - * @return the delete method of the repository or {@literal null} if not available. + * @return the delete method of the repository or {@link Optional#empty()} if not available. * @see #hasDelete() */ Optional getDeleteMethod(); diff --git a/src/main/java/org/springframework/data/repository/core/EntityInformation.java b/src/main/java/org/springframework/data/repository/core/EntityInformation.java index 533a3475f..a69b950be 100644 --- a/src/main/java/org/springframework/data/repository/core/EntityInformation.java +++ b/src/main/java/org/springframework/data/repository/core/EntityInformation.java @@ -15,6 +15,7 @@ */ package org.springframework.data.repository.core; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -39,6 +40,7 @@ public interface EntityInformation extends EntityMetadata { * @param entity must never be {@literal null} * @return */ + @Nullable ID getId(T entity); /** diff --git a/src/main/java/org/springframework/data/repository/core/NamedQueries.java b/src/main/java/org/springframework/data/repository/core/NamedQueries.java index 9894cbba8..ef0c5b0c3 100644 --- a/src/main/java/org/springframework/data/repository/core/NamedQueries.java +++ b/src/main/java/org/springframework/data/repository/core/NamedQueries.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-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. @@ -26,16 +26,18 @@ public interface NamedQueries { * Returns whether the map contains a named query for the given name. If this method returns {@literal true} you can * expect {@link #getQuery(String)} to return a non-{@literal null} query for the very same name. * - * @param queryName + * @param queryName must not be {@literal null} or empty. * @return + * @throws IllegalArgumentException in case the given name is {@literal null} or empty. */ boolean hasQuery(String queryName); /** - * Returns the named query with the given name or {@literal null} if none exists. + * Returns the named query with the given name. * - * @param queryName + * @param queryName must not be {@literal null} or empty. * @return + * @throws IllegalArgumentException in case no query with the given name exists. */ String getQuery(String queryName); -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java index f49850e24..079c13a71 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java @@ -20,7 +20,7 @@ import java.lang.reflect.Method; import org.springframework.data.util.Streamable; /** - * Aditional repository specific information + * Additional repository specific information * * @author Oliver Gierke */ @@ -75,7 +75,7 @@ public interface RepositoryInformation extends RepositoryMetadata { /** * Returns the target class method that is backing the given method. This can be necessary if a repository interface - * redeclares a method of the core repository interface (e.g. for transaction behaviour customization). Returns the + * redeclares a method of the core repository interface (e.g. for transaction behavior customization). Returns the * method itself if the target class does not implement the given method. Implementations need to make sure the * {@link Method} returned can be invoked via reflection, i.e. needs to be accessible. * diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java b/src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java index 5785c7676..e1ce205fb 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java @@ -31,14 +31,14 @@ public interface RepositoryMetadata { /** * Returns the id class the given class is declared for. * - * @return the id class of the entity managed by the repository for or {@code null} if none found. + * @return the id class of the entity managed by the repository. */ Class getIdType(); /** * Returns the domain class the repository is declared for. * - * @return the domain class the repository is handling or {@code null} if none found. + * @return the domain class the repository is handling. */ Class getDomainType(); diff --git a/src/main/java/org/springframework/data/repository/core/package-info.java b/src/main/java/org/springframework/data/repository/core/package-info.java index b869e32f5..1ff4a7832 100644 --- a/src/main/java/org/springframework/data/repository/core/package-info.java +++ b/src/main/java/org/springframework/data/repository/core/package-info.java @@ -1,5 +1,5 @@ /** - * Core abstractions for repository implementation. + * Core abstractions for repository implementation. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.core; - diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMetadata.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMetadata.java index 6f3dbb0cb..be839993a 100644 --- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMetadata.java +++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMetadata.java @@ -18,6 +18,7 @@ package org.springframework.data.repository.core.support; import lombok.Getter; import java.util.List; +import java.util.function.Supplier; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; @@ -51,31 +52,23 @@ public class DefaultRepositoryMetadata extends AbstractRepositoryMetadata { super(repositoryInterface); Assert.isTrue(Repository.class.isAssignableFrom(repositoryInterface), MUST_BE_A_REPOSITORY); - this.idType = resolveIdType(repositoryInterface); - this.domainType = resolveDomainType(repositoryInterface); - } - - private static Class resolveIdType(Class repositoryInterface) { - - TypeInformation information = ClassTypeInformation.from(repositoryInterface); - List> arguments = information.getSuperTypeInformation(Repository.class).getTypeArguments(); + List> arguments = ClassTypeInformation.from(repositoryInterface) // + .getRequiredSuperTypeInformation(Repository.class)// + .getTypeArguments(); - if (arguments.size() < 2 || arguments.get(1) == null) { - throw new IllegalArgumentException(String.format("Could not resolve id type of %s!", repositoryInterface)); - } - - return arguments.get(1).getType(); + this.domainType = resolveTypeParameter(arguments, 0, + () -> String.format("Could not resolve domain type of %s!", repositoryInterface)); + this.idType = resolveTypeParameter(arguments, 1, + () -> String.format("Could not resolve id type of %s!", repositoryInterface)); } - private static Class resolveDomainType(Class repositoryInterface) { - - TypeInformation information = ClassTypeInformation.from(repositoryInterface); - List> arguments = information.getSuperTypeInformation(Repository.class).getTypeArguments(); + private static Class resolveTypeParameter(List> arguments, int index, + Supplier exceptionMessage) { - if (arguments.isEmpty() || arguments.get(0) == null) { - throw new IllegalArgumentException(String.format("Could not resolve domain type of %s!", repositoryInterface)); + if (arguments.size() <= index || arguments.get(index) == null) { + throw new IllegalArgumentException(exceptionMessage.get()); } - return arguments.get(0).getType(); + return arguments.get(index).getType(); } } diff --git a/src/main/java/org/springframework/data/repository/core/support/DelegatingEntityInformation.java b/src/main/java/org/springframework/data/repository/core/support/DelegatingEntityInformation.java index 3c6ccb678..2c80bb989 100644 --- a/src/main/java/org/springframework/data/repository/core/support/DelegatingEntityInformation.java +++ b/src/main/java/org/springframework/data/repository/core/support/DelegatingEntityInformation.java @@ -19,6 +19,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.lang.Nullable; /** * Useful base class to implement custom {@link EntityInformation}s and delegate execution of standard methods from @@ -35,6 +36,7 @@ public class DelegatingEntityInformation implements EntityInformation getJavaType() { return delegate.getJavaType(); } @@ -43,6 +45,7 @@ public class DelegatingEntityInformation implements EntityInformation implements EntityInformation implements EntityInformation getIdType() { return delegate.getIdType(); } diff --git a/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java b/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java index d7e13152b..bf0324c7e 100644 --- a/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java +++ b/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java @@ -17,10 +17,12 @@ package org.springframework.data.repository.core.support; import lombok.RequiredArgsConstructor; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -31,6 +33,7 @@ import org.springframework.data.domain.DomainEvents; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.util.AnnotationDetectionMethodCallback; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ReflectionUtils; @@ -86,7 +89,7 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Object result = invocation.proceed(); @@ -110,10 +113,10 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr static class EventPublishingMethod { private static Map, EventPublishingMethod> CACHE = new ConcurrentReferenceHashMap<>(); - private static EventPublishingMethod NONE = new EventPublishingMethod(null, null); + private static @SuppressWarnings("null") EventPublishingMethod NONE = new EventPublishingMethod(null, null); private final Method publishingMethod; - private final Method clearingMethod; + private final @Nullable Method clearingMethod; /** * Creates an {@link EventPublishingMethod} for the given type. @@ -122,6 +125,7 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * @return an {@link EventPublishingMethod} for the given type or {@literal null} in case the given type does not * expose an event publishing method. */ + @Nullable public static EventPublishingMethod of(Class type) { Assert.notNull(type, "Type must not be null!"); @@ -132,18 +136,8 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr return eventPublishingMethod.orNull(); } - AnnotationDetectionMethodCallback publishing = new AnnotationDetectionMethodCallback<>( - DomainEvents.class); - ReflectionUtils.doWithMethods(type, publishing); - - // TODO: Lazify this as the inspection might not be needed if the publishing callback didn't find an annotation in - // the first place - - AnnotationDetectionMethodCallback clearing = new AnnotationDetectionMethodCallback<>( - AfterDomainEventPublication.class); - ReflectionUtils.doWithMethods(type, clearing); - - EventPublishingMethod result = from(publishing, clearing); + EventPublishingMethod result = from(getDetector(type, DomainEvents.class), + () -> getDetector(type, AfterDomainEventPublication.class)); CACHE.put(type, result); @@ -156,7 +150,7 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * @param object can be {@literal null}. * @param publisher must not be {@literal null}. */ - public void publishEventsFrom(Object object, ApplicationEventPublisher publisher) { + public void publishEventsFrom(@Nullable Object object, ApplicationEventPublisher publisher) { if (object == null) { return; @@ -179,10 +173,20 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * * @return */ + @Nullable private EventPublishingMethod orNull() { return this == EventPublishingMethod.NONE ? null : this; } + private static AnnotationDetectionMethodCallback getDetector(Class type, + Class annotation) { + + AnnotationDetectionMethodCallback callback = new AnnotationDetectionMethodCallback<>(annotation); + ReflectionUtils.doWithMethods(type, callback); + + return callback; + } + /** * Creates a new {@link EventPublishingMethod} using the given pre-populated * {@link AnnotationDetectionMethodCallback} looking up an optional clearing method from the given callback. @@ -192,16 +196,16 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * @return */ private static EventPublishingMethod from(AnnotationDetectionMethodCallback publishing, - AnnotationDetectionMethodCallback clearing) { + Supplier> clearing) { if (!publishing.hasFoundAnnotation()) { return EventPublishingMethod.NONE; } - Method eventMethod = publishing.getMethod(); + Method eventMethod = publishing.getRequiredMethod(); ReflectionUtils.makeAccessible(eventMethod); - return new EventPublishingMethod(eventMethod, getClearingMethod(clearing)); + return new EventPublishingMethod(eventMethod, getClearingMethod(clearing.get())); } /** @@ -210,13 +214,14 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * @param clearing must not be {@literal null}. * @return */ + @Nullable private static Method getClearingMethod(AnnotationDetectionMethodCallback clearing) { if (!clearing.hasFoundAnnotation()) { return null; } - Method method = clearing.getMethod(); + Method method = clearing.getRequiredMethod(); ReflectionUtils.makeAccessible(method); return method; @@ -230,7 +235,7 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * @return */ @SuppressWarnings("unchecked") - private static Collection asCollection(Object source) { + private static Collection asCollection(@Nullable Object source) { if (source == null) { return Collections.emptyList(); diff --git a/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java b/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java index 113b41714..33de571fb 100644 --- a/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java +++ b/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java @@ -70,6 +70,7 @@ public interface MethodLookup { interface MethodPredicate extends BiPredicate { @Override + @SuppressWarnings("null") boolean test(InvokedMethod invokedMethod, Method candidate); } diff --git a/src/main/java/org/springframework/data/repository/core/support/PersistableEntityInformation.java b/src/main/java/org/springframework/data/repository/core/support/PersistableEntityInformation.java index c68d7102b..b0f6217ff 100644 --- a/src/main/java/org/springframework/data/repository/core/support/PersistableEntityInformation.java +++ b/src/main/java/org/springframework/data/repository/core/support/PersistableEntityInformation.java @@ -18,6 +18,7 @@ package org.springframework.data.repository.core.support; import org.springframework.core.ResolvableType; import org.springframework.data.domain.Persistable; import org.springframework.data.repository.core.EntityMetadata; +import org.springframework.lang.Nullable; /** * Implementation of {@link EntityMetadata} that assumes the entity handled implements {@link Persistable} and uses @@ -38,7 +39,14 @@ public class PersistableEntityInformation, ID> extends public PersistableEntityInformation(Class domainClass) { super(domainClass); - this.idClass = (Class) ResolvableType.forClass(Persistable.class, domainClass).resolveGeneric(0); + + Class idClass = ResolvableType.forClass(Persistable.class, domainClass).resolveGeneric(0); + + if (idClass == null) { + throw new IllegalArgumentException(String.format("Could not resolve identifier type for %s!", domainClass)); + } + + this.idClass = (Class) idClass; } /* @@ -54,6 +62,7 @@ public class PersistableEntityInformation, ID> extends * (non-Javadoc) * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object) */ + @Nullable @Override public ID getId(T entity) { return entity.getId(); diff --git a/src/main/java/org/springframework/data/repository/core/support/PersistentEntityInformation.java b/src/main/java/org/springframework/data/repository/core/support/PersistentEntityInformation.java index 527ad324d..c876afd27 100644 --- a/src/main/java/org/springframework/data/repository/core/support/PersistentEntityInformation.java +++ b/src/main/java/org/springframework/data/repository/core/support/PersistentEntityInformation.java @@ -18,6 +18,7 @@ package org.springframework.data.repository.core.support; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.lang.Nullable; /** * {@link EntityInformation} implementation that uses a {@link PersistentEntity} to obtain id type information and uses @@ -46,6 +47,7 @@ public class PersistentEntityInformation extends AbstractEntityInformatio * (non-Javadoc) * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object) */ + @Nullable @Override public ID getId(T entity) { return (ID) persistentEntity.getIdentifierAccessor(entity).getIdentifier(); diff --git a/src/main/java/org/springframework/data/repository/core/support/PropertiesBasedNamedQueries.java b/src/main/java/org/springframework/data/repository/core/support/PropertiesBasedNamedQueries.java index 770b0b85c..9f50385fa 100644 --- a/src/main/java/org/springframework/data/repository/core/support/PropertiesBasedNamedQueries.java +++ b/src/main/java/org/springframework/data/repository/core/support/PropertiesBasedNamedQueries.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-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. @@ -15,6 +15,9 @@ */ package org.springframework.data.repository.core.support; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import java.util.Properties; import org.springframework.data.repository.core.NamedQueries; @@ -25,33 +28,40 @@ import org.springframework.util.Assert; * * @author Oliver Gierke */ +@RequiredArgsConstructor public class PropertiesBasedNamedQueries implements NamedQueries { - public static final NamedQueries EMPTY = new PropertiesBasedNamedQueries(new Properties()); + private static final String NO_QUERY_FOUND = "No query with name %s found! Make sure you call hasQuery(…) before calling this method!"; - private final Properties properties; + public static final NamedQueries EMPTY = new PropertiesBasedNamedQueries(new Properties()); - /** - * Creates a new {@link PropertiesBasedNamedQueries} for the given {@link Properties} instance. - * - * @param properties - */ - public PropertiesBasedNamedQueries(Properties properties) { - Assert.notNull(properties, "Properties must not be null!"); - this.properties = properties; - } + private final @NonNull Properties properties; - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.repository.core.NamedQueries#hasNamedQuery(java.lang.String) */ public boolean hasQuery(String queryName) { + + Assert.hasText(queryName, "Query name must not be null or empty!"); + return properties.containsKey(queryName); } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.repository.core.NamedQueries#getNamedQuery(java.lang.String) */ public String getQuery(String queryName) { - return properties.getProperty(queryName); + + Assert.hasText(queryName, "Query name must not be null or empty!"); + + String query = properties.getProperty(queryName); + + if (query == null) { + throw new IllegalArgumentException(String.format(NO_QUERY_FOUND, queryName)); + } + + return query; } } diff --git a/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java b/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java index 50c5c5ff4..023cdc5aa 100644 --- a/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java +++ b/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java @@ -25,6 +25,7 @@ import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.repository.util.NullableWrapper; import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.repository.util.ReactiveWrapperConverters; +import org.springframework.lang.Nullable; /** * Simple domain service to convert query results into a dedicated type. @@ -56,7 +57,8 @@ class QueryExecutionResultHandler { * @param returnTypeDescriptor can be {@literal null}, if so, no conversion is performed. * @return */ - public Object postProcessInvocationResult(Object result, TypeDescriptor returnTypeDescriptor) { + @Nullable + public Object postProcessInvocationResult(@Nullable Object result, @Nullable TypeDescriptor returnTypeDescriptor) { if (returnTypeDescriptor == null) { return result; @@ -95,14 +97,13 @@ class QueryExecutionResultHandler { } return conversionService.canConvert(result.getClass(), expectedReturnType) - ? conversionService.convert(result, expectedReturnType) : result; + ? conversionService.convert(result, expectedReturnType) + : result; } - if (Map.class.equals(expectedReturnType)) { - return CollectionFactory.createMap(expectedReturnType, 0); - } - - return null; + return Map.class.equals(expectedReturnType) // + ? CollectionFactory.createMap(expectedReturnType, 0) // + : null; } /** @@ -111,8 +112,9 @@ class QueryExecutionResultHandler { * @param source can be {@literal null}. * @return */ + @Nullable @SuppressWarnings("unchecked") - private static Object unwrapOptional(Object source) { + private static Object unwrapOptional(@Nullable Object source) { if (source == null) { return null; diff --git a/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java b/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java index dab50b904..5cd05b4c3 100644 --- a/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java +++ b/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java @@ -20,6 +20,8 @@ import java.lang.reflect.Field; import org.springframework.data.annotation.Id; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.util.AnnotationDetectionFieldCallback; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -56,26 +58,32 @@ public class ReflectionEntityInformation extends AbstractEntityInformatio public ReflectionEntityInformation(Class domainClass, final Class annotation) { super(domainClass); + Assert.notNull(annotation, "Annotation must not be null!"); - ReflectionUtils.doWithFields(domainClass, field -> { - if (field.getAnnotation(annotation) != null) { - ReflectionEntityInformation.this.field = field; - return; - } - }); + AnnotationDetectionFieldCallback callback = new AnnotationDetectionFieldCallback(annotation); + ReflectionUtils.doWithFields(domainClass, callback); + + try { + this.field = callback.getRequiredField(); + } catch (IllegalStateException o_O) { + throw new IllegalArgumentException(String.format("Couldn't find field with annotation %s!", annotation), o_O); + } - Assert.notNull(this.field, () -> String.format("No field annotated with %s found!", annotation.toString())); - ReflectionUtils.makeAccessible(field); + ReflectionUtils.makeAccessible(this.field); } /* * (non-Javadoc) * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object) */ + @Nullable @SuppressWarnings("unchecked") public ID getId(Object entity) { - return entity == null ? null : (ID) ReflectionUtils.getField(field, entity); + + Assert.notNull(entity, "Entity must not be null!"); + + return (ID) ReflectionUtils.getField(field, entity); } /* diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java index 1af2746a4..6bef828ce 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java @@ -18,6 +18,8 @@ package org.springframework.data.repository.core.support; import java.util.List; import java.util.Optional; +import javax.annotation.Nonnull; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; @@ -79,6 +81,7 @@ public abstract class RepositoryFactoryBeanSupport, * * @param repositoryInterface must not be {@literal null}. */ + @SuppressWarnings("null") protected RepositoryFactoryBeanSupport(Class repositoryInterface) { Assert.notNull(repositoryInterface, "Repository interface must not be null!"); @@ -231,6 +234,7 @@ public abstract class RepositoryFactoryBeanSupport, * (non-Javadoc) * @see org.springframework.beans.factory.FactoryBean#getObject() */ + @Nonnull public T getObject() { return this.repository.get(); } @@ -239,6 +243,7 @@ public abstract class RepositoryFactoryBeanSupport, * (non-Javadoc) * @see org.springframework.beans.factory.FactoryBean#getObjectType() */ + @Nonnull public Class getObjectType() { return 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 1f1eaa4c4..ec2ecf866 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 @@ -63,6 +63,7 @@ import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.repository.util.ReactiveWrappers; import org.springframework.data.util.Pair; import org.springframework.data.util.ReflectionUtils; +import org.springframework.lang.Nullable; import org.springframework.transaction.interceptor.TransactionalProxy; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; @@ -114,7 +115,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, private final List postProcessors; private Optional> repositoryBaseClass; - private QueryLookupStrategy.Key queryLookupStrategyKey; + private @Nullable QueryLookupStrategy.Key queryLookupStrategyKey; private List> queryPostProcessors; private NamedQueries namedQueries; private ClassLoader classLoader; @@ -123,6 +124,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, private QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener(); + @SuppressWarnings("null") public RepositoryFactorySupport() { this.repositoryInformationCache = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK); @@ -414,7 +416,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, * @return the {@link QueryLookupStrategy} to use or {@literal null} if no queries should be looked up. * @since 1.9 */ - protected Optional getQueryLookupStrategy(Key key, + protected Optional getQueryLookupStrategy(@Nullable Key key, EvaluationContextProvider evaluationContextProvider) { return Optional.empty(); } @@ -541,7 +543,8 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ - public Object invoke(MethodInvocation invocation) throws Throwable { + @Nullable + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Object result = doInvoke(invocation); @@ -586,11 +589,13 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, private final @NonNull RepositoryComposition composition; - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); Object[] arguments = invocation.getArguments(); diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFragmentsFactoryBean.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFragmentsFactoryBean.java index dfaf9dd49..0288a1797 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFragmentsFactoryBean.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFragmentsFactoryBean.java @@ -18,6 +18,8 @@ package org.springframework.data.repository.core.support; import java.util.List; import java.util.stream.Collectors; +import javax.annotation.Nonnull; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -47,6 +49,7 @@ public class RepositoryFragmentsFactoryBean * * @param fragmentBeanNames must not be {@literal null}. */ + @SuppressWarnings("null") public RepositoryFragmentsFactoryBean(List fragmentBeanNames) { Assert.notNull(fragmentBeanNames, "Fragment bean names must not be null!"); @@ -81,14 +84,17 @@ public class RepositoryFragmentsFactoryBean * (non-Javadoc) * @see org.springframework.beans.factory.FactoryBean#getObject() */ + @Nonnull @Override public RepositoryFragments getObject() throws Exception { return this.repositoryFragments; } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.beans.factory.FactoryBean#getObjectType() */ + @Nonnull @Override public Class getObjectType() { return RepositoryComposition.class; diff --git a/src/main/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptor.java b/src/main/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptor.java index 756ca1ce7..c35024cba 100644 --- a/src/main/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptor.java +++ b/src/main/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptor.java @@ -15,6 +15,8 @@ */ package org.springframework.data.repository.core.support; +import javax.annotation.Nullable; + import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -49,8 +51,9 @@ public enum SurroundingTransactionDetectorMethodInterceptor implements MethodInt * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { SURROUNDING_TX_ACTIVE.set(TransactionSynchronizationManager.isActualTransactionActive()); diff --git a/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java b/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java index bc0f74db2..b4cf8a225 100644 --- a/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.util.TxUtils; +import org.springframework.lang.Nullable; import org.springframework.transaction.interceptor.TransactionInterceptor; import org.springframework.util.Assert; @@ -34,8 +35,8 @@ public abstract class TransactionalRepositoryFactoryBeanSupport implements BeanFactoryAware { private String transactionManagerName = TxUtils.DEFAULT_TRANSACTION_MANAGER; - private RepositoryProxyPostProcessor txPostProcessor; - private RepositoryProxyPostProcessor exceptionPostProcessor; + private @Nullable RepositoryProxyPostProcessor txPostProcessor; + private @Nullable RepositoryProxyPostProcessor exceptionPostProcessor; private boolean enableDefaultTransactions = true; /** @@ -77,8 +78,18 @@ public abstract class TransactionalRepositoryFactoryBeanSupport parsers = new LinkedHashSet<>( - annotationParsers.length); + Set parsers = new LinkedHashSet<>(annotationParsers.length); Collections.addAll(parsers, annotationParsers); this.annotationParsers = parsers; } @@ -279,6 +280,7 @@ class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostPr * @author Juergen Hoeller * @since 1.1 */ + @SuppressWarnings({ "null", "unused" }) abstract static class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource { /** diff --git a/src/main/java/org/springframework/data/repository/core/support/package-info.java b/src/main/java/org/springframework/data/repository/core/support/package-info.java index d627d4d53..27c80182e 100644 --- a/src/main/java/org/springframework/data/repository/core/support/package-info.java +++ b/src/main/java/org/springframework/data/repository/core/support/package-info.java @@ -1,5 +1,5 @@ /** * Base classes to implement repositories for various data stores. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.core.support; - diff --git a/src/main/java/org/springframework/data/repository/history/RevisionRepository.java b/src/main/java/org/springframework/data/repository/history/RevisionRepository.java index 942317688..d7e31af2d 100755 --- a/src/main/java/org/springframework/data/repository/history/RevisionRepository.java +++ b/src/main/java/org/springframework/data/repository/history/RevisionRepository.java @@ -66,7 +66,7 @@ public interface RevisionRepository> ext * * @param id must not be {@literal null}. * @param revisionNumber must not be {@literal null}. - * @return the entity with the given ID in the given revision number. + * @return the {@link Revision} of the entity with the given ID in the given revision number. * @since 1.12 */ Optional> findRevision(ID id, N revisionNumber); diff --git a/src/main/java/org/springframework/data/repository/history/package-info.java b/src/main/java/org/springframework/data/repository/history/package-info.java index d3e9a74b8..7741e536e 100644 --- a/src/main/java/org/springframework/data/repository/history/package-info.java +++ b/src/main/java/org/springframework/data/repository/history/package-info.java @@ -1,5 +1,5 @@ /** * API for repositories using historiography. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.history; - diff --git a/src/main/java/org/springframework/data/repository/history/support/package-info.java b/src/main/java/org/springframework/data/repository/history/support/package-info.java index d5735adc6..e8de1607a 100644 --- a/src/main/java/org/springframework/data/repository/history/support/package-info.java +++ b/src/main/java/org/springframework/data/repository/history/support/package-info.java @@ -1,5 +1,5 @@ /** * Value objects to implement core repository interfaces for historiography. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.history.support; - diff --git a/src/main/java/org/springframework/data/repository/init/Jackson2ResourceReader.java b/src/main/java/org/springframework/data/repository/init/Jackson2ResourceReader.java index 0c93a7910..35966606e 100644 --- a/src/main/java/org/springframework/data/repository/init/Jackson2ResourceReader.java +++ b/src/main/java/org/springframework/data/repository/init/Jackson2ResourceReader.java @@ -108,8 +108,12 @@ public class Jackson2ResourceReader implements ResourceReader { private Object readSingle(JsonNode node, ClassLoader classLoader) throws IOException { JsonNode typeNode = node.findValue(typeKey); - String typeName = typeNode == null ? null : typeNode.asText(); + if (typeNode == null) { + throw new IllegalArgumentException(String.format("Could not find type for type key '%s'!", typeKey)); + } + + String typeName = typeNode == null ? null : typeNode.asText(); Class type = ClassUtils.resolveClassName(typeName, classLoader); return mapper.readerFor(type).readValue(node); diff --git a/src/main/java/org/springframework/data/repository/init/RepositoriesPopulatedEvent.java b/src/main/java/org/springframework/data/repository/init/RepositoriesPopulatedEvent.java index 8c9c654d0..619565ef9 100644 --- a/src/main/java/org/springframework/data/repository/init/RepositoriesPopulatedEvent.java +++ b/src/main/java/org/springframework/data/repository/init/RepositoriesPopulatedEvent.java @@ -15,6 +15,8 @@ */ package org.springframework.data.repository.init; +import javax.annotation.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.data.repository.support.Repositories; @@ -72,7 +74,7 @@ public class RepositoriesPopulatedEvent extends ApplicationEvent { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/repository/package-info.java b/src/main/java/org/springframework/data/repository/package-info.java index e88304e55..bad4a37e0 100644 --- a/src/main/java/org/springframework/data/repository/package-info.java +++ b/src/main/java/org/springframework/data/repository/package-info.java @@ -1,5 +1,5 @@ /** * Central interfaces for repository abstraction. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository; - diff --git a/src/main/java/org/springframework/data/repository/query/EvaluationContextExtensionInformation.java b/src/main/java/org/springframework/data/repository/query/EvaluationContextExtensionInformation.java index bf392b4c8..d7a34ae3c 100644 --- a/src/main/java/org/springframework/data/repository/query/EvaluationContextExtensionInformation.java +++ b/src/main/java/org/springframework/data/repository/query/EvaluationContextExtensionInformation.java @@ -73,7 +73,9 @@ class EvaluationContextExtensionInformation { public EvaluationContextExtensionInformation(Class type) { Assert.notNull(type, "Extension type must not be null!"); - Class rootObjectType = getRootObjectMethod(type).getReturnType(); + + Class rootObjectType = org.springframework.data.util.ReflectionUtils.findRequiredMethod(type, "getRootObject") + .getReturnType(); this.rootObjectInformation = Optional .ofNullable(Object.class.equals(rootObjectType) ? null : new RootObjectInformation(rootObjectType)); @@ -102,15 +104,6 @@ class EvaluationContextExtensionInformation { .orElse(RootObjectInformation.NONE); } - private static Method getRootObjectMethod(Class type) { - - try { - return type.getMethod("getRootObject"); - } catch (Exception e) { - return null; - } - } - /** * Static information about the given {@link EvaluationContextExtension} type. Discovers public static methods and * fields. The fields' values are obtained directly, the methods are exposed {@link Function} invocations. diff --git a/src/main/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProvider.java b/src/main/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProvider.java index e8f8c7fac..4f41f675a 100644 --- a/src/main/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProvider.java +++ b/src/main/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProvider.java @@ -19,6 +19,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -38,6 +39,7 @@ import org.springframework.data.repository.query.EvaluationContextExtensionInfor import org.springframework.data.repository.query.EvaluationContextExtensionInformation.RootObjectInformation; import org.springframework.data.repository.query.spi.EvaluationContextExtension; import org.springframework.data.repository.query.spi.Function; +import org.springframework.data.util.Lazy; import org.springframework.data.util.Optionals; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; @@ -49,6 +51,7 @@ import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.ReflectivePropertyAccessor; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -66,7 +69,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex private final Map, EvaluationContextExtensionInformation> extensionInformationCache = new HashMap<>(); - private List extensions; + private final Lazy> extensions; private Optional beanFactory = Optional.empty(); /** @@ -74,7 +77,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex * {@link BeanFactory} configured. */ public ExtensionAwareEvaluationContextProvider() { - this.extensions = null; + this.extensions = Lazy.of(() -> getExtensionsFrom(beanFactory)); } /** @@ -85,7 +88,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex public ExtensionAwareEvaluationContextProvider(List extensions) { Assert.notNull(extensions, "List of EvaluationContextExtensions must not be null!"); - this.extensions = extensions; + this.extensions = Lazy.of(() -> extensions); } /* @@ -108,7 +111,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex beanFactory.ifPresent(it -> ec.setBeanResolver(new BeanFactoryResolver(it))); - ExtensionAwarePropertyAccessor accessor = new ExtensionAwarePropertyAccessor(getExtensions()); + ExtensionAwarePropertyAccessor accessor = new ExtensionAwarePropertyAccessor(extensions.get()); ec.addPropertyAccessor(accessor); ec.addPropertyAccessor(new ReflectivePropertyAccessor()); @@ -149,23 +152,19 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex } /** - * Returns the {@link EvaluationContextExtension} to be used. Either from the current configuration or the configured - * {@link BeanFactory}. + * Looks up all {@link EvaluationContextExtension} instances from the given {@link ListableBeanFactory}. * + * @param beanFactory must not be {@literal null}. * @return */ - private List getExtensions() { + private static List getExtensionsFrom( + Optional beanFactory) { - if (this.extensions != null) { - return this.extensions; - } - - this.extensions = Collections.emptyList(); - - beanFactory.ifPresent(it -> this.extensions = new ArrayList<>( - it.getBeansOfType(EvaluationContextExtension.class, true, false).values())); + Collection extensions = beanFactory// + .map(it -> it.getBeansOfType(EvaluationContextExtension.class, true, false).values())// + .orElseGet(() -> Collections.emptyList()); - return extensions; + return new ArrayList<>(extensions); } /** @@ -228,7 +227,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex * @see org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider.ReadOnlyPropertyAccessor#canRead(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) */ @Override - public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { + public boolean canRead(EvaluationContext context, @Nullable Object target, String name) { if (target instanceof EvaluationContextExtension) { return true; @@ -246,7 +245,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex * @see org.springframework.expression.PropertyAccessor#read(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) */ @Override - public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + public TypedValue read(EvaluationContext context, @Nullable Object target, String name) { if (target instanceof EvaluationContextExtensionAdapter) { return lookupPropertyFrom(((EvaluationContextExtensionAdapter) target), name); @@ -259,16 +258,17 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex return adapters.stream()// .filter(it -> it.getProperties().containsKey(name))// .map(it -> lookupPropertyFrom(it, name))// - .findFirst().orElse(null); + .findFirst().orElse(TypedValue.NULL); } /* * (non-Javadoc) * @see org.springframework.expression.MethodResolver#resolve(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String, java.util.List) */ + @Nullable @Override - public MethodExecutor resolve(EvaluationContext context, Object target, final String name, - List argumentTypes) throws AccessException { + public MethodExecutor resolve(EvaluationContext context, @Nullable Object target, final String name, + List argumentTypes) { if (target instanceof EvaluationContextExtensionAdapter) { return getMethodExecutor((EvaluationContextExtensionAdapter) target, name, argumentTypes).orElse(null); @@ -284,7 +284,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex * @see org.springframework.expression.PropertyAccessor#canWrite(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) */ @Override - public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { + public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) { return false; } @@ -293,7 +293,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex * @see org.springframework.expression.PropertyAccessor#write(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String, java.lang.Object) */ @Override - public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { + public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) { // noop } @@ -301,6 +301,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex * (non-Javadoc) * @see org.springframework.expression.PropertyAccessor#getSpecificTargetClasses() */ + @Nullable @Override public Class[] getSpecificTargetClasses() { return null; diff --git a/src/main/java/org/springframework/data/repository/query/Parameter.java b/src/main/java/org/springframework/data/repository/query/Parameter.java index aa93c585a..59b5bfcdb 100644 --- a/src/main/java/org/springframework/data/repository/query/Parameter.java +++ b/src/main/java/org/springframework/data/repository/query/Parameter.java @@ -194,6 +194,10 @@ public class Parameter { Method method = parameter.getMethod(); + if (method == null) { + throw new IllegalStateException(String.format("Method parameter %s is not backed by a method!", parameter)); + } + ClassTypeInformation ownerType = ClassTypeInformation.from(parameter.getDeclaringClass()); TypeInformation parameterTypes = ownerType.getParameterTypes(method).get(parameter.getParameterIndex()); @@ -240,7 +244,7 @@ public class Parameter { Class originalType = parameter.getParameterType(); if (isWrapped(parameter) && shouldUnwrap(parameter)) { - return ResolvableType.forMethodParameter(parameter).getGeneric(0).getRawClass(); + return ResolvableType.forMethodParameter(parameter).getGeneric(0).resolve(Object.class); } return originalType; diff --git a/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java b/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java index b786f0c16..8f44435a1 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java @@ -21,6 +21,7 @@ import java.util.Locale; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -40,6 +41,7 @@ public interface QueryLookupStrategy { * @param xml * @return a strategy key from the given XML value */ + @Nullable public static Key create(String xml) { if (!StringUtils.hasText(xml)) { diff --git a/src/main/java/org/springframework/data/repository/query/ResultProcessor.java b/src/main/java/org/springframework/data/repository/query/ResultProcessor.java index e21670f34..f7503b9c2 100644 --- a/src/main/java/org/springframework/data/repository/query/ResultProcessor.java +++ b/src/main/java/org/springframework/data/repository/query/ResultProcessor.java @@ -27,6 +27,8 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; +import javax.annotation.Nonnull; + import org.springframework.core.CollectionFactory; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; @@ -34,6 +36,7 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.domain.Slice; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.util.ReactiveWrapperConverters; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -111,7 +114,8 @@ public class ResultProcessor { * @param source can be {@literal null}. * @return */ - public T processResult(Object source) { + @Nullable + public T processResult(@Nullable Object source) { return processResult(source, NoOpConverter.INSTANCE); } @@ -123,8 +127,9 @@ public class ResultProcessor { * @param preparingConverter must not be {@literal null}. * @return */ + @Nullable @SuppressWarnings("unchecked") - public T processResult(Object source, Converter preparingConverter) { + public T processResult(@Nullable Object source, Converter preparingConverter) { if (source == null || type.isInstance(source) || !type.isProjecting()) { return (T) source; @@ -203,7 +208,9 @@ public class ResultProcessor { return new ChainingConverter(targetType, source -> { Object intermediate = ChainingConverter.this.convert(source); - return targetType.isInstance(intermediate) ? intermediate : converter.convert(intermediate); + + return intermediate == null || targetType.isInstance(intermediate) ? intermediate + : converter.convert(intermediate); }); } @@ -211,6 +218,7 @@ public class ResultProcessor { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override public Object convert(Object source) { return delegate.convert(source); @@ -231,6 +239,7 @@ public class ResultProcessor { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nonnull @Override public Object convert(Object source) { return source; @@ -271,6 +280,7 @@ public class ResultProcessor { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override public Object convert(Object source) { diff --git a/src/main/java/org/springframework/data/repository/query/ReturnedType.java b/src/main/java/org/springframework/data/repository/query/ReturnedType.java index 567fb6f0c..aae5ac89b 100644 --- a/src/main/java/org/springframework/data/repository/query/ReturnedType.java +++ b/src/main/java/org/springframework/data/repository/query/ReturnedType.java @@ -29,10 +29,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; + import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; @@ -88,7 +91,7 @@ public abstract class ReturnedType { * @param source can be {@literal null}. * @return */ - public final boolean isInstance(Object source) { + public final boolean isInstance(@Nullable Object source) { return getReturnedType().isInstance(source); } @@ -119,6 +122,7 @@ public abstract class ReturnedType { * * @return */ + @Nullable public abstract Class getTypeToRead(); /** @@ -157,7 +161,7 @@ public abstract class ReturnedType { /* * (non-Javadoc) - * @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getReturnedType() + * @see org.springframework.data.repository.query.ReturnedType#getReturnedType() */ @Override public Class getReturnedType() { @@ -174,7 +178,7 @@ public abstract class ReturnedType { /* * (non-Javadoc) - * @see org.springframework.data.repository.query.ResultFactory.ReturnedType#isProjecting() + * @see org.springframework.data.repository.query.ReturnedType#isProjecting() */ @Override public boolean isProjecting() { @@ -183,8 +187,9 @@ public abstract class ReturnedType { /* * (non-Javadoc) - * @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getTypeToRead() + * @see org.springframework.data.repository.query.ReturnedType#getTypeToRead() */ + @Nullable @Override public Class getTypeToRead() { return isProjecting() && information.isClosed() ? null : domainType; @@ -192,7 +197,7 @@ public abstract class ReturnedType { /* * (non-Javadoc) - * @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getInputProperties() + * @see org.springframework.data.repository.query.ReturnedType#getInputProperties() */ @Override public List getInputProperties() { @@ -253,6 +258,7 @@ public abstract class ReturnedType { * (non-Javadoc) * @see org.springframework.data.repository.query.ResultFactory.ReturnedType#getTypeToRead() */ + @Nonnull public Class getTypeToRead() { return type; } diff --git a/src/main/java/org/springframework/data/repository/query/package-info.java b/src/main/java/org/springframework/data/repository/query/package-info.java index b4d3f64a9..550101be1 100644 --- a/src/main/java/org/springframework/data/repository/query/package-info.java +++ b/src/main/java/org/springframework/data/repository/query/package-info.java @@ -1,5 +1,5 @@ /** * Support classes to work with query methods. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.query; - diff --git a/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java b/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java index 1a27730bc..ad8601968 100644 --- a/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java +++ b/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java @@ -19,6 +19,8 @@ import java.util.Collections; import java.util.Optional; import java.util.Set; +import javax.annotation.Nonnull; + import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.ConversionService; @@ -28,6 +30,7 @@ import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -45,8 +48,8 @@ public class DomainClassConverter toEntityConverter = Optional.empty(); + private Optional toIdConverter = Optional.empty(); /** * Creates a new {@link DomainClassConverter} for the given {@link ConversionService}. @@ -64,6 +67,8 @@ public class DomainClassConverter getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, Object.class)); } @@ -72,11 +77,10 @@ public class DomainClassConverter it.convert(source, sourceType, targetType)).orElse(source); } /* @@ -85,9 +89,15 @@ public class DomainClassConverter it.matches(sourceType, targetType)).orElse(false); + } - return repositories.hasRepositoryFor(targetType.getType()) ? toEntityConverter.matches(sourceType, targetType) - : toIdConverter.matches(sourceType, targetType); + /** + * @param targetType + * @return + */ + private Optional getConverter(TypeDescriptor targetType) { + return repositories.hasRepositoryFor(targetType.getType()) ? toEntityConverter : toIdConverter; } /* @@ -98,12 +108,11 @@ public class DomainClassConverter this.conversionService.addConverter(it)); + this.toIdConverter = Optional.of(new ToIdConverter()); + this.toIdConverter.ifPresent(it -> this.conversionService.addConverter(it)); } /** @@ -130,6 +139,7 @@ public class DomainClassConverter getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, Object.class)); @@ -139,8 +149,9 @@ public class DomainClassConverter getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, Object.class)); @@ -208,8 +222,9 @@ public class DomainClassConverter new IllegalStateException("Repository doesn't have a save-method declared!")); - return invoke(method, object); + return invokeForNonNullResult(method, object); } /* @@ -229,7 +231,12 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker { return result; } - private Object convert(Object value, MethodParameter parameter) { + @Nullable + private Object convert(@Nullable Object value, MethodParameter parameter) { + + if (value == null) { + return value; + } try { return conversionService.convert(value, TypeDescriptor.forObject(value), new TypeDescriptor(parameter)); @@ -245,14 +252,29 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker { * @param arguments * @return */ + @Nullable @SuppressWarnings("unchecked") private T invoke(Method method, Object... arguments) { return (T) ReflectionUtils.invokeMethod(method, repository, arguments); } + private T invokeForNonNullResult(Method method, Object... arguments) { + + T result = invoke(method, arguments); + + if (result == null) { + throw new IllegalStateException(String.format("Invocation of method %s(%s) on %s unexpectedly returned null!", + method, Arrays.toString(arguments), repository)); + } + + return result; + } + @SuppressWarnings("unchecked") - private Optional returnAsOptional(Object source) { - return (Optional) (Optional.class.isInstance(source) ? source + private Optional returnAsOptional(@Nullable Object source) { + + return (Optional) (source instanceof Optional // + ? source // : Optional.ofNullable(QueryExecutionConverters.unwrap(source))); } @@ -265,7 +287,15 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker { protected Object convertId(Object id) { Assert.notNull(id, "Id must not be null!"); - return conversionService.convert(id, idType); + + Object result = conversionService.convert(id, idType); + + if (result == null) { + throw new IllegalStateException( + String.format("Identifier conversion of %s to %s unexpectedly returned null!", id, idType)); + } + + return result; } protected Iterable invokeFindAllReflectively(Pageable pageable) { @@ -275,11 +305,11 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker { Class[] types = method.getParameterTypes(); if (types.length == 0) { - return invoke(method); + return invokeForNonNullResult(method); } if (Pageable.class.isAssignableFrom(types[0])) { - return invoke(method, pageable); + return invokeForNonNullResult(method, pageable); } return invokeFindAll(pageable.getSort()); @@ -291,10 +321,10 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker { .orElseThrow(() -> new IllegalStateException("Repository doesn't have a find-all-method declared!")); if (method.getParameterCount() == 0) { - return invoke(method); + return invokeForNonNullResult(method); } - return invoke(method, sort); + return invokeForNonNullResult(method, sort); } /** @@ -303,7 +333,8 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker { * @param source can be {@literal null}. * @return */ - private static Object unwrapSingleElement(List source) { + @Nullable + private static Object unwrapSingleElement(@Nullable List source) { return source == null ? null : source.size() == 1 ? source.get(0) : source; } } diff --git a/src/main/java/org/springframework/data/repository/support/Repositories.java b/src/main/java/org/springframework/data/repository/support/Repositories.java index 66941b36a..c4a2d17ed 100644 --- a/src/main/java/org/springframework/data/repository/support/Repositories.java +++ b/src/main/java/org/springframework/data/repository/support/Repositories.java @@ -269,17 +269,17 @@ public class Repositories implements Iterable> { @Override public EntityInformation getEntityInformation() { - return null; + throw new UnsupportedOperationException(); } @Override public RepositoryInformation getRepositoryInformation() { - return null; + throw new UnsupportedOperationException(); } @Override public PersistentEntity getPersistentEntity() { - return null; + throw new UnsupportedOperationException(); } @Override diff --git a/src/main/java/org/springframework/data/repository/support/package-info.java b/src/main/java/org/springframework/data/repository/support/package-info.java index 875f15896..1413e89a5 100644 --- a/src/main/java/org/springframework/data/repository/support/package-info.java +++ b/src/main/java/org/springframework/data/repository/support/package-info.java @@ -1,4 +1,5 @@ /** * Support classes for integration of the repository programming model with 3rd party frameworks. */ -package org.springframework.data.repository.support; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.repository.support; diff --git a/src/main/java/org/springframework/data/repository/util/ClassUtils.java b/src/main/java/org/springframework/data/repository/util/ClassUtils.java index 8befaa0cf..f46aefcc1 100644 --- a/src/main/java/org/springframework/data/repository/util/ClassUtils.java +++ b/src/main/java/org/springframework/data/repository/util/ClassUtils.java @@ -23,6 +23,7 @@ import java.util.Collection; import org.springframework.data.repository.Repository; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -72,8 +73,7 @@ public abstract class ClassUtils { * @param interfaceName * @return */ - public static boolean isGenericRepositoryInterface(String interfaceName) { - + public static boolean isGenericRepositoryInterface(@Nullable String interfaceName) { return Repository.class.getName().equals(interfaceName); } @@ -123,9 +123,9 @@ public abstract class ClassUtils { * @param types * @return */ - public static boolean isOfType(Object object, Collection> types) { + public static boolean isOfType(@Nullable Object object, Collection> types) { - if (null == object) { + if (object == null) { return false; } diff --git a/src/main/java/org/springframework/data/repository/util/JavaslangCollections.java b/src/main/java/org/springframework/data/repository/util/JavaslangCollections.java index a012be42b..c6cc36973 100644 --- a/src/main/java/org/springframework/data/repository/util/JavaslangCollections.java +++ b/src/main/java/org/springframework/data/repository/util/JavaslangCollections.java @@ -26,10 +26,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.Converter; import org.springframework.data.repository.util.QueryExecutionConverters.WrapperType; +import org.springframework.lang.Nullable; /** * Converter implementations to map from and to Javaslang collections. @@ -52,6 +55,7 @@ class JavaslangCollections { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nonnull @Override public Object convert(Object source) { @@ -79,6 +83,7 @@ class JavaslangCollections { * (non-Javadoc) * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() */ + @Nonnull @Override public java.util.Set getConvertibleTypes() { return CONVERTIBLE_PAIRS; @@ -105,12 +110,13 @@ class JavaslangCollections { return true; } - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) - */ + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ + @Nullable @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source instanceof List) { return javaslang.collection.List.ofAll((Iterable) source); diff --git a/src/main/java/org/springframework/data/repository/util/NullableWrapper.java b/src/main/java/org/springframework/data/repository/util/NullableWrapper.java index f3d71a149..aed217f02 100644 --- a/src/main/java/org/springframework/data/repository/util/NullableWrapper.java +++ b/src/main/java/org/springframework/data/repository/util/NullableWrapper.java @@ -16,6 +16,7 @@ package org.springframework.data.repository.util; import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; /** * Simple value object to wrap a nullable delegate. Used to be able to write {@link Converter} implementations that @@ -27,14 +28,14 @@ import org.springframework.core.convert.converter.Converter; */ public class NullableWrapper { - private final Object value; + private final @Nullable Object value; /** * Creates a new {@link NullableWrapper} for the given value. * * @param value can be {@literal null}. */ - public NullableWrapper(Object value) { + public NullableWrapper(@Nullable Object value) { this.value = value; } @@ -44,6 +45,9 @@ public class NullableWrapper { * @return will never be {@literal null}. */ public Class getValueType() { + + Object value = this.value; + return value == null ? Object.class : value.getClass(); } @@ -52,6 +56,7 @@ public class NullableWrapper { * * @return the value can be {@literal null}. */ + @Nullable public Object getValue() { return value; } diff --git a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java index 50fb5d486..62bbcd969 100644 --- a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java +++ b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java @@ -36,6 +36,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.stream.Collectors; +import javax.annotation.Nonnull; + import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; @@ -43,7 +45,9 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Slice; +import org.springframework.data.util.StreamUtils; import org.springframework.data.util.Streamable; +import org.springframework.lang.Nullable; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -261,7 +265,8 @@ public abstract class QueryExecutionConverters { * @param source can be {@literal null}. * @return */ - public static Object unwrap(Object source) { + @Nullable + public static Object unwrap(@Nullable Object source) { if (source == null || !supports(source.getClass())) { return source; @@ -312,20 +317,26 @@ public abstract class QueryExecutionConverters { * (non-Javadoc) * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() */ + @Nonnull @Override public Set getConvertibleTypes() { return Streamable.of(wrapperTypes)// .map(it -> new ConvertiblePair(NullableWrapper.class, it))// - .stream().collect(Collectors.toSet()); + .stream().collect(StreamUtils.toUnmodifiableSet()); } /* * (non-Javadoc) * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) */ + @Nullable @Override - public final Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public final Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + + if (source == null) { + return null; + } NullableWrapper wrapper = (NullableWrapper) source; Object value = wrapper.getValue(); @@ -561,6 +572,7 @@ public abstract class QueryExecutionConverters { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override public Object convert(Object source) { return source instanceof Optional ? ((Optional) source).orNull() : source; @@ -581,6 +593,7 @@ public abstract class QueryExecutionConverters { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override public Object convert(Object source) { return source instanceof java.util.Optional ? ((java.util.Optional) source).orElse(null) : source; @@ -604,6 +617,7 @@ public abstract class QueryExecutionConverters { * (non-Javadoc) * @see scala.Function0#apply() */ + @Nullable @Override public Option apply() { return null; @@ -614,6 +628,7 @@ public abstract class QueryExecutionConverters { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override public Object convert(Object source) { return source instanceof Option ? ((Option) source).getOrElse(alternative) : source; @@ -634,6 +649,7 @@ public abstract class QueryExecutionConverters { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override @SuppressWarnings("unchecked") public Object convert(Object source) { @@ -664,6 +680,7 @@ public abstract class QueryExecutionConverters { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nullable @Override @SuppressWarnings("unchecked") public Object convert(Object source) { diff --git a/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java b/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java index e263b6023..cb0a66fe0 100644 --- a/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java +++ b/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java @@ -31,13 +31,17 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import javax.annotation.Nonnull; + import org.reactivestreams.Publisher; +import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.repository.util.ReactiveWrappers.ReactiveLibrary; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -179,6 +183,7 @@ public class ReactiveWrapperConverters { * @param targetWrapperType must not be {@literal null}. * @return */ + @Nullable @SuppressWarnings("unchecked") public static T toWrapper(Object reactiveObject, Class targetWrapperType) { @@ -227,6 +232,31 @@ public class ReactiveWrapperConverters { return GENERIC_CONVERSION_SERVICE.canConvert(sourceType, targetType); } + /** + * Returns the {@link ReactiveAdapter} for the given type. + * + * @param type must not be {@literal null}. + * @return + * @throws IllegalStateException if no adapter registry could be found. + * @throws IllegalArgumentException if no adapter could be found for the given type. + */ + private static ReactiveAdapter getRequiredAdapter(Class type) { + + ReactiveAdapterRegistry registry = REACTIVE_ADAPTER_REGISTRY; + + if (registry == null) { + throw new IllegalStateException("No reactive adapter registry found!"); + } + + ReactiveAdapter adapter = registry.getAdapter(type); + + if (adapter == null) { + throw new IllegalArgumentException(String.format("Expected to find reactive adapter for %s but couldn't!", type)); + } + + return adapter; + } + // ------------------------------------------------------------------------- // Wrapper descriptors // ------------------------------------------------------------------------- @@ -438,6 +468,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Flux convert(Publisher source) { return Flux.from(source); @@ -454,6 +485,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(Publisher source) { return Mono.from(source); @@ -474,9 +506,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Single convert(Publisher source) { - return (Single) REACTIVE_ADAPTER_REGISTRY.getAdapter(Single.class).fromPublisher(Mono.from(source)); + return (Single) getRequiredAdapter(Single.class).fromPublisher(Mono.from(source)); } } @@ -490,9 +523,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Completable convert(Publisher source) { - return (Completable) REACTIVE_ADAPTER_REGISTRY.getAdapter(Completable.class).fromPublisher(source); + return (Completable) getRequiredAdapter(Completable.class).fromPublisher(source); } } @@ -506,9 +540,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Observable convert(Publisher source) { - return (Observable) REACTIVE_ADAPTER_REGISTRY.getAdapter(Observable.class).fromPublisher(Flux.from(source)); + return (Observable) getRequiredAdapter(Observable.class).fromPublisher(Flux.from(source)); } } @@ -522,9 +557,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(Single source) { - return Flux.defer(() -> REACTIVE_ADAPTER_REGISTRY.getAdapter(Single.class).toPublisher(source)); + return Flux.defer(() -> getRequiredAdapter(Single.class).toPublisher(source)); } } @@ -538,9 +574,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(Single source) { - return Mono.defer(() -> Mono.from(REACTIVE_ADAPTER_REGISTRY.getAdapter(Single.class).toPublisher(source))); + return Mono.defer(() -> Mono.from(getRequiredAdapter(Single.class).toPublisher(source))); } } @@ -554,9 +591,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Flux convert(Single source) { - return Flux.defer(() -> REACTIVE_ADAPTER_REGISTRY.getAdapter(Single.class).toPublisher(source)); + return Flux.defer(() -> getRequiredAdapter(Single.class).toPublisher(source)); } } @@ -570,9 +608,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(Completable source) { - return Flux.defer(() -> REACTIVE_ADAPTER_REGISTRY.getAdapter(Completable.class).toPublisher(source)); + return Flux.defer(() -> getRequiredAdapter(Completable.class).toPublisher(source)); } } @@ -586,6 +625,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(Completable source) { return Mono.from(RxJava1CompletableToPublisherConverter.INSTANCE.convert(source)); @@ -602,9 +642,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(Observable source) { - return Flux.defer(() -> REACTIVE_ADAPTER_REGISTRY.getAdapter(Observable.class).toPublisher(source)); + return Flux.defer(() -> getRequiredAdapter(Observable.class).toPublisher(source)); } } @@ -618,9 +659,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(Observable source) { - return Mono.defer(() -> Mono.from(REACTIVE_ADAPTER_REGISTRY.getAdapter(Observable.class).toPublisher(source))); + return Mono.defer(() -> Mono.from(getRequiredAdapter(Observable.class).toPublisher(source))); } } @@ -634,9 +676,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Flux convert(Observable source) { - return Flux.defer(() -> REACTIVE_ADAPTER_REGISTRY.getAdapter(Observable.class).toPublisher(source)); + return Flux.defer(() -> getRequiredAdapter(Observable.class).toPublisher(source)); } } @@ -650,6 +693,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Single convert(Observable source) { return source.toSingle(); @@ -666,6 +710,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Observable convert(Single source) { return source.toObservable(); @@ -686,10 +731,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Single convert(Publisher source) { - return (io.reactivex.Single) REACTIVE_ADAPTER_REGISTRY.getAdapter(io.reactivex.Single.class) - .fromPublisher(source); + return (io.reactivex.Single) getRequiredAdapter(io.reactivex.Single.class).fromPublisher(source); } } @@ -703,10 +748,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Completable convert(Publisher source) { - return (io.reactivex.Completable) REACTIVE_ADAPTER_REGISTRY.getAdapter(io.reactivex.Completable.class) - .fromPublisher(source); + return (io.reactivex.Completable) getRequiredAdapter(io.reactivex.Completable.class).fromPublisher(source); } } @@ -720,10 +765,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Observable convert(Publisher source) { - return (io.reactivex.Observable) REACTIVE_ADAPTER_REGISTRY.getAdapter(io.reactivex.Observable.class) - .fromPublisher(source); + return (io.reactivex.Observable) getRequiredAdapter(io.reactivex.Observable.class).fromPublisher(source); } } @@ -737,9 +782,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(io.reactivex.Single source) { - return REACTIVE_ADAPTER_REGISTRY.getAdapter(io.reactivex.Single.class).toPublisher(source); + return getRequiredAdapter(io.reactivex.Single.class).toPublisher(source); } } @@ -753,9 +799,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(io.reactivex.Single source) { - return Mono.from(REACTIVE_ADAPTER_REGISTRY.getAdapter(io.reactivex.Single.class).toPublisher(source)); + return Mono.from(getRequiredAdapter(io.reactivex.Single.class).toPublisher(source)); } } @@ -769,9 +816,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Flux convert(io.reactivex.Single source) { - return Flux.from(REACTIVE_ADAPTER_REGISTRY.getAdapter(io.reactivex.Single.class).toPublisher(source)); + return Flux.from(getRequiredAdapter(io.reactivex.Single.class).toPublisher(source)); } } @@ -785,9 +833,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(io.reactivex.Completable source) { - return REACTIVE_ADAPTER_REGISTRY.getAdapter(io.reactivex.Completable.class).toPublisher(source); + return getRequiredAdapter(io.reactivex.Completable.class).toPublisher(source); } } @@ -801,6 +850,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(io.reactivex.Completable source) { return Mono.from(RxJava2CompletableToPublisherConverter.INSTANCE.convert(source)); @@ -817,6 +867,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(io.reactivex.Observable source) { return source.toFlowable(BackpressureStrategy.BUFFER); @@ -833,6 +884,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(io.reactivex.Observable source) { return Mono.from(source.toFlowable(BackpressureStrategy.BUFFER)); @@ -849,6 +901,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Flux convert(io.reactivex.Observable source) { return Flux.from(source.toFlowable(BackpressureStrategy.BUFFER)); @@ -865,6 +918,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Flowable convert(Publisher source) { return Flowable.fromPublisher(source); @@ -881,6 +935,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(io.reactivex.Flowable source) { return source; @@ -897,9 +952,10 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Maybe convert(Publisher source) { - return (io.reactivex.Maybe) REACTIVE_ADAPTER_REGISTRY.getAdapter(Maybe.class).fromPublisher(source); + return (io.reactivex.Maybe) getRequiredAdapter(Maybe.class).fromPublisher(source); } } @@ -913,6 +969,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Publisher convert(io.reactivex.Maybe source) { return source.toFlowable(); @@ -929,6 +986,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Mono convert(io.reactivex.Maybe source) { return Mono.from(source.toFlowable()); @@ -945,6 +1003,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public Flux convert(io.reactivex.Maybe source) { return Flux.from(source.toFlowable()); @@ -962,6 +1021,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Single convert(io.reactivex.Observable source) { return source.singleOrError(); @@ -979,6 +1039,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Maybe convert(io.reactivex.Observable source) { return source.singleElement(); @@ -996,6 +1057,7 @@ public class ReactiveWrapperConverters { INSTANCE; + @Nonnull @Override public io.reactivex.Observable convert(io.reactivex.Single source) { return source.toObservable(); @@ -1010,7 +1072,7 @@ public class ReactiveWrapperConverters { */ static class RegistryHolder { - static final ReactiveAdapterRegistry REACTIVE_ADAPTER_REGISTRY; + static final @Nullable ReactiveAdapterRegistry REACTIVE_ADAPTER_REGISTRY; static { diff --git a/src/main/java/org/springframework/data/repository/util/ReactiveWrappers.java b/src/main/java/org/springframework/data/repository/util/ReactiveWrappers.java index f166500ad..8703d6b8a 100644 --- a/src/main/java/org/springframework/data/repository/util/ReactiveWrappers.java +++ b/src/main/java/org/springframework/data/repository/util/ReactiveWrappers.java @@ -30,7 +30,6 @@ import java.util.Optional; import java.util.stream.Collectors; import org.reactivestreams.Publisher; - import org.springframework.core.ReactiveTypeDescriptor; import org.springframework.data.util.ReflectionUtils; import org.springframework.util.Assert; diff --git a/src/main/java/org/springframework/data/repository/util/VavrCollections.java b/src/main/java/org/springframework/data/repository/util/VavrCollections.java index e794bef4c..5fa1fb929 100644 --- a/src/main/java/org/springframework/data/repository/util/VavrCollections.java +++ b/src/main/java/org/springframework/data/repository/util/VavrCollections.java @@ -26,10 +26,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.Converter; import org.springframework.data.repository.util.QueryExecutionConverters.WrapperType; +import org.springframework.lang.Nullable; /** * Converter implementations to map from and to Vavr collections. @@ -52,6 +55,7 @@ class VavrCollections { * (non-Javadoc) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ + @Nonnull @Override public Object convert(Object source) { @@ -79,6 +83,7 @@ class VavrCollections { * (non-Javadoc) * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() */ + @Nonnull @Override public java.util.Set getConvertibleTypes() { return CONVERTIBLE_PAIRS; @@ -109,8 +114,9 @@ class VavrCollections { * (non-Javadoc) * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) */ + @Nullable @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source instanceof List) { return io.vavr.collection.List.ofAll((Iterable) source); diff --git a/src/main/java/org/springframework/data/repository/util/package-info.java b/src/main/java/org/springframework/data/repository/util/package-info.java index 73f805d00..0b6e96025 100644 --- a/src/main/java/org/springframework/data/repository/util/package-info.java +++ b/src/main/java/org/springframework/data/repository/util/package-info.java @@ -1,5 +1,5 @@ /** * Utility classes for repository implementations. */ +@org.springframework.lang.NonNullApi package org.springframework.data.repository.util; - diff --git a/src/main/java/org/springframework/data/support/ExampleMatcherAccessor.java b/src/main/java/org/springframework/data/support/ExampleMatcherAccessor.java index 369dccb2d..db2337d7e 100644 --- a/src/main/java/org/springframework/data/support/ExampleMatcherAccessor.java +++ b/src/main/java/org/springframework/data/support/ExampleMatcherAccessor.java @@ -22,6 +22,7 @@ import java.util.Collection; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher.PropertySpecifier; +import org.springframework.data.domain.ExampleMatcher.StringMatcher; /** * Accessor for the {@link ExampleMatcher} to use in modules that support query by example (QBE) querying. @@ -87,7 +88,9 @@ public class ExampleMatcherAccessor { } ExampleMatcher.PropertySpecifier specifier = getPropertySpecifier(path); - return specifier.getStringMatcher() != null ? specifier.getStringMatcher() : matcher.getDefaultStringMatcher(); + StringMatcher stringMatcher = specifier.getStringMatcher(); + + return stringMatcher != null ? stringMatcher : matcher.getDefaultStringMatcher(); } /** @@ -136,7 +139,9 @@ public class ExampleMatcherAccessor { } ExampleMatcher.PropertySpecifier specifier = getPropertySpecifier(path); - return specifier.getIgnoreCase() != null ? specifier.getIgnoreCase() : matcher.isIgnoreCaseEnabled(); + Boolean ignoreCase = specifier.getIgnoreCase(); + + return ignoreCase != null ? ignoreCase : matcher.isIgnoreCaseEnabled(); } /** diff --git a/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java b/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java index 101e3a9da..c78b89203 100644 --- a/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java +++ b/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-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. @@ -16,10 +16,11 @@ package org.springframework.data.support; import org.springframework.data.domain.Persistable; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * {@link IsNewStrategyFactory} that handles {@link Persistable} implementations directly by retuning a + * {@link IsNewStrategyFactory} that handles {@link Persistable} implementations directly by returning a * {@link PersistableIsNewStrategy} and delegating to {@link #doGetIsNewStrategy(Class)} for all other types. * * @author Oliver Gierke @@ -45,8 +46,8 @@ public abstract class IsNewStrategyFactorySupport implements IsNewStrategyFactor return strategy; } - throw new IllegalArgumentException(String.format("Unsupported entity %s! Could not determine IsNewStrategy.", - type.getName())); + throw new IllegalArgumentException( + String.format("Unsupported entity %s! Could not determine IsNewStrategy.", type.getName())); } /** @@ -55,5 +56,6 @@ public abstract class IsNewStrategyFactorySupport implements IsNewStrategyFactor * @param type will never be {@literal null}. * @return the {@link IsNewStrategy} to be used for the given type or {@literal null} in case none can be resolved. */ + @Nullable protected abstract IsNewStrategy doGetIsNewStrategy(Class type); } diff --git a/src/main/java/org/springframework/data/support/package-info.java b/src/main/java/org/springframework/data/support/package-info.java index eae8cdf7f..e6f7d23b5 100644 --- a/src/main/java/org/springframework/data/support/package-info.java +++ b/src/main/java/org/springframework/data/support/package-info.java @@ -1,4 +1,5 @@ /** * Core support classes. */ -package org.springframework.data.support; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.support; diff --git a/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java b/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java index 08fc25861..8a4c8ed0f 100644 --- a/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java +++ b/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java @@ -28,6 +28,7 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** @@ -41,8 +42,8 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa private final Iterable> annotationTypess; private final boolean considerInterfaces; - private ResourceLoader resourceLoader; - private Environment environment; + private @Nullable ResourceLoader resourceLoader; + private @Nullable Environment environment; /** * Creates a new {@link AnnotatedTypeScanner} for the given annotation types. @@ -107,12 +108,22 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa Set> types = new HashSet<>(); + ResourceLoader loader = resourceLoader; + ClassLoader classLoader = loader == null ? null : loader.getClassLoader(); + for (String basePackage : basePackages) { for (BeanDefinition definition : provider.findCandidateComponents(basePackage)) { + + String beanClassName = definition.getBeanClassName(); + + if (beanClassName == null) { + throw new IllegalStateException( + String.format("Unable to obtain bean class name from bean definition %s!", definition)); + } + try { - types.add(ClassUtils.forName(definition.getBeanClassName(), - resourceLoader == null ? null : resourceLoader.getClassLoader())); + types.add(ClassUtils.forName(beanClassName, classLoader)); } catch (ClassNotFoundException o_O) { throw new IllegalStateException(o_O); } diff --git a/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java b/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java index de09f2107..a7cadfbdf 100755 --- a/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java +++ b/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java @@ -17,9 +17,9 @@ package org.springframework.data.util; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.util.Optional; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.FieldCallback; @@ -34,7 +34,7 @@ import org.springframework.util.ReflectionUtils.FieldCallback; public class AnnotationDetectionFieldCallback implements FieldCallback { private final Class annotationType; - private Optional field = Optional.empty(); + private @Nullable Field field; /** * Creates a new {@link AnnotationDetectionFieldCallback} scanning for an annotation of the given type. @@ -54,36 +54,65 @@ public class AnnotationDetectionFieldCallback implements FieldCallback { */ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { - if (this.field.isPresent()) { + if (this.field != null) { return; } if (AnnotatedElementUtils.findMergedAnnotation(field, annotationType) != null) { ReflectionUtils.makeAccessible(field); - this.field = Optional.of(field); + this.field = field; } } + /** + * Returns the detected field. + * + * @return the field + */ + @Nullable + public Field getField() { + return field; + } + + /** + * Returns the field that was detected. + * + * @return + * @throws IllegalStateException in case no field with the configured annotation was found. + */ + public Field getRequiredField() { + + Field field = this.field; + + if (field == null) { + throw new IllegalStateException(String.format("No field found for annotation %s!", annotationType)); + } + + return field; + } + /** * Returns the type of the field. * * @return */ - public Optional> getType() { - return field.map(Field::getType); + @Nullable + public Class getType() { + + Field field = this.field; + + return field == null ? null : field.getType(); } /** * Returns the type of the field or throws an {@link IllegalArgumentException} if no field could be found. * * @return - * @throws IllegalStateException + * @throws IllegalStateException in case no field with the configured annotation was found. */ public Class getRequiredType() { - - return getType().orElseThrow(() -> new IllegalStateException( - String.format("Unable to obtain type! Didn't find field with annotation %s!", annotationType))); + return getRequiredField().getType(); } /** @@ -92,11 +121,18 @@ public class AnnotationDetectionFieldCallback implements FieldCallback { * @param source must not be {@literal null}. * @return */ + @Nullable @SuppressWarnings("unchecked") - public Optional getValue(Object source) { + public T getValue(Object source) { Assert.notNull(source, "Source object must not be null!"); - return field.map(it -> (T) ReflectionUtils.getField(it, source)); + Field field = this.field; + + if (field == null) { + return null; + } + + return (T) ReflectionUtils.getField(field, source); } } diff --git a/src/main/java/org/springframework/data/util/AnnotationDetectionMethodCallback.java b/src/main/java/org/springframework/data/util/AnnotationDetectionMethodCallback.java index 418a29f94..fe63bdbc5 100644 --- a/src/main/java/org/springframework/data/util/AnnotationDetectionMethodCallback.java +++ b/src/main/java/org/springframework/data/util/AnnotationDetectionMethodCallback.java @@ -19,6 +19,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils.MethodCallback; @@ -35,8 +36,8 @@ public class AnnotationDetectionMethodCallback implements private final boolean enforceUniqueness; private final Class annotationType; - private Method foundMethod; - private A annotation; + private @Nullable Method foundMethod; + private @Nullable A annotation; /** * Creates a new {@link AnnotationDetectionMethodCallback} for the given annotation type. @@ -64,13 +65,32 @@ public class AnnotationDetectionMethodCallback implements /** * @return the method */ + @Nullable public Method getMethod() { return foundMethod; } + /** + * Returns the method with the configured annotation. + * + * @return + * @throws IllegalStateException in case no method with the configured annotation was found. + */ + public Method getRequiredMethod() { + + Method method = this.foundMethod; + + if (method == null) { + throw new IllegalStateException(String.format("No method with annotation %s found!", annotationType)); + } + + return method; + } + /** * @return the annotation */ + @Nullable public A getAnnotation() { return annotation; } @@ -100,8 +120,8 @@ public class AnnotationDetectionMethodCallback implements if (foundAnnotation != null) { if (foundMethod != null && enforceUniqueness) { - throw new IllegalStateException(String.format(MULTIPLE_FOUND, foundAnnotation.getClass().getName(), foundMethod, - method)); + throw new IllegalStateException( + String.format(MULTIPLE_FOUND, foundAnnotation.getClass().getName(), foundMethod, method)); } this.annotation = foundAnnotation; diff --git a/src/main/java/org/springframework/data/util/DirectFieldAccessFallbackBeanWrapper.java b/src/main/java/org/springframework/data/util/DirectFieldAccessFallbackBeanWrapper.java index 28f1710b9..da8e4e1b1 100644 --- a/src/main/java/org/springframework/data/util/DirectFieldAccessFallbackBeanWrapper.java +++ b/src/main/java/org/springframework/data/util/DirectFieldAccessFallbackBeanWrapper.java @@ -22,6 +22,7 @@ import java.lang.reflect.Field; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.NotReadablePropertyException; import org.springframework.beans.NotWritablePropertyException; +import org.springframework.lang.Nullable; /** * Custom extension of {@link BeanWrapperImpl} that falls back to direct field access in case the object or type being @@ -44,6 +45,7 @@ public class DirectFieldAccessFallbackBeanWrapper extends BeanWrapperImpl { * @see org.springframework.beans.BeanWrapperImpl#getPropertyValue(java.lang.String) */ @Override + @Nullable public Object getPropertyValue(String propertyName) { try { @@ -67,7 +69,7 @@ public class DirectFieldAccessFallbackBeanWrapper extends BeanWrapperImpl { * @see org.springframework.beans.BeanWrapperImpl#setPropertyValue(java.lang.String, java.lang.Object) */ @Override - public void setPropertyValue(String propertyName, Object value) { + public void setPropertyValue(String propertyName, @Nullable Object value) { try { super.setPropertyValue(propertyName, value); diff --git a/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java b/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java index 821e1af8b..02c07fffa 100644 --- a/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java +++ b/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java @@ -21,6 +21,8 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Map; +import javax.annotation.Nonnull; + /** * Special {@link TypeDiscoverer} handling {@link GenericArrayType}s. * @@ -60,6 +62,7 @@ class GenericArrayTypeInformation extends ParentTypeAwareTypeInformation { * @see org.springframework.data.util.TypeDiscoverer#doGetComponentType() */ @Override + @Nonnull protected TypeInformation doGetComponentType() { Type componentType = type.getGenericComponentType(); diff --git a/src/main/java/org/springframework/data/util/Lazy.java b/src/main/java/org/springframework/data/util/Lazy.java index 8455dd52d..42ff67466 100644 --- a/src/main/java/org/springframework/data/util/Lazy.java +++ b/src/main/java/org/springframework/data/util/Lazy.java @@ -21,6 +21,7 @@ import lombok.RequiredArgsConstructor; import java.util.function.Function; import java.util.function.Supplier; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -37,7 +38,8 @@ import org.springframework.util.Assert; public class Lazy implements Supplier { private final Supplier supplier; - private T value; + private @Nullable T value = null; + private boolean resolved = false; /** * Creates a new {@link Lazy} to produce an object lazily. @@ -58,13 +60,44 @@ public class Lazy implements Supplier { */ public T get() { + T value = getNullable(); + if (value == null) { - this.value = supplier.get(); + throw new IllegalStateException("Expected lazy evaluation to yield a non-null value but got null!"); } return value; } + /** + * Returns the value of the lazy computation or the given default value in case the computation yields + * {@literal null}. + * + * @param value + * @return + */ + @Nullable + public T orElse(@Nullable T value) { + return orElseGet(() -> value); + } + + /** + * Returns the value of the lazy computation or the value produced by the given {@link Supplier} in case the original + * value is {@literal null}. + * + * @param supplier must not be {@literal null}. + * @return + */ + @Nullable + public T orElseGet(Supplier supplier) { + + Assert.notNull(supplier, "Default value supplier must not be null!"); + + T nullable = getNullable(); + + return nullable == null ? supplier.get() : nullable; + } + /** * Creates a new {@link Lazy} with the given {@link Function} lazily applied to the current one. * @@ -90,4 +123,26 @@ public class Lazy implements Supplier { return Lazy.of(() -> function.apply(get()).get()); } + + /** + * Returns the value of the lazy evaluation. + * + * @return + */ + @Nullable + private T getNullable() { + + T value = this.value; + + if (this.resolved) { + return value; + } + + value = supplier.get(); + + this.value = value; + this.resolved = true; + + return value; + } } diff --git a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java index 75719eec8..4fcc76471 100644 --- a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java @@ -20,12 +20,14 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -39,7 +41,7 @@ import org.springframework.util.StringUtils; class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation { private final ParameterizedType type; - private Boolean resolved; + private final Lazy resolved; /** * Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}. @@ -51,7 +53,9 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation Map, Type> typeVariableMap) { super(type, parent, typeVariableMap); + this.type = type; + this.resolved = Lazy.of(() -> isResolvedCompletely()); } /* @@ -59,6 +63,7 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation * @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType() */ @Override + @Nullable protected TypeInformation doGetMapValueType() { if (Map.class.isAssignableFrom(getType())) { @@ -127,7 +132,8 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation : target.getSuperTypeInformation(rawType); List> myParameters = getTypeArguments(); - List> typeParameters = otherTypeInformation.getTypeArguments(); + List> typeParameters = otherTypeInformation == null ? Collections.emptyList() + : otherTypeInformation.getTypeArguments(); if (myParameters.size() != typeParameters.size()) { return false; @@ -147,6 +153,7 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation * @see org.springframework.data.util.TypeDiscoverer#doGetComponentType() */ @Override + @Nullable protected TypeInformation doGetComponentType() { return createInfo(type.getActualTypeArguments()[0]); } @@ -156,7 +163,7 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation * @see org.springframework.data.util.ParentTypeAwareTypeInformation#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; @@ -168,7 +175,7 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation ParameterizedTypeInformation that = (ParameterizedTypeInformation) obj; - if (this.isResolvedCompletely() && that.isResolvedCompletely()) { + if (this.isResolved() && that.isResolved()) { return this.type.equals(that.type); } @@ -181,7 +188,7 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation */ @Override public int hashCode() { - return isResolvedCompletely() ? this.type.hashCode() : super.hashCode(); + return isResolved() ? this.type.hashCode() : super.hashCode(); } /* @@ -195,16 +202,16 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation StringUtils.collectionToCommaDelimitedString(getTypeArguments())); } - private boolean isResolvedCompletely() { + private boolean isResolved() { + return resolved.get(); + } - if (resolved != null) { - return resolved; - } + private boolean isResolvedCompletely() { Type[] typeArguments = type.getActualTypeArguments(); if (typeArguments.length == 0) { - return cacheAndReturn(false); + return false; } for (Type typeArgument : typeArguments) { @@ -213,21 +220,15 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation if (info instanceof ParameterizedTypeInformation) { if (!((ParameterizedTypeInformation) info).isResolvedCompletely()) { - return cacheAndReturn(false); + return false; } } if (!(info instanceof ClassTypeInformation)) { - return cacheAndReturn(false); + return false; } } - return cacheAndReturn(true); - } - - private boolean cacheAndReturn(boolean resolved) { - - this.resolved = resolved; - return resolved; + return true; } } diff --git a/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java b/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java index 8f24c3819..9dd54fe85 100644 --- a/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java @@ -20,6 +20,8 @@ import java.lang.reflect.TypeVariable; import java.util.HashMap; import java.util.Map; +import org.springframework.lang.Nullable; + /** * Base class for {@link TypeInformation} implementations that need parent type awareness. * @@ -78,12 +80,16 @@ public abstract class ParentTypeAwareTypeInformation extends TypeDiscoverer type, final FieldFilter filter) { + @Nullable + public static Field findField(Class type, FieldFilter filter) { return findField(type, new DescribedFieldFilter() { @@ -141,6 +143,7 @@ public class ReflectionUtils { * @return the field matching the given {@link DescribedFieldFilter} or {@literal null} if none found. * @throws IllegalStateException in case more than one matching field is found */ + @Nullable public static Field findField(Class type, DescribedFieldFilter filter) { return findField(type, filter, true); } @@ -155,6 +158,7 @@ public class ReflectionUtils { * @return the field matching the given {@link DescribedFieldFilter} or {@literal null} if none found. * @throws IllegalStateException if enforceUniqueness is true and more than one matching field is found */ + @Nullable public static Field findField(Class type, DescribedFieldFilter filter, boolean enforceUniqueness) { Assert.notNull(type, "Type must not be null!"); @@ -188,6 +192,25 @@ public class ReflectionUtils { return foundField; } + /** + * Finds the field of the given name on the given type. + * + * @param type must not be {@literal null}. + * @param name must not be {@literal null} or empty. + * @return + * @throws IllegalArgumentException in case the field can't be found. + */ + public static Field findRequiredField(Class type, String name) { + + Field result = org.springframework.util.ReflectionUtils.findField(type, name); + + if (result == null) { + throw new IllegalArgumentException(String.format("Unable to find field %s on %s!", name, type)); + } + + return result; + } + /** * Sets the given field on the given object to the given value. Will make sure the given field is accessible. * @@ -195,7 +218,7 @@ public class ReflectionUtils { * @param target must not be {@literal null}. * @param value */ - public static void setField(Field field, Object target, Object value) { + public static void setField(Field field, Object target, @Nullable Object value) { org.springframework.util.ReflectionUtils.makeAccessible(field); org.springframework.util.ReflectionUtils.setField(field, target, value); @@ -218,6 +241,32 @@ public class ReflectionUtils { .findFirst(); } + /** + * Returns the method with the given name of the given class and parameter types. + * + * @param type must not be {@literal null}. + * @param name must not be {@literal null}. + * @param parameterTypes must not be {@literal null}. + * @return + * @throws IllegalArgumentException in case the method cannot be resolved. + */ + public static Method findRequiredMethod(Class type, String name, Class... parameterTypes) { + + Method result = org.springframework.util.ReflectionUtils.findMethod(type, name, parameterTypes); + + if (result == null) { + + String parameterTypeNames = Arrays.stream(parameterTypes) // + .map(Object::toString) // + .collect(Collectors.joining(", ")); + + throw new IllegalArgumentException( + String.format("Unable to find method %s(%s)on %s!", name, parameterTypeNames, type)); + } + + return result; + } + /** * Returns a {@link Stream} of the return and parameters types of the given {@link Method}. * diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 7423f3fae..b579015b2 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -43,6 +43,7 @@ import java.util.stream.Collectors; import org.springframework.beans.BeanUtils; import org.springframework.core.GenericTypeResolver; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -211,6 +212,7 @@ class TypeDiscoverer implements TypeInformation { * (non-Javadoc) * @see org.springframework.data.util.TypeInformation#getProperty(java.lang.String) */ + @Nullable public TypeInformation getProperty(String fieldname) { int separatorIndex = fieldname.indexOf('.'); @@ -237,6 +239,7 @@ class TypeDiscoverer implements TypeInformation { * @param fieldname * @return */ + @SuppressWarnings("null") private Optional> getPropertyInformation(String fieldname) { Class rawType = getType(); @@ -280,6 +283,7 @@ class TypeDiscoverer implements TypeInformation { * @param descriptor must not be {@literal null} * @return */ + @Nullable private static Type getGenericType(PropertyDescriptor descriptor) { Method method = descriptor.getReadMethod(); @@ -319,6 +323,7 @@ class TypeDiscoverer implements TypeInformation { * (non-Javadoc) * @see org.springframework.data.util.TypeInformation#getActualType() */ + @Nullable public TypeInformation getActualType() { if (isMap()) { @@ -353,10 +358,12 @@ class TypeDiscoverer implements TypeInformation { * (non-Javadoc) * @see org.springframework.data.util.TypeInformation#getMapValueType() */ + @Nullable public TypeInformation getMapValueType() { - return valueType.get(); + return valueType.orElse(null); } + @Nullable protected TypeInformation doGetMapValueType() { return isMap() ? getTypeArgument(getBaseType(MAP_TYPES), 1) : getTypeArguments().stream().skip(1).findFirst().orElse(null); @@ -377,10 +384,12 @@ class TypeDiscoverer implements TypeInformation { * (non-Javadoc) * @see org.springframework.data.util.TypeInformation#getComponentType() */ + @Nullable public final TypeInformation getComponentType() { - return componentType.get(); + return componentType.orElse(null); } + @Nullable protected TypeInformation doGetComponentType() { Class rawType = getType(); @@ -429,6 +438,7 @@ class TypeDiscoverer implements TypeInformation { * (non-Javadoc) * @see org.springframework.data.util.TypeInformation#getSuperTypeInformation(java.lang.Class) */ + @Nullable public TypeInformation getSuperTypeInformation(Class superType) { Class rawType = getType(); @@ -478,7 +488,10 @@ class TypeDiscoverer implements TypeInformation { * @see org.springframework.data.util.TypeInformation#isAssignableFrom(org.springframework.data.util.TypeInformation) */ public boolean isAssignableFrom(TypeInformation target) { - return target.getSuperTypeInformation(getType()).equals(this); + + TypeInformation superTypeInformation = target.getSuperTypeInformation(getType()); + + return superTypeInformation == null ? false : superTypeInformation.equals(this); } /* @@ -498,6 +511,7 @@ class TypeDiscoverer implements TypeInformation { : createInfo(new SyntheticParamterizedType(type, arguments))); } + @Nullable private TypeInformation getTypeArgument(Class bound, int index) { Class[] arguments = GenericTypeResolver.resolveTypeArguments(getType(), bound); @@ -527,7 +541,7 @@ class TypeDiscoverer implements TypeInformation { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; @@ -582,6 +596,7 @@ class TypeDiscoverer implements TypeInformation { * @see java.lang.reflect.ParameterizedType#getOwnerType() */ @Override + @Nullable public Type getOwnerType() { return null; } diff --git a/src/main/java/org/springframework/data/util/TypeInformation.java b/src/main/java/org/springframework/data/util/TypeInformation.java index f212861df..23d1218dd 100644 --- a/src/main/java/org/springframework/data/util/TypeInformation.java +++ b/src/main/java/org/springframework/data/util/TypeInformation.java @@ -19,6 +19,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.List; +import org.springframework.lang.Nullable; + /** * Interface to access property types and resolving generics on the way. Starting with a {@link ClassTypeInformation} * you can traverse properties using {@link #getProperty(String)} to access type information. @@ -43,6 +45,7 @@ public interface TypeInformation { * @param property * @return */ + @Nullable TypeInformation getProperty(String property); /** @@ -80,6 +83,7 @@ public interface TypeInformation { * * @return */ + @Nullable TypeInformation getComponentType(); /** @@ -96,14 +100,14 @@ public interface TypeInformation { TypeInformation componentType = getComponentType(); if (componentType != null) { - return getComponentType(); + return componentType; } throw new IllegalStateException(String.format("Can't resolve required component type for %s!", getType())); } /** - * Returns whether the property is a {@link java.util.Map}. If this returns {@literal true} you can expect + * Returns whether the property is a {@ link java.util.Map}. If this returns {@literal true} you can expect * {@link #getComponentType()} as well as {@link #getMapValueType()} to return something not {@literal null}. * * @return @@ -115,6 +119,7 @@ public interface TypeInformation { * * @return */ + @Nullable TypeInformation getMapValueType(); /** @@ -122,7 +127,8 @@ public interface TypeInformation { * {@link IllegalStateException} if the map value type cannot be resolved. * * @return - * @throws IllegalStateException if the map value type cannot be resolved. + * @throws IllegalStateException if the map value type cannot be resolved, usually due to the current + * {@link java.util.Map} type being a raw one. * @since 2.0 */ default TypeInformation getRequiredMapValueType() { @@ -155,10 +161,33 @@ public interface TypeInformation { * Transparently returns the {@link java.util.Map} value type if the type is a {@link java.util.Map}, returns the * component type if the type {@link #isCollectionLike()} or the simple type if none of this applies. * - * @return + * @return the map value, collection component type or the current type, {@literal null} it the current type is a raw + * {@link java.util.Map} or {@link java.util.Collection}. */ + @Nullable TypeInformation getActualType(); + /** + * Transparently returns the {@link java.util.Map} value type if the type is a {@link java.util.Map}, returns the + * component type if the type {@link #isCollectionLike()} or the simple type if none of this applies. + * + * @return + * @throws IllegalArgumentException if the current type is a raw {@link java.util.Map} or {@link java.util.Collection} + * and no value or component type is available. + * @since 2.0 + */ + default TypeInformation getRequiredActualType() { + + TypeInformation result = getActualType(); + + if (result == null) { + throw new IllegalStateException( + "Expected to be able to resolve a type but got null! This usually stems from types implementing raw Map or Collection interfaces!"); + } + + return result; + } + /** * Returns a {@link TypeInformation} for the return type of the given {@link Method}. Will potentially resolve * generics information against the current types type parameter bindings. @@ -183,8 +212,30 @@ public interface TypeInformation { * @return the {@link TypeInformation} for the given raw super type or {@literal null} in case the current * {@link TypeInformation} does not implement the given type. */ + @Nullable TypeInformation getSuperTypeInformation(Class superType); + /** + * Returns the {@link TypeInformation} for the given raw super type. + * + * @param superType must not be {@literal null}. + * @return the {@link TypeInformation} for the given raw super type. + * @throws IllegalArgumentException in case the current {@link TypeInformation} does not implement the given type. + * @since 2.0 + */ + default TypeInformation getRequiredSuperTypeInformation(Class superType) { + + TypeInformation result = getSuperTypeInformation(superType); + + if (result == null) { + throw new IllegalArgumentException(String.format( + "Can't retrieve super type information for %s! Does current type really implement the given one?", + superType)); + } + + return result; + } + /** * Returns if the current {@link TypeInformation} can be safely assigned to the given one. Mimics semantics of * {@link Class#isAssignableFrom(Class)} but takes generics into account. Thus it will allow to detect that a diff --git a/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java b/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java index b9dd5439b..02d555791 100644 --- a/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java +++ b/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java @@ -22,6 +22,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Map; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -94,7 +95,7 @@ class TypeVariableTypeInformation extends ParentTypeAwareTypeInformation { * @see org.springframework.data.util.ParentTypeAwareTypeInformation#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; diff --git a/src/main/java/org/springframework/data/util/Version.java b/src/main/java/org/springframework/data/util/Version.java index 651edaa22..df38cbad2 100644 --- a/src/main/java/org/springframework/data/util/Version.java +++ b/src/main/java/org/springframework/data/util/Version.java @@ -18,6 +18,7 @@ package org.springframework.data.util; import java.util.ArrayList; import java.util.List; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -43,7 +44,8 @@ public class Version implements Comparable { public Version(int... parts) { Assert.notNull(parts, "Parts must not be null!"); - Assert.isTrue(parts.length > 0 && parts.length < 5, String.format("Invalid parts length. 0 < %s < 5", parts.length)); + Assert.isTrue(parts.length > 0 && parts.length < 5, + String.format("Invalid parts length. 0 < %s < 5", parts.length)); this.major = parts[0]; this.minor = parts.length > 1 ? parts[1] : 0; @@ -139,11 +141,7 @@ public class Version implements Comparable { * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ - public int compareTo(Version that) { - - if (that == null) { - return 1; - } + public int compareTo(@SuppressWarnings("null") Version that) { if (major != that.major) { return major - that.major; @@ -169,7 +167,7 @@ public class Version implements Comparable { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/data/util/package-info.java b/src/main/java/org/springframework/data/util/package-info.java index 8cc907d47..04a8e670c 100644 --- a/src/main/java/org/springframework/data/util/package-info.java +++ b/src/main/java/org/springframework/data/util/package-info.java @@ -1,4 +1,5 @@ /** * Core utility APIs such as a type information framework to resolve generic types. */ -package org.springframework.data.util; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.util; diff --git a/src/main/java/org/springframework/data/web/HateoasPageableHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/HateoasPageableHandlerMethodArgumentResolver.java index b43995416..5c9143b98 100644 --- a/src/main/java/org/springframework/data/web/HateoasPageableHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/HateoasPageableHandlerMethodArgumentResolver.java @@ -27,6 +27,7 @@ import org.springframework.hateoas.TemplateVariable; import org.springframework.hateoas.TemplateVariable.VariableType; import org.springframework.hateoas.TemplateVariables; import org.springframework.hateoas.mvc.UriComponentsContributor; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -39,8 +40,9 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Oliver Gierke * @author Nick Williams */ -public class HateoasPageableHandlerMethodArgumentResolver extends PageableHandlerMethodArgumentResolver implements - UriComponentsContributor { +@SuppressWarnings("null") +public class HateoasPageableHandlerMethodArgumentResolver extends PageableHandlerMethodArgumentResolver + implements UriComponentsContributor { private static final HateoasSortHandlerMethodArgumentResolver DEFAULT_SORT_RESOLVER = new HateoasSortHandlerMethodArgumentResolver(); @@ -59,7 +61,7 @@ public class HateoasPageableHandlerMethodArgumentResolver extends PageableHandle * * @param sortResolver */ - public HateoasPageableHandlerMethodArgumentResolver(HateoasSortHandlerMethodArgumentResolver sortResolver) { + public HateoasPageableHandlerMethodArgumentResolver(@Nullable HateoasSortHandlerMethodArgumentResolver sortResolver) { super(getDefaultedSortResolver(sortResolver)); this.sortResolver = getDefaultedSortResolver(sortResolver); @@ -100,7 +102,7 @@ public class HateoasPageableHandlerMethodArgumentResolver extends PageableHandle * @see org.springframework.hateoas.mvc.UriComponentsContributor#enhance(org.springframework.web.util.UriComponentsBuilder, org.springframework.core.MethodParameter, java.lang.Object) */ @Override - public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) { + public void enhance(UriComponentsBuilder builder, @Nullable MethodParameter parameter, Object value) { if (!(value instanceof Pageable)) { return; @@ -114,14 +116,14 @@ public class HateoasPageableHandlerMethodArgumentResolver extends PageableHandle int pageNumber = pageable.getPageNumber(); builder.replaceQueryParam(pagePropertyName, isOneIndexedParameters() ? pageNumber + 1 : pageNumber); - builder.replaceQueryParam(sizePropertyName, pageable.getPageSize() <= getMaxPageSize() ? pageable.getPageSize() - : getMaxPageSize()); + builder.replaceQueryParam(sizePropertyName, + pageable.getPageSize() <= getMaxPageSize() ? pageable.getPageSize() : getMaxPageSize()); this.sortResolver.enhance(builder, parameter, pageable.getSort()); } private static HateoasSortHandlerMethodArgumentResolver getDefaultedSortResolver( - HateoasSortHandlerMethodArgumentResolver sortResolver) { + @Nullable HateoasSortHandlerMethodArgumentResolver sortResolver) { return sortResolver == null ? DEFAULT_SORT_RESOLVER : sortResolver; } } diff --git a/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java index 4c3784bd8..028654c34 100644 --- a/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java @@ -36,8 +36,9 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Thomas Darimont * @author Nick Williams */ -public class HateoasSortHandlerMethodArgumentResolver extends SortHandlerMethodArgumentResolver implements - UriComponentsContributor { +@SuppressWarnings("null") +public class HateoasSortHandlerMethodArgumentResolver extends SortHandlerMethodArgumentResolver + implements UriComponentsContributor { /** * Returns the template variables for the sort parameter. diff --git a/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java b/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java index 64bf85db3..a122d1f1b 100644 --- a/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java +++ b/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java @@ -34,6 +34,7 @@ import org.springframework.data.projection.Accessor; import org.springframework.data.projection.MethodInterceptorFactory; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import com.fasterxml.jackson.databind.ObjectMapper; @@ -127,15 +128,16 @@ public class JsonProjectingMethodInterceptorFactory implements MethodInterceptor * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ + @Nullable @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); TypeInformation returnType = ClassTypeInformation.fromReturnTypeOf(method); ResolvableType type = ResolvableType.forMethodReturnType(method); String jsonPath = getJsonPath(method); - if (returnType.getActualType().getType().isInterface()) { + if (returnType.getRequiredActualType().getType().isInterface()) { List result = context.read(jsonPath); return result.isEmpty() ? null : result.get(0); @@ -144,7 +146,8 @@ public class JsonProjectingMethodInterceptorFactory implements MethodInterceptor boolean isCollectionResult = Collection.class.isAssignableFrom(type.getRawClass()); type = isCollectionResult ? type : ResolvableType.forClassWithGenerics(List.class, type); type = isCollectionResult && JsonPath.isPathDefinite(jsonPath) - ? ResolvableType.forClassWithGenerics(List.class, type) : type; + ? ResolvableType.forClassWithGenerics(List.class, type) + : type; List result = (List) context.read(jsonPath, new ResolvableTypeRef(type)); diff --git a/src/main/java/org/springframework/data/web/MapDataBinder.java b/src/main/java/org/springframework/data/web/MapDataBinder.java index 77e943d0f..edaa37863 100644 --- a/src/main/java/org/springframework/data/web/MapDataBinder.java +++ b/src/main/java/org/springframework/data/web/MapDataBinder.java @@ -23,6 +23,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; + import org.springframework.beans.AbstractPropertyAccessor; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; @@ -45,6 +47,7 @@ import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.bind.WebDataBinder; @@ -77,10 +80,18 @@ class MapDataBinder extends WebDataBinder { * (non-Javadoc) * @see org.springframework.validation.DataBinder#getTarget() */ + @Nonnull @Override @SuppressWarnings("unchecked") public Map getTarget() { - return (Map) super.getTarget(); + + Object target = super.getTarget(); + + if (target == null) { + throw new IllegalStateException("Target bean should never be null!"); + } + + return (Map) target; } /* @@ -136,6 +147,7 @@ class MapDataBinder extends WebDataBinder { * (non-Javadoc) * @see org.springframework.beans.PropertyAccessor#getPropertyTypeDescriptor(java.lang.String) */ + @Nullable @Override public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { throw new UnsupportedOperationException(); @@ -145,6 +157,7 @@ class MapDataBinder extends WebDataBinder { * (non-Javadoc) * @see org.springframework.beans.AbstractPropertyAccessor#getPropertyValue(java.lang.String) */ + @Nullable @Override public Object getPropertyValue(String propertyName) throws BeansException { throw new UnsupportedOperationException(); @@ -155,7 +168,7 @@ class MapDataBinder extends WebDataBinder { * @see org.springframework.beans.AbstractPropertyAccessor#setPropertyValue(java.lang.String, java.lang.Object) */ @Override - public void setPropertyValue(String propertyName, Object value) throws BeansException { + public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException { if (!isWritableProperty(propertyName)) { throw new NotWritablePropertyException(type, propertyName); @@ -174,22 +187,33 @@ class MapDataBinder extends WebDataBinder { propertyType = propertyName.endsWith("]") ? propertyType.getActualType() : propertyType; - if (conversionRequired(value, propertyType.getType())) { + if (propertyType != null && conversionRequired(value, propertyType.getType())) { PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(owningType.getType(), leafProperty.getSegment()); + + if (descriptor == null) { + throw new IllegalStateException(String.format("Couldn't find PropertyDescriptor for %s on %s!", + leafProperty.getSegment(), owningType.getType())); + } + MethodParameter methodParameter = new MethodParameter(descriptor.getReadMethod(), -1); TypeDescriptor typeDescriptor = TypeDescriptor.nested(methodParameter, 0); + if (typeDescriptor == null) { + throw new IllegalStateException( + String.format("Couldn't obtain type descriptor for method parameter %s!", methodParameter)); + } + value = conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor); } expression.setValue(context, value); } - private boolean conversionRequired(Object source, Class targetType) { + private boolean conversionRequired(@Nullable Object source, Class targetType) { - if (targetType.isInstance(source)) { + if (source == null || targetType.isInstance(source)) { return false; } @@ -234,7 +258,7 @@ class MapDataBinder extends WebDataBinder { * @see org.springframework.context.expression.MapAccessor#canRead(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) */ @Override - public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { + public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException { return true; } @@ -244,7 +268,11 @@ class MapDataBinder extends WebDataBinder { */ @Override @SuppressWarnings("unchecked") - public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException { + + if (target == null) { + return TypedValue.NULL; + } PropertyPath path = PropertyPath.from(name, type); diff --git a/src/main/java/org/springframework/data/web/MethodParameterAwarePagedResourcesAssembler.java b/src/main/java/org/springframework/data/web/MethodParameterAwarePagedResourcesAssembler.java index c4d41fcfe..024ee7fa9 100644 --- a/src/main/java/org/springframework/data/web/MethodParameterAwarePagedResourcesAssembler.java +++ b/src/main/java/org/springframework/data/web/MethodParameterAwarePagedResourcesAssembler.java @@ -15,6 +15,9 @@ */ package org.springframework.data.web; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import org.springframework.core.MethodParameter; import org.springframework.util.Assert; import org.springframework.web.util.UriComponents; @@ -38,7 +41,7 @@ class MethodParameterAwarePagedResourcesAssembler extends PagedResourcesAssem * @param baseUri can be {@literal null}. */ public MethodParameterAwarePagedResourcesAssembler(MethodParameter parameter, - HateoasPageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) { + @Nullable HateoasPageableHandlerMethodArgumentResolver resolver, @Nullable UriComponents baseUri) { super(resolver, baseUri); @@ -50,6 +53,7 @@ class MethodParameterAwarePagedResourcesAssembler extends PagedResourcesAssem * (non-Javadoc) * @see org.springframework.data.web.PagedResourcesAssembler#getMethodParameter() */ + @Nonnull @Override protected MethodParameter getMethodParameter() { return parameter; diff --git a/src/main/java/org/springframework/data/web/PageableArgumentResolver.java b/src/main/java/org/springframework/data/web/PageableArgumentResolver.java index 2a57d0f94..4e448971c 100644 --- a/src/main/java/org/springframework/data/web/PageableArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/PageableArgumentResolver.java @@ -15,8 +15,11 @@ */ package org.springframework.data.web; +import javax.annotation.Nonnull; + import org.springframework.core.MethodParameter; import org.springframework.data.domain.Pageable; +import org.springframework.lang.Nullable; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -43,9 +46,10 @@ public interface PageableArgumentResolver extends HandlerMethodArgumentResolver * @param mavContainer the ModelAndViewContainer for the current request * @param webRequest the current request * @param binderFactory a factory for creating {@link WebDataBinder} instances - * @return the resolved argument value, or {@code null} + * @return the resolved argument value. */ + @Nonnull @Override - Pageable resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, - WebDataBinderFactory binderFactory); + Pageable resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory); } diff --git a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java index dc851fe42..a953adfc1 100644 --- a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java @@ -25,6 +25,7 @@ import org.springframework.core.MethodParameter; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -85,7 +86,7 @@ public class PageableHandlerMethodArgumentResolver implements PageableArgumentRe * @param sortResolver the sort resolver to use * @since 1.13 */ - public PageableHandlerMethodArgumentResolver(SortArgumentResolver sortResolver) { + public PageableHandlerMethodArgumentResolver(@Nullable SortArgumentResolver sortResolver) { this.sortResolver = sortResolver == null ? DEFAULT_SORT_RESOLVER : sortResolver; } @@ -235,8 +236,8 @@ public class PageableHandlerMethodArgumentResolver implements PageableArgumentRe * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) */ @Override - public Pageable resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public Pageable resolveArgument(MethodParameter methodParameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { assertPageableUniqueness(methodParameter); @@ -249,7 +250,7 @@ public class PageableHandlerMethodArgumentResolver implements PageableArgumentRe Optional pageSize = parseAndApplyBoundaries(pageSizeString, maxPageSize, false); if (!(page.isPresent() && pageSize.isPresent()) && !defaultOrFallback.isPresent()) { - return null; + return Pageable.unpaged(); } int p = page @@ -276,12 +277,14 @@ public class PageableHandlerMethodArgumentResolver implements PageableArgumentRe * @param parameter the {@link MethodParameter} potentially qualified. * @return the name of the request parameter. */ - protected String getParameterNameToUse(String source, MethodParameter parameter) { + protected String getParameterNameToUse(String source, @Nullable MethodParameter parameter) { StringBuilder builder = new StringBuilder(prefix); - if (parameter != null && parameter.hasParameterAnnotation(Qualifier.class)) { - builder.append(parameter.getParameterAnnotation(Qualifier.class).value()); + Qualifier qualifier = parameter == null ? null : parameter.getParameterAnnotation(Qualifier.class); + + if (qualifier != null) { + builder.append(qualifier.value()); builder.append(qualifierDelimiter); } @@ -290,16 +293,16 @@ public class PageableHandlerMethodArgumentResolver implements PageableArgumentRe private Pageable getDefaultFromAnnotationOrFallback(MethodParameter methodParameter) { - if (methodParameter.hasParameterAnnotation(PageableDefault.class)) { - return getDefaultPageRequestFrom(methodParameter); + PageableDefault defaults = methodParameter.getParameterAnnotation(PageableDefault.class); + + if (defaults != null) { + return getDefaultPageRequestFrom(methodParameter, defaults); } return fallbackPageable; } - private static Pageable getDefaultPageRequestFrom(MethodParameter parameter) { - - PageableDefault defaults = parameter.getParameterAnnotation(PageableDefault.class); + private static Pageable getDefaultPageRequestFrom(MethodParameter parameter, PageableDefault defaults) { Integer defaultPageNumber = defaults.page(); Integer defaultPageSize = getSpecificPropertyOrDefaultFromValue(defaults, "size"); @@ -325,7 +328,7 @@ public class PageableHandlerMethodArgumentResolver implements PageableArgumentRe * @param shiftIndex whether to shift the index if {@link #oneIndexedParameters} is set to true. * @return */ - private Optional parseAndApplyBoundaries(String parameter, int upper, boolean shiftIndex) { + private Optional parseAndApplyBoundaries(@Nullable String parameter, int upper, boolean shiftIndex) { if (!StringUtils.hasText(parameter)) { return Optional.empty(); diff --git a/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java b/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java index 3ac0b833b..ac859ff6a 100644 --- a/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java +++ b/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java @@ -35,6 +35,7 @@ import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.core.EmbeddedWrapper; import org.springframework.hateoas.core.EmbeddedWrappers; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponents; @@ -60,10 +61,11 @@ public class PagedResourcesAssembler implements ResourceAssembler, Pa * base URI. If the former is {@literal null}, a default one will be created. If the latter is {@literal null}, calls * to {@link #toResource(Page)} will use the current request's URI to build the relevant previous and next links. * - * @param resolver - * @param baseUri + * @param resolver can be {@literal null}. + * @param baseUri can be {@literal null}. */ - public PagedResourcesAssembler(HateoasPageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) { + public PagedResourcesAssembler(@Nullable HateoasPageableHandlerMethodArgumentResolver resolver, + @Nullable UriComponents baseUri) { this.pageableResolver = resolver == null ? new HateoasPageableHandlerMethodArgumentResolver() : resolver; this.baseUri = Optional.ofNullable(baseUri); @@ -87,6 +89,7 @@ public class PagedResourcesAssembler implements ResourceAssembler, Pa * @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object) */ @Override + @SuppressWarnings("null") public PagedResources> toResource(Page entity) { return toResource(entity, it -> new Resource<>(it)); } @@ -276,6 +279,7 @@ public class PagedResourcesAssembler implements ResourceAssembler, Pa * @return * @since 1.7 */ + @Nullable protected MethodParameter getMethodParameter() { return null; } diff --git a/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java b/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java index c75aabda7..1ac8ad507 100644 --- a/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java @@ -17,6 +17,8 @@ package org.springframework.data.web; import java.util.List; +import javax.annotation.Nonnull; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -26,6 +28,7 @@ import org.springframework.hateoas.Link; import org.springframework.hateoas.MethodLinkBuilderFactory; import org.springframework.hateoas.core.MethodParameters; import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory; +import org.springframework.lang.Nullable; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -60,7 +63,7 @@ public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArg * @param linkBuilderFactory can be {@literal null}, will be defaulted to a {@link ControllerLinkBuilderFactory}. */ public PagedResourcesAssemblerArgumentResolver(HateoasPageableHandlerMethodArgumentResolver resolver, - MethodLinkBuilderFactory linkBuilderFactory) { + @Nullable MethodLinkBuilderFactory linkBuilderFactory) { this.resolver = resolver; this.linkBuilderFactory = linkBuilderFactory == null ? new ControllerLinkBuilderFactory() : linkBuilderFactory; @@ -79,9 +82,10 @@ public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArg * (non-Javadoc) * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) */ + @Nonnull @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { UriComponents fromUriString = resolveBaseUri(parameter); MethodParameter pageableParameter = findMatchingPageableParameter(parameter); @@ -99,6 +103,7 @@ public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArg * @param parameter must not be {@literal null}. * @return the {@link UriComponents} representing the base URI, or {@literal null} if it can't be resolved eagerly. */ + @Nullable private UriComponents resolveBaseUri(MethodParameter parameter) { try { @@ -116,6 +121,7 @@ public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArg * @param parameter must not be {@literal null}. * @return */ + @Nullable private static MethodParameter findMatchingPageableParameter(MethodParameter parameter) { MethodParameters parameters = new MethodParameters(parameter.getMethod()); @@ -154,7 +160,9 @@ public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArg throw new IllegalStateException(PARAMETER_AMBIGUITY); } - private static MethodParameter returnIfQualifiersMatch(MethodParameter pageableParameter, Qualifier assemblerQualifier) { + @Nullable + private static MethodParameter returnIfQualifiersMatch(MethodParameter pageableParameter, + @Nullable Qualifier assemblerQualifier) { if (assemblerQualifier == null) { return pageableParameter; diff --git a/src/main/java/org/springframework/data/web/ProjectingJackson2HttpMessageConverter.java b/src/main/java/org/springframework/data/web/ProjectingJackson2HttpMessageConverter.java index 3b86849df..c75df529c 100644 --- a/src/main/java/org/springframework/data/web/ProjectingJackson2HttpMessageConverter.java +++ b/src/main/java/org/springframework/data/web/ProjectingJackson2HttpMessageConverter.java @@ -31,6 +31,7 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; @@ -112,7 +113,7 @@ public class ProjectingJackson2HttpMessageConverter extends MappingJackson2HttpM * @see org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#canRead(java.lang.reflect.Type, java.lang.Class, org.springframework.http.MediaType) */ @Override - public boolean canRead(Type type, Class contextClass, MediaType mediaType) { + public boolean canRead(Type type, @Nullable Class contextClass, @Nullable MediaType mediaType) { if (!canRead(mediaType)) { return false; @@ -137,7 +138,7 @@ public class ProjectingJackson2HttpMessageConverter extends MappingJackson2HttpM * @see org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#canWrite(java.lang.Class, org.springframework.http.MediaType) */ @Override - public boolean canWrite(Class clazz, MediaType mediaType) { + public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { return false; } @@ -146,8 +147,9 @@ public class ProjectingJackson2HttpMessageConverter extends MappingJackson2HttpM * @see org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#read(java.lang.reflect.Type, java.lang.Class, org.springframework.http.HttpInputMessage) */ @Override - public Object read(Type type, Class contextClass, HttpInputMessage inputMessage) + public Object read(Type type, @Nullable Class contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { - return projectionFactory.createProjection(ResolvableType.forType(type).getRawClass(), inputMessage.getBody()); + return projectionFactory.createProjection(ResolvableType.forType(type).resolve(Object.class), + inputMessage.getBody()); } } diff --git a/src/main/java/org/springframework/data/web/SortArgumentResolver.java b/src/main/java/org/springframework/data/web/SortArgumentResolver.java index 9b59971bb..98a55df2c 100644 --- a/src/main/java/org/springframework/data/web/SortArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/SortArgumentResolver.java @@ -15,6 +15,9 @@ */ package org.springframework.data.web; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import org.springframework.core.MethodParameter; import org.springframework.data.domain.Sort; import org.springframework.web.bind.WebDataBinder; @@ -45,7 +48,8 @@ public interface SortArgumentResolver extends HandlerMethodArgumentResolver { * @param binderFactory a factory for creating {@link WebDataBinder} instances * @return the resolved argument value, or {@code null} */ + @Nonnull @Override - Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, - WebDataBinderFactory binderFactory); + Sort resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory); } diff --git a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java index 4d2777033..728159c89 100644 --- a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import javax.annotation.Nullable; + import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.MethodParameter; import org.springframework.data.domain.Sort; @@ -106,8 +108,8 @@ public class SortHandlerMethodArgumentResolver implements SortArgumentResolver { * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) */ @Override - public Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public Sort resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { String[] directionParameter = webRequest.getParameterValues(getSortParameter(parameter)); @@ -184,15 +186,17 @@ public class SortHandlerMethodArgumentResolver implements SortArgumentResolver { /** * Returns the sort parameter to be looked up from the request. Potentially applies qualifiers to it. * - * @param parameter will never be {@literal null}. + * @param parameter can be {@literal null}. * @return */ - protected String getSortParameter(MethodParameter parameter) { + protected String getSortParameter(@Nullable MethodParameter parameter) { StringBuilder builder = new StringBuilder(); - if (parameter != null && parameter.hasParameterAnnotation(Qualifier.class)) { - builder.append(parameter.getParameterAnnotation(Qualifier.class).value()).append(qualifierDelimiter); + Qualifier qualifier = parameter != null ? parameter.getParameterAnnotation(Qualifier.class) : null; + + if (qualifier != null) { + builder.append(qualifier.value()).append(qualifierDelimiter); } return builder.append(sortParameter).toString(); diff --git a/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java b/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java index cb6eac4d6..520d7265e 100644 --- a/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java +++ b/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.domain.Pageable; +import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** @@ -45,6 +46,10 @@ abstract class SpringDataAnnotationUtils { Method method = parameter.getMethod(); + if (method == null) { + throw new IllegalArgumentException(String.format("Method parameter %s is not backed by a method.", parameter)); + } + if (containsMoreThanOnePageableParameter(method)) { Annotation[][] annotations = method.getParameterAnnotations(); assertQualifiersFor(method.getParameterTypes(), annotations); @@ -89,8 +94,15 @@ abstract class SpringDataAnnotationUtils { Object propertyDefaultValue = AnnotationUtils.getDefaultValue(annotation, property); Object propertyValue = AnnotationUtils.getValue(annotation, property); - return (T) (ObjectUtils.nullSafeEquals(propertyDefaultValue, propertyValue) ? AnnotationUtils.getValue(annotation) - : propertyValue); + Object result = ObjectUtils.nullSafeEquals(propertyDefaultValue, propertyValue) // + ? AnnotationUtils.getValue(annotation) // + : propertyValue; + + if (result == null) { + throw new IllegalStateException("Exepected to be able to look up an annotation property value but failed!"); + } + + return (T) result; } /** @@ -131,7 +143,8 @@ abstract class SpringDataAnnotationUtils { * @param annotations must not be {@literal null}. * @return */ - public static Qualifier findAnnotation(Annotation[] annotations) { + @Nullable + private static Qualifier findAnnotation(Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation instanceof Qualifier) { diff --git a/src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java b/src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java index 6009ceaef..3c291188f 100644 --- a/src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java +++ b/src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java @@ -18,6 +18,8 @@ package org.springframework.data.web; import java.io.IOException; import java.util.Map; +import javax.annotation.Nullable; + import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpInputMessage; @@ -61,7 +63,7 @@ public class XmlBeamHttpMessageConverter extends AbstractHttpMessageConverter type) { - Class rawType = ResolvableType.forType(type).getRawClass(); + Class rawType = ResolvableType.forType(type).resolve(Object.class); Boolean result = supportedTypesCache.get(rawType); if (result != null) { @@ -80,7 +82,7 @@ public class XmlBeamHttpMessageConverter extends AbstractHttpMessageConverter clazz, MediaType mediaType) { + public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { return false; } diff --git a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java index 890d6bb43..e5018d218 100644 --- a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java +++ b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.data.web.config; import java.util.List; import java.util.Optional; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -36,6 +37,7 @@ import org.springframework.data.web.XmlBeamHttpMessageConverter; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -53,10 +55,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author Jens Schauder */ @Configuration -public class SpringDataWebConfiguration implements WebMvcConfigurer { +public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLoaderAware { private final ApplicationContext context; private final ObjectFactory conversionService; + private @Nullable ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); private @Autowired Optional pageableResolverCustomizer; private @Autowired Optional sortResolverCustomizer; @@ -71,6 +74,15 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer { this.conversionService = conversionService; } + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + /* * (non-Javadoc) * @see org.springframework.data.web.config.SpringDataWebConfiguration#pageableResolver() @@ -127,9 +139,9 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer { argumentResolvers.add(pageableResolver()); ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( - conversionService.getObject()); + getRequiredConversionService()); resolver.setBeanFactory(context); - resolver.setBeanClassLoader(context.getClassLoader()); + forwardBeanClassLoader(resolver); argumentResolvers.add(resolver); } @@ -145,8 +157,8 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer { && ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", context.getClassLoader())) { ProjectingJackson2HttpMessageConverter converter = new ProjectingJackson2HttpMessageConverter(new ObjectMapper()); - converter.setBeanClassLoader(context.getClassLoader()); converter.setBeanFactory(context); + forwardBeanClassLoader(converter); converters.add(0, converter); } @@ -163,4 +175,22 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer { protected void customizeSortResolver(SortHandlerMethodArgumentResolver sortResolver) { sortResolverCustomizer.ifPresent(c -> c.customize(sortResolver)); } + + private ConversionService getRequiredConversionService() { + + ConversionService conversionService = this.conversionService.getObject(); + + if (conversionService == null) { + throw new IllegalStateException("No ConversionService configured!"); + } + + return conversionService; + } + + private void forwardBeanClassLoader(BeanClassLoaderAware target) { + + if (beanClassLoader != null) { + target.setBeanClassLoader(beanClassLoader); + } + } } diff --git a/src/main/java/org/springframework/data/web/config/package-info.java b/src/main/java/org/springframework/data/web/config/package-info.java index 695a28ab4..5f3e4f310 100644 --- a/src/main/java/org/springframework/data/web/config/package-info.java +++ b/src/main/java/org/springframework/data/web/config/package-info.java @@ -1,20 +1,5 @@ -/* - * Copyright 2013 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. - */ /** - * - * @author Oliver Gierke + * Spring Data web configuration. */ -package org.springframework.data.web.config; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.web.config; diff --git a/src/main/java/org/springframework/data/web/package-info.java b/src/main/java/org/springframework/data/web/package-info.java index 01ea191d1..753aa3e87 100644 --- a/src/main/java/org/springframework/data/web/package-info.java +++ b/src/main/java/org/springframework/data/web/package-info.java @@ -1,4 +1,5 @@ /** * Integration with Spring MVC. */ -package org.springframework.data.web; \ No newline at end of file +@org.springframework.lang.NonNullApi +package org.springframework.data.web; diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java index 3253fa6a1..778804da9 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java @@ -15,6 +15,7 @@ */ package org.springframework.data.web.querydsl; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map.Entry; import java.util.Optional; @@ -100,7 +101,7 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR Optional annotation = Optional .ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); - TypeInformation domainType = extractTypeInfo(parameter).getActualType(); + TypeInformation domainType = extractTypeInfo(parameter).getRequiredActualType(); Optional>> bindings = annotation// .map(QuerydslPredicate::bindings)// @@ -125,7 +126,18 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR return annotation.filter(it -> !Object.class.equals(it.root()))// .> map(it -> ClassTypeInformation.from(it.root()))// - .orElseGet(() -> detectDomainType(ClassTypeInformation.fromReturnTypeOf(parameter.getMethod()))); + .orElseGet(() -> detectDomainType(parameter)); + } + + private static TypeInformation detectDomainType(MethodParameter parameter) { + + Method method = parameter.getMethod(); + + if (method == null) { + throw new IllegalArgumentException("Method parameter is not backed by a method!"); + } + + return detectDomainType(ClassTypeInformation.fromReturnTypeOf(method)); } private static TypeInformation detectDomainType(TypeInformation source) { diff --git a/src/main/java/org/springframework/data/web/querydsl/package-info.java b/src/main/java/org/springframework/data/web/querydsl/package-info.java new file mode 100644 index 000000000..71c09b26b --- /dev/null +++ b/src/main/java/org/springframework/data/web/querydsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Querydsl-specific web support. + */ +package org.springframework.data.web.querydsl; diff --git a/src/test/java/org/springframework/data/domain/SortUnitTests.java b/src/test/java/org/springframework/data/domain/SortUnitTests.java index 7c0bee0d0..c56cc6a46 100755 --- a/src/test/java/org/springframework/data/domain/SortUnitTests.java +++ b/src/test/java/org/springframework/data/domain/SortUnitTests.java @@ -47,6 +47,7 @@ public class SortUnitTests { * * @throws Exception */ + @SuppressWarnings("null") @Test(expected = IllegalArgumentException.class) public void preventsNullProperties() throws Exception { @@ -90,14 +91,15 @@ public class SortUnitTests { public void allowsCombiningSorts() { Sort sort = Sort.by("foo").and(Sort.by("bar")); - assertThat(sort).containsExactly(new Sort.Order("foo"), new Sort.Order("bar")); + assertThat(sort).containsExactly(Order.by("foo"), Order.by("bar")); } @Test public void handlesAdditionalNullSort() { - Sort sort = Sort.by("foo").and(null); - assertThat(sort).containsExactly(new Sort.Order("foo")); + Sort sort = Sort.by("foo").and(Sort.unsorted()); + + assertThat(sort).containsExactly(Order.by("foo")); } @Test // DATACMNS-281, DATACMNS-1021 @@ -108,7 +110,7 @@ public class SortUnitTests { @Test // DATACMNS-281, DATACMNS-1021 public void orderDoesNotIgnoreCaseByDefault() { - assertThat(new Order(Direction.ASC, "foo").isIgnoreCase()).isFalse(); + assertThat(Order.by("foo").isIgnoreCase()).isFalse(); assertThat(Order.asc("foo").isIgnoreCase()).isFalse(); assertThat(Order.desc("foo").isIgnoreCase()).isFalse(); } @@ -123,8 +125,8 @@ public class SortUnitTests { @Test // DATACMNS-436 public void ordersWithDifferentIgnoreCaseDoNotEqual() { - Order foo = new Order("foo"); - Order fooIgnoreCase = new Order("foo").ignoreCase(); + Order foo = Order.by("foo"); + Order fooIgnoreCase = Order.by("foo").ignoreCase(); assertThat(foo).isNotEqualTo(fooIgnoreCase); assertThat(foo.hashCode()).isNotEqualTo(fooIgnoreCase.hashCode()); @@ -132,28 +134,28 @@ public class SortUnitTests { @Test // DATACMNS-491 public void orderWithNullHandlingHintNullsFirst() { - assertThat(new Order("foo").nullsFirst().getNullHandling()).isEqualTo(NULLS_FIRST); + assertThat(Order.by("foo").nullsFirst().getNullHandling()).isEqualTo(NULLS_FIRST); } @Test // DATACMNS-491 public void orderWithNullHandlingHintNullsLast() { - assertThat(new Order("foo").nullsLast().getNullHandling()).isEqualTo(NULLS_LAST); + assertThat(Order.by("foo").nullsLast().getNullHandling()).isEqualTo(NULLS_LAST); } @Test // DATACMNS-491 public void orderWithNullHandlingHintNullsNative() { - assertThat(new Order("foo").nullsNative().getNullHandling()).isEqualTo(NATIVE); + assertThat(Order.by("foo").nullsNative().getNullHandling()).isEqualTo(NATIVE); } @Test // DATACMNS-491 public void orderWithDefaultNullHandlingHint() { - assertThat(new Order("foo").getNullHandling()).isEqualTo(NATIVE); + assertThat(Order.by("foo").getNullHandling()).isEqualTo(NATIVE); } @Test // DATACMNS-908 public void createsNewOrderForDifferentProperty() { - Order source = new Order(Direction.DESC, "foo").nullsFirst().ignoreCase(); + Order source = Order.desc("foo").nullsFirst().ignoreCase(); Order result = source.withProperty("bar"); assertThat(result.getProperty()).isEqualTo("bar"); @@ -163,6 +165,7 @@ public class SortUnitTests { } @Test + @SuppressWarnings("null") public void preventsNullDirection() { assertThatExceptionOfType(IllegalArgumentException.class)// diff --git a/src/test/java/org/springframework/data/geo/DistanceUnitTests.java b/src/test/java/org/springframework/data/geo/DistanceUnitTests.java index f380491a0..90e514bde 100755 --- a/src/test/java/org/springframework/data/geo/DistanceUnitTests.java +++ b/src/test/java/org/springframework/data/geo/DistanceUnitTests.java @@ -39,9 +39,7 @@ public class DistanceUnitTests { @Test // DATACMNS-437 public void defaultsMetricToNeutralOne() { - assertThat(new Distance(2.5).getMetric()).isEqualTo((Metric) Metrics.NEUTRAL); - assertThat(new Distance(2.5, null).getMetric()).isEqualTo((Metric) Metrics.NEUTRAL); } @Test // DATACMNS-437 diff --git a/src/test/java/org/springframework/data/mapping/context/PropertyMatchUnitTests.java b/src/test/java/org/springframework/data/mapping/context/PropertyMatchUnitTests.java index 2748dc988..6ad46a62f 100755 --- a/src/test/java/org/springframework/data/mapping/context/PropertyMatchUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/context/PropertyMatchUnitTests.java @@ -68,11 +68,4 @@ public class PropertyMatchUnitTests { assertThat(match.matches("this$1", Object.class)).isTrue(); assertThat(match.matches("name", String.class)).isFalse(); } - - static class Sample { - - public Object this$0; - public Object this$1; - public String name; - } } diff --git a/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java index 09fb71a9f..5f45251ec 100755 --- a/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java @@ -25,7 +25,7 @@ import java.lang.annotation.Target; import java.util.Map; import java.util.Optional; -import javax.annotation.Nullable; +import org.springframework.lang.Nullable; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java b/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java index 7383e2bd1..239e649aa 100755 --- a/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java @@ -39,11 +39,13 @@ public class ProxyProjectionFactoryUnitTests { ProjectionFactory factory = new ProxyProjectionFactory(); + @SuppressWarnings("null") @Test(expected = IllegalArgumentException.class) // DATACMNS-630 public void rejectsNullProjectionType() { factory.createProjection(null); } + @SuppressWarnings("null") @Test(expected = IllegalArgumentException.class) // DATACMNS-630 public void rejectsNullProjectionTypeWithSource() { factory.createProjection(null, new Object()); @@ -51,7 +53,7 @@ public class ProxyProjectionFactoryUnitTests { @Test // DATACMNS-630 public void returnsNullForNullSource() { - assertThat(factory.createProjection(CustomerExcerpt.class, null)).isNull(); + assertThat(factory.createNullableProjection(CustomerExcerpt.class, null)).isNull(); } @Test // DATAREST-221, DATACMNS-630 diff --git a/src/test/java/org/springframework/data/repository/core/support/PersistableEntityInformationUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/PersistableEntityInformationUnitTests.java index a010a8d19..371811356 100755 --- a/src/test/java/org/springframework/data/repository/core/support/PersistableEntityInformationUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/PersistableEntityInformationUnitTests.java @@ -32,9 +32,8 @@ import org.springframework.data.domain.Persistable; @RunWith(MockitoJUnitRunner.class) public class PersistableEntityInformationUnitTests { - @SuppressWarnings({ "rawtypes", - "unchecked" }) static final PersistableEntityInformation metadata = new PersistableEntityInformation( - Persistable.class); + @SuppressWarnings({ "rawtypes", "unchecked" }) // + static final PersistableEntityInformation metadata = new PersistableEntityInformation(PersistableEntity.class); @Mock Persistable persistable; @@ -68,16 +67,13 @@ public class PersistableEntityInformationUnitTests { assertThat(info.getJavaType()).isEqualTo(PersistableEntity.class); } - @SuppressWarnings("serial") static class PersistableEntity implements Persistable { public Long getId() { - return null; } public boolean isNew() { - return false; } } diff --git a/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java b/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java index f9451dde9..91a42dd27 100755 --- a/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.repository.init; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.Collection; @@ -23,6 +24,7 @@ import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; @@ -92,6 +94,7 @@ public class ResourceReaderRepositoryInitializerUnitTests { throws Exception { when(reader.readFrom(any(), any())).thenReturn(reference); + when(productRepository.save(any())).then(AdditionalAnswers.returnsFirstArg()); ResourceReaderRepositoryPopulator populator = new ResourceReaderRepositoryPopulator(reader); populator.setResources(resource); diff --git a/src/test/java/org/springframework/data/repository/support/CrudRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/support/CrudRepositoryInvokerUnitTests.java index 590aee774..b8b13e3d0 100755 --- a/src/test/java/org/springframework/data/repository/support/CrudRepositoryInvokerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/CrudRepositoryInvokerUnitTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.repository.support; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.repository.support.RepositoryInvocationTestUtils.*; @@ -26,6 +27,7 @@ import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.core.convert.support.GenericConversionService; @@ -56,6 +58,9 @@ public class CrudRepositoryInvokerUnitTests { @Test // DATACMNS-589, DATAREST-216 public void invokesRedeclaredSave() { + + when(orderRepository.save(any())).then(AdditionalAnswers.returnsFirstArg()); + getInvokerFor(orderRepository, expectInvocationOnType(OrderRepository.class)).invokeSave(new Order()); } diff --git a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java index 74f099888..c10090a00 100755 --- a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java @@ -16,9 +16,11 @@ package org.springframework.data.repository.support; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; +import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -177,13 +179,13 @@ public class DomainClassConverterUnitTests { converter.setApplicationContext(initContextWithRepo()); @SuppressWarnings("rawtypes") - DomainClassConverter.ToIdConverter toIdConverter = (ToIdConverter) ReflectionTestUtils.getField(converter, + Optional toIdConverter = (Optional) ReflectionTestUtils.getField(converter, "toIdConverter"); Method method = Wrapper.class.getMethod("foo", User.class); TypeDescriptor target = TypeDescriptor.nested(new MethodParameter(method, 0), 0); - assertThat(toIdConverter.matches(SUB_USER_TYPE, target)).isFalse(); + assertThat(toIdConverter).map(it -> it.matches(SUB_USER_TYPE, target)).hasValue(false); } private ApplicationContext initContextWithRepo() { diff --git a/src/test/java/org/springframework/data/repository/support/PaginginAndSortingRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/support/PaginginAndSortingRepositoryInvokerUnitTests.java index 389e952a5..c25f4a3d1 100755 --- a/src/test/java/org/springframework/data/repository/support/PaginginAndSortingRepositoryInvokerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/PaginginAndSortingRepositoryInvokerUnitTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.repository.support; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.repository.support.RepositoryInvocationTestUtils.*; @@ -65,6 +66,8 @@ public class PaginginAndSortingRepositoryInvokerUnitTests { RepositoryWithRedeclaredFindAllWithPageable repository = mock(RepositoryWithRedeclaredFindAllWithPageable.class); Method method = RepositoryWithRedeclaredFindAllWithPageable.class.getMethod("findAll", Pageable.class); + when(repository.findAll(any(Pageable.class))).thenReturn(Page.empty()); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10)); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged()); } diff --git a/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java index 4a29b4376..15cdb4cee 100755 --- a/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java @@ -23,7 +23,6 @@ import static org.springframework.data.repository.support.RepositoryInvocationTe import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; @@ -31,12 +30,12 @@ import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -45,7 +44,6 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.Param; -import org.springframework.data.repository.support.CrudRepositoryInvokerUnitTests.Person; import org.springframework.data.repository.support.CrudRepositoryInvokerUnitTests.PersonRepository; import org.springframework.data.repository.support.RepositoryInvocationTestUtils.VerifyingMethodInterceptor; import org.springframework.format.support.DefaultFormattingConversionService; @@ -60,8 +58,6 @@ import org.springframework.util.MultiValueMap; @RunWith(MockitoJUnitRunner.class) public class ReflectionRepositoryInvokerUnitTests { - static final Page EMPTY_PAGE = new PageImpl<>(Collections.emptyList()); - ConversionService conversionService; @Before @@ -75,6 +71,8 @@ public class ReflectionRepositoryInvokerUnitTests { ManualCrudRepository repository = mock(ManualCrudRepository.class); Method method = ManualCrudRepository.class.getMethod("save", Domain.class); + when(repository.save(any())).then(AdditionalAnswers.returnsFirstArg()); + getInvokerFor(repository, expectInvocationOf(method)).invokeSave(new Domain()); } @@ -118,6 +116,8 @@ public class ReflectionRepositoryInvokerUnitTests { Method method = RepoWithFindAllWithSort.class.getMethod("findAll", Sort.class); RepoWithFindAllWithSort repository = mock(RepoWithFindAllWithSort.class); + when(repository.findAll(any())).thenReturn(Page.empty()); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged()); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10)); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.unsorted()); @@ -130,6 +130,8 @@ public class ReflectionRepositoryInvokerUnitTests { Method method = RepoWithFindAllWithPageable.class.getMethod("findAll", Pageable.class); RepoWithFindAllWithPageable repository = mock(RepoWithFindAllWithPageable.class); + when(repository.findAll(any())).thenReturn(Page.empty()); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged()); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10)); } diff --git a/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java b/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java index f6432ba52..71a0230d6 100755 --- a/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java +++ b/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java @@ -41,8 +41,8 @@ public class AnnotationDetectionFieldCallbackUnitTests { AnnotationDetectionFieldCallback callback = new AnnotationDetectionFieldCallback(Autowired.class); ReflectionUtils.doWithFields(Sample.class, callback); - assertThat(callback.getType()).hasValue(String.class); - assertThat(callback.getValue(new Sample("foo"))).hasValue("foo"); + assertThat(callback.getType()).isEqualTo(String.class); + assertThat(callback. getValue(new Sample("foo"))).isEqualTo("foo"); } @Test // DATACMNS-616 @@ -51,8 +51,8 @@ public class AnnotationDetectionFieldCallbackUnitTests { AnnotationDetectionFieldCallback callback = new AnnotationDetectionFieldCallback(Autowired.class); ReflectionUtils.doWithFields(Empty.class, callback); - assertThat(callback.getType()).isNotPresent(); - assertThat(callback.getValue(new Empty())).isNotPresent(); + assertThat(callback.getType()).isNull(); + assertThat(callback. getValue(new Empty())).isNull(); } @Value diff --git a/src/test/java/org/springframework/data/util/VersionUnitTests.java b/src/test/java/org/springframework/data/util/VersionUnitTests.java index 14e532af2..c7bd32706 100755 --- a/src/test/java/org/springframework/data/util/VersionUnitTests.java +++ b/src/test/java/org/springframework/data/util/VersionUnitTests.java @@ -124,7 +124,6 @@ public class VersionUnitTests { assertThat(version.compareTo(nextBugfix)).isLessThan(0); assertThat(version.compareTo(nextBuild)).isLessThan(0); - assertThat(version.compareTo(null)).isGreaterThan(0); assertThat(nextMajor.compareTo(version)).isGreaterThan(0); assertThat(nextMinor.compareTo(version)).isGreaterThan(0); assertThat(nextBugfix.compareTo(version)).isGreaterThan(0); diff --git a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java index 36da66e4a..e6e20943d 100755 --- a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java @@ -143,12 +143,12 @@ public class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefa PageableHandlerMethodArgumentResolver resolver = getResolver(); resolver.setFallbackPageable(Pageable.unpaged()); - assertSupportedAndResult(supportedMethodParameter, null, new ServletWebRequest(new MockHttpServletRequest()), - resolver); + assertSupportedAndResult(supportedMethodParameter, Pageable.unpaged(), + new ServletWebRequest(new MockHttpServletRequest()), resolver); } @Test // DATACMNS-477 - public void returnsNullIfFallbackIsUnpagedAndOnlyPageIsGiven() throws Exception { + public void returnsFallbackIfOnlyPageIsGiven() throws Exception { PageableHandlerMethodArgumentResolver resolver = getResolver(); resolver.setFallbackPageable(Pageable.unpaged()); @@ -156,11 +156,12 @@ public class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefa MockHttpServletRequest request = new MockHttpServletRequest(); request.addParameter("page", "20"); - assertThat(resolver.resolveArgument(supportedMethodParameter, null, new ServletWebRequest(request), null)).isNull(); + assertThat(resolver.resolveArgument(supportedMethodParameter, null, new ServletWebRequest(request), null)) + .isEqualTo(Pageable.unpaged()); } @Test // DATACMNS-477 - public void returnsNullIfFallbackIsUnpagedAndOnlySizeIsGiven() throws Exception { + public void returnsFallbackIfFallbackIsUnpagedAndOnlySizeIsGiven() throws Exception { PageableHandlerMethodArgumentResolver resolver = getResolver(); resolver.setFallbackPageable(Pageable.unpaged()); @@ -168,7 +169,8 @@ public class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefa MockHttpServletRequest request = new MockHttpServletRequest(); request.addParameter("size", "10"); - assertThat(resolver.resolveArgument(supportedMethodParameter, null, new ServletWebRequest(request), null)).isNull(); + assertThat(resolver.resolveArgument(supportedMethodParameter, null, new ServletWebRequest(request), null)) + .isEqualTo(Pageable.unpaged()); } @Test // DATACMNS-563