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