#831 - Polishing.

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

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

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

View File

@@ -27,6 +27,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -74,6 +75,7 @@ public class Affordance {
* @param mediaType
* @return
*/
@Nullable
@SuppressWarnings("unchecked")
public <T extends AffordanceModel> T getAffordanceModel(MediaType mediaType) {
return (T) this.affordanceModels.get(mediaType);

View File

@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -124,7 +125,7 @@ public class CollectionModel<T> extends RepresentationModel<CollectionModel<T>>
* @see org.springframework.hateoas.ResourceSupport#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;

View File

@@ -89,7 +89,7 @@ public class EntityModel<T> extends RepresentationModel<EntityModel<T>> {
* @see org.springframework.hateoas.ResourceSupport#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;

View File

@@ -33,7 +33,6 @@ 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;
@@ -459,18 +458,17 @@ public class Link implements Serializable {
/**
* Factory method to easily create {@link Link} instances from RFC-5988 compatible {@link String} representations of a
* link. Will return {@literal null} if input {@link String} is either empty or {@literal null}.
* link.
*
* @param element an RFC-5899 compatible representation of a link.
* @throws IllegalArgumentException if a non-empty {@link String} was given that does not adhere to RFC-5899.
* @throws IllegalArgumentException if a {@link String} was given that does not adhere to RFC-5899.
* @throws IllegalArgumentException if no {@code rel} attribute could be found.
* @return
*/
@Nullable
public static Link valueOf(String element) {
if (!StringUtils.hasText(element)) {
return null;
throw new IllegalArgumentException(String.format("Given link header %s is not RFC5988 compliant!", element));
}
Matcher matcher = URI_AND_ATTRIBUTES_PATTERN.matcher(element);

View File

@@ -29,6 +29,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -95,7 +96,7 @@ public class Links implements Iterable<Link> {
* @param source a comma separated list of {@link Link} representations.
* @return the {@link Links} represented by the given {@link String}.
*/
public static Links parse(String source) {
public static Links parse(@Nullable String source) {
if (!StringUtils.hasText(source)) {
return NONE;
@@ -416,13 +417,13 @@ public class Links implements Iterable<Link> {
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object arg0) {
public boolean equals(@Nullable Object obj) {
if (!(arg0 instanceof Links)) {
if (!(obj instanceof Links)) {
return false;
}
Links that = (Links) arg0;
Links that = (Links) obj;
return this.links.equals(that.links);
}

View File

@@ -135,7 +135,7 @@ public class PagedModel<T> extends CollectionModel<T> {
* @see org.springframework.hateoas.Resources#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
@@ -262,7 +262,7 @@ public class PagedModel<T> extends CollectionModel<T> {
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;

View File

@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -238,7 +239,7 @@ public class RepresentationModel<T extends RepresentationModel<? extends T>> {
*/
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;

View File

@@ -25,6 +25,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -91,7 +92,7 @@ class StringLinkRelation implements LinkRelation, Serializable {
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.hateoas.client;
import net.minidev.json.JSONArray;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@@ -25,11 +27,11 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minidev.json.JSONArray;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Links;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import com.jayway.jsonpath.InvalidPathException;
@@ -124,7 +126,7 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer {
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(MediaType delimiter) {
public boolean supports(@NonNull MediaType delimiter) {
return this.mediaTypes.stream() //
.anyMatch(mediaType -> mediaType.isCompatibleWith(delimiter));

View File

@@ -57,11 +57,11 @@ class Rels {
/**
* Returns the link contained in the given representation of the given {@link MediaType}.
*
* @param representation
* @param mediaType
* @param representation will never be {@literal null}.
* @param mediaType will never be {@literal null}.
* @return
*/
Optional<Link> findInResponse(@Nullable String representation, @Nullable MediaType mediaType);
Optional<Link> findInResponse(String representation, MediaType mediaType);
}
/**

View File

@@ -207,6 +207,9 @@ public class Traverson {
*/
public class TraversalBuilder {
private static final String MEDIA_TYPE_HEADER_NOT_FOUND = "Response for request to %s did not expose a content type! Unable to identify links!";
private static final String LINK_NOT_FOUND = "Expected to find link with rel '%s' in response %s!";
private final List<Hop> rels = new ArrayList<>();
private Map<String, Object> templateParameters = new HashMap<>();
private HttpHeaders headers = new HttpHeaders();
@@ -224,9 +227,9 @@ public class Traverson {
Assert.notNull(rels, "Rels must not be null!");
for (String rel : rels) {
this.rels.add(Hop.rel(rel));
}
Arrays.stream(rels) //
.map(Hop::rel) //
.forEach(this.rels::add);
return this;
}
@@ -327,6 +330,7 @@ public class Traverson {
HttpEntity<?> requestEntity = prepareRequest(mergeHeaders(this.headers, uriAndHeaders.getHttpHeaders()));
String forObject = operations.exchange(uriAndHeaders.getUri(), GET, requestEntity, String.class).getBody();
return JsonPath.read(forObject, jsonPath);
}
@@ -359,7 +363,7 @@ public class Traverson {
}
/**
* Returns the templated {@link Link} found for the last rel in the rels configured to follow.
* Returns the templated {@link Link} found for the last relation in the rels configured to follow.
*
* @return
* @since 0.17
@@ -370,28 +374,28 @@ public class Traverson {
private Link traverseToLink(boolean expandFinalUrl) {
Assert.isTrue(this.rels.size() > 0, "At least one rel needs to be provided!");
Assert.isTrue(rels.size() > 0, "At least one rel needs to be provided!");
URIAndHeaders expandedFinalUriAndHeaders = traverseToExpandedFinalUrl();
UriStringAndHeaders finalUriAndHeaders = traverseToFinalUrl();
return new Link(expandFinalUrl ? expandedFinalUriAndHeaders.getUri().toString() : finalUriAndHeaders.getUri(),
this.rels.get(this.rels.size() - 1).getRel());
rels.get(rels.size() - 1).getRel());
}
private UriStringAndHeaders traverseToFinalUrl() {
UriStringAndHeaders uriAndHeaders = getAndFindLinkWithRel(baseUri.toString(), this.rels.iterator(),
HttpHeaders.EMPTY);
UriStringAndHeaders uriAndHeaders = getAndFindLinkWithRel(baseUri.toString(), rels.iterator(), HttpHeaders.EMPTY);
return new UriStringAndHeaders(new UriTemplate(uriAndHeaders.getUri()).toString(),
uriAndHeaders.getHttpHeaders());
}
private URIAndHeaders traverseToExpandedFinalUrl() {
UriStringAndHeaders uriAndHeaders = getAndFindLinkWithRel(baseUri.toString(), this.rels.iterator(),
HttpHeaders.EMPTY);
return new URIAndHeaders(new UriTemplate(uriAndHeaders.getUri()).expand(this.templateParameters),
UriStringAndHeaders uriAndHeaders = getAndFindLinkWithRel(baseUri.toString(), rels.iterator(), HttpHeaders.EMPTY);
return new URIAndHeaders(new UriTemplate(uriAndHeaders.getUri()).expand(templateParameters),
uriAndHeaders.getHttpHeaders());
}
@@ -402,28 +406,28 @@ public class Traverson {
}
HttpEntity<?> request = prepareRequest(mergeHeaders(this.headers, extraHeaders));
UriTemplate template = new UriTemplate(uri);
URI target = new UriTemplate(uri).expand();
ResponseEntity<String> responseEntity = operations.exchange(template.expand(), GET, request, String.class);
ResponseEntity<String> responseEntity = operations.exchange(target, GET, request, String.class);
MediaType contentType = responseEntity.getHeaders().getContentType();
if (contentType == null) {
throw new IllegalStateException(String.format(MEDIA_TYPE_HEADER_NOT_FOUND, target));
}
String responseBody = responseEntity.getBody();
Hop thisHop = rels.next();
Rel rel = Rels.getRelFor(thisHop.getRel(), discoverers);
Link link = rel.findInResponse(responseBody, contentType) //
.orElseThrow(() -> new IllegalStateException(
String.format("Expected to find link with rel '%s' in response %s!", rel, responseBody)));
Link link = rel.findInResponse(responseBody == null ? "" : responseBody, contentType) //
.orElseThrow(() -> new IllegalStateException(String.format(LINK_NOT_FOUND, rel, responseBody)));
/*
* Don't expand if the parameters are empty
*/
if (!thisHop.hasParameters()) {
return getAndFindLinkWithRel(link.getHref(), rels, thisHop.getHeaders());
} else {
return getAndFindLinkWithRel(link.expand(thisHop.getMergedParameters(this.templateParameters)).getHref(), rels,
thisHop.getHeaders());
}
String linkTarget = thisHop.hasParameters() //
? link.expand(thisHop.getMergedParameters(templateParameters)).getHref() //
: link.getHref();
return getAndFindLinkWithRel(linkTarget, rels, thisHop.getHeaders());
}
/**

View File

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

View File

@@ -24,6 +24,7 @@ import org.springframework.context.support.ReloadableResourceBundleMessageSource
import org.springframework.hateoas.client.LinkDiscoverer;
import org.springframework.hateoas.client.LinkDiscoverers;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.hateoas.server.LinkRelationProvider.LookupContext;
import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider;
import org.springframework.hateoas.server.core.DefaultLinkRelationProvider;
import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider;
@@ -83,14 +84,15 @@ class HateoasConfiguration {
@Primary
@Bean
DelegatingLinkRelationProvider _relProvider(PluginRegistry<LinkRelationProvider, Class<?>> relProviderPluginRegistry) {
DelegatingLinkRelationProvider _relProvider(
PluginRegistry<LinkRelationProvider, LookupContext> relProviderPluginRegistry) {
return new DelegatingLinkRelationProvider(relProviderPluginRegistry);
}
@Bean
PluginRegistryFactoryBean<LinkRelationProvider, Class<?>> relProviderPluginRegistry() {
PluginRegistryFactoryBean<LinkRelationProvider, LookupContext> relProviderPluginRegistry() {
PluginRegistryFactoryBean<LinkRelationProvider, Class<?>> factory = new PluginRegistryFactoryBean<>();
PluginRegistryFactoryBean<LinkRelationProvider, LookupContext> factory = new PluginRegistryFactoryBean<>();
factory.setType(LinkRelationProvider.class);
factory.setExclusions(new Class[] { DelegatingLinkRelationProvider.class });

View File

@@ -32,6 +32,7 @@ import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.lang.NonNull;
import org.springframework.util.MimeType;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
@@ -92,6 +93,7 @@ class WebFluxHateoasConfiguration {
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
@NonNull
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
@@ -143,5 +145,4 @@ class WebFluxHateoasConfiguration {
configurer.registerDefaults(false);
}
}
}

View File

@@ -31,6 +31,7 @@ import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2Http
import org.springframework.hateoas.server.mvc.UriComponentsContributor;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -107,6 +108,7 @@ class WebMvcHateoasConfiguration {
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
@NonNull
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
*/
package org.springframework.hateoas.mediatype;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.beans.FeatureDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
@@ -31,8 +34,6 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
@@ -46,6 +47,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
public class PropertyUtils {
@@ -67,43 +69,45 @@ public class PropertyUtils {
}
return getPropertyDescriptors(object.getClass()) //
.collect(HashMap::new, //
(hashMap, descriptor) -> {
try {
Method readMethod = descriptor.getReadMethod();
ReflectionUtils.makeAccessible(readMethod);
hashMap.put(descriptor.getName(), readMethod.invoke(object));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}, //
HashMap::putAll);
.collect(HashMap::new, (hashMap, descriptor) -> {
try {
Method readMethod = descriptor.getReadMethod();
ReflectionUtils.makeAccessible(readMethod);
hashMap.put(descriptor.getName(), readMethod.invoke(object));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}, HashMap::putAll);
}
public static List<String> findPropertyNames(ResolvableType resolvableType) {
Class<?> type = resolvableType.getRawClass();
if (WebStack.WEBFLUX.isAvailable()) {
if (Mono.class.equals(resolvableType.getRawClass()) || Flux.class.equals(resolvableType.getRawClass())) {
if (Mono.class.equals(type) || Flux.class.equals(type)) {
ResolvableType generic = resolvableType.getGeneric(0);
return findPropertyNames(generic);
}
}
if (resolvableType.getRawClass() == null) {
if (type == null) {
return Collections.emptyList();
}
if (resolvableType.getRawClass().equals(EntityModel.class)) {
Class<?> genericEntityModelParameter = resolvableType.resolveGeneric(0);
if (genericEntityModelParameter == null) {
return Collections.emptyList();
}
return findPropertyNames(genericEntityModelParameter);
} else {
return findPropertyNames(resolvableType.getRawClass());
if (!type.equals(EntityModel.class)) {
return findPropertyNames(type);
}
Class<?> genericEntityModelParameter = resolvableType.resolveGeneric(0);
return genericEntityModelParameter == null //
? Collections.emptyList() //
: findPropertyNames(genericEntityModelParameter);
}
public static List<String> findPropertyNames(Class<?> clazz) {
@@ -118,16 +122,19 @@ public class PropertyUtils {
T obj = BeanUtils.instantiateClass(clazz);
properties.forEach((key, value) -> {
Optional<PropertyDescriptor> possibleProperty = Optional.ofNullable(BeanUtils.getPropertyDescriptor(clazz, key));
possibleProperty.ifPresent(property -> {
try {
Method writeMethod = property.getWriteMethod();
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(obj, value);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
Optional.ofNullable(BeanUtils.getPropertyDescriptor(clazz, key)) //
.ifPresent(property -> {
try {
Method writeMethod = property.getWriteMethod();
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(obj, value);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
});
return obj;
@@ -135,7 +142,7 @@ public class PropertyUtils {
/**
* Take a {@link Class} and find all properties that are NOT to be ignored, and return them as a {@link Stream}.
*
*
* @param clazz
* @return
*/
@@ -159,16 +166,14 @@ public class PropertyUtils {
Field descriptorField = ReflectionUtils.findField(clazz, descriptor.getName());
if (descriptorField == null) {
return false;
}
return toBeIgnoredByJackson(AnnotationUtils.getAnnotations(descriptorField));
return descriptorField == null //
? false //
: toBeIgnoredByJackson(AnnotationUtils.getAnnotations(descriptorField));
}
/**
* Check if a given {@link PropertyDescriptor} has {@link JsonIgnore} on the getter.
*
*
* @param descriptor
* @return
*/
@@ -178,26 +183,24 @@ public class PropertyUtils {
/**
* Scan a list of {@link Annotation}s for {@link JsonIgnore} annotations.
*
*
* @param annotations
* @return
*/
private static boolean toBeIgnoredByJackson(@Nullable Annotation[] annotations) {
if (annotations == null) {
return false;
}
return Arrays.stream(annotations) //
.filter(annotation -> annotation.annotationType().equals(JsonIgnore.class)) //
.findFirst() //
.map(annotation -> (Boolean) AnnotationUtils.getAnnotationAttributes(annotation).get("value")) //
.orElse(false);
return annotations == null //
? false
: Arrays.stream(annotations) //
.filter(annotation -> annotation.annotationType().equals(JsonIgnore.class)) //
.findFirst() //
.map(annotation -> (Boolean) AnnotationUtils.getAnnotationAttributes(annotation).get("value")) //
.orElse(false);
}
/**
* Check if a field name is to be ignored due to {@link JsonIgnoreProperties}.
*
*
* @param clazz
* @param field
* @return
@@ -206,15 +209,12 @@ public class PropertyUtils {
Annotation[] annotations = AnnotationUtils.getAnnotations(clazz);
if (annotations == null) {
return false;
}
return Arrays.stream(annotations) //
.filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)) //
.map(annotation -> (String[]) AnnotationUtils.getAnnotationAttributes(annotation).get("value")) //
.flatMap(Arrays::stream) //
.anyMatch(propertyName -> propertyName.equalsIgnoreCase(field));
return annotations == null //
? false //
: Arrays.stream(annotations) //
.filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)) //
.map(annotation -> (String[]) AnnotationUtils.getAnnotationAttributes(annotation).get("value")) //
.flatMap(Arrays::stream) //
.anyMatch(propertyName -> propertyName.equalsIgnoreCase(field));
}
}

View File

@@ -1,9 +1,7 @@
/**
* Value objects to build ALPS metadata.
*
*
* @see https://alps.io
*/
@NonNullApi
@org.springframework.lang.NonNullApi
package org.springframework.hateoas.mediatype.alps;
import org.springframework.lang.NonNullApi;

View File

@@ -43,28 +43,28 @@ import com.fasterxml.jackson.annotation.JsonProperty;
class CollectionJson<T> {
private String version;
private String href;
private @Nullable String href;
private @JsonInclude(Include.NON_EMPTY) Links links;
private @JsonInclude(Include.NON_EMPTY) List<CollectionJsonItem<T>> items;
private @JsonInclude(Include.NON_EMPTY) List<CollectionJsonQuery> queries;
private @JsonInclude(Include.NON_NULL) CollectionJsonTemplate template;
private @JsonInclude(Include.NON_NULL) CollectionJsonError error;
private @JsonInclude(Include.NON_NULL) @Nullable CollectionJsonTemplate template;
private @JsonInclude(Include.NON_NULL) @Nullable CollectionJsonError error;
@JsonCreator
CollectionJson(@JsonProperty("version") String version, //
@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) {
@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;
this.links = links == null ? Links.NONE : links;
this.items = items == null ? Collections.emptyList() : items;
this.queries = queries;
this.queries = queries == null ? Collections.emptyList() : queries;
this.template = template;
this.error = error;
}
@@ -92,6 +92,8 @@ class CollectionJson<T> {
CollectionJson<T> withOwnSelfLink() {
String href = this.href;
if (href == null) {
return this;
}

View File

@@ -35,16 +35,17 @@ import com.fasterxml.jackson.annotation.JsonProperty;
class CollectionJsonData {
@JsonInclude(Include.NON_NULL) //
private String name;
private @Nullable String name;
@JsonInclude(Include.NON_NULL) //
private Object value;
private @Nullable Object value;
@JsonInclude(Include.NON_NULL) //
private String prompt;
private @Nullable String prompt;
@JsonCreator
CollectionJsonData(@JsonProperty("name") @Nullable String name, @JsonProperty("value") @Nullable Object value,
CollectionJsonData(@JsonProperty("name") @Nullable String name, //
@JsonProperty("value") @Nullable Object value, //
@JsonProperty("prompt") @Nullable String prompt) {
this.name = name;

View File

@@ -49,10 +49,10 @@ import com.fasterxml.jackson.databind.JavaType;
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class CollectionJsonItem<T> {
private String href;
private @Nullable String href;
private List<CollectionJsonData> data;
private @JsonInclude(Include.NON_EMPTY) Links links;
private @Getter(onMethod = @__({ @JsonIgnore }), value = AccessLevel.PRIVATE) T rawData;
private @Nullable @Getter(onMethod = @__({ @JsonIgnore }), value = AccessLevel.PRIVATE) T rawData;
@JsonCreator
CollectionJsonItem(@JsonProperty("href") @Nullable String href, //
@@ -60,7 +60,7 @@ class CollectionJsonItem<T> {
@JsonProperty("links") @Nullable Links links) {
this.href = href;
this.data = data;
this.data = data == null ? Collections.emptyList() : data;
this.links = links == null ? Links.NONE : links;
this.rawData = null;
}
@@ -81,7 +81,7 @@ class CollectionJsonItem<T> {
*/
public List<CollectionJsonData> getData() {
if (this.data != null) {
if (!this.data.isEmpty()) {
return this.data;
}
@@ -89,8 +89,10 @@ class CollectionJsonItem<T> {
return Collections.singletonList(new CollectionJsonData().withValue(this.rawData));
}
return PropertyUtils.findProperties(this.rawData).entrySet().stream()
.map(entry -> new CollectionJsonData().withName(entry.getKey()).withValue(entry.getValue()))
return PropertyUtils.findProperties(this.rawData).entrySet().stream() //
.map(entry -> new CollectionJsonData() //
.withName(entry.getKey()) //
.withValue(entry.getValue())) //
.collect(Collectors.toList());
}
@@ -103,7 +105,7 @@ class CollectionJsonItem<T> {
@Nullable
public Object toRawData(JavaType javaType) {
if (this.data == null) {
if (this.data.isEmpty()) {
return null;
}
@@ -125,6 +127,8 @@ class CollectionJsonItem<T> {
public CollectionJsonItem<T> withOwnSelfLink() {
String href = this.href;
if (href == null) {
return this;
}

View File

@@ -19,10 +19,11 @@ import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.client.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.HypermediaMappingInformation;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import com.fasterxml.jackson.databind.Module;
@@ -53,6 +54,7 @@ class CollectionJsonMediaTypeConfiguration implements HypermediaMappingInformati
* (non-Javadoc)
* @see org.springframework.hateoas.config.HypermediaMappingInformation#getJacksonModule()
*/
@NonNull
@Override
public Module getJacksonModule() {
return new Jackson2CollectionJsonModule();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
stand bisher hauptsächlich darin, dass * Copyright 2015-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,9 @@
package org.springframework.hateoas.mediatype.collectionjson;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -105,8 +103,8 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(Links links, JsonGenerator jgen, SerializerProvider provider) throws IOException {
JavaType type = provider.getTypeFactory().constructCollectionType(List.class, Link.class);
@@ -120,6 +118,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean isEmpty(SerializerProvider provider, Links value) {
return value.isEmpty();
}
@@ -148,6 +147,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(Links value) {
return false;
}
@@ -158,6 +158,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -180,7 +181,12 @@ class Jackson2CollectionJsonModule extends SimpleModule {
this.property = property;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(RepresentationModel<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
@@ -207,31 +213,54 @@ class Jackson2CollectionJsonModule extends SimpleModule {
provider.findValueSerializer(CollectionJsonDocument.class, property).serialize(doc, jgen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new CollectionJsonResourceSupportSerializer(property);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(RepresentationModel<?> value) {
return true;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -254,7 +283,12 @@ class Jackson2CollectionJsonModule extends SimpleModule {
this.property = property;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(EntityModel<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
String href = value.getRequiredLink(IanaLinkRelations.SELF).getHref();
@@ -277,31 +311,54 @@ class Jackson2CollectionJsonModule extends SimpleModule {
.serialize(doc, jgen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new CollectionJsonResourceSerializer(property);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(EntityModel<?> value) {
return true;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -320,6 +377,7 @@ class Jackson2CollectionJsonModule 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
@SuppressWarnings("null")
public void serialize(CollectionModel<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
@@ -362,6 +420,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean isEmpty(SerializerProvider provider, CollectionModel<?> value) {
return value.getContent().isEmpty();
}
@@ -371,6 +430,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(CollectionModel<?> value) {
return value.getContent().size() == 1;
}
@@ -381,6 +441,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -403,7 +464,12 @@ class Jackson2CollectionJsonModule extends SimpleModule {
this.property = property;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(PagedModel<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
CollectionJson<?> collectionJson = new CollectionJson<>() //
@@ -419,36 +485,64 @@ class Jackson2CollectionJsonModule extends SimpleModule {
provider.findValueSerializer(CollectionJsonDocument.class, property).serialize(doc, jgen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new CollectionJsonPagedResourcesSerializer(property);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean isEmpty(PagedModel<?> value) {
return value.getContent().size() == 0;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(PagedModel<?> value) {
return value.getContent().size() == 1;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -487,6 +581,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public Links deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
JavaType type = ctx.getTypeFactory().constructCollectionLikeType(List.class, Link.class);
@@ -537,6 +632,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
*/
@Override
@Nullable
@SuppressWarnings("null")
public RepresentationModel<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
TypeFactory typeFactory = ctxt.getTypeFactory();
@@ -561,7 +657,6 @@ class Jackson2CollectionJsonModule extends SimpleModule {
(left, right) -> right);
CollectionJsonItem<?> firstItem = items.get(0).withOwnSelfLink();
RepresentationModel<?> resource = (RepresentationModel<?>) firstItem.toRawData(this.contentType);
if (resource != null) {
@@ -571,9 +666,11 @@ class Jackson2CollectionJsonModule extends SimpleModule {
return resource;
}
if (withOwnSelfLink.getTemplate() != null) {
CollectionJsonTemplate template = withOwnSelfLink.getTemplate();
Map<String, Object> properties = withOwnSelfLink.getTemplate().getData().stream()
if (template != null) {
Map<String, Object> properties = template.getData().stream()
.collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue));
RepresentationModel<?> resourceSupport = (RepresentationModel<?>) PropertyUtils
@@ -582,20 +679,24 @@ class Jackson2CollectionJsonModule extends SimpleModule {
return resourceSupport.add(withOwnSelfLink.getLinks());
} else {
return new RepresentationModel<>().add(withOwnSelfLink.getLinks());
}
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext context, @Nullable BeanProperty property)
throws JsonMappingException {
if (property != null) {
return new CollectionJsonResourceSupportDeserializer(property.getType().getContentType());
} else {
return new CollectionJsonResourceSupportDeserializer(ctxt.getContextualType());
}
JavaType type = property == null //
? context.getContextualType() //
: property.getType().getContentType();
return new CollectionJsonResourceSupportDeserializer(type);
}
}
@@ -616,18 +717,31 @@ class Jackson2CollectionJsonModule extends SimpleModule {
this.contentType = contentType;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentType()
*/
@Override
public JavaType getContentType() {
return this.contentType;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public EntityModel<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JavaType rootType = JacksonHelper.findRootType(this.contentType);
@@ -635,18 +749,20 @@ class Jackson2CollectionJsonModule extends SimpleModule {
CollectionJsonDocument<?> document = jp.getCodec().readValue(jp, wrappedType);
List<? extends CollectionJsonItem<?>> items = Optional.ofNullable(document.getCollection().getItems())
.orElse(new ArrayList<>());
Links links = document.getCollection().withOwnSelfLink().getLinks();
CollectionJson<?> collection = document.getCollection();
List<? extends CollectionJsonItem<?>> items = collection.getItems();
Links links = collection.withOwnSelfLink().getLinks();
CollectionJsonTemplate template = collection.getTemplate();
if (items.size() == 0 && document.getCollection().getTemplate() != null) {
if (items.isEmpty() && template != null) {
Map<String, Object> properties = document.getCollection().getTemplate().getData().stream()
Map<String, Object> properties = template.getData().stream()
.collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue));
Object obj = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties);
return new EntityModel<>(obj, links);
} else {
Links merged = items.stream() //
@@ -667,6 +783,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
@@ -723,7 +840,8 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, @Nullable BeanProperty property)
throws JsonMappingException {
JavaType contextualType = property == null //
@@ -738,6 +856,7 @@ class Jackson2CollectionJsonModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public T deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JavaType rootType = JacksonHelper.findRootType(contentType);

View File

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

View File

@@ -20,7 +20,6 @@ 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.
@@ -32,6 +31,24 @@ import org.springframework.lang.Nullable;
*/
public interface CurieProvider {
public static CurieProvider NONE = new CurieProvider() {
@Override
public HalLinkRelation getNamespacedRelFrom(Link link) {
throw new UnsupportedOperationException();
}
@Override
public HalLinkRelation getNamespacedRelFor(LinkRelation rel) {
throw new UnsupportedOperationException();
}
@Override
public Collection<? extends Object> getCurieInformation(Links links) {
throw new UnsupportedOperationException();
}
};
/**
* Returns the rel to be rendered for the given {@link Link}. Will potentially prefix the rel but also might decide
* not to, depending on the actual rel.
@@ -49,7 +66,6 @@ public interface CurieProvider {
* @return
* @since 0.17
*/
@Nullable
HalLinkRelation getNamespacedRelFor(LinkRelation rel);
/**

View File

@@ -110,7 +110,6 @@ 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());
}
@@ -120,7 +119,6 @@ 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

@@ -39,7 +39,6 @@ import org.springframework.util.Assert;
*/
class HalEmbeddedBuilder {
private static final HalLinkRelation DEFAULT_REL = HalLinkRelation.uncuried("content");
private static final String INVALID_EMBEDDED_WRAPPER = "Embedded wrapper %s returned null for both the static rel and the rel target type! Make sure one of the two returns a non-null value!";
private final Map<HalLinkRelation, Object> embeddeds = new HashMap<>();
@@ -51,12 +50,13 @@ class HalEmbeddedBuilder {
* Creates a new {@link HalEmbeddedBuilder} using the given {@link LinkRelationProvider} and prefer collection rels
* flag.
*
* @param provider can be {@literal null}.
* @param provider must not be {@literal null}.
* @param curieProvider must not be {@literal null}.
* @param preferCollectionRels whether to prefer to ask the provider for collection rels.
*/
public HalEmbeddedBuilder(LinkRelationProvider provider, CurieProvider curieProvider, boolean preferCollectionRels) {
Assert.notNull(provider, "Relprovider must not be null!");
Assert.notNull(provider, "LinkRelationProvider must not be null!");
this.provider = provider;
this.curieProvider = curieProvider;
@@ -69,7 +69,7 @@ class HalEmbeddedBuilder {
*
* @param source can be {@literal null}.
*/
public void add(Object source) {
public void add(@Nullable Object source) {
EmbeddedWrapper wrapper = wrappers.wrap(source);
@@ -114,10 +114,6 @@ class HalEmbeddedBuilder {
.map(HalLinkRelation::of) //
.orElseGet(() -> {
if (provider == null) {
return DEFAULT_REL;
}
Class<?> type = wrapper.getRelTargetType();
if (type == null) {
@@ -128,12 +124,9 @@ class HalEmbeddedBuilder {
? provider.getCollectionResourceRelFor(type) //
: provider.getItemResourceRelFor(type);
if (curieProvider != null) {
rel = curieProvider.getNamespacedRelFor(rel);
}
return rel == null ? DEFAULT_REL : HalLinkRelation.of(rel);
return curieProvider != CurieProvider.NONE //
? curieProvider.getNamespacedRelFor(rel) //
: HalLinkRelation.of(rel);
});
}

View File

@@ -45,7 +45,7 @@ public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
private static final String RELATION_MESSAGE_TEMPLATE = "_links.%s.title";
private final String curie;
private final @Nullable String curie;
private final @NonNull @Getter String localPart;
/**
@@ -54,7 +54,7 @@ public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
* @param relation must not be {@literal null}.
* @return
*/
public static HalLinkRelation of(@Nullable LinkRelation relation) {
public static HalLinkRelation of(LinkRelation relation) {
Assert.notNull(relation, "LinkRelation must not be null!");
@@ -166,6 +166,7 @@ public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
* @see org.springframework.context.MessageSourceResolvable#getCodes()
*/
@Override
@org.springframework.lang.NonNull
public String[] getCodes() {
return Stream.of(value(), localPart) //

View File

@@ -85,7 +85,8 @@ public class HalMediaTypeConfiguration implements HypermediaMappingInformation {
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2HalModule());
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider,
curieProvider.getIfAvailable(), messageSourceAccessor, halConfiguration.getIfAvailable(HalConfiguration::new)));
curieProvider.getIfAvailable(() -> CurieProvider.NONE), messageSourceAccessor,
halConfiguration.getIfAvailable(HalConfiguration::new)));
return mapper;
}

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.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -109,19 +110,19 @@ public class Jackson2HalModule extends SimpleModule {
private static final long serialVersionUID = -1844788111509966406L;
private final BeanProperty property;
private final @Nullable BeanProperty property;
private final CurieProvider curieProvider;
private final EmbeddedMapper mapper;
private final MessageSourceAccessor accessor;
private final HalConfiguration halConfiguration;
public HalLinkListSerializer(@Nullable CurieProvider curieProvider, EmbeddedMapper mapper,
@Nullable MessageSourceAccessor accessor, HalConfiguration halConfiguration) {
public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, MessageSourceAccessor accessor,
HalConfiguration halConfiguration) {
this(null, curieProvider, mapper, accessor, halConfiguration);
}
public HalLinkListSerializer(@Nullable BeanProperty property, @Nullable CurieProvider curieProvider,
@Nullable EmbeddedMapper mapper, @Nullable MessageSourceAccessor accessor, HalConfiguration halConfiguration) {
public HalLinkListSerializer(@Nullable BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper,
MessageSourceAccessor accessor, HalConfiguration halConfiguration) {
super(TypeFactory.defaultInstance().constructType(Links.class));
@@ -132,25 +133,19 @@ public class Jackson2HalModule extends SimpleModule {
this.halConfiguration = halConfiguration;
}
/**
* Needed to support Jackson
*/
HalLinkListSerializer() {
this(null, null, null, null, new HalConfiguration().withRenderSingleLinks(RenderSingleLinks.AS_SINGLE));
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(Links value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
// sort links according to their relation
Map<LinkRelation, List<Object>> sortedLinks = new LinkedHashMap<>();
List<Link> links = new ArrayList<>();
boolean prefixingRequired = curieProvider != null;
boolean prefixingRequired = curieProvider != CurieProvider.NONE;
boolean curiedLinkPresent = false;
boolean skipCuries = !jgen.getOutputContext().getParent().inRoot();
@@ -238,6 +233,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)
throws JsonMappingException {
return new HalLinkListSerializer(property, curieProvider, mapper, accessor, halConfiguration);
@@ -268,6 +264,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean isEmpty(SerializerProvider provider, Links value) {
return value.isEmpty();
}
@@ -277,6 +274,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(Links value) {
return value.toList().size() == 1;
}
@@ -287,6 +285,7 @@ public class Jackson2HalModule extends SimpleModule {
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -326,6 +325,7 @@ public class Jackson2HalModule extends SimpleModule {
* org.codehaus.jackson.map.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(Collection<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
Map<HalLinkRelation, Object> embeddeds = embeddedMapper.map(value);
@@ -342,36 +342,64 @@ public class Jackson2HalModule extends SimpleModule {
provider.findValueSerializer(Map.class, property).serialize(embeddeds, jgen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new HalResourcesSerializer(property, embeddedMapper);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean isEmpty(SerializerProvider provider, Collection<?> value) {
return value.isEmpty();
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(Collection<?> value) {
return value.size() == 1;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -389,7 +417,7 @@ public class Jackson2HalModule extends SimpleModule {
private static final long serialVersionUID = 3700806118177419817L;
private final BeanProperty property;
private final @Nullable BeanProperty property;
private final Map<Class<?>, JsonSerializer<Object>> serializers;
private final HalConfiguration halConfiguration;
@@ -398,7 +426,7 @@ public class Jackson2HalModule extends SimpleModule {
*
* @param property
*/
public OptionalListJackson2Serializer(BeanProperty property, HalConfiguration halConfiguration) {
public OptionalListJackson2Serializer(@Nullable BeanProperty property, HalConfiguration halConfiguration) {
super(TypeFactory.defaultInstance().constructType(List.class));
@@ -412,6 +440,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@SuppressWarnings("null")
public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
throw new UnsupportedOperationException("not implemented");
}
@@ -421,6 +450,7 @@ public class Jackson2HalModule 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
@SuppressWarnings("null")
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
List<?> list = (List<?>) value;
@@ -475,6 +505,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(Object arg0) {
return false;
}
@@ -484,6 +515,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean isEmpty(SerializerProvider provider, Object value) {
return false;
}
@@ -495,6 +527,7 @@ public class Jackson2HalModule extends SimpleModule {
* com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)
throws JsonMappingException {
return new OptionalListJackson2Serializer(property, halConfiguration);
@@ -552,6 +585,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public List<Link> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
List<Link> result = new ArrayList<>();
@@ -631,6 +665,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public List<Object> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
List<Object> result = new ArrayList<>();
@@ -658,13 +693,18 @@ public class Jackson2HalModule extends SimpleModule {
return result;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property)
throws JsonMappingException {
JavaType vc = property.getType().getContentType();
HalResourcesDeserializer des = new HalResourcesDeserializer(vc);
return des;
JavaType type = property.getType().getContentType();
return new HalResourcesDeserializer(type);
}
}
@@ -676,7 +716,7 @@ public class Jackson2HalModule extends SimpleModule {
public static class HalHandlerInstantiator extends HandlerInstantiator {
private final Map<Class<?>, Object> serializers = new HashMap<>();
private final AutowireCapableBeanFactory delegate;
private final @Nullable AutowireCapableBeanFactory delegate;
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider,
MessageSourceAccessor messageSourceAccessor) {
@@ -692,7 +732,7 @@ public class Jackson2HalModule extends SimpleModule {
* @param curieProvider can be {@literal null}.
* @param messageSourceAccessor can be {@literal null}.
*/
public HalHandlerInstantiator(LinkRelationProvider provider, @Nullable CurieProvider curieProvider,
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider,
MessageSourceAccessor messageSourceAccessor, HalConfiguration halConfiguration) {
this(provider, curieProvider, messageSourceAccessor, true, halConfiguration);
}
@@ -708,17 +748,17 @@ public class Jackson2HalModule extends SimpleModule {
* @param accessor can be {@literal null}.
* @param enforceEmbeddedCollections
*/
public HalHandlerInstantiator(@Nullable LinkRelationProvider provider, @Nullable CurieProvider curieProvider,
@Nullable MessageSourceAccessor accessor, boolean enforceEmbeddedCollections,
HalConfiguration halConfiguration) {
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider,
MessageSourceAccessor accessor, boolean enforceEmbeddedCollections, HalConfiguration halConfiguration) {
this(provider, curieProvider, accessor, enforceEmbeddedCollections, null, halConfiguration);
}
private HalHandlerInstantiator(@Nullable LinkRelationProvider provider, @Nullable CurieProvider curieProvider,
@Nullable MessageSourceAccessor accessor, boolean enforceEmbeddedCollections,
private HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider,
MessageSourceAccessor accessor, boolean enforceEmbeddedCollections,
@Nullable AutowireCapableBeanFactory delegate, HalConfiguration halConfiguration) {
Assert.notNull(provider, "RelProvider must not be null!");
Assert.notNull(curieProvider, "CurieProvider must not be null!");
EmbeddedMapper mapper = new EmbeddedMapper(provider, curieProvider, enforceEmbeddedCollections);
@@ -734,8 +774,9 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#deserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
*/
@Override
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated,
Class<?> deserClass) {
@SuppressWarnings("null")
public JsonDeserializer<?> deserializerInstance(@NonNull DeserializationConfig config, @NonNull Annotated annotated,
@NonNull Class<?> deserClass) {
return (JsonDeserializer<?>) findInstance(deserClass);
}
@@ -744,8 +785,9 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#keyDeserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
*/
@Override
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated,
Class<?> keyDeserClass) {
@SuppressWarnings("null")
public KeyDeserializer keyDeserializerInstance(@NonNull DeserializationConfig config, @NonNull Annotated annotated,
@NonNull Class<?> keyDeserClass) {
return (KeyDeserializer) findInstance(keyDeserClass);
}
@@ -754,7 +796,9 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#serializerInstance(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
*/
@Override
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) {
@SuppressWarnings("null")
public JsonSerializer<?> serializerInstance(@NonNull SerializationConfig config, @NonNull Annotated annotated,
@NonNull Class<?> serClass) {
return (JsonSerializer<?>) findInstance(serClass);
}
@@ -763,8 +807,9 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeResolverBuilderInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
*/
@Override
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated,
Class<?> builderClass) {
@SuppressWarnings("null")
public TypeResolverBuilder<?> typeResolverBuilderInstance(@NonNull MapperConfig<?> config,
@NonNull Annotated annotated, @NonNull Class<?> builderClass) {
return (TypeResolverBuilder<?>) findInstance(builderClass);
}
@@ -773,7 +818,9 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeIdResolverInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
*/
@Override
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) {
@SuppressWarnings("null")
public TypeIdResolver typeIdResolverInstance(@NonNull MapperConfig<?> config, @NonNull Annotated annotated,
@NonNull Class<?> resolverClass) {
return (TypeIdResolver) findInstance(resolverClass);
}
@@ -804,6 +851,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean isEmpty(SerializerProvider provider, Boolean value) {
return value == null || Boolean.FALSE.equals(value);
}
@@ -813,6 +861,7 @@ public class Jackson2HalModule 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
@SuppressWarnings("null")
public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeBoolean(value);
}
@@ -822,6 +871,7 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.std.StdScalarSerializer#getSchema(com.fasterxml.jackson.databind.SerializerProvider, java.lang.reflect.Type)
*/
@Override
@SuppressWarnings("null")
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode("boolean", true);
}
@@ -831,8 +881,10 @@ public class Jackson2HalModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.std.StdScalarSerializer#acceptJsonFormatVisitor(com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper, com.fasterxml.jackson.databind.JavaType)
*/
@Override
@SuppressWarnings("null")
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException {
if (visitor != null) {
visitor.expectBooleanFormat(typeHint);
}
@@ -855,11 +907,10 @@ public class Jackson2HalModule extends SimpleModule {
* whether to prefer collection relations.
*
* @param relProvider must not be {@literal null}.
* @param curieProvider can be {@literal null}.
* @param curieProvider must not be {@literal null}.
* @param preferCollectionRels
*/
public EmbeddedMapper(LinkRelationProvider relProvider, @Nullable CurieProvider curieProvider,
boolean preferCollectionRels) {
public EmbeddedMapper(LinkRelationProvider relProvider, CurieProvider curieProvider, boolean preferCollectionRels) {
Assert.notNull(relProvider, "RelProvider must not be null!");
@@ -880,9 +931,7 @@ public class Jackson2HalModule extends SimpleModule {
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider, curieProvider, preferCollectionRels);
for (Object resource : source) {
builder.add(resource);
}
source.forEach(builder::add);
return builder.asMap();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Collection of components needed to deserialize a HAL-FORMS document.
*
*
* @author Greg Turnquist
*/
class HalFormsDeserializers {
@@ -58,7 +58,12 @@ class HalFormsDeserializers {
this(TypeFactory.defaultInstance().constructCollectionLikeType(List.class, Object.class));
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public List<Object> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
List<Object> result = new ArrayList<>();
@@ -86,18 +91,31 @@ class HalFormsDeserializers {
return result;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentType()
*/
@Override
public JavaType getContentType() {
return this.contentType;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
*/
@Override
@Nullable
public JsonDeserializer<Object> getContentDeserializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
@@ -142,6 +160,7 @@ class HalFormsDeserializers {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public List<MediaType> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return MediaType.parseMediaTypes(p.getText());
}

View File

@@ -17,7 +17,6 @@ package org.springframework.hateoas.mediatype.hal.forms;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Singular;
import lombok.Value;
import lombok.experimental.Wither;
@@ -29,6 +28,7 @@ import java.util.Map;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.PagedModel;
import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.hateoas.mediatype.hal.HalLinkRelation;
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer;
import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule.HalFormsLinksDeserializer;
@@ -57,12 +57,14 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonPropertyOrder({ "resource", "resources", "embedded", "links", "templates", "metadata" })
public class HalFormsDocument<T> {
@Nullable //
@JsonUnwrapped //
@JsonInclude(Include.NON_NULL) //
@Wither(value = AccessLevel.PRIVATE, onMethod = @__({ @Nullable })) //
private T resource;
@JsonInclude(Include.NON_EMPTY) @JsonIgnore //
@Nullable //
@JsonInclude(Include.NON_EMPTY) //
@JsonIgnore //
@Wither(AccessLevel.PRIVATE) //
private Collection<T> resources;
@@ -70,19 +72,19 @@ public class HalFormsDocument<T> {
@JsonInclude(Include.NON_EMPTY) //
private Map<HalLinkRelation, Object> embedded;
@Nullable //
@JsonProperty("page") //
@JsonInclude(Include.NON_NULL) //
@Wither(onMethod = @__({ @Nullable })) //
private PagedModel.PageMetadata pageMetadata;
@Singular //
// @Singular //
@JsonProperty("_links") //
@JsonInclude(Include.NON_EMPTY) //
@JsonSerialize(using = HalLinkListSerializer.class) //
@JsonDeserialize(using = HalFormsLinksDeserializer.class) //
private Links links;
@Singular //
// @Singular //
@JsonProperty("_templates") //
@JsonInclude(Include.NON_EMPTY) //
private Map<String, HalFormsTemplate> templates;
@@ -147,6 +149,14 @@ public class HalFormsDocument<T> {
return this.templates.get(key);
}
public HalFormsDocument<T> withPageMetadata(@Nullable PageMetadata metadata) {
return new HalFormsDocument<T>(resource, resources, embedded, metadata, links, templates);
}
private HalFormsDocument<T> withResource(@Nullable T resource) {
return new HalFormsDocument<T>(resource, resources, embedded, pageMetadata, links, templates);
}
/**
* Adds the given {@link Link} to the current document.
*

View File

@@ -23,10 +23,10 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.mediatype.hal.CurieProvider;
import org.springframework.hateoas.client.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.HypermediaMappingInformation;
import org.springframework.hateoas.mediatype.hal.CurieProvider;
import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider;
import org.springframework.http.MediaType;
@@ -71,9 +71,9 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2HalFormsModule());
mapper.setHandlerInstantiator(
new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider, curieProvider.getIfAvailable(),
messageSourceAccessor, true, halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new)));
mapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider,
curieProvider.getIfAvailable(() -> CurieProvider.NONE), messageSourceAccessor, true,
halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new)));
return mapper;
}

View File

@@ -54,8 +54,7 @@ class HalFormsSerializers {
/**
* Serializer for {@link CollectionModel}.
*/
static class HalFormsResourceSerializer extends ContainerSerializer<EntityModel<?>>
implements ContextualSerializer {
static class HalFormsResourceSerializer extends ContainerSerializer<EntityModel<?>> implements ContextualSerializer {
private static final long serialVersionUID = -7912243216469101379L;
@@ -71,9 +70,13 @@ class HalFormsSerializers {
this(null);
}
/*
* (non-Javadoc)
* @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(EntityModel<?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
@SuppressWarnings("null")
public void serialize(EntityModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
HalFormsDocument<?> doc = HalFormsDocument.forResource(value.getContent()) //
.withLinks(value.getLinks()) //
@@ -82,30 +85,53 @@ class HalFormsSerializers {
provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(EntityModel<?> resource) {
return false;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSerializer) {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new HalFormsResourceSerializer(property);
@@ -135,9 +161,13 @@ class HalFormsSerializers {
this(null, embeddedMapper);
}
/*
* (non-Javadoc)
* @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 {
@SuppressWarnings("null")
public void serialize(CollectionModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
Map<HalLinkRelation, Object> embeddeds = embeddedMapper.map(value);
@@ -162,30 +192,53 @@ class HalFormsSerializers {
provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(CollectionModel<?> resources) {
return resources.getContent().size() == 1;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSerializer) {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new HalFormsResourcesSerializer(property, embeddedMapper);

View File

@@ -65,6 +65,7 @@ public class HalFormsTemplate {
private List<HalFormsProperty> properties;
private List<MediaType> contentTypes;
@SuppressWarnings("null")
private HalFormsTemplate() {
this(null, null, Collections.emptyList(), Collections.emptyList());
}
@@ -75,7 +76,7 @@ public class HalFormsTemplate {
/**
* Returns a new {@link HalFormsTemplate} with the given {@link HalFormsProperty} added.
*
*
* @param property must not be {@literal null}.
* @return
*/
@@ -91,7 +92,7 @@ public class HalFormsTemplate {
/**
* Returns a new {@link HalFormsTemplate} with the given {@link MediaType} added as content type.
*
*
* @param mediaType must not be {@literal null}.
* @return
*/

View File

@@ -101,6 +101,7 @@ class Jackson2HalFormsModule extends SimpleModule {
abstract class PagedModelMixin<T> extends PagedModel<T> {
@Nullable
@Override
@JsonProperty("page")
@JsonInclude(Include.NON_EMPTY)
@@ -138,6 +139,7 @@ class Jackson2HalFormsModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public Links deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Links.of(delegate.deserialize(p, ctxt));
}
@@ -150,7 +152,7 @@ class Jackson2HalFormsModule extends SimpleModule {
private final Map<Class<?>, Object> serializers = new HashMap<>();
public HalFormsHandlerInstantiator(LinkRelationProvider resolver, @Nullable CurieProvider curieProvider,
public HalFormsHandlerInstantiator(LinkRelationProvider resolver, CurieProvider curieProvider,
MessageSourceAccessor accessor, boolean enforceEmbeddedCollections,
HalFormsConfiguration halFormsConfiguration) {
@@ -166,6 +168,7 @@ class Jackson2HalFormsModule extends SimpleModule {
public HalFormsHandlerInstantiator(LinkRelationProvider relProvider, CurieProvider curieProvider,
MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections,
AutowireCapableBeanFactory beanFactory) {
this(relProvider, curieProvider, messageSource, enforceEmbeddedCollections,
beanFactory.getBean(HalFormsConfiguration.class));
}

View File

@@ -1,9 +1,7 @@
/**
* HAL-specific extensions, SPIs and Jackson customizations.
*
*
* @see http://stateless.co/hal_specification.html
*/
@NonNullApi
@org.springframework.lang.NonNullApi
package org.springframework.hateoas.mediatype.hal;
import org.springframework.lang.NonNullApi;

View File

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

View File

@@ -23,12 +23,14 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.PagedModel;
import org.springframework.hateoas.PagedModel.PageMetadata;
@@ -139,7 +141,12 @@ public class Jackson2UberModule extends SimpleModule {
this(null);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(RepresentationModel<?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
@@ -153,30 +160,53 @@ public class Jackson2UberModule extends SimpleModule {
.serialize(doc, gen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(RepresentationModel<?> value) {
return false;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new UberRepresentationModelSerializer(property);
@@ -202,7 +232,12 @@ public class Jackson2UberModule extends SimpleModule {
this(null);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(EntityModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
UberDocument doc = new UberDocument().withUber(new Uber() //
@@ -214,30 +249,53 @@ public class Jackson2UberModule extends SimpleModule {
.serialize(doc, gen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(EntityModel<?> value) {
return false;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new UberEntityModelSerializer(property);
@@ -269,6 +327,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
@SuppressWarnings("null")
public void serialize(CollectionModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
UberDocument doc = new UberDocument() //
@@ -281,30 +340,53 @@ public class Jackson2UberModule extends SimpleModule {
.serialize(doc, gen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
*/
@Override
@Nullable
public JavaType getContentType() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
*/
@Override
@Nullable
public JsonSerializer<?> getContentSerializer() {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(CollectionModel<?> value) {
return value.getContent().size() == 1;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new UberCollectionModelSerializer(property);
@@ -335,6 +417,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
@SuppressWarnings("null")
public void serialize(PagedModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
UberDocument doc = new UberDocument() //
@@ -372,6 +455,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
*/
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(PagedModel<?> value) {
return value.getContent().size() == 1;
}
@@ -382,6 +466,7 @@ public class Jackson2UberModule extends SimpleModule {
*/
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
return null;
}
@@ -391,6 +476,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
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
return new UberPagedModelSerializer(property);
}
@@ -420,6 +506,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public RepresentationModel<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
UberDocument doc = p.getCodec().readValue(p, UberDocument.class);
@@ -467,15 +554,13 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
if (property != null) {
JavaType vc = property.getType().getContentType();
return new UberRepresentationModelDeserializer(vc);
} else {
return new UberRepresentationModelDeserializer(ctxt.getContextualType());
}
JavaType type = property == null ? ctxt.getContextualType() : property.getType().getContentType();
return new UberRepresentationModelDeserializer(type);
}
/**
@@ -508,7 +593,12 @@ public class Jackson2UberModule extends SimpleModule {
this.contentType = contentType;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public EntityModel<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
UberDocument doc = p.getCodec().readValue(p, UberDocument.class);
@@ -527,20 +617,24 @@ public class Jackson2UberModule extends SimpleModule {
// Primitive type
List<UberData> data = uberData.getData();
if (data == null) {
throw new IllegalStateException();
}
if (isPrimitiveType(data)) {
Object scalarValue = data.get(0).getValue();
UberData firstItem = data.get(0);
Object scalarValue = firstItem.getValue();
return new EntityModel<>(scalarValue, links);
}
Map<String, Object> properties;
if (data == null) {
properties = new HashMap<>();
} else {
properties = data.stream().collect(Collectors.toMap(UberData::getName, UberData::getValue));
}
Map<String, Object> properties = data == null //
? new HashMap<>() //
: data.stream().collect(Collectors.toMap(UberData::getName, UberData::getValue));
JavaType rootType = JacksonHelper.findRootType(this.contentType);
Object value = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties);
return new EntityModel<>(value, links);
@@ -560,15 +654,13 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
if (property != null) {
JavaType vc = property.getType().getContentType();
return new UberEntityModelDeserializer(vc);
} else {
return new UberEntityModelDeserializer(ctxt.getContextualType());
}
JavaType type = property == null ? ctxt.getContextualType() : property.getType().getContentType();
return new UberEntityModelDeserializer(type);
}
/**
@@ -606,10 +698,10 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public CollectionModel<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JavaType rootType = JacksonHelper.findRootType(this.contentType);
UberDocument doc = p.getCodec().readValue(p, UberDocument.class);
return extractResources(doc, rootType, this.contentType);
@@ -623,16 +715,18 @@ public class Jackson2UberModule extends SimpleModule {
return this.contentType;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
if (property != null) {
JavaType vc = property.getType().getContentType();
return new UberCollectionModelDeserializer(vc);
} else {
return new UberCollectionModelDeserializer(ctxt.getContextualType());
}
JavaType type = property == null ? ctxt.getContextualType() : property.getType().getContentType();
return new UberCollectionModelDeserializer(type);
}
/**
@@ -670,6 +764,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public PagedModel<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JavaType rootType = JacksonHelper.findRootType(this.contentType);
@@ -690,16 +785,18 @@ public class Jackson2UberModule extends SimpleModule {
return this.contentType;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty)
*/
@Override
@SuppressWarnings("null")
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
if (property != null) {
JavaType vc = property.getType().getContentType();
return new UberPagedModelDeserializer(vc);
} else {
return new UberPagedModelDeserializer(ctxt.getContextualType());
}
JavaType type = property == null ? ctxt.getContextualType() : property.getType().getContentType();
return new UberPagedModelDeserializer(type);
}
/**
@@ -726,55 +823,65 @@ public class Jackson2UberModule extends SimpleModule {
for (UberData uberData : doc.getUber().getData()) {
if (uberData.getName() != null && uberData.getName().equals("page")) {
String name = uberData.getName();
if (name != null && name.equals("page")) {
continue;
}
if (uberData.getLinks().isEmpty()) {
if (!uberData.getLinks().isEmpty()) {
continue;
}
List<Link> resourceLinks = new ArrayList<>();
EntityModel<?> resource = null;
List<Link> resourceLinks = new ArrayList<>();
EntityModel<?> resource = null;
List<UberData> data = uberData.getData();
if (data == null) {
throw new RuntimeException("No content!");
}
List<UberData> data = uberData.getData();
for (UberData item : data) {
if (data == null) {
throw new RuntimeException("No content!");
}
for (UberData item : data) {
List<LinkRelation> rel = item.getRel();
if (rel != null) {
item.getLinks().forEach(resourceLinks::add);
} else {
// Primitive type
List<UberData> itemData = item.getData();
if (isPrimitiveType(itemData)) {
if (itemData == null) {
throw new IllegalStateException();
}
UberData firstItem = itemData.get(0);
Object scalarValue = firstItem.getValue();
resource = new EntityModel<>(scalarValue, uberData.getLinks());
if (item.getRel() != null) {
item.getRel().forEach(rel -> resourceLinks.add(new Link(item.getUrl(), rel)));
} else {
// Primitive type
List<UberData> itemData = item.getData();
if (isPrimitiveType(itemData)) {
Object scalarValue = itemData.get(0).getValue();
resource = new EntityModel<>(scalarValue, uberData.getLinks());
} else {
Map<String, Object> properties;
if (itemData == null) {
properties = new HashMap<>();
} else {
properties = itemData.stream() //
Map<String, Object> properties = itemData == null //
? new HashMap<>() //
: itemData.stream() //
.collect(Collectors.toMap(UberData::getName, UberData::getValue));
}
Object obj = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties);
resource = new EntityModel<>(obj, uberData.getLinks());
}
Object obj = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties);
resource = new EntityModel<>(obj, uberData.getLinks());
}
}
}
if (resource != null) {
if (resource != null) {
resource.add(resourceLinks);
content.add(resource);
} else {
throw new RuntimeException("No content!");
}
resource.add(resourceLinks);
content.add(resource);
} else {
throw new RuntimeException("No content!");
}
}
@@ -803,12 +910,12 @@ public class Jackson2UberModule extends SimpleModule {
private static PageMetadata extractPagingMetadata(UberDocument doc) {
return doc.getUber().getData().stream() //
.filter(uberData -> uberData.getName() != null && uberData.getName().equals("page")) //
.filter(uberData -> Optional.ofNullable(uberData.getName()).map("page"::equals).orElse(false)) //
.findFirst().map(Jackson2UberModule::convertUberDataToPageMetaData) //
.orElse(null);
}
@NotNull
@SuppressWarnings("null")
private static PageMetadata convertUberDataToPageMetaData(UberData uberData) {
int size = 0;
@@ -817,26 +924,30 @@ public class Jackson2UberModule extends SimpleModule {
int totalPages = 0;
List<UberData> content = uberData.getData();
if (content != null) {
for (UberData data : content) {
String name = data.getName();
Object value = data.getValue();
switch (name) {
case "size":
size = (int) data.getValue();
size = (int) value;
break;
case "number":
number = (int) data.getValue();
number = (int) value;
break;
case "totalElements":
totalElements = (int) data.getValue();
totalElements = (int) value;
break;
case "totalPages":
totalPages = (int) data.getValue();
totalPages = (int) value;
break;
default:
@@ -863,6 +974,7 @@ public class Jackson2UberModule extends SimpleModule {
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
@SuppressWarnings("null")
public UberAction deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return UberAction.valueOf(p.getText().toUpperCase());
}

View File

@@ -63,18 +63,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
class UberData {
private String id;
private String name;
private String label;
private List<LinkRelation> rel;
private String url;
private UberAction action;
private @Nullable String id, name, label;
private @Nullable List<LinkRelation> rel;
private @Nullable String url;
private @Nullable UberAction action;
private boolean transclude;
private String model;
private List<String> sending;
private List<String> accepting;
private Object value;
private List<UberData> data;
private @Nullable String model;
private @Nullable List<String> sending;
private @Nullable List<String> accepting;
private @Nullable Object value;
private @Nullable List<UberData> data;
@JsonCreator
UberData(@JsonProperty("id") @Nullable String id, @JsonProperty("name") @Nullable String name,
@@ -136,13 +134,15 @@ class UberData {
@JsonIgnore
public List<Link> getLinks() {
if (this.url == null) {
String url = this.url;
if (url == null) {
return Links.NONE.toList();
}
return Optional.ofNullable(this.rel) //
.map(rels -> rels.stream() //
.map(rel -> new Link(this.url, rel)) //
.map(rel -> new Link(url, rel)) //
.collect(Collectors.toList())) //
.orElse(Collections.emptyList());
}
@@ -204,6 +204,7 @@ class UberData {
return data;
}
@SuppressWarnings("null")
static List<UberData> extractLinksAndContent(PagedModel<?> resources) {
List<UberData> collectionOfResources = extractLinksAndContent((CollectionModel<?>) resources);
@@ -266,7 +267,7 @@ class UberData {
private static Optional<UberData> extractContent(@Nullable Object content) {
return Optional.ofNullable(content) //
.filter(it -> !RESOURCE_TYPES.contains(content.getClass())) //
.filter(it -> !RESOURCE_TYPES.contains(it.getClass())) //
.map(it -> new UberData() //
.withName(StringUtils.uncapitalize(it.getClass().getSimpleName())) //
.withData(extractProperties(it)));

View File

@@ -19,10 +19,11 @@ import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.client.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.HypermediaMappingInformation;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import com.fasterxml.jackson.databind.Module;
@@ -53,6 +54,7 @@ class UberMediaTypeConfiguration implements HypermediaMappingInformation {
* (non-Javadoc)
* @see org.springframework.hateoas.config.HypermediaMappingInformation#getJacksonModule()
*/
@NonNull
@Override
public Module getJacksonModule() {
return new Jackson2UberModule();

View File

@@ -22,6 +22,7 @@ import java.util.List;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -138,7 +139,7 @@ public class VndErrors implements Iterable<VndErrors.VndError> {
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
@@ -235,7 +236,7 @@ public class VndErrors implements Iterable<VndErrors.VndError> {
* @see org.springframework.hateoas.ResourceSupport#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;

View File

@@ -1,27 +1,10 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Value objects to ease creating {@link org.springframework.hateoas.Link}s and link driven representations for REST webservices.
* Value objects to ease creating {@link org.springframework.hateoas.Link}s and link driven representations for REST
* webservices.
*
* @author Oliver Drotbohm
* @author Jens Schauder
* @author Greg Turnquist
*/
@NonNullApi
@org.springframework.lang.NonNullApi
package org.springframework.hateoas;
import org.springframework.lang.NonNullApi;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,6 @@ package org.springframework.hateoas.server.core;
import lombok.experimental.UtilityClass;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.util.UriUtils;
@@ -40,7 +39,7 @@ class EncodingUtils {
* @param source must not be {@literal null}.
* @return
*/
public static String encodePath(@Nullable Object source) {
public static String encodePath(Object source) {
Assert.notNull(source, "Path value must not be null!");
@@ -57,7 +56,7 @@ class EncodingUtils {
* @param source must not be {@literal null}.
* @return
*/
public static String encodeParameter(@Nullable Object source) {
public static String encodeParameter(Object source) {
Assert.notNull(source, "Request parameter value must not be null!");

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,13 +20,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.hateoas.EntityModel;
import org.springframework.core.annotation.AliasFor;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
/**
* Annotation to configure the relation to be used when embedding objects in HAL representations of {@link EntityModel}s
* and {@link CollectionModel}.
*
*
* @author Alexander Baetz
* @author Oliver Gierke
*/
@@ -37,15 +38,24 @@ public @interface Relation {
String NO_RELATION = "";
/**
* Defines the relation to be used when referring to a single resource.
*
* Defines the relation to be used when referring to a single resource. Alias for {@link #itemRelation()}.
*
* @return
*/
@AliasFor("itemRelation")
String value() default NO_RELATION;
/**
* Defines the relation to be used when referring to a single resource. Alias of {@link #value()}.
*
* @return
*/
@AliasFor("value")
String itemRelation() default NO_RELATION;
/**
* Defines the relation to be used when referring to a collection of resources.
*
*
* @return
*/
String collectionRelation() default NO_RELATION;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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