diff --git a/pom.xml b/pom.xml index 6dc3c5ac..3fca4d53 100644 --- a/pom.xml +++ b/pom.xml @@ -559,12 +559,6 @@ ${slf4j.version} - - com.google.code.findbugs - jsr305 - ${jsr305.version} - - org.slf4j jcl-over-slf4j diff --git a/src/main/java/org/springframework/hateoas/Affordance.java b/src/main/java/org/springframework/hateoas/Affordance.java index d5de9a2e..90bf4305 100644 --- a/src/main/java/org/springframework/hateoas/Affordance.java +++ b/src/main/java/org/springframework/hateoas/Affordance.java @@ -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 getAffordanceModel(MediaType mediaType) { return (T) this.affordanceModels.get(mediaType); diff --git a/src/main/java/org/springframework/hateoas/CollectionModel.java b/src/main/java/org/springframework/hateoas/CollectionModel.java index b1c65386..652c23fe 100644 --- a/src/main/java/org/springframework/hateoas/CollectionModel.java +++ b/src/main/java/org/springframework/hateoas/CollectionModel.java @@ -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 extends RepresentationModel> * @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; diff --git a/src/main/java/org/springframework/hateoas/EntityModel.java b/src/main/java/org/springframework/hateoas/EntityModel.java index 22b625a6..58f44c30 100644 --- a/src/main/java/org/springframework/hateoas/EntityModel.java +++ b/src/main/java/org/springframework/hateoas/EntityModel.java @@ -89,7 +89,7 @@ public class EntityModel extends RepresentationModel> { * @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; diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index 93d58cad..2e6b3c95 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -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); diff --git a/src/main/java/org/springframework/hateoas/Links.java b/src/main/java/org/springframework/hateoas/Links.java index 4b05d326..98f9656b 100644 --- a/src/main/java/org/springframework/hateoas/Links.java +++ b/src/main/java/org/springframework/hateoas/Links.java @@ -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 { * @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 { * @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); } diff --git a/src/main/java/org/springframework/hateoas/PagedModel.java b/src/main/java/org/springframework/hateoas/PagedModel.java index e66b08ef..2feedd22 100644 --- a/src/main/java/org/springframework/hateoas/PagedModel.java +++ b/src/main/java/org/springframework/hateoas/PagedModel.java @@ -135,7 +135,7 @@ public class PagedModel extends CollectionModel { * @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 extends CollectionModel { * @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; diff --git a/src/main/java/org/springframework/hateoas/RepresentationModel.java b/src/main/java/org/springframework/hateoas/RepresentationModel.java index 097288e7..afa37818 100755 --- a/src/main/java/org/springframework/hateoas/RepresentationModel.java +++ b/src/main/java/org/springframework/hateoas/RepresentationModel.java @@ -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> { */ @Override @SuppressWarnings("unchecked") - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/src/main/java/org/springframework/hateoas/StringLinkRelation.java b/src/main/java/org/springframework/hateoas/StringLinkRelation.java index ac018cfe..f4da3c00 100644 --- a/src/main/java/org/springframework/hateoas/StringLinkRelation.java +++ b/src/main/java/org/springframework/hateoas/StringLinkRelation.java @@ -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; diff --git a/src/main/java/org/springframework/hateoas/client/JsonPathLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/client/JsonPathLinkDiscoverer.java index fbbb0a15..15da13d3 100644 --- a/src/main/java/org/springframework/hateoas/client/JsonPathLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/client/JsonPathLinkDiscoverer.java @@ -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)); diff --git a/src/main/java/org/springframework/hateoas/client/Rels.java b/src/main/java/org/springframework/hateoas/client/Rels.java index 1299df0b..cc50c137 100644 --- a/src/main/java/org/springframework/hateoas/client/Rels.java +++ b/src/main/java/org/springframework/hateoas/client/Rels.java @@ -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 findInResponse(@Nullable String representation, @Nullable MediaType mediaType); + Optional findInResponse(String representation, MediaType mediaType); } /** diff --git a/src/main/java/org/springframework/hateoas/client/Traverson.java b/src/main/java/org/springframework/hateoas/client/Traverson.java index d9011982..d0fd1989 100644 --- a/src/main/java/org/springframework/hateoas/client/Traverson.java +++ b/src/main/java/org/springframework/hateoas/client/Traverson.java @@ -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 rels = new ArrayList<>(); private Map 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 responseEntity = operations.exchange(template.expand(), GET, request, String.class); + ResponseEntity 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()); } /** diff --git a/src/main/java/org/springframework/hateoas/client/package-info.java b/src/main/java/org/springframework/hateoas/client/package-info.java index 0df51da2..67c03370 100644 --- a/src/main/java/org/springframework/hateoas/client/package-info.java +++ b/src/main/java/org/springframework/hateoas/client/package-info.java @@ -1,7 +1,5 @@ /** * Client side support. */ -@NonNullApi +@org.springframework.lang.NonNullApi package org.springframework.hateoas.client; - -import org.springframework.lang.NonNullApi; \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java index 0652860b..ad388323 100644 --- a/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java @@ -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> relProviderPluginRegistry) { + DelegatingLinkRelationProvider _relProvider( + PluginRegistry relProviderPluginRegistry) { return new DelegatingLinkRelationProvider(relProviderPluginRegistry); } @Bean - PluginRegistryFactoryBean> relProviderPluginRegistry() { + PluginRegistryFactoryBean relProviderPluginRegistry() { - PluginRegistryFactoryBean> factory = new PluginRegistryFactoryBean<>(); + PluginRegistryFactoryBean factory = new PluginRegistryFactoryBean<>(); factory.setType(LinkRelationProvider.class); factory.setExclusions(new Class[] { DelegatingLinkRelationProvider.class }); diff --git a/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java index 55bf5910..b67a5c17 100644 --- a/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java @@ -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); } } - } diff --git a/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java index 3bdc985b..af69f217 100644 --- a/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java @@ -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 { diff --git a/src/main/java/org/springframework/hateoas/config/package-info.java b/src/main/java/org/springframework/hateoas/config/package-info.java index a25e559c..ca44d9dc 100644 --- a/src/main/java/org/springframework/hateoas/config/package-info.java +++ b/src/main/java/org/springframework/hateoas/config/package-info.java @@ -1,7 +1,5 @@ /** * Spring container configuration support. */ -@NonNullApi +@org.springframework.lang.NonNullApi package org.springframework.hateoas.config; - -import org.springframework.lang.NonNullApi; \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java b/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java index 9245b4c2..b1d181f6 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java +++ b/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java @@ -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 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 findPropertyNames(Class clazz) { @@ -118,16 +122,19 @@ public class PropertyUtils { T obj = BeanUtils.instantiateClass(clazz); properties.forEach((key, value) -> { - Optional 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)); } - } diff --git a/src/main/java/org/springframework/hateoas/mediatype/alps/package-info.java b/src/main/java/org/springframework/hateoas/mediatype/alps/package-info.java index 07e85bf0..8b93b04d 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/alps/package-info.java +++ b/src/main/java/org/springframework/hateoas/mediatype/alps/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJson.java b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJson.java index 31b7ca79..94d70155 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJson.java +++ b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJson.java @@ -43,28 +43,28 @@ import com.fasterxml.jackson.annotation.JsonProperty; class CollectionJson { private String version; - private String href; + private @Nullable String href; private @JsonInclude(Include.NON_EMPTY) Links links; private @JsonInclude(Include.NON_EMPTY) List> items; private @JsonInclude(Include.NON_EMPTY) List 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> items, // - @JsonProperty("queries") @Nullable List 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> items, // + @JsonProperty("queries") @Nullable List 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 { CollectionJson withOwnSelfLink() { + String href = this.href; + if (href == null) { return this; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonData.java b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonData.java index 7fd95ab7..684300e6 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonData.java +++ b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonData.java @@ -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; diff --git a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonItem.java b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonItem.java index 171451b8..caa4f982 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonItem.java +++ b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonItem.java @@ -49,10 +49,10 @@ import com.fasterxml.jackson.databind.JavaType; @RequiredArgsConstructor(access = AccessLevel.PACKAGE) class CollectionJsonItem { - private String href; + private @Nullable String href; private List 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 { @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 { */ public List getData() { - if (this.data != null) { + if (!this.data.isEmpty()) { return this.data; } @@ -89,8 +89,10 @@ class CollectionJsonItem { 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 { @Nullable public Object toRawData(JavaType javaType) { - if (this.data == null) { + if (this.data.isEmpty()) { return null; } @@ -125,6 +127,8 @@ class CollectionJsonItem { public CollectionJsonItem withOwnSelfLink() { + String href = this.href; + if (href == null) { return this; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonMediaTypeConfiguration.java index 81e16e0b..ae487481 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonMediaTypeConfiguration.java @@ -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(); diff --git a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/Jackson2CollectionJsonModule.java b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/Jackson2CollectionJsonModule.java index 8f864c1a..8113b099 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/Jackson2CollectionJsonModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/Jackson2CollectionJsonModule.java @@ -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 properties = withOwnSelfLink.getTemplate().getData().stream() + if (template != null) { + + Map 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 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> items = Optional.ofNullable(document.getCollection().getItems()) - .orElse(new ArrayList<>()); - Links links = document.getCollection().withOwnSelfLink().getLinks(); + CollectionJson collection = document.getCollection(); + List> 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 properties = document.getCollection().getTemplate().getData().stream() + Map 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); diff --git a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/package-info.java b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/package-info.java index bda30ccd..aaa9e792 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/collectionjson/package-info.java +++ b/src/main/java/org/springframework/hateoas/mediatype/collectionjson/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/CurieProvider.java b/src/main/java/org/springframework/hateoas/mediatype/hal/CurieProvider.java index e61157df..dd159f7b 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/CurieProvider.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/CurieProvider.java @@ -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 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); /** diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProvider.java b/src/main/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProvider.java index 05590b5e..c82a1a42 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProvider.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProvider.java @@ -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); diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilder.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilder.java index 77a22615..f6318bba 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilder.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilder.java @@ -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 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); }); } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalLinkRelation.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalLinkRelation.java index e4ac797d..31d676c4 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalLinkRelation.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalLinkRelation.java @@ -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) // diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java index 9feda5cd..2e135866 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java @@ -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; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java index 2524cc64..a3b6717d 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java @@ -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> sortedLinks = new LinkedHashMap<>(); List 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 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, JsonSerializer> 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 deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { List 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 deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { List 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, 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(); } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDeserializers.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDeserializers.java index faf19336..6809fece 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDeserializers.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDeserializers.java @@ -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 deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { List 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 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 deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return MediaType.parseMediaTypes(p.getText()); } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java index ddf38a6c..faf7e2ee 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java @@ -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 { + @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 resources; @@ -70,19 +72,19 @@ public class HalFormsDocument { @JsonInclude(Include.NON_EMPTY) // private Map 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 templates; @@ -147,6 +149,14 @@ public class HalFormsDocument { return this.templates.get(key); } + public HalFormsDocument withPageMetadata(@Nullable PageMetadata metadata) { + return new HalFormsDocument(resource, resources, embedded, metadata, links, templates); + } + + private HalFormsDocument withResource(@Nullable T resource) { + return new HalFormsDocument(resource, resources, embedded, pageMetadata, links, templates); + } + /** * Adds the given {@link Link} to the current document. * diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java index 77cc2d98..5c80dcab 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java @@ -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; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java index c5e1da35..ae34477a 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java @@ -54,8 +54,7 @@ class HalFormsSerializers { /** * Serializer for {@link CollectionModel}. */ - static class HalFormsResourceSerializer extends ContainerSerializer> - implements ContextualSerializer { + static class HalFormsResourceSerializer extends ContainerSerializer> 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 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); diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java index 2b8a6062..8a77a68e 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java @@ -65,6 +65,7 @@ public class HalFormsTemplate { private List properties; private List 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 */ diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java index b1cfca1e..fadc7395 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java @@ -101,6 +101,7 @@ class Jackson2HalFormsModule extends SimpleModule { abstract class PagedModelMixin extends PagedModel { + @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, 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)); } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/package-info.java b/src/main/java/org/springframework/hateoas/mediatype/hal/package-info.java index 34131164..d06af2a8 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/package-info.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/mediatype/package-info.java b/src/main/java/org/springframework/hateoas/mediatype/package-info.java index 938c85fd..b40850e9 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/package-info.java +++ b/src/main/java/org/springframework/hateoas/mediatype/package-info.java @@ -1,7 +1,5 @@ /** * Spring container configuration support. */ -@NonNullApi +@org.springframework.lang.NonNullApi package org.springframework.hateoas.mediatype; - -import org.springframework.lang.NonNullApi; \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/mediatype/uber/Jackson2UberModule.java b/src/main/java/org/springframework/hateoas/mediatype/uber/Jackson2UberModule.java index c033265f..5a576fea 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/uber/Jackson2UberModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/uber/Jackson2UberModule.java @@ -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 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 properties; - if (data == null) { - properties = new HashMap<>(); - } else { - properties = data.stream().collect(Collectors.toMap(UberData::getName, UberData::getValue)); - } + Map 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 resourceLinks = new ArrayList<>(); - EntityModel resource = null; + List resourceLinks = new ArrayList<>(); + EntityModel resource = null; - List data = uberData.getData(); - if (data == null) { - throw new RuntimeException("No content!"); - } + List data = uberData.getData(); - for (UberData item : data) { + if (data == null) { + throw new RuntimeException("No content!"); + } + + for (UberData item : data) { + + List rel = item.getRel(); + + if (rel != null) { + item.getLinks().forEach(resourceLinks::add); + } else { + + // Primitive type + List 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 itemData = item.getData(); - if (isPrimitiveType(itemData)) { - - Object scalarValue = itemData.get(0).getValue(); - resource = new EntityModel<>(scalarValue, uberData.getLinks()); - } else { - - Map properties; - if (itemData == null) { - properties = new HashMap<>(); - } else { - properties = itemData.stream() // + Map 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 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()); } diff --git a/src/main/java/org/springframework/hateoas/mediatype/uber/UberData.java b/src/main/java/org/springframework/hateoas/mediatype/uber/UberData.java index b5c1d594..db5ca594 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/uber/UberData.java +++ b/src/main/java/org/springframework/hateoas/mediatype/uber/UberData.java @@ -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 rel; - private String url; - private UberAction action; + private @Nullable String id, name, label; + private @Nullable List rel; + private @Nullable String url; + private @Nullable UberAction action; private boolean transclude; - private String model; - private List sending; - private List accepting; - private Object value; - private List data; + private @Nullable String model; + private @Nullable List sending; + private @Nullable List accepting; + private @Nullable Object value; + private @Nullable List data; @JsonCreator UberData(@JsonProperty("id") @Nullable String id, @JsonProperty("name") @Nullable String name, @@ -136,13 +134,15 @@ class UberData { @JsonIgnore public List 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 extractLinksAndContent(PagedModel resources) { List collectionOfResources = extractLinksAndContent((CollectionModel) resources); @@ -266,7 +267,7 @@ class UberData { private static Optional 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))); diff --git a/src/main/java/org/springframework/hateoas/mediatype/uber/UberMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/uber/UberMediaTypeConfiguration.java index 48e42672..6276d1b6 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/uber/UberMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/uber/UberMediaTypeConfiguration.java @@ -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(); diff --git a/src/main/java/org/springframework/hateoas/mediatype/vnderrors/VndErrors.java b/src/main/java/org/springframework/hateoas/mediatype/vnderrors/VndErrors.java index 867e8de2..ec07a096 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/vnderrors/VndErrors.java +++ b/src/main/java/org/springframework/hateoas/mediatype/vnderrors/VndErrors.java @@ -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 { * @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 { * @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; diff --git a/src/main/java/org/springframework/hateoas/package-info.java b/src/main/java/org/springframework/hateoas/package-info.java index ae0fa1eb..7e409b15 100644 --- a/src/main/java/org/springframework/hateoas/package-info.java +++ b/src/main/java/org/springframework/hateoas/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/server/LinkBuilder.java b/src/main/java/org/springframework/hateoas/server/LinkBuilder.java index 1104bb7b..c80b6383 100644 --- a/src/main/java/org/springframework/hateoas/server/LinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/LinkBuilder.java @@ -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. diff --git a/src/main/java/org/springframework/hateoas/server/LinkRelationProvider.java b/src/main/java/org/springframework/hateoas/server/LinkRelationProvider.java index 21098a26..a81b63d2 100644 --- a/src/main/java/org/springframework/hateoas/server/LinkRelationProvider.java +++ b/src/main/java/org/springframework/hateoas/server/LinkRelationProvider.java @@ -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> { +public interface LinkRelationProvider extends Plugin { /** * 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> { * @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()); + } + } } diff --git a/src/main/java/org/springframework/hateoas/server/core/AnnotationLinkRelationProvider.java b/src/main/java/org/springframework/hateoas/server/core/AnnotationLinkRelationProvider.java index 1b8cb8f6..5cdb79fd 100644 --- a/src/main/java/org/springframework/hateoas/server/core/AnnotationLinkRelationProvider.java +++ b/src/main/java/org/springframework/hateoas/server/core/AnnotationLinkRelationProvider.java @@ -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)); } } diff --git a/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java b/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java index 6aab5168..bb623629 100644 --- a/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java @@ -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) { diff --git a/src/main/java/org/springframework/hateoas/server/core/ControllerEntityLinksFactoryBean.java b/src/main/java/org/springframework/hateoas/server/core/ControllerEntityLinksFactoryBean.java index 7db017b3..3b14b26c 100644 --- a/src/main/java/org/springframework/hateoas/server/core/ControllerEntityLinksFactoryBean.java +++ b/src/main/java/org/springframework/hateoas/server/core/ControllerEntityLinksFactoryBean.java @@ -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 implements - ApplicationContextAware { +public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean + implements ApplicationContextAware { private Class annotation; private LinkBuilderFactory linkBuilderFactory; @@ -45,7 +46,7 @@ public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean annotation) { @@ -55,14 +56,14 @@ public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean 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 getObjectType() { return ControllerEntityLinks.class; } - /* + /* * (non-Javadoc) * @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance() */ @@ -98,7 +100,7 @@ public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean delimiter) { + public boolean supports(LookupContext delimiter) { return true; } } diff --git a/src/main/java/org/springframework/hateoas/server/core/DelegatingLinkRelationProvider.java b/src/main/java/org/springframework/hateoas/server/core/DelegatingLinkRelationProvider.java index 218f409d..2df78b4d 100644 --- a/src/main/java/org/springframework/hateoas/server/core/DelegatingLinkRelationProvider.java +++ b/src/main/java/org/springframework/hateoas/server/core/DelegatingLinkRelationProvider.java @@ -28,7 +28,16 @@ import org.springframework.plugin.core.PluginRegistry; @RequiredArgsConstructor public class DelegatingLinkRelationProvider implements LinkRelationProvider { - private final @NonNull PluginRegistry> providers; + private final @NonNull PluginRegistry 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); } } diff --git a/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java b/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java index 14c81139..daecb076 100644 --- a/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java +++ b/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java @@ -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_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 { diff --git a/src/main/java/org/springframework/hateoas/server/core/EmbeddedWrappers.java b/src/main/java/org/springframework/hateoas/server/core/EmbeddedWrappers.java index 25513cec..2be61c59 100644 --- a/src/main/java/org/springframework/hateoas/server/core/EmbeddedWrappers.java +++ b/src/main/java/org/springframework/hateoas/server/core/EmbeddedWrappers.java @@ -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; diff --git a/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java b/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java index b48971d4..49491912 100644 --- a/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java +++ b/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java @@ -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!"); diff --git a/src/main/java/org/springframework/hateoas/server/core/HeaderLinksResponseEntity.java b/src/main/java/org/springframework/hateoas/server/core/HeaderLinksResponseEntity.java index f95aace8..923f05d2 100644 --- a/src/main/java/org/springframework/hateoas/server/core/HeaderLinksResponseEntity.java +++ b/src/main/java/org/springframework/hateoas/server/core/HeaderLinksResponseEntity.java @@ -44,8 +44,11 @@ public class HeaderLinksResponseEntity> extends private HeaderLinksResponseEntity(ResponseEntity 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> extends */ private static > HttpHeaders getHeadersWithLinks(ResponseEntity 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()); diff --git a/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java index a42b9630..5b63c0dd 100644 --- a/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java +++ b/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java @@ -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 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 implements LinkB String fragment = components.getFragment(); - if (StringUtils.hasText(fragment)) { + if (fragment != null && !fragment.trim().isEmpty()) { builder.fragment(encoded ? fragment : encodeFragment(fragment)); } diff --git a/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java b/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java index bf4260b2..4943f312 100644 --- a/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java @@ -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 diff --git a/src/main/java/org/springframework/hateoas/server/core/MethodParameters.java b/src/main/java/org/springframework/hateoas/server/core/MethodParameters.java index 37c66746..0d0c159e 100644 --- a/src/main/java/org/springframework/hateoas/server/core/MethodParameters.java +++ b/src/main/java/org/springframework/hateoas/server/core/MethodParameters.java @@ -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 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(); } } } diff --git a/src/main/java/org/springframework/hateoas/server/core/Relation.java b/src/main/java/org/springframework/hateoas/server/core/Relation.java index d8c65c3b..13095ff7 100644 --- a/src/main/java/org/springframework/hateoas/server/core/Relation.java +++ b/src/main/java/org/springframework/hateoas/server/core/Relation.java @@ -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; diff --git a/src/main/java/org/springframework/hateoas/server/core/TypeReferences.java b/src/main/java/org/springframework/hateoas/server/core/TypeReferences.java index 8d5e042e..571d1122 100644 --- a/src/main/java/org/springframework/hateoas/server/core/TypeReferences.java +++ b/src/main/java/org/springframework/hateoas/server/core/TypeReferences.java @@ -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); } diff --git a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java index a59c07e7..6f987671 100644 --- a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java +++ b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java @@ -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; } /** diff --git a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkRelationProvider.java b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkRelationProvider.java index b379c34b..c249dcf7 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkRelationProvider.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkRelationProvider.java @@ -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> providers; + private final PluginRegistry providers; - public ControllerLinkRelationProvider(Class controller, PluginRegistry> 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 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); } } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/JacksonSerializers.java b/src/main/java/org/springframework/hateoas/server/mvc/JacksonSerializers.java index 1f72a1d7..3df1b497 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/JacksonSerializers.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/JacksonSerializers.java @@ -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()); } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorHandlerMethodReturnValueHandler.java b/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorHandlerMethodReturnValueHandler.java index d7e4da32..de47c6ef 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorHandlerMethodReturnValueHandler.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorHandlerMethodReturnValueHandler.java @@ -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; diff --git a/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorInvoker.java b/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorInvoker.java index 0240bdc7..a0f76591 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorInvoker.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/RepresentationModelProcessorInvoker.java @@ -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 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); } } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/TypeConstrainedMappingJackson2HttpMessageConverter.java b/src/main/java/org/springframework/hateoas/server/mvc/TypeConstrainedMappingJackson2HttpMessageConverter.java index 975d8b1e..eb4d91da 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/TypeConstrainedMappingJackson2HttpMessageConverter.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/TypeConstrainedMappingJackson2HttpMessageConverter.java @@ -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 supportedMediaTypes, ObjectMapper objectMapper) { + public TypeConstrainedMappingJackson2HttpMessageConverter(Class type, List 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); } } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java index 07373b8e..0d89d438 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java @@ -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); diff --git a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java index e41bff03..a644143c 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java @@ -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); } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java index a457f4ee..7de64414 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java @@ -48,6 +48,7 @@ import org.springframework.web.util.UriTemplate; * @author Oliver Trosien * @author Greg Turnquist */ +@SuppressWarnings("deprecation") public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport { private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer diff --git a/src/test/java/org/springframework/hateoas/LinkUnitTest.java b/src/test/java/org/springframework/hateoas/LinkUnitTest.java index d60b553f..98099bd4 100755 --- a/src/test/java/org/springframework/hateoas/LinkUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinkUnitTest.java @@ -28,7 +28,6 @@ import org.junit.Test; import org.springframework.core.ResolvableType; import org.springframework.hateoas.support.Employee; import org.springframework.http.HttpMethod; -import org.springframework.util.Assert; /** * Unit tests for {@link Link}. @@ -59,11 +58,13 @@ public class LinkUnitTest { }); } + @SuppressWarnings("null") @Test(expected = IllegalArgumentException.class) public void rejectsNullHref() { new Link(null); } + @SuppressWarnings("null") @Test(expected = IllegalArgumentException.class) public void rejectsNullRel() { new Link("foo", (String) null); @@ -111,16 +112,6 @@ public class LinkUnitTest { assertThat(new Link("foo")).isNotEqualTo(new RepresentationModel<>()); } - @Test - public void returnsNullForNullOrEmptyLink() { - - assertSoftly(softly -> { - - softly.assertThat(Link.valueOf(null)).isNull(); - softly.assertThat(Link.valueOf("")).isNull(); - }); - } - /** * @see #54 * @see #100 diff --git a/src/test/java/org/springframework/hateoas/client/Server.java b/src/test/java/org/springframework/hateoas/client/Server.java index fb5a4901..cbeceb58 100644 --- a/src/test/java/org/springframework/hateoas/client/Server.java +++ b/src/test/java/org/springframework/hateoas/client/Server.java @@ -17,6 +17,7 @@ package org.springframework.hateoas.client; import static net.jadler.Jadler.*; import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.*; import java.io.Closeable; import java.io.IOException; @@ -24,13 +25,16 @@ import java.nio.charset.Charset; import java.util.Collections; import java.util.UUID; +import org.springframework.context.MessageSource; +import org.springframework.context.support.MessageSourceAccessor; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.MediaTypes; -import org.springframework.hateoas.EntityModel; -import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.core.EvoInflectorLinkRelationProvider; @@ -61,7 +65,8 @@ public class Server implements Closeable { this.mapper = new ObjectMapper(); this.mapper.registerModule(new Jackson2HalModule()); - this.mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null, null)); + this.mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, CurieProvider.NONE, + new MessageSourceAccessor(mock(MessageSource.class)))); initJadler() // .withDefaultResponseContentType(MediaTypes.HAL_JSON.toString()) // diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProviderUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProviderUnitTest.java index 0ba45455..41651791 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/DefaultCurieProviderUnitTest.java @@ -27,9 +27,6 @@ import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; import org.springframework.hateoas.UriTemplate; -import org.springframework.hateoas.mediatype.hal.CurieProvider; -import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider; -import org.springframework.hateoas.mediatype.hal.HalLinkRelation; import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider.Curie; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; @@ -47,6 +44,7 @@ public class DefaultCurieProviderUnitTest { CurieProvider provider = new DefaultCurieProvider("acme", URI_TEMPLATE); + @SuppressWarnings("null") @Test(expected = IllegalArgumentException.class) public void preventsNullCurieName() { new DefaultCurieProvider(null, URI_TEMPLATE); @@ -57,6 +55,7 @@ public class DefaultCurieProviderUnitTest { new DefaultCurieProvider("", URI_TEMPLATE); } + @SuppressWarnings("null") @Test(expected = IllegalArgumentException.class) public void preventsNullUriTemplateName() { new DefaultCurieProvider("acme", null); diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilderUnitTest.java index 580a5938..620a3788 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/HalEmbeddedBuilderUnitTest.java @@ -26,10 +26,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.UriTemplate; -import org.springframework.hateoas.mediatype.hal.CurieProvider; -import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider; -import org.springframework.hateoas.mediatype.hal.HalEmbeddedBuilder; -import org.springframework.hateoas.mediatype.hal.HalLinkRelation; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.core.EmbeddedWrapper; import org.springframework.hateoas.server.core.EmbeddedWrappers; @@ -55,7 +51,7 @@ public class HalEmbeddedBuilderUnitTest { @Test public void rendersSingleElementsWithSingleEntityRel() { - Map map = setUpBuilder(null, "foo", 1L); + Map map = setUpBuilder(CurieProvider.NONE, "foo", 1L); assertThat(map.get(uncuried("string"))).isEqualTo("foo"); assertThat(map.get(uncuried("long"))).isEqualTo(1L); @@ -64,7 +60,7 @@ public class HalEmbeddedBuilderUnitTest { @Test public void rendersMultipleElementsWithCollectionResourceRel() { - Map map = setUpBuilder(null, "foo", "bar", 1L); + Map map = setUpBuilder(CurieProvider.NONE, "foo", "bar", 1L); assertThat(map.containsKey(uncuried("string"))).isFalse(); assertThat(map.get(uncuried("long"))).isEqualTo(1L); @@ -77,7 +73,7 @@ public class HalEmbeddedBuilderUnitTest { @Test public void correctlyPilesUpResourcesInCollectionRel() { - Map map = setUpBuilder(null, "foo", "bar", "foobar", 1L); + Map map = setUpBuilder(CurieProvider.NONE, "foo", "bar", "foobar", 1L); assertThat(map.containsKey(uncuried("string"))).isFalse(); assertHasValues(map, uncuried("strings"), "foo", "bar", "foobar"); @@ -90,7 +86,7 @@ public class HalEmbeddedBuilderUnitTest { @Test public void forcesCollectionRelToBeUsedIfConfigured() { - HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, null, true); + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, CurieProvider.NONE, true); builder.add("Sample"); assertThat(builder.asMap().get(uncuried("string"))).isNull(); @@ -105,7 +101,7 @@ public class HalEmbeddedBuilderUnitTest { EmbeddedWrappers wrappers = new EmbeddedWrappers(false); - HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, null, true); + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, CurieProvider.NONE, true); builder.add(wrappers.wrap("MyValue", LinkRelation.of("foo"))); assertThat(builder.asMap().get(uncuried("foo"))).isInstanceOf(String.class); @@ -115,8 +111,9 @@ public class HalEmbeddedBuilderUnitTest { * @see #195 */ @Test(expected = IllegalArgumentException.class) + @SuppressWarnings("null") public void rejectsNullRelProvider() { - new HalEmbeddedBuilder(null, null, false); + new HalEmbeddedBuilder(null, CurieProvider.NONE, false); } /** diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java index dda31775..cc24cee9 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java @@ -16,6 +16,7 @@ package org.springframework.hateoas.mediatype.hal; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.io.IOException; import java.util.ArrayList; @@ -31,19 +32,13 @@ import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.StaticMessageSource; -import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest; -import org.springframework.hateoas.CollectionModel; -import org.springframework.hateoas.EntityModel; -import org.springframework.hateoas.IanaLinkRelations; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.Links; -import org.springframework.hateoas.PagedModel; +import org.springframework.hateoas.*; import org.springframework.hateoas.PagedModel.PageMetadata; -import org.springframework.hateoas.RepresentationModel; -import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLinks; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalHandlerInstantiator; +import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider; +import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider; import org.springframework.hateoas.server.core.EmbeddedWrappers; import com.fasterxml.jackson.databind.ObjectMapper; @@ -85,12 +80,18 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final String SINGLE_WITH_ONE_EXTRA_ATTRIBUTES = "{\"_links\":{\"self\":{\"href\":\"localhost\",\"title\":\"the title\"}}}"; static final String SINGLE_WITH_ALL_EXTRA_ATTRIBUTES = "{\"_links\":{\"self\":{\"href\":\"localhost\",\"hreflang\":\"en\",\"title\":\"the title\",\"type\":\"the type\",\"deprecation\":\"/customers/deprecated\"}}}"; + MessageSource messageSource = mock(MessageSource.class); + @Before public void setUpModule() { + MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource); + LinkRelationProvider provider = new DelegatingLinkRelationProvider(new AnnotationLinkRelationProvider(), + DefaultLinkRelationProvider.INSTANCE); + mapper.registerModule(new Jackson2HalModule()); mapper.setHandlerInstantiator( - new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), null, null, new HalConfiguration())); + new HalHandlerInstantiator(provider, CurieProvider.NONE, accessor, new HalConfiguration())); } /** @@ -227,8 +228,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimplePojo("test1", 1), new Link("localhost"))); - CollectionModel> resources = new CollectionModel<>( - content); + CollectionModel> resources = new CollectionModel<>(content); resources.add(new Link("localhost")); assertThat(write(resources)).isEqualTo(SINGLE_EMBEDDED_RESOURCE_REFERENCE); @@ -240,12 +240,10 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimplePojo("test1", 1), new Link("localhost"))); - CollectionModel> expected = new CollectionModel<>( - content); + CollectionModel> expected = new CollectionModel<>(content); expected.add(new Link("localhost")); - CollectionModel> result = mapper.readValue( - SINGLE_EMBEDDED_RESOURCE_REFERENCE, + CollectionModel> result = mapper.readValue(SINGLE_EMBEDDED_RESOURCE_REFERENCE, mapper.getTypeFactory().constructParametricType(CollectionModel.class, mapper.getTypeFactory().constructParametricType(EntityModel.class, SimplePojo.class))); @@ -268,8 +266,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg CollectionModel> expected = setupResources(); expected.add(new Link("localhost")); - CollectionModel> result = mapper.readValue( - LIST_EMBEDDED_RESOURCE_REFERENCE, + CollectionModel> result = mapper.readValue(LIST_EMBEDDED_RESOURCE_REFERENCE, mapper.getTypeFactory().constructParametricType(CollectionModel.class, mapper.getTypeFactory().constructParametricType(EntityModel.class, SimplePojo.class))); @@ -285,8 +282,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost"))); - CollectionModel> resources = new CollectionModel<>( - content); + CollectionModel> resources = new CollectionModel<>(content); resources.add(new Link("localhost")); assertThat(write(resources)).isEqualTo(ANNOTATED_EMBEDDED_RESOURCE_REFERENCE); @@ -301,14 +297,12 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost"))); - CollectionModel> expected = new CollectionModel<>( - content); + CollectionModel> expected = new CollectionModel<>(content); expected.add(new Link("localhost")); - CollectionModel> result = mapper.readValue( - ANNOTATED_EMBEDDED_RESOURCE_REFERENCE, - mapper.getTypeFactory().constructParametricType(CollectionModel.class, mapper.getTypeFactory() - .constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); + CollectionModel> result = mapper.readValue(ANNOTATED_EMBEDDED_RESOURCE_REFERENCE, + mapper.getTypeFactory().constructParametricType(CollectionModel.class, + mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); assertThat(result).isEqualTo(expected); } @@ -327,10 +321,9 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg @Test public void deserializesMultipleAnnotatedResourceResourcesAsEmbedded() throws Exception { - CollectionModel> result = mapper.readValue( - ANNOTATED_EMBEDDED_RESOURCES_REFERENCE, - mapper.getTypeFactory().constructParametricType(CollectionModel.class, mapper.getTypeFactory() - .constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); + CollectionModel> result = mapper.readValue(ANNOTATED_EMBEDDED_RESOURCES_REFERENCE, + mapper.getTypeFactory().constructParametricType(CollectionModel.class, + mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); assertThat(result).isEqualTo(setupAnnotatedResources()); } @@ -348,10 +341,9 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg */ @Test public void deserializesPagedResource() throws Exception { - PagedModel> result = mapper.readValue( - ANNOTATED_PAGED_RESOURCES, - mapper.getTypeFactory().constructParametricType(PagedModel.class, mapper.getTypeFactory() - .constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); + PagedModel> result = mapper.readValue(ANNOTATED_PAGED_RESOURCES, + mapper.getTypeFactory().constructParametricType(PagedModel.class, + mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); assertThat(result).isEqualTo(setupAnnotatedPagedResources()); } @@ -362,8 +354,8 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg @Test public void rendersCuriesCorrectly() throws Exception { - CollectionModel resources = new CollectionModel<>(Collections.emptySet(), - new Link("foo"), new Link("bar", "myrel")); + CollectionModel resources = new CollectionModel<>(Collections.emptySet(), new Link("foo"), + new Link("bar", "myrel")); assertThat(getCuriedObjectMapper().writeValueAsString(resources)).isEqualTo(CURIED_DOCUMENT); } @@ -418,7 +410,8 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg } }; - assertThat(getCuriedObjectMapper(provider, null).writeValueAsString(resources)).isEqualTo(MULTIPLE_CURIES_DOCUMENT); + assertThat(getCuriedObjectMapper(provider, messageSource).writeValueAsString(resources)) + .isEqualTo(MULTIPLE_CURIES_DOCUMENT); } /** @@ -456,7 +449,8 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg @Test public void rendersSingleLinkAsArrayWhenConfigured() throws Exception { - mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), null, null, + mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), CurieProvider.NONE, + new MessageSourceAccessor(messageSource), new HalConfiguration().withRenderSingleLinks(RenderSingleLinks.AS_ARRAY))); RepresentationModel resourceSupport = new RepresentationModel<>(); @@ -488,7 +482,10 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg @Test // #811 public void rendersSpecificRelWithSingleLinkAsArrayIfConfigured() throws Exception { - mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), null, null, + AnnotationLinkRelationProvider provider = new AnnotationLinkRelationProvider(); + MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource); + + mapper.setHandlerInstantiator(new HalHandlerInstantiator(provider, CurieProvider.NONE, accessor, new HalConfiguration().withRenderSingleLinksFor("foo", RenderSingleLinks.AS_ARRAY))); RepresentationModel resource = new RepresentationModel<>(); @@ -498,14 +495,14 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg .isEqualTo("{\"_links\":{\"foo\":[{\"href\":\"/some-href\"}]}}"); } - private static void verifyResolvedTitle(String resourceBundleKey) throws Exception { + private void verifyResolvedTitle(String resourceBundleKey) throws Exception { LocaleContextHolder.setLocale(Locale.US); StaticMessageSource messageSource = new StaticMessageSource(); messageSource.addMessage(resourceBundleKey, Locale.US, "Foobar's title!"); - ObjectMapper objectMapper = getCuriedObjectMapper(null, messageSource); + ObjectMapper objectMapper = getCuriedObjectMapper(CurieProvider.NONE, messageSource); RepresentationModel resource = new RepresentationModel<>(); resource.add(new Link("target", "ns:foobar")); @@ -540,19 +537,53 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg return new CollectionModel<>(content); } - private static ObjectMapper getCuriedObjectMapper() { - - return getCuriedObjectMapper(new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}")), - null); + private ObjectMapper getCuriedObjectMapper() { + return getCuriedObjectMapper(new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}"))); } - private static ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) { + private ObjectMapper getCuriedObjectMapper(CurieProvider provider) { + return getCuriedObjectMapper(provider, messageSource); + } + + private ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jackson2HalModule()); mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), provider, - messageSource == null ? null : new MessageSourceAccessor(messageSource))); + new MessageSourceAccessor(messageSource))); return mapper; } + + public enum DefaultLinkRelationProvider implements LinkRelationProvider { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.server.LinkRelationProvider#getItemResourceRelFor(java.lang.Class) + */ + @Override + public LinkRelation getItemResourceRelFor(Class type) { + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.server.LinkRelationProvider#getCollectionResourceRelFor(java.lang.Class) + */ + @Override + public LinkRelation getCollectionResourceRelFor(Class type) { + return LinkRelation.of("content"); + } + + /* + * (non-Javadoc) + * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) + */ + @Override + public boolean supports(LookupContext delimiter) { + return delimiter.isCollectionRelationLookup(); + } + } } diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java index 8f6e0a0a..8d85af02 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.*; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -28,14 +29,14 @@ import java.io.OutputStream; import org.junit.Before; import org.junit.Test; +import org.springframework.context.MessageSource; +import org.springframework.context.support.MessageSourceAccessor; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.RepresentationModel; -import org.springframework.hateoas.mediatype.hal.forms.HalFormsDocument; -import org.springframework.hateoas.mediatype.hal.forms.HalFormsProperty; -import org.springframework.hateoas.mediatype.hal.forms.HalFormsTemplate; -import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule; +import org.springframework.hateoas.mediatype.hal.CurieProvider; +import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider; import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; @@ -59,6 +60,9 @@ public class HalFormsMessageConverterUnitTest { this.mapper = new ObjectMapper(); this.mapper.registerModule(new Jackson2HalFormsModule()); + this.mapper.setHandlerInstantiator( + new Jackson2HalFormsModule.HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), CurieProvider.NONE, + new MessageSourceAccessor(mock(MessageSource.class)), true, new HalFormsConfiguration())); TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( RepresentationModel.class); diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java index bc39eb44..132a9b4e 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java @@ -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. @@ -16,6 +16,7 @@ package org.springframework.hateoas.mediatype.hal.forms; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.io.IOException; import java.util.ArrayList; @@ -43,10 +44,13 @@ import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider; +import org.springframework.hateoas.mediatype.hal.Jackson2HalIntegrationTest; import org.springframework.hateoas.mediatype.hal.SimpleAnnotatedPojo; import org.springframework.hateoas.mediatype.hal.SimplePojo; import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule.HalFormsHandlerInstantiator; +import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider; +import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider; import org.springframework.hateoas.server.core.EmbeddedWrappers; import org.springframework.hateoas.support.MappingUtils; @@ -56,6 +60,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; /** * @author Greg Turnquist + * @author Oliver Drotbohm */ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegrationTest { @@ -64,12 +69,17 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling new Link("bar", IanaLinkRelations.PREV) // ); + MessageSource messageSource = mock(MessageSource.class); + @Before public void setUpModule() { + LinkRelationProvider provider = new DelegatingLinkRelationProvider(new AnnotationLinkRelationProvider(), + Jackson2HalIntegrationTest.DefaultLinkRelationProvider.INSTANCE); + mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator( // - new AnnotationLinkRelationProvider(), null, null, true, new HalFormsConfiguration())); + provider, CurieProvider.NONE, new MessageSourceAccessor(messageSource), true, new HalFormsConfiguration())); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); } @@ -118,8 +128,7 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling @Test public void rendersResource() throws Exception { - EntityModel resource = new EntityModel<>(new SimplePojo("test1", 1), - new Link("localhost")); + EntityModel resource = new EntityModel<>(new SimplePojo("test1", 1), new Link("localhost")); assertThat(write(resource)) .isEqualTo(MappingUtils.read(new ClassPathResource("simple-resource-unwrapped.json", getClass()))); @@ -128,8 +137,7 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling @Test public void deserializesResource() throws IOException { - EntityModel expected = new EntityModel<>(new SimplePojo("test1", 1), - new Link("localhost")); + EntityModel expected = new EntityModel<>(new SimplePojo("test1", 1), new Link("localhost")); EntityModel result = mapper.readValue( MappingUtils.read(new ClassPathResource("simple-resource-unwrapped.json", getClass())), @@ -176,8 +184,7 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimplePojo("test1", 1), new Link("localhost"))); - CollectionModel> resources = new CollectionModel<>( - content); + CollectionModel> resources = new CollectionModel<>(content); resources.add(new Link("localhost")); assertThat(write(resources)) @@ -190,8 +197,7 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimplePojo("test1", 1), new Link("localhost"))); - CollectionModel> expected = new CollectionModel<>( - content); + CollectionModel> expected = new CollectionModel<>(content); expected.add(new Link("localhost")); CollectionModel> result = mapper.readValue( @@ -232,8 +238,7 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost"))); - CollectionModel> resources = new CollectionModel<>( - content); + CollectionModel> resources = new CollectionModel<>(content); resources.add(new Link("localhost")); assertThat(write(resources)) @@ -246,14 +251,13 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling List> content = new ArrayList<>(); content.add(new EntityModel<>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost"))); - CollectionModel> expected = new CollectionModel<>( - content); + CollectionModel> expected = new CollectionModel<>(content); expected.add(new Link("localhost")); CollectionModel> result = mapper.readValue( MappingUtils.read(new ClassPathResource("annotated-resource-resources.json", getClass())), - mapper.getTypeFactory().constructParametricType(CollectionModel.class, mapper.getTypeFactory() - .constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); + mapper.getTypeFactory().constructParametricType(CollectionModel.class, + mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); assertThat(result).isEqualTo(expected); } @@ -269,8 +273,8 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling CollectionModel> result = mapper.readValue( MappingUtils.read(new ClassPathResource("annotated-embedded-resources-reference.json", getClass())), - mapper.getTypeFactory().constructParametricType(CollectionModel.class, mapper.getTypeFactory() - .constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); + mapper.getTypeFactory().constructParametricType(CollectionModel.class, + mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); assertThat(result).isEqualTo(setupAnnotatedResources()); } @@ -285,8 +289,8 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling public void deserializesPagedResource() throws Exception { PagedModel> result = mapper.readValue( MappingUtils.read(new ClassPathResource("annotated-paged-resources.json", getClass())), - mapper.getTypeFactory().constructParametricType(PagedModel.class, mapper.getTypeFactory() - .constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); + mapper.getTypeFactory().constructParametricType(PagedModel.class, + mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class))); assertThat(result).isEqualTo(setupAnnotatedPagedResources()); } @@ -294,8 +298,8 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling @Test public void rendersCuriesCorrectly() throws Exception { - CollectionModel resources = new CollectionModel<>(Collections.emptySet(), - new Link("foo"), new Link("bar", "myrel")); + CollectionModel resources = new CollectionModel<>(Collections.emptySet(), new Link("foo"), + new Link("bar", "myrel")); assertThat(getCuriedObjectMapper().writeValueAsString(resources)) .isEqualTo(MappingUtils.read(new ClassPathResource("curied-document.json", getClass()))); @@ -341,7 +345,7 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling } }; - assertThat(getCuriedObjectMapper(provider, null).writeValueAsString(resources)) + assertThat(getCuriedObjectMapper(provider, messageSource).writeValueAsString(resources)) .isEqualTo(MappingUtils.read(new ClassPathResource("multiple-curies-document.json", getClass()))); } @@ -396,7 +400,7 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling StaticMessageSource messageSource = new StaticMessageSource(); messageSource.addMessage(resourceBundleKey, Locale.US, "Foobar's title!"); - ObjectMapper objectMapper = getCuriedObjectMapper(null, messageSource); + ObjectMapper objectMapper = getCuriedObjectMapper(CurieProvider.NONE, messageSource); RepresentationModel resource = new RepresentationModel<>(); resource.add(new Link("target", "ns:foobar")); @@ -429,19 +433,19 @@ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2Marshalling content.add(new EntityModel<>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost"))); content.add(new EntityModel<>(new SimpleAnnotatedPojo("test2", 2), new Link("localhost"))); - return new PagedModel<>(content, new PagedModel.PageMetadata(2, 0, 4), - PAGINATION_LINKS); + return new PagedModel<>(content, new PagedModel.PageMetadata(2, 0, 4), PAGINATION_LINKS); } - private static ObjectMapper getCuriedObjectMapper() { + private ObjectMapper getCuriedObjectMapper() { return getCuriedObjectMapper(new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}")), - null); + messageSource); } - private static ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) { + private ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) { ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), provider, messageSource == null ? null : new MessageSourceAccessor(messageSource), true, new HalFormsConfiguration())); diff --git a/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java b/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java index 1d84354f..494376b6 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java @@ -16,6 +16,7 @@ package org.springframework.hateoas.mediatype.vnderror; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.io.FileInputStream; import java.io.IOException; @@ -25,8 +26,11 @@ import java.nio.charset.Charset; import org.junit.Before; import org.junit.Test; +import org.springframework.context.MessageSource; +import org.springframework.context.support.MessageSourceAccessor; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.Link; +import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; import org.springframework.hateoas.mediatype.vnderrors.VndErrors; import org.springframework.hateoas.mediatype.vnderrors.VndErrors.VndError; @@ -63,7 +67,8 @@ public class VndErrorsMarshallingTest { jackson2Mapper = new com.fasterxml.jackson.databind.ObjectMapper(); jackson2Mapper.registerModule(new Jackson2HalModule()); - jackson2Mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null, null)); + jackson2Mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, CurieProvider.NONE, + new MessageSourceAccessor(mock(MessageSource.class)))); jackson2Mapper.configure(SerializationFeature.INDENT_OUTPUT, true); VndError error = new VndError("42", "Validation failed!", // diff --git a/src/test/java/org/springframework/hateoas/server/core/DelegatingRelProviderUnitTest.java b/src/test/java/org/springframework/hateoas/server/core/DelegatingRelProviderUnitTest.java index b31d90d9..a6e8e81f 100755 --- a/src/test/java/org/springframework/hateoas/server/core/DelegatingRelProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/server/core/DelegatingRelProviderUnitTest.java @@ -20,7 +20,7 @@ import static org.assertj.core.api.Assertions.*; import org.junit.Test; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.server.LinkRelationProvider; -import org.springframework.plugin.core.PluginRegistry; +import org.springframework.hateoas.server.LinkRelationProvider.LookupContext; /** * Unit tests for {@link DelegatingLinkRelationProvider}. @@ -32,22 +32,20 @@ public class DelegatingRelProviderUnitTest { @Test public void foo() { - PluginRegistry> registry = PluginRegistry.of(new AnnotationLinkRelationProvider(), + LinkRelationProvider delegatingProvider = new DelegatingLinkRelationProvider(new AnnotationLinkRelationProvider(), new DefaultLinkRelationProvider()); - LinkRelationProvider delegatingProvider = new DelegatingLinkRelationProvider(registry); - - assertThat(delegatingProvider.supports(Sample.class)).isTrue(); + assertThat(delegatingProvider.supports(LookupContext.forCollectionResourceRelLookup(Sample.class))).isTrue(); + assertThat(delegatingProvider.supports(LookupContext.forItemResourceRelLookup(Sample.class))).isTrue(); assertThat(delegatingProvider.getItemResourceRelFor(Sample.class)).isEqualTo(LinkRelation.of("foo")); assertThat(delegatingProvider.getCollectionResourceRelFor(Sample.class)).isEqualTo(LinkRelation.of("bar")); - assertThat(delegatingProvider.supports(String.class)).isTrue(); + assertThat(delegatingProvider.supports(LookupContext.forCollectionResourceRelLookup(String.class))).isTrue(); + assertThat(delegatingProvider.supports(LookupContext.forItemResourceRelLookup(String.class))).isTrue(); assertThat(delegatingProvider.getItemResourceRelFor(String.class)).isEqualTo(LinkRelation.of("string")); assertThat(delegatingProvider.getCollectionResourceRelFor(String.class)).isEqualTo(LinkRelation.of("stringList")); } - @Relation(value = "foo", collectionRelation = "bar") - static class Sample { - - } + @Relation(itemRelation = "foo", collectionRelation = "bar") + static class Sample {} }