#831 - Introduce null handling.
This commit is contained in:
@@ -20,6 +20,7 @@ import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,7 @@ public class AnnotationAttribute {
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @param attributeName can be {@literal null}, defaults to {@code value}.
|
||||
*/
|
||||
public AnnotationAttribute(Class<? extends Annotation> annotationType, String attributeName) {
|
||||
public AnnotationAttribute(Class<? extends Annotation> annotationType, @Nullable String attributeName) {
|
||||
|
||||
Assert.notNull(annotationType, "AnnotationType must not be null!");
|
||||
|
||||
@@ -70,6 +71,7 @@ public class AnnotationAttribute {
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String getValueFrom(MethodParameter parameter) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
@@ -83,6 +85,7 @@ public class AnnotationAttribute {
|
||||
* @param annotatedElement must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String getValueFrom(AnnotatedElement annotatedElement) {
|
||||
|
||||
Assert.notNull(annotatedElement, "Annotated element must not be null!");
|
||||
@@ -96,6 +99,7 @@ public class AnnotationAttribute {
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String getValueFrom(Annotation annotation) {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.LinkRelationProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
@@ -37,6 +38,7 @@ public class AnnotationLinkRelationProvider implements LinkRelationProvider, Ord
|
||||
* @see org.springframework.hateoas.server.LinkRelationProvider#getCollectionResourceRelFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public LinkRelation getCollectionResourceRelFor(Class<?> type) {
|
||||
|
||||
Relation annotation = lookupAnnotation(type);
|
||||
@@ -53,6 +55,7 @@ public class AnnotationLinkRelationProvider implements LinkRelationProvider, Ord
|
||||
* @see org.springframework.hateoas.server.LinkRelationProvider#getItemResourceRelFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public LinkRelation getItemResourceRelFor(Class<?> type) {
|
||||
|
||||
Relation annotation = lookupAnnotation(type);
|
||||
@@ -82,6 +85,7 @@ public class AnnotationLinkRelationProvider implements LinkRelationProvider, Ord
|
||||
return lookupAnnotation(delimiter) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Relation lookupAnnotation(Class<?> type) {
|
||||
return annotationCache.computeIfAbsent(type, key -> AnnotationUtils.getAnnotation(key, Relation.class));
|
||||
}
|
||||
|
||||
@@ -22,10 +22,12 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
@@ -59,7 +61,7 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @param mappingAttributeName if {@literal null}, it defaults to {@code value}.
|
||||
*/
|
||||
public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation, String mappingAttributeName) {
|
||||
public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation, @Nullable String mappingAttributeName) {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
@@ -72,6 +74,7 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMapping(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
@@ -86,6 +89,7 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMapping(Method method) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
@@ -97,6 +101,7 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMapping(Class<?> type, Method method) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
@@ -105,7 +110,7 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
String[] mapping = getMappingFrom(findMergedAnnotation(method, annotationType));
|
||||
String typeMapping = getMapping(type);
|
||||
|
||||
if (mapping == null || mapping.length == 0) {
|
||||
if (mapping.length == 0) {
|
||||
return typeMapping;
|
||||
}
|
||||
|
||||
@@ -131,6 +136,10 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
|
||||
RequestMethod[] requestMethods = (RequestMethod[]) value;
|
||||
|
||||
if (requestMethods == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<HttpMethod> requestMethodNames = new ArrayList<>();
|
||||
|
||||
for (RequestMethod requestMethod : requestMethods) {
|
||||
@@ -140,7 +149,7 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
return requestMethodNames;
|
||||
}
|
||||
|
||||
private String[] getMappingFrom(Annotation annotation) {
|
||||
private String[] getMappingFrom(@Nullable Annotation annotation) {
|
||||
|
||||
if (annotation == null) {
|
||||
return new String[0];
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -84,7 +85,7 @@ public class CachingMappingDiscoverer implements MappingDiscoverer {
|
||||
return METHODS.computeIfAbsent(key(type, method), __ -> delegate.getRequestMethod(type, method));
|
||||
}
|
||||
|
||||
private static String key(Class<?> type, Method method) {
|
||||
private static String key(Class<?> type, @Nullable Method method) {
|
||||
|
||||
StringBuilder builder = new StringBuilder(type.getName());
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
@@ -84,6 +85,7 @@ public class DummyInvocationUtils {
|
||||
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object invoke(org.aopalliance.intercept.MethodInvocation invocation) {
|
||||
|
||||
Method method = invocation.getMethod();
|
||||
|
||||
@@ -17,8 +17,9 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A wrapper to handle values to be embedded into a {@link EntityModel}.
|
||||
@@ -66,5 +67,6 @@ public interface EmbeddedWrapper {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Class<?> getRelTargetType();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Optional;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -48,7 +49,8 @@ public class EmbeddedWrappers {
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
public EmbeddedWrapper wrap(Object source) {
|
||||
@Nullable
|
||||
public EmbeddedWrapper wrap(@Nullable Object source) {
|
||||
return wrap(source, AbstractEmbeddedWrapper.NO_REL);
|
||||
}
|
||||
|
||||
@@ -70,7 +72,8 @@ public class EmbeddedWrappers {
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public EmbeddedWrapper wrap(Object source, LinkRelation rel) {
|
||||
@Nullable
|
||||
public EmbeddedWrapper wrap(@Nullable Object source, LinkRelation rel) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
@@ -134,16 +137,17 @@ public class EmbeddedWrappers {
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public Class<?> getRelTargetType() {
|
||||
|
||||
Object peek = peek();
|
||||
|
||||
peek = peek instanceof EntityModel ? ((EntityModel<Object>) peek).getContent() : peek;
|
||||
|
||||
if (peek == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
peek = peek instanceof EntityModel ? ((EntityModel<Object>) peek).getContent() : peek;
|
||||
|
||||
return AopUtils.getTargetClass(peek);
|
||||
}
|
||||
|
||||
@@ -152,6 +156,7 @@ public class EmbeddedWrappers {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Object peek();
|
||||
}
|
||||
|
||||
@@ -245,6 +250,7 @@ public class EmbeddedWrappers {
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrappers.AbstractEmbeddedWrapper#peek()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
protected Object peek() {
|
||||
return value.isEmpty() ? null : value.iterator().next();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
@@ -39,7 +40,7 @@ class EncodingUtils {
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodePath(Object source) {
|
||||
public static String encodePath(@Nullable Object source) {
|
||||
|
||||
Assert.notNull(source, "Path value must not be null!");
|
||||
|
||||
@@ -56,7 +57,7 @@ class EncodingUtils {
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodeParameter(Object source) {
|
||||
public static String encodeParameter(@Nullable Object source) {
|
||||
|
||||
Assert.notNull(source, "Request parameter value must not be null!");
|
||||
|
||||
|
||||
@@ -44,7 +44,9 @@ public class HeaderLinksResponseEntity<T extends RepresentationModel<?>> extends
|
||||
private HeaderLinksResponseEntity(ResponseEntity<T> entity) {
|
||||
|
||||
super(entity.getBody(), getHeadersWithLinks(entity), entity.getStatusCode());
|
||||
entity.getBody().removeLinks();
|
||||
if (entity.getBody() != null) {
|
||||
entity.getBody().removeLinks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +100,7 @@ public class HeaderLinksResponseEntity<T extends RepresentationModel<?>> extends
|
||||
*/
|
||||
private static <T extends RepresentationModel<?>> HttpHeaders getHeadersWithLinks(ResponseEntity<T> entity) {
|
||||
|
||||
Links links = entity.getBody().getLinks();
|
||||
Links links = entity.getBody() != null ? entity.getBody().getLinks() : Links.NONE;
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.putAll(entity.getHeaders());
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Strategy interface to discover a URI mapping and related {@link org.springframework.hateoas.Affordance}s for either a
|
||||
@@ -35,6 +36,7 @@ public interface MappingDiscoverer {
|
||||
* @param type must not be {@literal null}.
|
||||
* @return the type-level mapping or {@literal null} in case none is present.
|
||||
*/
|
||||
@Nullable
|
||||
String getMapping(Class<?> type);
|
||||
|
||||
/**
|
||||
@@ -53,6 +55,7 @@ public interface MappingDiscoverer {
|
||||
* @param method must not be {@literal null}.
|
||||
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
|
||||
*/
|
||||
@Nullable
|
||||
String getMapping(Class<?> type, Method method);
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
@@ -58,7 +59,7 @@ public class MethodParameters {
|
||||
* @param method must not be {@literal null}.
|
||||
* @param namingAnnotation can be {@literal null}.
|
||||
*/
|
||||
public MethodParameters(Method method, AnnotationAttribute namingAnnotation) {
|
||||
public MethodParameters(Method method, @Nullable AnnotationAttribute namingAnnotation) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
@@ -145,7 +146,7 @@ public class MethodParameters {
|
||||
* @param parameterIndex
|
||||
* @param attribute can be {@literal null}
|
||||
*/
|
||||
public AnnotationNamingMethodParameter(Method method, int parameterIndex, AnnotationAttribute attribute) {
|
||||
public AnnotationNamingMethodParameter(Method method, int parameterIndex, @Nullable AnnotationAttribute attribute) {
|
||||
|
||||
super(method, parameterIndex);
|
||||
this.attribute = attribute;
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -69,6 +70,7 @@ public class SpringAffordanceBuilder {
|
||||
List<QueryParameter> queryMethodParameters = invocationMethodParameters.getParametersWith(RequestParam.class)
|
||||
.stream() //
|
||||
.map(methodParameter -> methodParameter.getParameterAnnotation(RequestParam.class)) //
|
||||
.filter(Objects::nonNull) //
|
||||
.map(requestParam -> new QueryParameter(requestParam.name(), requestParam.value(), requestParam.required())) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.HashMap;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -186,6 +187,7 @@ public class TypeReferences {
|
||||
* @see java.lang.reflect.ParameterizedType#getOwnerType()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Type getOwnerType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
@@ -37,7 +38,7 @@ public class UriTemplateFactory {
|
||||
* @param mapping must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static UriTemplate templateFor(String mapping) {
|
||||
public static UriTemplate templateFor(@Nullable String mapping) {
|
||||
|
||||
Assert.hasText(mapping, "Mapping must not be null or empty!");
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -80,7 +81,7 @@ public class WebHandler {
|
||||
|
||||
public static <T extends LinkBuilder> Function<Function<String, UriComponentsBuilder>, T> linkTo(
|
||||
Object invocationValue, LinkBuilderCreator<T> creator,
|
||||
BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
|
||||
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
|
||||
|
||||
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
|
||||
|
||||
@@ -184,17 +185,23 @@ public class WebHandler {
|
||||
} else if (value instanceof Collection) {
|
||||
|
||||
for (Object element : (Collection<?>) value) {
|
||||
builder.queryParam(key, encodeParameter(element));
|
||||
if (key != null) {
|
||||
builder.queryParam(key, encodeParameter(element));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (SKIP_VALUE.equals(value)) {
|
||||
|
||||
if (parameter.isRequired()) {
|
||||
builder.queryParam(key, String.format("{%s}", parameter.getVariableName()));
|
||||
if (key != null) {
|
||||
builder.queryParam(key, String.format("{%s}", parameter.getVariableName()));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
builder.queryParam(key, encodeParameter(parameter.asString()));
|
||||
if (key != null) {
|
||||
builder.queryParam(key, encodeParameter(parameter.asString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +240,7 @@ public class WebHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
return annotation.required() //
|
||||
return annotation != null && annotation.required() //
|
||||
&& annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE);
|
||||
}
|
||||
};
|
||||
@@ -244,6 +251,7 @@ public class WebHandler {
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#verifyParameterValue(org.springframework.core.MethodParameter, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
@@ -254,7 +262,7 @@ public class WebHandler {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!annotation.required() || parameter.isOptional()) {
|
||||
if (!(annotation != null && annotation.required()) || parameter.isOptional()) {
|
||||
return SKIP_VALUE;
|
||||
}
|
||||
|
||||
@@ -325,6 +333,7 @@ public class WebHandler {
|
||||
* @param value could be {@literal null}.
|
||||
* @return the verified value.
|
||||
*/
|
||||
@Nullable
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
return value;
|
||||
}
|
||||
@@ -377,6 +386,7 @@ public class WebHandler {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String getVariableName() {
|
||||
|
||||
if (attribute == null) {
|
||||
@@ -384,7 +394,7 @@ public class WebHandler {
|
||||
}
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String annotationAttributeValue = attribute.getValueFrom(annotation);
|
||||
String annotationAttributeValue = annotation != null ? attribute.getValueFrom(annotation) : "";
|
||||
|
||||
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName();
|
||||
}
|
||||
@@ -403,6 +413,7 @@ public class WebHandler {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String asString() {
|
||||
|
||||
return value == null //
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* Implementations of core API interfaces.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.hateoas.server.core;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -52,7 +52,9 @@ public class RepresentationModelProcessorHandlerMethodReturnValueHandler impleme
|
||||
static final Field CONTENT_FIELD = ReflectionUtils.findField(CollectionModel.class, "content");
|
||||
|
||||
static {
|
||||
ReflectionUtils.makeAccessible(CONTENT_FIELD);
|
||||
if (CONTENT_FIELD != null) {
|
||||
ReflectionUtils.makeAccessible(CONTENT_FIELD);
|
||||
}
|
||||
}
|
||||
|
||||
private final @NonNull HandlerMethodReturnValueHandler delegate;
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.server.RepresentationModelProcessor;
|
||||
import org.springframework.hateoas.server.core.EmbeddedWrapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -61,12 +62,14 @@ public class RepresentationModelProcessorInvoker {
|
||||
ResolvableType processorType = ResolvableType.forClass(RepresentationModelProcessor.class, processor.getClass());
|
||||
Class<?> rawType = processorType.getGeneric(0).resolve();
|
||||
|
||||
if (EntityModel.class.isAssignableFrom(rawType)) {
|
||||
this.processors.add(new ResourceProcessorWrapper(processor));
|
||||
} else if (CollectionModel.class.isAssignableFrom(rawType)) {
|
||||
this.processors.add(new ResourcesProcessorWrapper(processor));
|
||||
} else {
|
||||
this.processors.add(new DefaultProcessorWrapper(processor));
|
||||
if (rawType != null) {
|
||||
if (EntityModel.class.isAssignableFrom(rawType)) {
|
||||
this.processors.add(new ResourceProcessorWrapper(processor));
|
||||
} else if (CollectionModel.class.isAssignableFrom(rawType)) {
|
||||
this.processors.add(new ResourcesProcessorWrapper(processor));
|
||||
} else {
|
||||
this.processors.add(new DefaultProcessorWrapper(processor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +107,8 @@ public class RepresentationModelProcessorInvoker {
|
||||
if (RepresentationModelProcessorHandlerMethodReturnValueHandler.RESOURCES_TYPE.isAssignableFrom(referenceType)) {
|
||||
|
||||
CollectionModel<?> resources = (CollectionModel<?>) value;
|
||||
ResolvableType elementTargetType = ResolvableType
|
||||
.forClass(CollectionModel.class, referenceType.getRawClass()).getGeneric(0);
|
||||
ResolvableType elementTargetType = ResolvableType.forClass(CollectionModel.class, referenceType.getRawClass())
|
||||
.getGeneric(0);
|
||||
List<Object> result = new ArrayList<>(resources.getContent().size());
|
||||
|
||||
for (Object element : resources) {
|
||||
@@ -119,8 +122,10 @@ public class RepresentationModelProcessorInvoker {
|
||||
result.add(invokeProcessorsFor(element, elementTargetType));
|
||||
}
|
||||
|
||||
ReflectionUtils.setField(RepresentationModelProcessorHandlerMethodReturnValueHandler.CONTENT_FIELD, resources,
|
||||
result);
|
||||
if (RepresentationModelProcessorHandlerMethodReturnValueHandler.CONTENT_FIELD != null) {
|
||||
ReflectionUtils.setField(RepresentationModelProcessorHandlerMethodReturnValueHandler.CONTENT_FIELD, resources,
|
||||
result);
|
||||
}
|
||||
}
|
||||
|
||||
return (T) invokeProcessorsFor(Object.class.cast(value), referenceType);
|
||||
@@ -147,11 +152,18 @@ public class RepresentationModelProcessorInvoker {
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
private static boolean isRawTypeAssignable(ResolvableType left, Class<?> right) {
|
||||
private static boolean isRawTypeAssignable(@Nullable ResolvableType left, @Nullable Class<?> right) {
|
||||
|
||||
Assert.notNull(right, "right cannot be null!");
|
||||
|
||||
return getRawType(left).isAssignableFrom(right);
|
||||
}
|
||||
|
||||
private static Class<?> getRawType(ResolvableType type) {
|
||||
private static Class<?> getRawType(@Nullable ResolvableType type) {
|
||||
|
||||
if (type == null) {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
Class<?> rawType = type.getRawClass();
|
||||
return rawType == null ? Object.class : rawType;
|
||||
@@ -247,8 +259,8 @@ public class RepresentationModelProcessorInvoker {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProcessorWrapper} to deal with {@link RepresentationModelProcessor}s for {@link EntityModel}s.
|
||||
* Will fall back to peeking into the {@link EntityModel}'s content for type resolution.
|
||||
* {@link ProcessorWrapper} to deal with {@link RepresentationModelProcessor}s for {@link EntityModel}s. Will fall
|
||||
* back to peeking into the {@link EntityModel}'s content for type resolution.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@@ -278,15 +290,14 @@ public class RepresentationModelProcessorInvoker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link EntityModel} matches the given target {@link ResolvableType}. We
|
||||
* inspect the {@link EntityModel}'s value to determine the match.
|
||||
* Returns whether the given {@link EntityModel} matches the given target {@link ResolvableType}. We inspect the
|
||||
* {@link EntityModel}'s value to determine the match.
|
||||
*
|
||||
* @param resource
|
||||
* @param target must not be {@literal null}.
|
||||
* @return whether the given {@link EntityModel} can be assigned to the given target
|
||||
* {@link ResolvableType}
|
||||
* @return whether the given {@link EntityModel} can be assigned to the given target {@link ResolvableType}
|
||||
*/
|
||||
private static boolean isValueTypeMatch(EntityModel<?> resource, ResolvableType target) {
|
||||
private static boolean isValueTypeMatch(@Nullable EntityModel<?> resource, @Nullable ResolvableType target) {
|
||||
|
||||
if (resource == null || !isRawTypeAssignable(target, resource.getClass())) {
|
||||
return false;
|
||||
@@ -302,7 +313,12 @@ public class RepresentationModelProcessorInvoker {
|
||||
return type != null && type.getGeneric(0).isAssignableFrom(ResolvableType.forClass(content.getClass()));
|
||||
}
|
||||
|
||||
private static ResolvableType findGenericType(ResolvableType source, Class<?> type) {
|
||||
@Nullable
|
||||
private static ResolvableType findGenericType(@Nullable ResolvableType source, Class<?> type) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> rawType = getRawType(source);
|
||||
|
||||
@@ -319,8 +335,8 @@ public class RepresentationModelProcessorInvoker {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProcessorWrapper} for {@link RepresentationModelProcessor}s targeting {@link CollectionModel}.
|
||||
* Will peek into the content of the {@link CollectionModel} for type matching decisions if needed.
|
||||
* {@link ProcessorWrapper} for {@link RepresentationModelProcessor}s targeting {@link CollectionModel}. Will peek
|
||||
* into the content of the {@link CollectionModel} for type matching decisions if needed.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@@ -350,15 +366,14 @@ public class RepresentationModelProcessorInvoker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link CollectionModel} instance matches the given
|
||||
* {@link ResolvableType}. We predict this by inspecting the first element of the content of the
|
||||
* {@link CollectionModel}.
|
||||
* Returns whether the given {@link CollectionModel} instance matches the given {@link ResolvableType}. We predict
|
||||
* this by inspecting the first element of the content of the {@link CollectionModel}.
|
||||
*
|
||||
* @param resources the {@link CollectionModel} to inspect.
|
||||
* @param target that target {@link ResolvableType}.
|
||||
* @return
|
||||
*/
|
||||
static boolean isValueTypeMatch(CollectionModel<?> resources, ResolvableType target) {
|
||||
static boolean isValueTypeMatch(@Nullable CollectionModel<?> resources, ResolvableType target) {
|
||||
|
||||
if (resources == null) {
|
||||
return false;
|
||||
@@ -372,8 +387,7 @@ public class RepresentationModelProcessorInvoker {
|
||||
|
||||
ResolvableType superType = null;
|
||||
|
||||
for (Class<?> resourcesType : Arrays.<Class<?>> asList(resources.getClass(),
|
||||
CollectionModel.class)) {
|
||||
for (Class<?> resourcesType : Arrays.<Class<?>> asList(resources.getClass(), CollectionModel.class)) {
|
||||
|
||||
superType = getSuperType(target, resourcesType);
|
||||
|
||||
@@ -407,18 +421,18 @@ public class RepresentationModelProcessorInvoker {
|
||||
*/
|
||||
private static ResolvableType getSuperType(ResolvableType source, Class<?> superType) {
|
||||
|
||||
if (source.getRawClass().equals(superType)) {
|
||||
if (source.getRawClass() != null && source.getRawClass().equals(superType)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
ResolvableType candidate = source.getSuperType();
|
||||
|
||||
if (superType.isAssignableFrom(candidate.getRawClass())) {
|
||||
if (candidate.getRawClass() != null && superType.isAssignableFrom(candidate.getRawClass())) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
for (ResolvableType interfaces : source.getInterfaces()) {
|
||||
if (superType.isAssignableFrom(interfaces.getRawClass())) {
|
||||
if (interfaces.getRawClass() != null && superType.isAssignableFrom(interfaces.getRawClass())) {
|
||||
return interfaces;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,19 @@ package org.springframework.hateoas.server.mvc;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.hateoas.server.RepresentationModelProcessor;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
|
||||
/**
|
||||
* Special {@link RequestMappingHandlerAdapter} that tweaks the {@link HandlerMethodReturnValueHandlerComposite} to be
|
||||
* proxied by a {@link RepresentationModelProcessorHandlerMethodReturnValueHandler} which will invoke the {@link RepresentationModelProcessor}
|
||||
* s found in the application context and eventually delegate to the originally configured
|
||||
* {@link HandlerMethodReturnValueHandler}.
|
||||
* proxied by a {@link RepresentationModelProcessorHandlerMethodReturnValueHandler} which will invoke the
|
||||
* {@link RepresentationModelProcessor} s found in the application context and eventually delegate to the originally
|
||||
* configured {@link HandlerMethodReturnValueHandler}.
|
||||
* <p/>
|
||||
* This is a separate component as it might make sense to deploy it in a standalone SpringMVC application to enable post
|
||||
* processing. It would actually make most sense in Spring HATEOAS project.
|
||||
@@ -45,9 +43,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
@RequiredArgsConstructor
|
||||
public class RepresentationModelProcessorInvokingHandlerAdapter extends RequestMappingHandlerAdapter {
|
||||
|
||||
private static final Method RETURN_VALUE_HANDLER_METHOD = ReflectionUtils
|
||||
.findMethod(RepresentationModelProcessorInvokingHandlerAdapter.class, "getReturnValueHandlers");
|
||||
|
||||
private @NonNull final RepresentationModelProcessorInvoker invoker;
|
||||
|
||||
/*
|
||||
@@ -79,7 +74,7 @@ public class RepresentationModelProcessorInvokingHandlerAdapter extends RequestM
|
||||
@SuppressWarnings("unchecked")
|
||||
private HandlerMethodReturnValueHandlerComposite getReturnValueHandlersComposite() {
|
||||
|
||||
Object handlers = ReflectionUtils.invokeMethod(RETURN_VALUE_HANDLER_METHOD, this);
|
||||
Object handlers = this.getReturnValueHandlers();
|
||||
|
||||
if (handlers instanceof HandlerMethodReturnValueHandlerComposite) {
|
||||
return (HandlerMethodReturnValueHandlerComposite) handlers;
|
||||
|
||||
@@ -17,8 +17,7 @@ package org.springframework.hateoas.server.mvc;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
@@ -39,9 +38,8 @@ class UriComponentsBuilderFactory {
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping with scheme tweaked in case the
|
||||
* request contains an {@code X-Forwarded-Ssl} header, which is not (yet) supported by the underlying
|
||||
* {@link UriComponentsBuilder}. If no {@link RequestContextHolder} exists (you're outside a Spring Web call), fall
|
||||
* back to relative URIs.
|
||||
* request contains an {@code X-Forwarded-Ssl} header. If no {@link RequestContextHolder} exists (you're outside a
|
||||
* Spring Web call), fall back to relative URIs.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -55,22 +53,7 @@ class UriComponentsBuilderFactory {
|
||||
|
||||
return baseUri != null //
|
||||
? UriComponentsBuilder.fromUri(baseUri) //
|
||||
: cacheBaseUri(ServletUriComponentsBuilder.fromServletMapping(getCurrentRequest()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110 gets fixed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static HttpServletRequest getCurrentRequest() {
|
||||
|
||||
RequestAttributes requestAttributes = getRequestAttributes();
|
||||
HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
|
||||
Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
|
||||
|
||||
return servletRequest;
|
||||
: cacheBaseUri(ServletUriComponentsBuilder.fromCurrentServletMapping());
|
||||
}
|
||||
|
||||
private static RequestAttributes getRequestAttributes() {
|
||||
@@ -92,6 +75,7 @@ class UriComponentsBuilderFactory {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static URI getCachedBaseUri() {
|
||||
return (URI) getRequestAttributes().getAttribute(CACHE_KEY, RequestAttributes.SCOPE_REQUEST);
|
||||
}
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
* Spring MVC helper classes to build {@link org.springframework.hateoas.Link}s and assemble
|
||||
* {@link org.springframework.hateoas.RepresentationModel} types.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.hateoas.server.mvc;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Server-side components for hypermedia handling.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.hateoas.server;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -18,11 +18,11 @@ package org.springframework.hateoas.server.reactive;
|
||||
import static org.springframework.hateoas.server.reactive.HypermediaWebFilter.*;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
@@ -31,6 +31,7 @@ import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.WebHandler;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
@@ -230,7 +231,7 @@ public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<
|
||||
*
|
||||
* @param exchange
|
||||
*/
|
||||
private static UriComponentsBuilder getBuilder(ServerWebExchange exchange) {
|
||||
private static UriComponentsBuilder getBuilder(@Nullable ServerWebExchange exchange) {
|
||||
|
||||
return exchange == null //
|
||||
? UriComponentsBuilder.fromPath("/") //
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Spring WebFlux components to build {@link org.springframework.hateoas.Link}s and assemble
|
||||
* {@link org.springframework.hateoas.RepresentationModel} types.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.hateoas.server.reactive;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
Reference in New Issue
Block a user