#831 - Polishing.

Significant rework of the nullability annotation introduction to not produce any warnings anymore. Added nullability annotations where still missing. Removed the ones that were added erroneously.

Tightened nullability contracts in a couple of places, most prominently LinkRelationProvider, that now uses a LookupContext as plugin delimiter so that implementations can selectively be looked up for item or collection link relation lookup.

Removed obsolete declaration of Findbugs dependency.
This commit is contained in:
Oliver Drotbohm
2019-03-19 23:41:08 +01:00
parent fdee61fde7
commit 75ad0988cd
78 changed files with 1224 additions and 626 deletions

View File

@@ -20,21 +20,23 @@ import java.net.URI;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.lang.Nullable;
/**
* Builder to ease building {@link Link} instances.
*
* @author Ricardo Gladwell
* @author Oliver Drotbohm
*/
public interface LinkBuilder {
/**
* Adds the given object's {@link String} representation as sub-resource to the current URI.
*
* @param object
* @param object can be {@literal null}.
* @return
*/
LinkBuilder slash(Object object);
LinkBuilder slash(@Nullable Object object);
/**
* Creates a URI of the link built by the current builder instance.

View File

@@ -15,15 +15,26 @@
*/
package org.springframework.hateoas.server;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.LinkRelationProvider.LookupContext;
import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider;
import org.springframework.lang.Nullable;
import org.springframework.plugin.core.Plugin;
/**
* API to provide {@link LinkRelation}s for collections and items of the given type.
* API to provide {@link LinkRelation}s for collections and items of the given type. Implementations can be selected
* based on the {@link LookupContext}, for item resource relations, collection resource relations or both.
*
* @author Oliver Gierke
* @see #supports(LookupContext)
*/
public interface LinkRelationProvider extends Plugin<Class<?>> {
public interface LinkRelationProvider extends Plugin<LookupContext> {
/**
* Returns the relation type to be used to point to an item resource of the given type.
@@ -40,4 +51,106 @@ public interface LinkRelationProvider extends Plugin<Class<?>> {
* @return
*/
LinkRelation getCollectionResourceRelFor(Class<?> type);
/**
* Callback method to manually select {@link LinkRelationProvider} implementations based on a given
* {@link LookupContext}. User code shouldn't need to call this method explicitly but rather use
* {@link DelegatingLinkRelationProvider}, equip that with a set of {@link LinkRelationProvider} implementations as
* that will perform the selection of the matching one on invocations of {@link #getItemResourceRelFor(Class)} and
* {@link #getCollectionResourceRelFor(Class)} transparently.
*
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
boolean supports(LookupContext delimiter);
/**
* {@link LinkRelationProvider} selection context for item resource relation lookups
* ({@link #forItemResourceRelLookup(Class)}, collection resource relation lookups
* {@link #forCollectionResourceRelLookup(Class)} or both {@link #forType(Class)}.
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PRIVATE)
@EqualsAndHashCode
static class LookupContext {
private enum ResourceType {
ITEM, COLLECTION;
}
private final @NonNull @Getter Class<?> type;
private final @Nullable ResourceType resourceType;
/**
* Creates a {@link LookupContext} for the type in general, i.e. both item and collection relation lookups.
*
* @param type must not be {@literal null}.
* @return
*/
public static LookupContext forType(Class<?> type) {
return new LookupContext(type, null);
}
/**
* Creates a {@link LookupContext} to lookup the item resource relation for the given type.
*
* @param type must not be {@literal null}.
* @return
*/
public static LookupContext forItemResourceRelLookup(Class<?> type) {
return new LookupContext(type, ResourceType.ITEM);
}
/**
* Creates a {@link LookupContext} to lookup the collection resource relation for the given type.
*
* @param type must not be {@literal null}.
* @return
*/
public static LookupContext forCollectionResourceRelLookup(Class<?> type) {
return new LookupContext(type, ResourceType.COLLECTION);
}
/**
* Returns whether the current context includes the item relation lookup.
*
* @return
*/
public boolean isItemRelationLookup() {
return resourceType == null || ResourceType.ITEM.equals(resourceType);
}
/**
* Returns whether the current context includes the collection relation lookup.
*
* @return
*/
public boolean isCollectionRelationLookup() {
return resourceType == null || ResourceType.COLLECTION.equals(resourceType);
}
/**
* Returns whether the lookup is executed for the given type.
*
* @param type must not be {@literal null}.
* @return
*/
public boolean handlesType(Class<?> type) {
return this.type.equals(type);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
ResourceType resourceType = this.resourceType;
return String.format("LookupContext for %s for %s resource relations.", type.getName(),
resourceType == null ? "ITEM & COLLECTION" : resourceType.name());
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2019 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.
@@ -19,12 +19,15 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link LinkRelationProvider} that evaluates the {@link Relation} annotation on entity types.
*
* @author Oliver Gierke
* @author Alexander Baetz
* @author Greg Turnquist
@@ -38,13 +41,12 @@ 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);
if (annotation == null || Relation.NO_RELATION.equals(annotation.collectionRelation())) {
return null;
throw new IllegalArgumentException(String.format("No collection relation found for type %s!", type.getName()));
}
return LinkRelation.of(annotation.collectionRelation());
@@ -55,13 +57,14 @@ public class AnnotationLinkRelationProvider implements LinkRelationProvider, Ord
* @see org.springframework.hateoas.server.LinkRelationProvider#getItemResourceRelFor(java.lang.Class)
*/
@Override
@Nullable
public LinkRelation getItemResourceRelFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
Relation annotation = lookupAnnotation(type);
if (annotation == null || Relation.NO_RELATION.equals(annotation.value())) {
return null;
throw new IllegalStateException(String.format("Type %s is not supported!", type.getName()));
}
return LinkRelation.of(annotation.value());
@@ -81,12 +84,27 @@ public class AnnotationLinkRelationProvider implements LinkRelationProvider, Ord
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Class<?> delimiter) {
return lookupAnnotation(delimiter) != null;
public boolean supports(LookupContext context) {
Relation relation = lookupAnnotation(context.getType());
if (relation == null) {
return false;
}
if (context.isItemRelationLookup()) {
return !relation.value().equals(Relation.NO_RELATION);
}
if (context.isCollectionRelationLookup()) {
return !relation.collectionRelation().equals(Relation.NO_RELATION);
}
return false;
}
@Nullable
private Relation lookupAnnotation(Class<?> type) {
return annotationCache.computeIfAbsent(type, key -> AnnotationUtils.getAnnotation(key, Relation.class));
return annotationCache.computeIfAbsent(type, key -> AnnotatedElementUtils.getMergedAnnotation(key, Relation.class));
}
}

View File

@@ -44,6 +44,7 @@ public class CachingMappingDiscoverer implements MappingDiscoverer {
* (non-Javadoc)
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class)
*/
@Nullable
@Override
public String getMapping(Class<?> type) {
@@ -56,6 +57,7 @@ public class CachingMappingDiscoverer implements MappingDiscoverer {
* (non-Javadoc)
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
*/
@Nullable
@Override
public String getMapping(Method method) {
@@ -68,6 +70,7 @@ public class CachingMappingDiscoverer implements MappingDiscoverer {
* (non-Javadoc)
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
*/
@Nullable
@Override
public String getMapping(Class<?> type, Method method) {

View File

@@ -28,16 +28,17 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.hateoas.server.ExposesResourceFor;
import org.springframework.hateoas.server.LinkBuilder;
import org.springframework.hateoas.server.LinkBuilderFactory;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
/**
* {@link FactoryBean} implementation to create {@link ControllerEntityLinks} instances looking up controller classes
* from an {@link ApplicationContext}. The controller types are identified by the annotation type configured.
*
*
* @author Oliver Gierke
*/
public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean<ControllerEntityLinks> implements
ApplicationContextAware {
public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean<ControllerEntityLinks>
implements ApplicationContextAware {
private Class<? extends Annotation> annotation;
private LinkBuilderFactory<? extends LinkBuilder> linkBuilderFactory;
@@ -45,7 +46,7 @@ public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean<Contro
/**
* Configures the annotation type to inspect the {@link ApplicationContext} for beans that carry the given annotation.
*
*
* @param annotation must not be {@literal null}.
*/
public void setAnnotation(Class<? extends Annotation> annotation) {
@@ -55,14 +56,14 @@ public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean<Contro
/**
* Configures the {@link LinkBuilderFactory} to be used to create {@link LinkBuilder} instances.
*
*
* @param linkBuilderFactory the linkBuilderFactory to set
*/
public void setLinkBuilderFactory(LinkBuilderFactory<? extends LinkBuilder> linkBuilderFactory) {
this.linkBuilderFactory = linkBuilderFactory;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@@ -71,16 +72,17 @@ public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean<Contro
this.context = context;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#getObjectType()
*/
@NonNull
@Override
public Class<?> getObjectType() {
return ControllerEntityLinks.class;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance()
*/
@@ -98,7 +100,7 @@ public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean<Contro
return new ControllerEntityLinks(controllerTypes, linkBuilderFactory);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#afterPropertiesSet()
*/

View File

@@ -62,7 +62,7 @@ public class DefaultLinkRelationProvider implements LinkRelationProvider, Ordere
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Class<?> delimiter) {
public boolean supports(LookupContext delimiter) {
return true;
}
}

View File

@@ -28,7 +28,16 @@ import org.springframework.plugin.core.PluginRegistry;
@RequiredArgsConstructor
public class DelegatingLinkRelationProvider implements LinkRelationProvider {
private final @NonNull PluginRegistry<LinkRelationProvider, Class<?>> providers;
private final @NonNull PluginRegistry<LinkRelationProvider, LookupContext> providers;
/**
* Creates a new {@link DefaultLinkRelationProvider} for the given {@link LinkRelationProvider}s.
*
* @param providers must not be {@literal null}.
*/
public DelegatingLinkRelationProvider(LinkRelationProvider... providers) {
this(PluginRegistry.of(providers));
}
/*
* (non-Javadoc)
@@ -36,7 +45,10 @@ public class DelegatingLinkRelationProvider implements LinkRelationProvider {
*/
@Override
public LinkRelation getItemResourceRelFor(Class<?> type) {
return providers.getRequiredPluginFor(type).getItemResourceRelFor(type);
LookupContext context = LookupContext.forItemResourceRelLookup(type);
return providers.getRequiredPluginFor(context).getItemResourceRelFor(type);
}
/*
@@ -45,7 +57,10 @@ public class DelegatingLinkRelationProvider implements LinkRelationProvider {
*/
@Override
public LinkRelation getCollectionResourceRelFor(java.lang.Class<?> type) {
return providers.getRequiredPluginFor(type).getCollectionResourceRelFor(type);
LookupContext context = LookupContext.forCollectionResourceRelLookup(type);
return providers.getRequiredPluginFor(context).getCollectionResourceRelFor(type);
}
/*
@@ -53,7 +68,7 @@ public class DelegatingLinkRelationProvider implements LinkRelationProvider {
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(java.lang.Class<?> delimiter) {
public boolean supports(LookupContext delimiter) {
return providers.hasPluginFor(delimiter);
}
}

View File

@@ -21,17 +21,12 @@ import lombok.Value;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
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;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.springframework.util.ReflectionUtils;
/**
@@ -41,9 +36,6 @@ import org.springframework.util.ReflectionUtils;
*/
public class DummyInvocationUtils {
private static final ObjenesisStd OBJENESIS = new ObjenesisStd();
private static final Map<Class<?>, Class<?>> CLASS_CACHE = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
/**
* Method interceptor that records the last method invocation and creates a proxy for the return value that exposes
* the method invocation.
@@ -86,6 +78,7 @@ public class DummyInvocationUtils {
*/
@Override
@Nullable
@SuppressWarnings("null")
public Object invoke(org.aopalliance.intercept.MethodInvocation invocation) {
Method method = invocation.getMethod();
@@ -164,30 +157,6 @@ public class DummyInvocationUtils {
return (T) factory.getProxy(classLoader);
}
/**
* Returns the already created proxy class for the given source type or creates a new one.
*
* @param type must not be {@literal null}.
* @param classLoader must not be {@literal null}.
* @return
*/
private static Class<?> getOrCreateEnhancedClass(Class<?> type, ClassLoader classLoader) {
Assert.notNull(type, "Source type must not be null!");
Assert.notNull(classLoader, "ClassLoader must not be null!");
return CLASS_CACHE.computeIfAbsent(type, key -> {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(key);
enhancer.setInterfaces(new Class<?>[] { LastInvocationAware.class });
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
enhancer.setClassLoader(classLoader);
return enhancer.createClass();
});
}
@Value
private static class SimpleMethodInvocation implements MethodInvocation {

View File

@@ -20,8 +20,9 @@ import java.util.Collections;
import java.util.Optional;
import org.springframework.aop.support.AopUtils;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.LinkRelation;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -186,6 +187,7 @@ public class EmbeddedWrappers {
* (non-Javadoc)
* @see org.springframework.hateoas.hal.EmbeddedWrapper#getValue()
*/
@NonNull
@Override
public Object getValue() {
return value;
@@ -195,6 +197,7 @@ public class EmbeddedWrappers {
* (non-Javadoc)
* @see org.springframework.hateoas.EmbeddedWrappers.AbstractElementWrapper#peek()
*/
@NonNull
@Override
protected Object peek() {
return getValue();
@@ -283,6 +286,7 @@ public class EmbeddedWrappers {
public EmptyCollectionEmbeddedWrapper(Class<?> type) {
Assert.notNull(type, "Element type must not be null!");
this.type = type;
}
@@ -308,6 +312,7 @@ public class EmbeddedWrappers {
* (non-Javadoc)
* @see org.springframework.hateoas.core.EmbeddedWrapper#getRelTargetType()
*/
@NonNull
@Override
public Class<?> getRelTargetType() {
return type;

View File

@@ -17,7 +17,6 @@ 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;
@@ -40,7 +39,7 @@ class EncodingUtils {
* @param source must not be {@literal null}.
* @return
*/
public static String encodePath(@Nullable Object source) {
public static String encodePath(Object source) {
Assert.notNull(source, "Path value must not be null!");
@@ -57,7 +56,7 @@ class EncodingUtils {
* @param source must not be {@literal null}.
* @return
*/
public static String encodeParameter(@Nullable Object source) {
public static String encodeParameter(Object source) {
Assert.notNull(source, "Request parameter value must not be null!");

View File

@@ -44,8 +44,11 @@ public class HeaderLinksResponseEntity<T extends RepresentationModel<?>> extends
private HeaderLinksResponseEntity(ResponseEntity<T> entity) {
super(entity.getBody(), getHeadersWithLinks(entity), entity.getStatusCode());
if (entity.getBody() != null) {
entity.getBody().removeLinks();
T body = entity.getBody();
if (body != null) {
body.removeLinks();
}
}
@@ -100,7 +103,9 @@ public class HeaderLinksResponseEntity<T extends RepresentationModel<?>> extends
*/
private static <T extends RepresentationModel<?>> HttpHeaders getHeadersWithLinks(ResponseEntity<T> entity) {
Links links = entity.getBody() != null ? entity.getBody().getLinks() : Links.NONE;
T body = entity.getBody();
Links links = body != null ? body.getLinks() : Links.NONE;
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(entity.getHeaders());

View File

@@ -32,6 +32,7 @@ import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.LinkBuilder;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
@@ -84,7 +85,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
* (non-Javadoc)
* @see org.springframework.hateoas.LinkBuilder#slash(java.lang.Object)
*/
public T slash(Object object) {
public T slash(@Nullable Object object) {
object = object instanceof Optional ? ((Optional<?>) object).orElse(null) : object;
@@ -117,7 +118,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
String fragment = components.getFragment();
if (StringUtils.hasText(fragment)) {
if (fragment != null && !fragment.trim().isEmpty()) {
builder.fragment(encoded ? fragment : encodeFragment(fragment));
}

View File

@@ -24,7 +24,7 @@ import org.springframework.lang.Nullable;
/**
* Strategy interface to discover a URI mapping and related {@link org.springframework.hateoas.Affordance}s for either a
* given type or method.
*
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@@ -32,7 +32,7 @@ public interface MappingDiscoverer {
/**
* Returns the mapping associated with the given type.
*
*
* @param type must not be {@literal null}.
* @return the type-level mapping or {@literal null} in case none is present.
*/
@@ -41,16 +41,17 @@ public interface MappingDiscoverer {
/**
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
*
*
* @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(Method method);
/**
* Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the
* mapping for a super type method being invoked on a sub-type with a type mapping.
*
*
* @param type must not be {@literal null}.
* @param method must not be {@literal null}.
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
@@ -61,7 +62,7 @@ public interface MappingDiscoverer {
/**
* Returns the HTTP verbs for the given {@link Method} invoked on the given type. This can be used to build hypermedia
* templates.
*
*
* @param type
* @param method
* @return

View File

@@ -33,7 +33,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Value object to represent {@link MethodParameters} to allow to easily find the ones with a given annotation.
*
*
* @author Oliver Gierke
*/
public class MethodParameters {
@@ -45,7 +45,7 @@ public class MethodParameters {
/**
* Creates a new {@link MethodParameters} from the given {@link Method}.
*
*
* @param method must not be {@literal null}.
*/
public MethodParameters(Method method) {
@@ -55,7 +55,7 @@ public class MethodParameters {
/**
* Creates a new {@link MethodParameters} for the given {@link Method} and {@link AnnotationAttribute}. If the latter
* is given, method parameter names will be looked up from the annotation attribute if present.
*
*
* @param method must not be {@literal null}.
* @param namingAnnotation can be {@literal null}.
*/
@@ -71,7 +71,7 @@ public class MethodParameters {
/**
* Returns all {@link MethodParameter}s.
*
*
* @return
*/
public List<MethodParameter> getParameters() {
@@ -80,7 +80,7 @@ public class MethodParameters {
/**
* Returns the {@link MethodParameter} with the given name or {@literal null} if none found.
*
*
* @param name must not be {@literal null} or empty.
* @return
*/
@@ -95,7 +95,7 @@ public class MethodParameters {
/**
* Returns all parameters of the given type.
*
*
* @param type must not be {@literal null}.
* @return
* @since 0.9
@@ -111,7 +111,7 @@ public class MethodParameters {
/**
* Returns all {@link MethodParameter}s annotated with the given annotation type.
*
*
* @param annotation must not be {@literal null}.
* @return
*/
@@ -130,7 +130,7 @@ public class MethodParameters {
/**
* Custom {@link MethodParameter} extension that will favor the name configured in the {@link AnnotationAttribute} if
* set over discovering it.
*
*
* @author Oliver Gierke
*/
private static class AnnotationNamingMethodParameter extends SynthesizingMethodParameter {
@@ -141,7 +141,7 @@ public class MethodParameters {
/**
* Creates a new {@link AnnotationNamingMethodParameter} for the given {@link Method}'s parameter with the given
* index.
*
*
* @param method must not be {@literal null}.
* @param parameterIndex
* @param attribute can be {@literal null}
@@ -153,10 +153,11 @@ public class MethodParameters {
}
/*
/*
* (non-Javadoc)
* @see org.springframework.core.MethodParameter#getParameterName()
*/
@Nullable
@Override
public String getParameterName() {
@@ -172,8 +173,7 @@ public class MethodParameters {
}
}
name = super.getParameterName();
return name;
return super.getParameterName();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2019 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.
@@ -20,13 +20,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.hateoas.EntityModel;
import org.springframework.core.annotation.AliasFor;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
/**
* Annotation to configure the relation to be used when embedding objects in HAL representations of {@link EntityModel}s
* and {@link CollectionModel}.
*
*
* @author Alexander Baetz
* @author Oliver Gierke
*/
@@ -37,15 +38,24 @@ public @interface Relation {
String NO_RELATION = "";
/**
* Defines the relation to be used when referring to a single resource.
*
* Defines the relation to be used when referring to a single resource. Alias for {@link #itemRelation()}.
*
* @return
*/
@AliasFor("itemRelation")
String value() default NO_RELATION;
/**
* Defines the relation to be used when referring to a single resource. Alias of {@link #value()}.
*
* @return
*/
@AliasFor("value")
String itemRelation() default NO_RELATION;
/**
* Defines the relation to be used when referring to a collection of resources.
*
*
* @return
*/
String collectionRelation() default NO_RELATION;

View File

@@ -109,7 +109,7 @@ public class TypeReferences {
* @see org.springframework.core.ParameterizedTypeReference#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
return this == obj || obj instanceof SyntheticParameterizedTypeReference
&& this.type.equals(((SyntheticParameterizedTypeReference<?>) obj).type);
}

View File

@@ -104,6 +104,7 @@ public class WebHandler {
for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR
.getBoundParameters(invocation)) {
values.put(parameter.getVariableName(), encodePath(parameter.asString()));
}
@@ -222,7 +223,7 @@ public class WebHandler {
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#createParameter(org.springframework.core.MethodParameter, java.lang.Object, org.springframework.hateoas.core.AnnotationAttribute)
*/
@Override
protected BoundMethodParameter createParameter(final MethodParameter parameter, Object value,
protected BoundMethodParameter createParameter(final MethodParameter parameter, @Nullable Object value,
AnnotationAttribute attribute) {
return new BoundMethodParameter(parameter, value, attribute) {
@@ -252,7 +253,7 @@ public class WebHandler {
*/
@Override
@Nullable
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
protected Object verifyParameterValue(MethodParameter parameter, @Nullable Object value) {
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
@@ -334,7 +335,7 @@ public class WebHandler {
* @return the verified value.
*/
@Nullable
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
protected Object verifyParameterValue(MethodParameter parameter, @Nullable Object value) {
return value;
}
@@ -414,12 +415,21 @@ public class WebHandler {
*
* @return
*/
@Nullable
public String asString() {
return value == null //
? null //
: (String) CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
Object value = this.value;
if (value == null) {
throw new IllegalStateException("Cannot turn null value into required String!");
}
Object result = CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
if (result == null) {
throw new IllegalStateException(String.format("Conversion of value %s resulted in null!", value));
}
return (String) result;
}
/**

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.hateoas.server.mvc;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.ExposesResourceFor;
import org.springframework.hateoas.server.LinkRelationProvider;
@@ -23,21 +23,34 @@ import org.springframework.plugin.core.PluginRegistry;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
* {@link LinkRelationProvider} inspecting {@link ExposesResourceFor} annotations on controller classes.
*
* @author Oliver Drotbohm
*/
public class ControllerLinkRelationProvider implements LinkRelationProvider {
private final Class<?> controllerType;
private final Class<?> entityType;
private final PluginRegistry<LinkRelationProvider, Class<?>> providers;
private final PluginRegistry<LinkRelationProvider, LookupContext> providers;
public ControllerLinkRelationProvider(Class<?> controller, PluginRegistry<LinkRelationProvider, Class<?>> providers) {
/**
* Creates a new {@link ControllerLinkRelationProvider}
*
* @param controller must not be {@literal null}.
* @param providers must not be {@literal null}.
*/
public ControllerLinkRelationProvider(Class<?> controller,
PluginRegistry<LinkRelationProvider, LookupContext> providers) {
Assert.notNull(controller, "Controller must not be null!");
Assert.notNull(providers, "LinkRelationProviders must not be null!");
ExposesResourceFor annotation = AnnotationUtils.findAnnotation(controller, ExposesResourceFor.class);
ExposesResourceFor annotation = AnnotatedElementUtils.findMergedAnnotation(controller, ExposesResourceFor.class);
Assert.notNull(annotation, "Controller must be annotated with ExposesResourceFor!");
if (annotation == null) {
throw new IllegalArgumentException(
String.format("Controller %s must be annotated with @ExposesResourceFor(…)!", controller.getName()));
}
this.controllerType = controller;
this.entityType = annotation.value();
@@ -50,7 +63,10 @@ public class ControllerLinkRelationProvider implements LinkRelationProvider {
*/
@Override
public LinkRelation getItemResourceRelFor(Class<?> resource) {
return providers.getRequiredPluginFor(entityType).getItemResourceRelFor(resource);
LookupContext context = LookupContext.forItemResourceRelLookup(entityType);
return providers.getRequiredPluginFor(context).getItemResourceRelFor(resource);
}
/*
@@ -59,7 +75,10 @@ public class ControllerLinkRelationProvider implements LinkRelationProvider {
*/
@Override
public LinkRelation getCollectionResourceRelFor(Class<?> resource) {
return providers.getRequiredPluginFor(entityType).getCollectionResourceRelFor(resource);
LookupContext context = LookupContext.forCollectionResourceRelLookup(entityType);
return providers.getRequiredPluginFor(context).getCollectionResourceRelFor(resource);
}
/*
@@ -67,7 +86,7 @@ public class ControllerLinkRelationProvider implements LinkRelationProvider {
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Class<?> delimiter) {
return controllerType.equals(delimiter);
public boolean supports(LookupContext context) {
return context.handlesType(controllerType);
}
}

View File

@@ -50,6 +50,7 @@ public class JacksonSerializers {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public MediaType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return MediaType.parseMediaType(p.getText());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2019 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.
@@ -19,6 +19,7 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
@@ -29,6 +30,7 @@ import org.springframework.hateoas.server.RepresentationModelProcessor;
import org.springframework.hateoas.server.core.HeaderLinksResponseEntity;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -84,8 +86,8 @@ public class RepresentationModelProcessorHandlerMethodReturnValueHandler impleme
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
Object value = returnValue;
@@ -99,8 +101,14 @@ public class RepresentationModelProcessorHandlerMethodReturnValueHandler impleme
return;
}
Method method = returnType.getMethod();
if (method == null) {
throw new IllegalStateException(String.format("Return type %s does not expose a method!", returnType));
}
// We have a Resource or Resources - find suitable processors
ResolvableType targetType = ResolvableType.forMethodReturnType(returnType.getMethod());
ResolvableType targetType = ResolvableType.forMethodReturnType(method);
// Unbox HttpEntity
if (HTTP_ENTITY_TYPE.isAssignableFrom(targetType)) {
@@ -127,7 +135,7 @@ public class RepresentationModelProcessorHandlerMethodReturnValueHandler impleme
* @param originalValue the original input value.
* @return
*/
Object rewrapResult(RepresentationModel<?> newBody, Object originalValue) {
Object rewrapResult(RepresentationModel<?> newBody, @Nullable Object originalValue) {
if (!(originalValue instanceof HttpEntity)) {
return rootLinksAsHeaders ? HeaderLinksResponseEntity.wrap(newBody) : newBody;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2019 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.
@@ -107,8 +107,13 @@ public class RepresentationModelProcessorInvoker {
if (RepresentationModelProcessorHandlerMethodReturnValueHandler.RESOURCES_TYPE.isAssignableFrom(referenceType)) {
CollectionModel<?> resources = (CollectionModel<?>) value;
ResolvableType elementTargetType = ResolvableType.forClass(CollectionModel.class, referenceType.getRawClass())
.getGeneric(0);
Class<?> rawClass = referenceType.getRawClass();
if (rawClass == null) {
throw new IllegalArgumentException(String.format("%s does not expose a raw type!", referenceType));
}
ResolvableType elementTargetType = ResolvableType.forClass(CollectionModel.class, rawClass).getGeneric(0);
List<Object> result = new ArrayList<>(resources.getContent().size());
for (Object element : resources) {
@@ -421,7 +426,9 @@ public class RepresentationModelProcessorInvoker {
*/
private static ResolvableType getSuperType(ResolvableType source, Class<?> superType) {
if (source.getRawClass() != null && source.getRawClass().equals(superType)) {
Class<?> rawType = source.getRawClass();
if (rawType != null && rawType.equals(superType)) {
return source;
}
@@ -452,7 +459,7 @@ public class RepresentationModelProcessorInvoker {
public static RepresentationModelProcessorInvoker.CustomOrderAwareComparator INSTANCE = new CustomOrderAwareComparator();
@Override
protected int getOrder(Object obj) {
protected int getOrder(@Nullable Object obj) {
return super.getOrder(obj);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2019 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.
@@ -20,6 +20,7 @@ import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -28,7 +29,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* Extension of {@link MappingJackson2HttpMessageConverter} to constrain the ability to read and write HTTP message
* based on the target type. Useful in case the {@link ObjectMapper} about to be configured has customizations that
* shall only be applied to object trees of a certain base type.
*
*
* @author Oliver Gierke
*/
public class TypeConstrainedMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
@@ -37,7 +38,7 @@ public class TypeConstrainedMappingJackson2HttpMessageConverter extends MappingJ
/**
* Creates a new {@link TypeConstrainedMappingJackson2HttpMessageConverter} for the given type.
*
*
* @param type must not be {@literal null}.
*/
public TypeConstrainedMappingJackson2HttpMessageConverter(Class<?> type) {
@@ -53,38 +54,39 @@ public class TypeConstrainedMappingJackson2HttpMessageConverter extends MappingJ
* @param supportedMediaTypes
* @param objectMapper
*/
public TypeConstrainedMappingJackson2HttpMessageConverter(Class<?> type, List<MediaType> supportedMediaTypes, ObjectMapper objectMapper) {
public TypeConstrainedMappingJackson2HttpMessageConverter(Class<?> type, List<MediaType> supportedMediaTypes,
ObjectMapper objectMapper) {
this(type);
setSupportedMediaTypes(supportedMediaTypes);
setObjectMapper(objectMapper);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#canRead(java.lang.Class, org.springframework.http.MediaType)
*/
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return type.isAssignableFrom(clazz) && super.canRead(clazz, mediaType);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#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) {
return this.type.isAssignableFrom(getJavaType(type, contextClass).getRawClass())
&& super.canRead(type, contextClass, mediaType);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#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 type.isAssignableFrom(clazz) && super.canWrite(clazz, mediaType);
}
}

View File

@@ -60,6 +60,10 @@ class UriComponentsBuilderFactory {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
throw new IllegalStateException("Could not look up RequestAttributes!");
}
Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING);
Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);

View File

@@ -17,6 +17,7 @@ package org.springframework.hateoas.server.mvc;
import org.springframework.core.MethodParameter;
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
import org.springframework.lang.Nullable;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.util.UriComponentsBuilder;
@@ -24,7 +25,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* SPI callback to enhance a {@link UriComponentsBuilder} when referring to a method through a dummy method invocation.
* Will usually be implemented in implementations of {@link HandlerMethodArgumentResolver} as they represent exactly the
* same functionality inverted.
*
*
* @see MethodLinkBuilderFactory#linkTo(Object)
* @author Oliver Gierke
*/
@@ -32,7 +33,7 @@ public interface UriComponentsContributor {
/**
* Returns whether the {@link UriComponentsBuilder} supports the given {@link MethodParameter}.
*
*
* @param parameter will never be {@literal null}.
* @return
*/
@@ -40,10 +41,10 @@ public interface UriComponentsContributor {
/**
* Enhance the given {@link UriComponentsBuilder} with the given value.
*
*
* @param builder will never be {@literal null}.
* @param parameter will never be {@literal null}.
* @param parameter can be {@literal null}.
* @param value can be {@literal null}.
*/
void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value);
void enhance(UriComponentsBuilder builder, @Nullable MethodParameter parameter, @Nullable Object value);
}

View File

@@ -48,6 +48,7 @@ import org.springframework.web.util.UriTemplate;
* @author Oliver Trosien
* @author Greg Turnquist
*/
@SuppressWarnings("deprecation")
public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<WebMvcLinkBuilder> {
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer