DATACMNS-1114 - Introduced usage of nullable annotations for API validation.

Marked all packages with Spring Frameworks @NonNullApi. Added Spring's @Nullable to methods, parameters and fields that take or produce null values. Adapted using code to make sure the IDE can evaluate the null flow properly. Fixed Javadoc in places where an invalid null handling policy was advertised. Strengthened null requirements for types that expose null-instances.

Removed null handling from converters for JodaTime and ThreeTenBP. Introduced factory methods Page.empty() and Page.empty(Pageable). Introduced default methods getRequiredGetter(), …Setter() and …Field() on PersistentProperty to allow non-nullable lookups of members. The same for TypeInformation.getrequiredActualType(), …SuperTypeInformation().

Tweaked PersistentPropertyCreator.addPropertiesForRemainingDescriptors() to filter unsuitable PropertyDescriptors before actually trying to create a Property instance from them as the new stronger nullability requirements would cause exceptions downstream.

Lazy.get() now expects a non-null return value. Clients being able to cope with null need to call ….orElse(…).

Original pull request: #232.
This commit is contained in:
Oliver Gierke
2017-06-27 08:41:16 +02:00
parent d9b16d8a27
commit 049970874d
234 changed files with 2274 additions and 1179 deletions

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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<Object> 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));

View File

@@ -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<String, Object> getTarget() {
return (Map<String, Object>) super.getTarget();
Object target = super.getTarget();
if (target == null) {
throw new IllegalStateException("Target bean should never be null!");
}
return (Map<String, Object>) 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);

View File

@@ -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<T> 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<T> extends PagedResourcesAssem
* (non-Javadoc)
* @see org.springframework.data.web.PagedResourcesAssembler#getMethodParameter()
*/
@Nonnull
@Override
protected MethodParameter getMethodParameter() {
return parameter;

View File

@@ -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);
}

View File

@@ -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<Integer> 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<Integer> parseAndApplyBoundaries(String parameter, int upper, boolean shiftIndex) {
private Optional<Integer> parseAndApplyBoundaries(@Nullable String parameter, int upper, boolean shiftIndex) {
if (!StringUtils.hasText(parameter)) {
return Optional.empty();

View File

@@ -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<T> implements ResourceAssembler<Page<T>, 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<T> implements ResourceAssembler<Page<T>, Pa
* @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public PagedResources<Resource<T>> toResource(Page<T> entity) {
return toResource(entity, it -> new Resource<>(it));
}
@@ -276,6 +279,7 @@ public class PagedResourcesAssembler<T> implements ResourceAssembler<Page<T>, Pa
* @return
* @since 1.7
*/
@Nullable
protected MethodParameter getMethodParameter() {
return null;
}

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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<Ob
@Override
protected boolean supports(Class<?> 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<Ob
* @see org.springframework.http.converter.HttpMessageConverter#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;
}

View File

@@ -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> conversionService;
private @Nullable ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private @Autowired Optional<PageableHandlerMethodArgumentResolverCustomizer> pageableResolverCustomizer;
private @Autowired Optional<SortHandlerMethodArgumentResolverCustomizer> 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);
}
}
}

View File

@@ -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;
@org.springframework.lang.NonNullApi
package org.springframework.data.web.config;

View File

@@ -1,4 +1,5 @@
/**
* Integration with Spring MVC.
*/
package org.springframework.data.web;
@org.springframework.lang.NonNullApi
package org.springframework.data.web;

View File

@@ -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<QuerydslPredicate> annotation = Optional
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class));
TypeInformation<?> domainType = extractTypeInfo(parameter).getActualType();
TypeInformation<?> domainType = extractTypeInfo(parameter).getRequiredActualType();
Optional<Class<? extends QuerydslBinderCustomizer<?>>> bindings = annotation//
.map(QuerydslPredicate::bindings)//
@@ -125,7 +126,18 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR
return annotation.filter(it -> !Object.class.equals(it.root()))//
.<TypeInformation<?>> 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) {

View File

@@ -0,0 +1,4 @@
/**
* Querydsl-specific web support.
*/
package org.springframework.data.web.querydsl;