#831 - Introduce null handling.

This commit is contained in:
Greg Turnquist
2019-03-05 15:52:52 -06:00
parent c4e1bedec8
commit 40250d1738
71 changed files with 520 additions and 216 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.hateoas;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
@@ -55,7 +56,7 @@ public class EntityModel<T> extends RepresentationModel<EntityModel<T>> {
* @param content must not be {@literal null}.
* @param links the links to add to the {@link EntityModel}.
*/
public EntityModel(T content, Iterable<Link> links) {
public EntityModel(@Nullable T content, Iterable<Link> links) {
Assert.notNull(content, "Content must not be null!");
Assert.isTrue(!(content instanceof Collection), "Content must not be a collection! Use Resources instead!");
@@ -69,6 +70,7 @@ public class EntityModel<T> extends RepresentationModel<EntityModel<T>> {
* @return the content
*/
@JsonUnwrapped
@Nullable
public T getContent() {
return content;
}

View File

@@ -33,6 +33,7 @@ import java.util.regex.Pattern;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -238,8 +239,14 @@ public class Link implements Serializable {
*/
public Link andAffordance(HttpMethod httpMethod, ResolvableType inputType, List<QueryParameter> queryMethodParameters,
ResolvableType outputType) {
return andAffordance(httpMethod.toString().toLowerCase() + inputType.resolve().getSimpleName(), httpMethod,
inputType, queryMethodParameters, outputType);
String name = httpMethod.toString().toLowerCase();
if (inputType.resolve() != null) {
name += inputType.resolve().getSimpleName();
}
return andAffordance(name, httpMethod, inputType, queryMethodParameters, outputType);
}
/**
@@ -458,6 +465,7 @@ public class Link implements Serializable {
* @throws IllegalArgumentException if no {@code rel} attribute could be found.
* @return
*/
@Nullable
public static Link valueOf(String element) {
if (!StringUtils.hasText(element)) {

View File

@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -51,7 +52,7 @@ public class PagedModel<T> extends CollectionModel<T> {
* @param metadata
* @param links
*/
public PagedModel(Collection<T> content, PageMetadata metadata, Link... links) {
public PagedModel(Collection<T> content, @Nullable PageMetadata metadata, Link... links) {
this(content, metadata, Arrays.asList(links));
}
@@ -62,7 +63,7 @@ public class PagedModel<T> extends CollectionModel<T> {
* @param metadata
* @param links
*/
public PagedModel(Collection<T> content, PageMetadata metadata, Iterable<Link> links) {
public PagedModel(Collection<T> content, @Nullable PageMetadata metadata, Iterable<Link> links) {
super(content, links);
@@ -75,6 +76,7 @@ public class PagedModel<T> extends CollectionModel<T> {
* @return the metadata
*/
@JsonProperty("page")
@Nullable
public PageMetadata getMetadata() {
return metadata;
}

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.hateoas;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.io.Serializable;
@@ -36,7 +34,6 @@ import com.fasterxml.jackson.annotation.JsonValue;
* @author Oliver Drotbohm
*/
@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
class StringLinkRelation implements LinkRelation, Serializable {
private static final long serialVersionUID = -3904935345545567957L;
@@ -44,6 +41,13 @@ class StringLinkRelation implements LinkRelation, Serializable {
@NonNull String relation;
private StringLinkRelation(String relation) {
Assert.notNull(relation, "relation must not be null!");
this.relation = relation;
}
/**
* Returns a (potentially cached) {@link LinkRelation} for the given value.
*

View File

@@ -27,6 +27,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.hateoas.TemplateVariable.VariableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
@@ -285,7 +286,7 @@ public class UriTemplate implements Iterable<TemplateVariable>, Serializable {
* @param variable must not be {@literal null}.
* @param value can be {@literal null}.
*/
private static void appendToBuilder(UriComponentsBuilder builder, TemplateVariable variable, Object value) {
private static void appendToBuilder(UriComponentsBuilder builder, TemplateVariable variable, @Nullable Object value) {
if (value == null) {

View File

@@ -19,6 +19,7 @@ import java.util.Optional;
import org.springframework.hateoas.Link;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.jayway.jsonpath.JsonPath;
@@ -60,7 +61,7 @@ class Rels {
* @param mediaType
* @return
*/
Optional<Link> findInResponse(String representation, MediaType mediaType);
Optional<Link> findInResponse(@Nullable String representation, @Nullable MediaType mediaType);
}
/**
@@ -141,7 +142,7 @@ class Rels {
* @see org.springframework.hateoas.client.Rels.Rel#findInResponse(java.lang.String, org.springframework.http.MediaType)
*/
@Override
public Optional<Link> findInResponse(String representation, MediaType mediaType) {
public Optional<Link> findInResponse(@Nullable String representation, @Nullable MediaType mediaType) {
return Optional.of(new Link(JsonPath.read(representation, jsonPath).toString(), rel));
}
}

View File

@@ -40,6 +40,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
@@ -139,7 +140,7 @@ public class Traverson {
* @param operations
* @return
*/
public Traverson setRestOperations(RestOperations operations) {
public Traverson setRestOperations(@Nullable RestOperations operations) {
this.operations = operations == null //
? createDefaultTemplate(this.mediaTypes) //
@@ -155,7 +156,7 @@ public class Traverson {
* @param discoverer can be {@literal null}.
* @return
*/
public Traverson setLinkDiscoverers(List<? extends LinkDiscoverer> discoverer) {
public Traverson setLinkDiscoverers(@Nullable List<? extends LinkDiscoverer> discoverer) {
List<? extends LinkDiscoverer> defaultedDiscoverers = discoverer == null //
? DEFAULTS.getLinkDiscoverers(mediaTypes) //
@@ -282,6 +283,7 @@ public class Traverson {
* @param type must not be {@literal null}.
* @return
*/
@Nullable
public <T> T toObject(Class<T> type) {
Assert.notNull(type, "Target type must not be null!");
@@ -299,6 +301,7 @@ public class Traverson {
* @param type must not be {@literal null}.
* @return
*/
@Nullable
public <T> T toObject(ParameterizedTypeReference<T> type) {
Assert.notNull(type, "Target type must not be null!");

View File

@@ -1,5 +1,7 @@
/**
* Client side support.
*/
@NonNullApi
package org.springframework.hateoas.client;
import org.springframework.lang.NonNullApi;

View File

@@ -17,6 +17,7 @@ package org.springframework.hateoas.config;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
@@ -43,9 +44,10 @@ class HypermediaConfigurationImportSelector implements ImportSelector {
Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableHypermediaSupport.class.getName());
Collection<MediaType> types = Arrays.stream((HypermediaType[]) attributes.get("type")) //
.flatMap(it -> it.getMediaTypes().stream()) //
.collect(Collectors.toList());
Collection<MediaType> types = attributes == null ? Collections.emptyList()
: Arrays.stream((HypermediaType[]) attributes.get("type")) //
.flatMap(it -> it.getMediaTypes().stream()) //
.collect(Collectors.toList());
Collection<MediaTypeConfigurationProvider> configurationProviders = SpringFactoriesLoader.loadFactories(
MediaTypeConfigurationProvider.class, HypermediaConfigurationImportSelector.class.getClassLoader());

View File

@@ -19,6 +19,7 @@ import java.util.List;
import java.util.Optional;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
@@ -63,6 +64,7 @@ public interface HypermediaMappingInformation {
* @return
* @see #configureObjectMapper(ObjectMapper)
*/
@Nullable
default Module getJacksonModule() {
return null;
}

View File

@@ -1,5 +1,7 @@
/**
* Spring container configuration support.
*/
@NonNullApi
package org.springframework.hateoas.config;
import org.springframework.lang.NonNullApi;

View File

@@ -22,6 +22,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -37,6 +38,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.support.WebStack;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -54,7 +56,11 @@ public class PropertyUtils {
FIELDS_TO_IGNORE.add("links");
}
public static Map<String, Object> findProperties(Object object) {
public static Map<String, Object> findProperties(@Nullable Object object) {
if (object == null) {
return Collections.emptyMap();
}
if (object.getClass().equals(EntityModel.class)) {
return findProperties(((EntityModel<?>) object).getContent());
@@ -83,8 +89,18 @@ public class PropertyUtils {
}
}
if (resolvableType.getRawClass() == null) {
return Collections.emptyList();
}
if (resolvableType.getRawClass().equals(EntityModel.class)) {
return findPropertyNames(resolvableType.resolveGeneric(0));
Class<?> genericEntityModelParameter = resolvableType.resolveGeneric(0);
if (genericEntityModelParameter == null) {
return Collections.emptyList();
}
return findPropertyNames(genericEntityModelParameter);
} else {
return findPropertyNames(resolvableType.getRawClass());
}
@@ -143,6 +159,10 @@ public class PropertyUtils {
Field descriptorField = ReflectionUtils.findField(clazz, descriptor.getName());
if (descriptorField == null) {
return false;
}
return toBeIgnoredByJackson(AnnotationUtils.getAnnotations(descriptorField));
}
@@ -162,7 +182,7 @@ public class PropertyUtils {
* @param annotations
* @return
*/
private static boolean toBeIgnoredByJackson(Annotation[] annotations) {
private static boolean toBeIgnoredByJackson(@Nullable Annotation[] annotations) {
if (annotations != null) {
for (Annotation annotation : annotations) {
@@ -184,12 +204,16 @@ public class PropertyUtils {
*/
private static boolean toBeIgnoredByJackson(Class<?> clazz, String field) {
for (Annotation annotation : AnnotationUtils.getAnnotations(clazz)) {
if (annotation.annotationType().equals(JsonIgnoreProperties.class)) {
String[] namesOfPropertiesToIgnore = (String[]) AnnotationUtils.getAnnotationAttributes(annotation).get("value");
for (String propertyToIgnore : namesOfPropertiesToIgnore) {
if (propertyToIgnore.equalsIgnoreCase(field)) {
return true;
Annotation[] annotations = AnnotationUtils.getAnnotations(clazz);
if (annotations != null) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(JsonIgnoreProperties.class)) {
String[] namesOfPropertiesToIgnore = (String[]) AnnotationUtils.getAnnotationAttributes(annotation).get("value");
for (String propertyToIgnore : namesOfPropertiesToIgnore) {
if (propertyToIgnore.equalsIgnoreCase(field)) {
return true;
}
}
}
}

View File

@@ -3,5 +3,7 @@
*
* @see https://alps.io
*/
@NonNullApi
package org.springframework.hateoas.mediatype.alps;
import org.springframework.lang.NonNullApi;

View File

@@ -26,6 +26,7 @@ import java.util.List;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.Links.MergeMode;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -52,12 +53,12 @@ class CollectionJson<T> {
@JsonCreator
CollectionJson(@JsonProperty("version") String version, //
@JsonProperty("href") String href, //
@JsonProperty("links") Links links, //
@JsonProperty("items") List<CollectionJsonItem<T>> items, //
@JsonProperty("queries") List<CollectionJsonQuery> queries, //
@JsonProperty("template") CollectionJsonTemplate template, //
@JsonProperty("error") CollectionJsonError error) {
@JsonProperty("href") @Nullable String href, //
@JsonProperty("links") @Nullable Links links, //
@JsonProperty("items") @Nullable List<CollectionJsonItem<T>> items, //
@JsonProperty("queries") @Nullable List<CollectionJsonQuery> queries, //
@JsonProperty("template") @Nullable CollectionJsonTemplate template, //
@JsonProperty("error") @Nullable CollectionJsonError error) {
this.version = version;
this.href = href;

View File

@@ -18,6 +18,8 @@ package org.springframework.hateoas.mediatype.collectionjson;
import lombok.Value;
import lombok.experimental.Wither;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -42,9 +44,9 @@ class CollectionJsonData {
private String prompt;
@JsonCreator
CollectionJsonData(@JsonProperty("name") String name, @JsonProperty("value") Object value,
@JsonProperty("prompt") String prompt) {
CollectionJsonData(@JsonProperty("name") @Nullable String name, @JsonProperty("value") @Nullable Object value,
@JsonProperty("prompt") @Nullable String prompt) {
this.name = name;
this.value = value;
this.prompt = prompt;

View File

@@ -19,6 +19,8 @@ import lombok.AccessLevel;
import lombok.Value;
import lombok.experimental.Wither;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -34,8 +36,8 @@ class CollectionJsonError {
private String message;
@JsonCreator
CollectionJsonError(@JsonProperty("title") String title, @JsonProperty("code") String code,
@JsonProperty("message") String message) {
CollectionJsonError(@JsonProperty("title") @Nullable String title, @JsonProperty("code") @Nullable String code,
@JsonProperty("message") @Nullable String message) {
this.title = title;
this.code = code;

View File

@@ -30,6 +30,7 @@ import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.Links.MergeMode;
import org.springframework.hateoas.mediatype.PropertyUtils;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -54,9 +55,9 @@ class CollectionJsonItem<T> {
private @Getter(onMethod = @__({ @JsonIgnore }), value = AccessLevel.PRIVATE) T rawData;
@JsonCreator
CollectionJsonItem(@JsonProperty("href") String href, //
@JsonProperty("data") List<CollectionJsonData> data, //
@JsonProperty("links") Links links) {
CollectionJsonItem(@JsonProperty("href") @Nullable String href, //
@JsonProperty("data") @Nullable List<CollectionJsonData> data, //
@JsonProperty("links") @Nullable Links links) {
this.href = href;
this.data = data;
@@ -84,7 +85,7 @@ class CollectionJsonItem<T> {
return this.data;
}
if (PRIMITIVE_TYPES.contains(this.rawData.getClass())) {
if (this.rawData != null && PRIMITIVE_TYPES.contains(this.rawData.getClass())) {
return Collections.singletonList(new CollectionJsonData().withValue(this.rawData));
}
@@ -99,8 +100,13 @@ class CollectionJsonItem<T> {
* @param javaType - type of the object to create
* @return
*/
@Nullable
public Object toRawData(JavaType javaType) {
if (this.data == null) {
return null;
}
if (PRIMITIVE_TYPES.contains(javaType.getRawClass())) {
return this.data.get(0).getValue();
}

View File

@@ -22,6 +22,8 @@ import lombok.experimental.Wither;
import java.util.List;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -33,21 +35,17 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@Wither
class CollectionJsonQuery {
@JsonInclude(Include.NON_NULL)
private String rel;
@JsonInclude(Include.NON_NULL) private String rel;
@JsonInclude(Include.NON_NULL)
private String href;
@JsonInclude(Include.NON_NULL)
private String prompt;
@JsonInclude(Include.NON_NULL) private String href;
@JsonInclude(Include.NON_EMPTY)
private List<CollectionJsonData> data;
@JsonInclude(Include.NON_NULL) private String prompt;
@JsonInclude(Include.NON_EMPTY) private List<CollectionJsonData> data;
@JsonCreator
CollectionJsonQuery(@JsonProperty("rel") String rel, @JsonProperty("href") String href,
@JsonProperty("prompt") String prompt, @JsonProperty("data") List<CollectionJsonData> data) {
CollectionJsonQuery(@JsonProperty("rel") @Nullable String rel, @JsonProperty("href") @Nullable String href,
@JsonProperty("prompt") @Nullable String prompt, @JsonProperty("data") @Nullable List<CollectionJsonData> data) {
this.rel = rel;
this.href = href;

View File

@@ -20,6 +20,8 @@ import lombok.experimental.Wither;
import java.util.List;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -33,7 +35,7 @@ class CollectionJsonTemplate {
private List<CollectionJsonData> data;
@JsonCreator
CollectionJsonTemplate(@JsonProperty("data") List<CollectionJsonData> data) {
CollectionJsonTemplate(@JsonProperty("data") @Nullable List<CollectionJsonData> data) {
this.data = data;
}

View File

@@ -38,6 +38,7 @@ import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.JacksonHelper;
import org.springframework.hateoas.mediatype.PropertyUtils;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -137,6 +138,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -155,6 +157,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -171,7 +174,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
this(null);
}
CollectionJsonResourceSupportSerializer(BeanProperty property) {
CollectionJsonResourceSupportSerializer(@Nullable BeanProperty property) {
super(RepresentationModel.class, false);
this.property = property;
@@ -211,11 +214,13 @@ class Jackson2CollectionJsonModule extends SimpleModule {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -226,6 +231,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -242,7 +248,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
this(null);
}
CollectionJsonResourceSerializer(BeanProperty property) {
CollectionJsonResourceSerializer(@Nullable BeanProperty property) {
super(EntityModel.class, false);
this.property = property;
@@ -278,11 +284,13 @@ class Jackson2CollectionJsonModule extends SimpleModule {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -293,6 +301,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -333,6 +342,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -342,6 +352,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -369,6 +380,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -385,7 +397,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
this(null);
}
CollectionJsonPagedResourcesSerializer(BeanProperty property) {
CollectionJsonPagedResourcesSerializer(@Nullable BeanProperty property) {
super(CollectionModel.class, false);
this.property = property;
@@ -414,11 +426,13 @@ class Jackson2CollectionJsonModule extends SimpleModule {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -434,6 +448,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -452,6 +467,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -461,6 +477,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -509,6 +526,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -518,6 +536,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@Nullable
public RepresentationModel<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
TypeFactory typeFactory = ctxt.getTypeFactory();
@@ -544,8 +563,12 @@ class Jackson2CollectionJsonModule extends SimpleModule {
CollectionJsonItem<?> firstItem = items.get(0).withOwnSelfLink();
RepresentationModel<?> resource = (RepresentationModel<?>) firstItem.toRawData(this.contentType);
return resource.add(firstItem.getLinks().merge(merged));
if (resource != null) {
resource.add(firstItem.getLinks().merge(merged));
}
return resource;
}
if (withOwnSelfLink.getTemplate() != null) {
@@ -599,6 +622,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
}
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -689,6 +713,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -819,6 +844,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @param resource
* @return
*/
@Nullable
private static CollectionJsonTemplate findTemplate(RepresentationModel<?> resource) {
if (!resource.hasLink(IanaLinkRelations.SELF)) {

View File

@@ -0,0 +1,7 @@
/**
* Value objects to build Collection+JSON representations.
*/
@NonNullApi
package org.springframework.hateoas.mediatype.collectionjson;
import org.springframework.lang.NonNullApi;

View File

@@ -20,6 +20,7 @@ import java.util.Collection;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Links;
import org.springframework.lang.Nullable;
/**
* API to provide HAL curie information for links.
@@ -48,6 +49,7 @@ public interface CurieProvider {
* @return
* @since 0.17
*/
@Nullable
HalLinkRelation getNamespacedRelFor(LinkRelation rel);
/**

View File

@@ -26,6 +26,7 @@ import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.UriTemplate;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
@@ -75,7 +76,7 @@ public class DefaultCurieProvider implements CurieProvider {
* @param defaultCurieName can be {@literal null}.
* @since 0.19
*/
public DefaultCurieProvider(Map<String, UriTemplate> curies, String defaultCurieName) {
public DefaultCurieProvider(Map<String, UriTemplate> curies, @Nullable String defaultCurieName) {
Assert.notNull(curies, "Curies must not be null!");
@@ -109,6 +110,7 @@ public class DefaultCurieProvider implements CurieProvider {
* @see org.springframework.hateoas.hal.CurieProvider#getNamespacedRelFrom(org.springframework.hateoas.Link)
*/
@Override
@Nullable
public HalLinkRelation getNamespacedRelFrom(Link link) {
return getNamespacedRelFor(link.getRel());
}
@@ -118,6 +120,7 @@ public class DefaultCurieProvider implements CurieProvider {
* @see org.springframework.hateoas.hal.CurieProvider#getNamespacedRelFrom(java.lang.String)
*/
@Override
@Nullable
public HalLinkRelation getNamespacedRelFor(LinkRelation relation) {
HalLinkRelation result = HalLinkRelation.of(relation);

View File

@@ -22,11 +22,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.hateoas.server.core.EmbeddedWrapper;
import org.springframework.hateoas.server.core.EmbeddedWrappers;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -47,7 +48,8 @@ class HalEmbeddedBuilder {
private final EmbeddedWrappers wrappers;
/**
* Creates a new {@link HalEmbeddedBuilder} using the given {@link LinkRelationProvider} and prefer collection rels flag.
* Creates a new {@link HalEmbeddedBuilder} using the given {@link LinkRelationProvider} and prefer collection rels
* flag.
*
* @param provider can be {@literal null}.
* @param preferCollectionRels whether to prefer to ask the provider for collection rels.
@@ -99,7 +101,7 @@ class HalEmbeddedBuilder {
}
@SuppressWarnings("unchecked")
private Collection<Object> asCollection(Object source) {
private Collection<Object> asCollection(@Nullable Object source) {
return source instanceof Collection //
? (Collection<Object>) source //

View File

@@ -26,6 +26,7 @@ import java.util.stream.Stream;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.LinkRelation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -53,7 +54,7 @@ public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
* @param relation must not be {@literal null}.
* @return
*/
public static HalLinkRelation of(LinkRelation relation) {
public static HalLinkRelation of(@Nullable LinkRelation relation) {
Assert.notNull(relation, "LinkRelation must not be null!");

View File

@@ -37,6 +37,7 @@ import org.springframework.hateoas.Links;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLinks;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -114,13 +115,13 @@ public class Jackson2HalModule extends SimpleModule {
private final MessageSourceAccessor accessor;
private final HalConfiguration halConfiguration;
public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, MessageSourceAccessor accessor,
HalConfiguration halConfiguration) {
public HalLinkListSerializer(@Nullable CurieProvider curieProvider, EmbeddedMapper mapper,
@Nullable MessageSourceAccessor accessor, HalConfiguration halConfiguration) {
this(null, curieProvider, mapper, accessor, halConfiguration);
}
public HalLinkListSerializer(BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper,
MessageSourceAccessor accessor, HalConfiguration halConfiguration) {
public HalLinkListSerializer(@Nullable BeanProperty property, @Nullable CurieProvider curieProvider,
@Nullable EmbeddedMapper mapper, @Nullable MessageSourceAccessor accessor, HalConfiguration halConfiguration) {
super(TypeFactory.defaultInstance().constructType(Links.class));
@@ -220,6 +221,7 @@ public class Jackson2HalModule extends SimpleModule {
* @param relation must not be {@literal null} or empty.
* @return
*/
@Nullable
private String getTitle(HalLinkRelation relation) {
Assert.notNull(relation, "Local relation must not be null or empty!");
@@ -246,6 +248,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -255,6 +258,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -282,14 +286,15 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
}
/**
* Custom {@link JsonSerializer} to render {@link EntityModel}-Lists in HAL compatible JSON. Renders the
* list as a Map.
* Custom {@link JsonSerializer} to render {@link EntityModel}-Lists in HAL compatible JSON. Renders the list as a
* Map.
*
* @author Alexander Baetz
* @author Oliver Gierke
@@ -306,7 +311,7 @@ public class Jackson2HalModule extends SimpleModule {
this(null, embeddedMapper);
}
public HalResourcesSerializer(BeanProperty property, EmbeddedMapper embeddedMapper) {
public HalResourcesSerializer(@Nullable BeanProperty property, EmbeddedMapper embeddedMapper) {
super(TypeFactory.defaultInstance().constructType(Collection.class));
@@ -344,11 +349,13 @@ public class Jackson2HalModule extends SimpleModule {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -364,6 +371,7 @@ public class Jackson2HalModule extends SimpleModule {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -446,6 +454,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -455,6 +464,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -522,6 +532,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -531,6 +542,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -588,7 +600,7 @@ public class Jackson2HalModule extends SimpleModule {
this(TypeFactory.defaultInstance().constructCollectionLikeType(List.class, vc), vc);
}
private HalResourcesDeserializer(JavaType type, JavaType contentType) {
private HalResourcesDeserializer(JavaType type, @Nullable JavaType contentType) {
super(type);
this.contentType = contentType;
@@ -599,6 +611,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -608,6 +621,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -670,22 +684,22 @@ public class Jackson2HalModule extends SimpleModule {
}
/**
* Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider} and
* {@link MessageSourceAccessor}. Registers a prepared {@link HalResourcesSerializer} and
* Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider}
* and {@link MessageSourceAccessor}. Registers a prepared {@link HalResourcesSerializer} and
* {@link HalLinkListSerializer} falling back to instantiation expecting a default constructor.
*
* @param provider must not be {@literal null}.
* @param curieProvider can be {@literal null}.
* @param messageSourceAccessor can be {@literal null}.
*/
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider,
public HalHandlerInstantiator(LinkRelationProvider provider, @Nullable CurieProvider curieProvider,
MessageSourceAccessor messageSourceAccessor, HalConfiguration halConfiguration) {
this(provider, curieProvider, messageSourceAccessor, true, halConfiguration);
}
/**
* Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider} and
* {@link MessageSourceAccessor} and whether to enforce embedded collections. Registers a prepared
* Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider}
* and {@link MessageSourceAccessor} and whether to enforce embedded collections. Registers a prepared
* {@link HalResourcesSerializer} and {@link HalLinkListSerializer} falling back to instantiation expecting a
* default constructor.
*
@@ -694,13 +708,15 @@ public class Jackson2HalModule extends SimpleModule {
* @param accessor can be {@literal null}.
* @param enforceEmbeddedCollections
*/
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageSourceAccessor accessor,
boolean enforceEmbeddedCollections, HalConfiguration halConfiguration) {
public HalHandlerInstantiator(@Nullable LinkRelationProvider provider, @Nullable CurieProvider curieProvider,
@Nullable MessageSourceAccessor accessor, boolean enforceEmbeddedCollections,
HalConfiguration halConfiguration) {
this(provider, curieProvider, accessor, enforceEmbeddedCollections, null, halConfiguration);
}
private HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageSourceAccessor accessor,
boolean enforceEmbeddedCollections, AutowireCapableBeanFactory delegate, HalConfiguration halConfiguration) {
private HalHandlerInstantiator(@Nullable LinkRelationProvider provider, @Nullable CurieProvider curieProvider,
@Nullable MessageSourceAccessor accessor, boolean enforceEmbeddedCollections,
@Nullable AutowireCapableBeanFactory delegate, HalConfiguration halConfiguration) {
Assert.notNull(provider, "RelProvider must not be null!");
@@ -835,14 +851,15 @@ public class Jackson2HalModule extends SimpleModule {
private boolean preferCollectionRels;
/**
* Creates a new {@link EmbeddedMapper} for the given {@link LinkRelationProvider}, {@link CurieProvider} and flag whether to
* prefer collection relations.
* Creates a new {@link EmbeddedMapper} for the given {@link LinkRelationProvider}, {@link CurieProvider} and flag
* whether to prefer collection relations.
*
* @param relProvider must not be {@literal null}.
* @param curieProvider can be {@literal null}.
* @param preferCollectionRels
*/
public EmbeddedMapper(LinkRelationProvider relProvider, CurieProvider curieProvider, boolean preferCollectionRels) {
public EmbeddedMapper(LinkRelationProvider relProvider, @Nullable CurieProvider curieProvider,
boolean preferCollectionRels) {
Assert.notNull(relProvider, "RelProvider must not be null!");
@@ -888,7 +905,8 @@ public class Jackson2HalModule extends SimpleModule {
private final Link link;
private final String title;
public HalLink(Link link, String title) {
public HalLink(Link link, @Nullable String title) {
this.link = link;
this.title = title;
}
@@ -899,6 +917,7 @@ public class Jackson2HalModule extends SimpleModule {
}
@JsonInclude(Include.NON_NULL)
@Nullable
public String getTitle() {
return title;
}

View File

@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
@@ -91,6 +92,7 @@ class HalFormsDeserializers {
}
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -120,6 +122,7 @@ class HalFormsDeserializers {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -129,6 +132,7 @@ class HalFormsDeserializers {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}

View File

@@ -32,6 +32,7 @@ import org.springframework.hateoas.PagedModel;
import org.springframework.hateoas.mediatype.hal.HalLinkRelation;
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer;
import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule.HalFormsLinksDeserializer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -58,7 +59,7 @@ public class HalFormsDocument<T> {
@JsonUnwrapped //
@JsonInclude(Include.NON_NULL) //
@Wither(AccessLevel.PRIVATE) //
@Wither(value = AccessLevel.PRIVATE, onMethod = @__({ @Nullable })) //
private T resource;
@JsonInclude(Include.NON_EMPTY) @JsonIgnore //
@@ -71,6 +72,7 @@ public class HalFormsDocument<T> {
@JsonProperty("page") //
@JsonInclude(Include.NON_NULL) //
@Wither(onMethod = @__({ @Nullable })) //
private PagedModel.PageMetadata pageMetadata;
@Singular //
@@ -95,7 +97,7 @@ public class HalFormsDocument<T> {
* @param resource can be {@literal null}.
* @return
*/
public static <T> HalFormsDocument<T> forResource(T resource) {
public static <T> HalFormsDocument<T> forResource(@Nullable T resource) {
return new HalFormsDocument<T>().withResource(resource);
}

View File

@@ -32,6 +32,7 @@ import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.hal.HalLinkRelation;
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
@@ -60,7 +61,7 @@ class HalFormsSerializers {
private final BeanProperty property;
HalFormsResourceSerializer(BeanProperty property) {
HalFormsResourceSerializer(@Nullable BeanProperty property) {
super(EntityModel.class, false);
this.property = property;
@@ -82,11 +83,13 @@ class HalFormsSerializers {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -97,6 +100,7 @@ class HalFormsSerializers {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSerializer) {
return null;
}
@@ -119,7 +123,7 @@ class HalFormsSerializers {
private final BeanProperty property;
private final Jackson2HalModule.EmbeddedMapper embeddedMapper;
HalFormsResourcesSerializer(BeanProperty property, Jackson2HalModule.EmbeddedMapper embeddedMapper) {
HalFormsResourcesSerializer(@Nullable BeanProperty property, Jackson2HalModule.EmbeddedMapper embeddedMapper) {
super(CollectionModel.class, false);
@@ -159,11 +163,13 @@ class HalFormsSerializers {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -174,6 +180,7 @@ class HalFormsSerializers {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSerializer) {
return null;
}

View File

@@ -30,6 +30,7 @@ import java.util.List;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsDeserializers.MediaTypesDeserializer;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -115,6 +116,7 @@ public class HalFormsTemplate {
this.contentTypes = mediaTypes;
}
@Nullable
String getMethod() {
return this.httpMethod == null ? null : this.httpMethod.toString().toLowerCase();
}

View File

@@ -40,6 +40,7 @@ import org.springframework.hateoas.mediatype.hal.forms.HalFormsSerializers.HalFo
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.hateoas.server.mvc.JacksonSerializers.MediaTypeDeserializer;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -127,6 +128,7 @@ class Jackson2HalFormsModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return delegate.getContentDeserializer();
}
@@ -148,7 +150,7 @@ class Jackson2HalFormsModule extends SimpleModule {
private final Map<Class<?>, Object> serializers = new HashMap<>();
public HalFormsHandlerInstantiator(LinkRelationProvider resolver, CurieProvider curieProvider,
public HalFormsHandlerInstantiator(LinkRelationProvider resolver, @Nullable CurieProvider curieProvider,
MessageSourceAccessor accessor, boolean enforceEmbeddedCollections,
HalFormsConfiguration halFormsConfiguration) {
@@ -168,6 +170,7 @@ class Jackson2HalFormsModule extends SimpleModule {
beanFactory.getBean(HalFormsConfiguration.class));
}
@Nullable
private Object findInstance(Class<?> type) {
return this.serializers.get(type);
}

View File

@@ -0,0 +1,7 @@
/**
* HAL-FORMS extension media type.
*/
@NonNullApi
package org.springframework.hateoas.mediatype.hal.forms;
import org.springframework.lang.NonNullApi;

View File

@@ -3,5 +3,7 @@
*
* @see http://stateless.co/hal_specification.html
*/
@NonNullApi
package org.springframework.hateoas.mediatype.hal;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* Spring container configuration support.
*/
@NonNullApi
package org.springframework.hateoas.mediatype;
import org.springframework.lang.NonNullApi;

View File

@@ -35,6 +35,7 @@ import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.JacksonHelper;
import org.springframework.hateoas.mediatype.PropertyUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -128,7 +129,7 @@ public class Jackson2UberModule extends SimpleModule {
private static final long serialVersionUID = -572866287910993300L;
private final BeanProperty property;
UberRepresentationModelSerializer(BeanProperty property) {
UberRepresentationModelSerializer(@Nullable BeanProperty property) {
super(RepresentationModel.class, false);
this.property = property;
@@ -153,11 +154,13 @@ public class Jackson2UberModule extends SimpleModule {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -168,6 +171,7 @@ public class Jackson2UberModule extends SimpleModule {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -182,14 +186,13 @@ public class Jackson2UberModule extends SimpleModule {
/**
* Custom {@link JsonSerializer} to render {@link EntityModel} into {@literal UBER+JSON}.
*/
static class UberEntityModelSerializer extends ContainerSerializer<EntityModel<?>>
implements ContextualSerializer {
static class UberEntityModelSerializer extends ContainerSerializer<EntityModel<?>> implements ContextualSerializer {
private static final long serialVersionUID = -5538560800604582741L;
private final BeanProperty property;
UberEntityModelSerializer(BeanProperty property) {
UberEntityModelSerializer(@Nullable BeanProperty property) {
super(EntityModel.class, false);
this.property = property;
@@ -200,8 +203,7 @@ public class Jackson2UberModule extends SimpleModule {
}
@Override
public void serialize(EntityModel<?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
public void serialize(EntityModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
UberDocument doc = new UberDocument().withUber(new Uber() //
.withVersion("1.0") //
@@ -213,11 +215,13 @@ public class Jackson2UberModule extends SimpleModule {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -228,6 +232,7 @@ public class Jackson2UberModule extends SimpleModule {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -249,7 +254,7 @@ public class Jackson2UberModule extends SimpleModule {
private BeanProperty property;
UberCollectionModelSerializer(BeanProperty property) {
UberCollectionModelSerializer(@Nullable BeanProperty property) {
super(CollectionModel.class, false);
this.property = property;
@@ -264,8 +269,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
public void serialize(CollectionModel<?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
public void serialize(CollectionModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
UberDocument doc = new UberDocument() //
.withUber(new Uber() //
@@ -278,11 +282,13 @@ public class Jackson2UberModule extends SimpleModule {
}
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -293,6 +299,7 @@ public class Jackson2UberModule extends SimpleModule {
}
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -307,14 +314,13 @@ public class Jackson2UberModule extends SimpleModule {
/**
* Custom {@link JsonSerializer} to render {@link PagedModel} into {@literal UBER+JSON}.
*/
static class UberPagedModelSerializer extends ContainerSerializer<PagedModel<?>>
implements ContextualSerializer {
static class UberPagedModelSerializer extends ContainerSerializer<PagedModel<?>> implements ContextualSerializer {
private static final long serialVersionUID = -7892297813593085984L;
private BeanProperty property;
UberPagedModelSerializer(BeanProperty property) {
UberPagedModelSerializer(@Nullable BeanProperty property) {
super(PagedModel.class, false);
this.property = property;
@@ -329,8 +335,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
public void serialize(PagedModel<?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
public void serialize(PagedModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
UberDocument doc = new UberDocument() //
.withUber(new Uber() //
@@ -347,6 +352,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
@@ -356,6 +362,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
@@ -374,6 +381,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -383,8 +391,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
return new UberPagedModelSerializer(property);
}
}
@@ -475,6 +482,7 @@ public class Jackson2UberModule extends SimpleModule {
* Accessor for deserializer use for deserializing content values.
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -567,6 +575,7 @@ public class Jackson2UberModule extends SimpleModule {
* Accesor for deserializer use for deserializing content values.
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -630,6 +639,7 @@ public class Jackson2UberModule extends SimpleModule {
* Accesor for deserializer use for deserializing content values.
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -696,6 +706,7 @@ public class Jackson2UberModule extends SimpleModule {
* Accesor for deserializer use for deserializing content values.
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
@@ -709,8 +720,7 @@ public class Jackson2UberModule extends SimpleModule {
* @param contentType
* @return
*/
private static CollectionModel<?> extractResources(UberDocument doc, JavaType rootType,
JavaType contentType) {
private static CollectionModel<?> extractResources(UberDocument doc, JavaType rootType, JavaType contentType) {
List<Object> content = new ArrayList<>();
@@ -785,15 +795,17 @@ public class Jackson2UberModule extends SimpleModule {
}
}
private static boolean isPrimitiveType(List<UberData> data) {
private static boolean isPrimitiveType(@Nullable List<UberData> data) {
return data != null && data.size() == 1 && data.get(0).getName() == null;
}
@Nullable
private static PageMetadata extractPagingMetadata(UberDocument doc) {
return doc.getUber().getData().stream()
.filter(uberData -> uberData.getName() != null && uberData.getName().equals("page")).findFirst()
.map(Jackson2UberModule::convertUberDataToPageMetaData).orElse(null);
return doc.getUber().getData().stream() //
.filter(uberData -> uberData.getName() != null && uberData.getName().equals("page")) //
.findFirst().map(Jackson2UberModule::convertUberDataToPageMetaData) //
.orElse(null);
}
@NotNull

View File

@@ -22,6 +22,7 @@ import lombok.experimental.Wither;
import java.util.List;
import org.springframework.hateoas.Links;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -45,8 +46,8 @@ class Uber {
private UberError error;
@JsonCreator
Uber(@JsonProperty("version") String version, @JsonProperty("data") List<UberData> data,
@JsonProperty("error") UberError error) {
Uber(@JsonProperty("version") String version, @JsonProperty("data") @Nullable List<UberData> data,
@JsonProperty("error") @Nullable UberError error) {
this.version = version;
this.data = data;
@@ -65,6 +66,10 @@ class Uber {
@JsonIgnore
Links getLinks() {
if (data == null) {
return Links.NONE;
}
return data.stream() //
.flatMap(uberData -> uberData.getLinks().stream()) //
.collect(Links.collector());

View File

@@ -20,6 +20,7 @@ import java.util.Arrays;
import org.springframework.hateoas.mediatype.uber.Jackson2UberModule.UberActionDeserializer;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -100,6 +101,7 @@ enum UberAction {
* @param method to map
* @return action, or null for GET
*/
@Nullable
static UberAction forRequestMethod(HttpMethod method) {
return HttpMethod.GET == method ? null : fromMethod(method);
}

View File

@@ -32,6 +32,7 @@ import org.springframework.hateoas.QueryParameter;
import org.springframework.hateoas.mediatype.PropertyUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
/**
* {@link AffordanceModel} for {@literal UBER+JSON}.
@@ -89,6 +90,7 @@ class UberAffordanceModel extends AffordanceModel {
}
}
@Nullable
UberAction getAction() {
return UberAction.forRequestMethod(getHttpMethod());
}

View File

@@ -41,6 +41,7 @@ import org.springframework.hateoas.PagedModel;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.PropertyUtils;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -76,12 +77,13 @@ class UberData {
private List<UberData> data;
@JsonCreator
UberData(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("label") String label,
@JsonProperty("rel") List<LinkRelation> rel, @JsonProperty("url") String url,
@JsonProperty("action") UberAction action, @JsonProperty("transclude") boolean transclude,
@JsonProperty("model") String model, @JsonProperty("sending") List<String> sending,
@JsonProperty("accepting") List<String> accepting, @JsonProperty("value") Object value,
@JsonProperty("data") List<UberData> data) {
UberData(@JsonProperty("id") @Nullable String id, @JsonProperty("name") @Nullable String name,
@JsonProperty("label") @Nullable String label, @JsonProperty("rel") @Nullable List<LinkRelation> rel,
@JsonProperty("url") @Nullable String url, @JsonProperty("action") UberAction action,
@JsonProperty("transclude") boolean transclude, @JsonProperty("model") @Nullable String model,
@JsonProperty("sending") @Nullable List<String> sending,
@JsonProperty("accepting") @Nullable List<String> accepting, @JsonProperty("value") @Nullable Object value,
@JsonProperty("data") @Nullable List<UberData> data) {
this.id = id;
this.name = name;
@@ -104,6 +106,7 @@ class UberData {
/**
* Don't render if it's {@link UberAction#READ}.
*/
@Nullable
public UberAction getAction() {
return action == UberAction.READ ? null : action;
}
@@ -111,6 +114,7 @@ class UberData {
/*
* Use a {@link Boolean} to support returning {@literal null}, and if it is {@literal null}, don't render.
*/
@Nullable
public Boolean isTemplated() {
return Optional.ofNullable(this.url) //
@@ -121,8 +125,9 @@ class UberData {
/*
* Use a {@link Boolean} to support returning {@literal null}, and if it is {@literal null}, don't render.
*/
@Nullable
public Boolean isTransclude() {
return this.transclude ? this.transclude : null;
return this.transclude ? true : null;
}
/**
@@ -131,6 +136,10 @@ class UberData {
@JsonIgnore
public List<Link> getLinks() {
if (this.url == null) {
return Links.NONE.toList();
}
return Optional.ofNullable(this.rel) //
.map(rels -> rels.stream() //
.map(rel -> new Link(this.url, rel)) //
@@ -146,8 +155,8 @@ class UberData {
/**
* Set of all Spring HATEOAS resource types.
*/
private static final HashSet<Class<?>> RESOURCE_TYPES = new HashSet<>(Arrays.asList(RepresentationModel.class,
EntityModel.class, CollectionModel.class, PagedModel.class));
private static final HashSet<Class<?>> RESOURCE_TYPES = new HashSet<>(
Arrays.asList(RepresentationModel.class, EntityModel.class, CollectionModel.class, PagedModel.class));
/**
* Convert a {@link RepresentationModel} into a list of {@link UberData}s, containing links and content.
@@ -180,8 +189,7 @@ class UberData {
}
/**
* Convert {@link CollectionModel} into a list of {@link UberData}, with each item nested in a
* sub-UberData.
* Convert {@link CollectionModel} into a list of {@link UberData}, with each item nested in a sub-UberData.
*
* @param resources
* @return
@@ -255,9 +263,9 @@ class UberData {
* @param content
* @return
*/
private static Optional<UberData> extractContent(Object content) {
private static Optional<UberData> extractContent(@Nullable Object content) {
return Optional.of(content) //
return Optional.ofNullable(content) //
.filter(it -> !RESOURCE_TYPES.contains(content.getClass())) //
.map(it -> new UberData() //
.withName(StringUtils.uncapitalize(it.getClass().getSimpleName())) //

View File

@@ -22,6 +22,8 @@ import lombok.experimental.Wither;
import java.util.List;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -39,12 +41,12 @@ class UberDocument {
private Uber uber;
@JsonCreator
UberDocument(@JsonProperty("version") String version, @JsonProperty("data") List<UberData> data,
@JsonProperty("error") UberError error) {
UberDocument(@JsonProperty("version") String version, @JsonProperty("data") @Nullable List<UberData> data,
@JsonProperty("error") @Nullable UberError error) {
this.uber = new Uber(version, data, error);
}
UberDocument() {
this("1.0", null, null);
}
}
}

View File

@@ -21,6 +21,8 @@ import lombok.experimental.Wither;
import java.util.List;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -37,7 +39,7 @@ class UberError {
private List<UberData> data;
@JsonCreator
UberError(@JsonProperty("data") List<UberData> data) {
UberError(@JsonProperty("data") @Nullable List<UberData> data) {
this.data = data;
}

View File

@@ -0,0 +1,7 @@
/**
* UBER media type objects.
*/
@NonNullApi
package org.springframework.hateoas.mediatype.uber;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* Vnd.Error media type.
*/
@NonNullApi
package org.springframework.hateoas.mediatype.vnderrors;
import org.springframework.lang.NonNullApi;

View File

@@ -21,4 +21,7 @@
* @author Jens Schauder
* @author Greg Turnquist
*/
@NonNullApi
package org.springframework.hateoas;
import org.springframework.lang.NonNullApi;

View File

@@ -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!");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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!");

View File

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

View File

@@ -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);
/**

View File

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

View File

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

View File

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

View File

@@ -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!");

View File

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

View File

@@ -1,5 +1,7 @@
/**
* Implementations of core API interfaces.
*/
@NonNullApi
package org.springframework.hateoas.server.core;
import org.springframework.lang.NonNullApi;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
/**
* Server-side components for hypermedia handling.
*/
@NonNullApi
package org.springframework.hateoas.server;
import org.springframework.lang.NonNullApi;

View File

@@ -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("/") //

View File

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

View File

@@ -0,0 +1,7 @@
/**
* Support utilities for hypermedia handling.
*/
@NonNullApi
package org.springframework.hateoas.support;
import org.springframework.lang.NonNullApi;