From 9e5db1874e94163d2bc6ddc971e58d4641ffdcce Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Tue, 12 Feb 2019 22:04:36 +0100 Subject: [PATCH] #812 - General overhaul and refactorings. Cleanups in Affordance API and implementations of hypermedia type (de)serializers. Added a lot more domain methods and types to Link, ResourceSupport etc. to be able to move a lot of representation building logic into those. AffordanceModelFactory is not a Spring Plugin anymore as that functionality is not needed currently as we statically look up all factories via the SpringFactoriesLoader mechanism. Redesigned LinkRelation to become a first class abstraction in the codebase. IanaLinkRelations is now a collection of constants. Link now keeps a LinkRelation instance around instead of a plain String. Tweaked LinkDiscoverer API to return Optional and Links instead of nullable Link and List. Tweaked API of CurieProvider to make use of the newly introduced HalLinkRelation based on the general LinkRelation. Additional fixes for the Kotlin extension functions. Make Kotlin build setup compile with JDK 8. Removed Objects helper class in favor of Spring's already existing Assert and it's usage in ResourceAssemblerSupport. --- pom.xml | 5 +- .../springframework/hateoas/Affordance.java | 14 +- .../hateoas/AffordanceModel.java | 34 +- .../hateoas/AffordanceModelFactory.java | 22 +- .../springframework/hateoas/EntityLinks.java | 6 +- ...nkRelation.java => IanaLinkRelations.java} | 395 ++++----- .../org/springframework/hateoas/IanaRels.java | 4 +- .../org/springframework/hateoas/Link.java | 179 ++-- .../springframework/hateoas/LinkBuilder.java | 30 +- .../hateoas/LinkDiscoverer.java | 77 +- .../hateoas/LinkDiscoverers.java | 36 +- .../springframework/hateoas/LinkRelation.java | 48 + .../org/springframework/hateoas/Links.java | 385 +++++++-- .../hateoas/PagedResources.java | 44 +- .../springframework/hateoas/RelProvider.java | 10 +- .../hateoas/ResourceSupport.java | 48 +- .../springframework/hateoas/Resources.java | 17 +- .../hateoas/StringLinkRelation.java | 78 ++ .../springframework/hateoas/UriTemplate.java | 45 +- .../springframework/hateoas/VndErrors.java | 27 +- .../springframework/hateoas/client/Rels.java | 39 +- .../hateoas/client/Traverson.java | 9 +- .../collectionjson/CollectionJson.java | 71 +- .../CollectionJsonAffordanceModel.java | 51 +- .../CollectionJsonDocument.java | 16 +- .../collectionjson/CollectionJsonItem.java | 53 +- .../CollectionJsonLinkDiscoverer.java | 113 ++- .../Jackson2CollectionJsonModule.java | 818 ++++++++---------- .../collectionjson/ResourceSupportMixin.java | 15 +- .../ConverterRegisteringWebMvcConfigurer.java | 52 +- .../hateoas/core/AnnotationRelProvider.java | 13 +- .../hateoas/core/DefaultRelProvider.java | 15 +- .../hateoas/core/DelegatingRelProvider.java | 7 +- .../hateoas/core/EmbeddedWrapper.java | 19 +- .../hateoas/core/EmbeddedWrappers.java | 84 +- .../hateoas/core/EvoInflectorRelProvider.java | 7 +- .../hateoas/core/JsonPathLinkDiscoverer.java | 66 +- .../hateoas/core/LinkBuilderSupport.java | 21 +- .../springframework/hateoas/core/Objects.java | 35 - .../hateoas/hal/CurieProvider.java | 13 +- .../hateoas/hal/DefaultCurieProvider.java | 29 +- .../hateoas/hal/HalEmbeddedBuilder.java | 62 +- .../hateoas/hal/HalLinkDiscoverer.java | 41 +- .../hateoas/hal/HalLinkRelation.java | 173 ++++ .../hateoas/hal/Jackson2HalModule.java | 128 ++- .../hateoas/hal/LinkMixin.java | 2 +- .../hateoas/hal/ResourceSupportMixin.java | 5 +- .../hateoas/hal/ResourcesMixin.java | 2 +- .../hal/forms/HalFormsAffordanceModel.java | 38 +- .../forms/HalFormsAffordanceModelFactory.java | 7 +- .../hateoas/hal/forms/HalFormsDocument.java | 45 +- .../hal/forms/HalFormsLinkDiscoverer.java | 32 +- .../hateoas/hal/forms/HalFormsProperty.java | 6 +- .../hal/forms/HalFormsSerializers.java | 53 +- .../hal/forms/Jackson2HalFormsModule.java | 54 +- .../hateoas/mvc/ControllerRelProvider.java | 11 +- .../mvc/HeaderLinksResponseEntity.java | 18 +- .../hateoas/mvc/JacksonSerializers.java | 10 +- .../hateoas/mvc/ResourceAssemblerSupport.java | 24 +- .../mvc/SpringMvcAffordanceBuilder.java | 31 +- .../hateoas/mvc/TypeReferences.java | 36 +- .../hateoas/uber/Jackson2UberModule.java | 268 +++--- .../hateoas/uber/PagedResourcesMixin.java | 32 - .../hateoas/uber/ResourceMixin.java | 32 - .../hateoas/uber/ResourceSupportMixin.java | 32 - .../hateoas/uber/ResourcesMixin.java | 32 - .../springframework/hateoas/uber/Uber.java | 15 +- .../hateoas/uber/UberAction.java | 21 +- .../uber/UberAffordanceModelFactory.java | 9 +- .../hateoas/uber/UberData.java | 220 ++--- .../hateoas/uber/UberLinkDiscoverer.java | 74 +- .../hateoas/mvc/LinkBuilderDsl.kt | 11 +- .../hateoas/IanaLinkRelationUnitTest.java | 34 +- .../hateoas/Jackson2LinkIntegrationTest.java | 4 +- .../hateoas/LinkIntegrationTest.java | 4 +- .../hateoas/LinkRelationUnitTest.java | 22 +- .../springframework/hateoas/LinkUnitTest.java | 182 ++-- .../hateoas/LinksUnitTest.java | 40 +- .../hateoas/PagedResourcesUnitTest.java | 4 +- .../hateoas/ResourceSupportUnitTest.java | 16 +- .../hateoas/StringLinkRelationUnitTests.java | 58 ++ .../hateoas/UriTemplateUnitTest.java | 48 +- .../hateoas/VndErrorsMarshallingTest.java | 4 +- .../alps/AlpsLinkDiscoverUnitTest.java | 10 +- .../hateoas/client/Server.java | 11 +- .../hateoas/client/TraversonTest.java | 118 ++- .../CollectionJsonLinkDiscovererUnitTest.java | 50 +- .../CollectionJsonSpecTest.java | 75 +- .../CollectionJsonWebMvcIntegrationTest.java | 19 +- ...Jackson2CollectionJsonIntegrationTest.java | 62 +- .../JacksonSerializationTest.java | 29 +- ...nableHypermediaSupportIntegrationTest.java | 27 +- .../core/AbstractLinkDiscovererUnitTest.java | 25 +- .../core/DelegatingRelProviderUnitTest.java | 9 +- .../core/EmbeddedWrappersUnitTest.java | 14 +- .../core/EvoInflectorRelProviderUnitTest.java | 8 +- .../hal/DefaultCurieProviderUnitTest.java | 42 +- .../hal/HalEmbeddedBuilderUnitTest.java | 64 +- .../hal/HalLinkDiscovererUnitTest.java | 37 +- .../hal/Jackson2HalIntegrationTest.java | 4 +- .../forms/HalFormsLinkDiscovererUnitTest.java | 30 +- .../HalFormsMessageConverterUnitTest.java | 8 +- .../HalFormsValidationIntegrationTest.java | 8 +- .../forms/HalFormsWebMvcIntegrationTest.java | 4 +- .../Jackson2HalFormsIntegrationTest.java | 8 +- .../ControllerLinkBuilderFactoryUnitTest.java | 18 +- .../mvc/ControllerLinkBuilderUnitTest.java | 31 +- .../hateoas/mvc/ForwardedHeaderUnitTest.java | 5 +- ...iableResourceAssemblerSupportUnitTest.java | 4 +- .../MultiMediaTypeWebMvcIntegrationTest.java | 4 +- .../SpringMvcAffordanceBuilderUnitTest.java | 88 -- .../uber/Jackson2UberIntegrationTest.java | 30 +- .../uber/UberWebMvcIntegrationTest.java | 8 +- .../mvc/AffordanceBuilderDslUnitTest.kt | 4 +- .../hateoas/mvc/LinkBuilderDslUnitTest.kt | 12 +- .../collectionjson/resource-template.json | 20 - 116 files changed, 3282 insertions(+), 2594 deletions(-) rename src/main/java/org/springframework/hateoas/{IanaLinkRelation.java => IanaLinkRelations.java} (59%) create mode 100644 src/main/java/org/springframework/hateoas/StringLinkRelation.java delete mode 100644 src/main/java/org/springframework/hateoas/core/Objects.java create mode 100644 src/main/java/org/springframework/hateoas/hal/HalLinkRelation.java delete mode 100644 src/main/java/org/springframework/hateoas/uber/PagedResourcesMixin.java delete mode 100644 src/main/java/org/springframework/hateoas/uber/ResourceMixin.java delete mode 100644 src/main/java/org/springframework/hateoas/uber/ResourceSupportMixin.java delete mode 100644 src/main/java/org/springframework/hateoas/uber/ResourcesMixin.java create mode 100644 src/test/java/org/springframework/hateoas/StringLinkRelationUnitTests.java delete mode 100644 src/test/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilderUnitTest.java delete mode 100644 src/test/resources/org/springframework/hateoas/collectionjson/resource-template.json diff --git a/pom.xml b/pom.xml index 6e698f5b..3acca4ca 100644 --- a/pom.xml +++ b/pom.xml @@ -631,7 +631,7 @@ 3.1.0 provided - + com.tngtech.archunit archunit @@ -729,6 +729,9 @@ org.jetbrains.kotlin kotlin-maven-plugin ${kotlin.version} + + ${source.level} + compile diff --git a/src/main/java/org/springframework/hateoas/Affordance.java b/src/main/java/org/springframework/hateoas/Affordance.java index c7a1edac..d5de9a2e 100644 --- a/src/main/java/org/springframework/hateoas/Affordance.java +++ b/src/main/java/org/springframework/hateoas/Affordance.java @@ -15,6 +15,8 @@ */ package org.springframework.hateoas; +import lombok.AccessLevel; +import lombok.Getter; import lombok.Value; import java.util.HashMap; @@ -42,8 +44,18 @@ public class Affordance { /** * Collection of {@link AffordanceModel}s related to this affordance. */ - private final Map affordanceModels = new HashMap<>(); + private final @Getter(AccessLevel.PACKAGE) Map affordanceModels = new HashMap<>(); + /** + * Creates a new {@link Affordance}. + * + * @param name + * @param link + * @param httpMethod + * @param inputType + * @param queryMethodParameters + * @param outputType + */ public Affordance(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType) { diff --git a/src/main/java/org/springframework/hateoas/AffordanceModel.java b/src/main/java/org/springframework/hateoas/AffordanceModel.java index 76451c46..8d8df39a 100644 --- a/src/main/java/org/springframework/hateoas/AffordanceModel.java +++ b/src/main/java/org/springframework/hateoas/AffordanceModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2018-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. @@ -23,11 +23,13 @@ import java.util.List; import org.springframework.core.ResolvableType; import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; /** * Collection of attributes needed to render any form of hypermedia. - * + * * @author Greg Turnquist + * @author Oliver Drotbohm */ @EqualsAndHashCode @AllArgsConstructor @@ -58,7 +60,7 @@ public abstract class AffordanceModel { * Collection of {@link QueryParameter}s to interrogate a resource. */ private List queryMethodParameters; - + /** * Response body domain type. */ @@ -72,4 +74,30 @@ public abstract class AffordanceModel { public String getURI() { return this.link.expand().getHref(); } + + /** + * Returns whether the {@link Affordance} has the given {@link HttpMethod}. + * + * @param method must not be {@literal null}. + * @return + */ + public boolean hasHttpMethod(HttpMethod method) { + + Assert.notNull(method, "HttpMethod must not be null!"); + + return this.httpMethod.equals(method); + } + + /** + * Returns whether the {@link Affordance} points to the target of the given {@link Link}. + * + * @param link must not be {@literal null}. + * @return + */ + public boolean pointsToTargetOf(Link link) { + + Assert.notNull(link, "Link must not be null!"); + + return getURI().equals(link.expand().getHref()); + } } diff --git a/src/main/java/org/springframework/hateoas/AffordanceModelFactory.java b/src/main/java/org/springframework/hateoas/AffordanceModelFactory.java index a7434480..fca05ff9 100644 --- a/src/main/java/org/springframework/hateoas/AffordanceModelFactory.java +++ b/src/main/java/org/springframework/hateoas/AffordanceModelFactory.java @@ -16,27 +16,23 @@ package org.springframework.hateoas; import java.util.List; -import java.util.Optional; import org.springframework.core.ResolvableType; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.plugin.core.Plugin; /** * @author Greg Turnquist * @author Oliver Gierke */ -public interface AffordanceModelFactory extends Plugin { +public interface AffordanceModelFactory { /** * Declare the {@link MediaType} this factory supports. * * @return */ - default MediaType getMediaType() { - return null; - } + MediaType getMediaType(); /** * Look up the {@link AffordanceModel} for this factory. @@ -51,18 +47,4 @@ public interface AffordanceModelFactory extends Plugin { */ AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType); - - /** - * Returns if a plugin should be invoked according to the given delimiter. - * - * @param delimiter - * @return if the plugin should be invoked - */ - @Override - default boolean supports(MediaType delimiter) { - - return Optional.ofNullable(getMediaType()) // - .map(mediaType -> mediaType.equals(delimiter)) // - .orElse(false); - } } diff --git a/src/main/java/org/springframework/hateoas/EntityLinks.java b/src/main/java/org/springframework/hateoas/EntityLinks.java index 26d31363..d7c7bac2 100644 --- a/src/main/java/org/springframework/hateoas/EntityLinks.java +++ b/src/main/java/org/springframework/hateoas/EntityLinks.java @@ -71,7 +71,7 @@ public interface EntityLinks extends Plugin> { /** * Creates a {@link Link} pointing to the collection resource of the given type. The relation type of the link will be - * determined by the implementation class and should be defaulted to {@link IanaLinkRelation#SELF}. + * determined by the implementation class and should be defaulted to {@link IanaLinkRelations#SELF}. * * @param type the entity type to point to, must not be {@literal null}. * @return the {@link Link} pointing to the collection resource exposed for the given entity. Will never be @@ -82,7 +82,7 @@ public interface EntityLinks extends Plugin> { /** * Creates a {@link Link} pointing to single resource backing the given entity type and id. The relation type of the - * link will be determined by the implementation class and should be defaulted to {@link IanaLinkRelation#SELF}. + * link will be determined by the implementation class and should be defaulted to {@link IanaLinkRelations#SELF}. * * @param type the entity type to point to, must not be {@literal null}. * @param id the identifier of the entity of the given type @@ -94,7 +94,7 @@ public interface EntityLinks extends Plugin> { /** * Creates a {@link Link} pointing to single resource backing the given entity. The relation type of the link will be - * determined by the implementation class and should be defaulted to {@link IanaLinkRelation#SELF}. + * determined by the implementation class and should be defaulted to {@link IanaLinkRelations#SELF}. * * @param entity the entity type to point to, must not be {@literal null}. * @return the {@link Link} pointing to the resource exposed for the given entity. Will never be {@literal null}. diff --git a/src/main/java/org/springframework/hateoas/IanaLinkRelation.java b/src/main/java/org/springframework/hateoas/IanaLinkRelations.java similarity index 59% rename from src/main/java/org/springframework/hateoas/IanaLinkRelation.java rename to src/main/java/org/springframework/hateoas/IanaLinkRelations.java index 5eefb4fe..3fac900e 100644 --- a/src/main/java/org/springframework/hateoas/IanaLinkRelation.java +++ b/src/main/java/org/springframework/hateoas/IanaLinkRelations.java @@ -15,167 +15,172 @@ */ package org.springframework.hateoas; +import lombok.experimental.UtilityClass; + import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; +import org.springframework.util.ReflectionUtils; + /** * Capture standard IANA-based link relations. * * @see https://www.iana.org/assignments/link-relations/link-relations.xhtml * @see https://tools.ietf.org/html/rfc8288 * @see https://github.com/link-relations/registry - * * @author Greg Turnquist * @author Roland Kulcsár + * @author Oliver Gierke * @since 1.0 */ -public enum IanaLinkRelation implements LinkRelation { +@UtilityClass +public class IanaLinkRelations { /** * Refers to a resource that is the subject of the link's context. * * @see https://tools.ietf.org/html/rfc6903 */ - ABOUT("about"), + public static final LinkRelation ABOUT = LinkRelation.of("about"); /** * Refers to a substitute for this context * * @see http://www.w3.org/TR/html5/links.html#link-type-alternate */ - ALTERNATE("alternate"), + public static final LinkRelation ALTERNATE = LinkRelation.of("alternate"); /** * Refers to an appendix. - * + * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - APPENDIX("appendix"), + public static final LinkRelation APPENDIX = LinkRelation.of("appendix"); /** * Refers to a collection of records, documents, or other materials of historical interest. * * @see http://www.w3.org/TR/2011/WD-html5-20110113/links.html#rel-archives */ - ARCHIVES("archives"), + public static final LinkRelation ARCHIVES = LinkRelation.of("archives"); /** * Refers to the context's author. * * @see http://www.w3.org/TR/html5/links.html#link-type-author */ - AUTHOR("author"), + public static final LinkRelation AUTHOR = LinkRelation.of("author"); /** * Identifies the entity that blocks access to a resource following receipt of a legal demand. - * + * * @see https://tools.ietf.org/html/rfc7725 */ - BLOCKED_BY("blocked-by"), + public static final LinkRelation BLOCKED_BY = LinkRelation.of("blocked-by"); /** * Gives a permanent link to use for bookmarking purposes. * * @see http://www.w3.org/TR/html5/links.html#link-type-bookmark */ - BOOKMARK("bookmark"), + public static final LinkRelation BOOKMARK = LinkRelation.of("bookmark"); /** * Designates the preferred version of a resource (the IRI and its contents). * * @see https://tools.ietf.org/html/rfc6596 */ - CANONICAL("canonical"), + public static final LinkRelation CANONICAL = LinkRelation.of("canonical"); /** * Refers to a chapter in a collection of resources. * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - CHAPTER("chapter"), + public static final LinkRelation CHAPTER = LinkRelation.of("chapter"); /** * Indicates that the link target is preferred over the link context for the purpose of referencing. * * @see https://datatracker.ietf.org/doc/draft-vandesompel-citeas/ */ - CITE_AS("cite-as"), + public static final LinkRelation CITE_AS = LinkRelation.of("cite-as"); /** * The target IRI points to a resource which represents the collection resource for the context IRI. * * @see https://tools.ietf.org/html/rfc6573 */ - COLLECTION("collection"), + public static final LinkRelation COLLECTION = LinkRelation.of("collection"); /** * Refers to a table of contents. + * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - CONTENTS("contents"), + public static final LinkRelation CONTENTS = LinkRelation.of("contents"); /** - * The document linked to was later converted to the document that contains this link relation. - * For example, an RFC can have a link to the Internet-Draft that became the RFC; in that case, - * the link relation would be "convertedFrom". - * + * The document linked to was later converted to the document that contains this link relation. For example, an RFC + * can have a link to the Internet-Draft that became the RFC; in that case, the link relation would be + * "convertedFrom". + * * @see https://tools.ietf.org/html/rfc7991 */ - CONVERTED_FROM("convertedFrom"), + public static final LinkRelation CONVERTED_FROM = LinkRelation.of("convertedFrom"); /** * Refers to a copyright statement that applies to the link's context. * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - COPYRIGHT("copyright"), + public static final LinkRelation COPYRIGHT = LinkRelation.of("copyright"); /** * The target IRI points to a resource where a submission form can be obtained. * * @see https://tools.ietf.org/html/rfc6861 */ - CREATE_FORM("create-form"), + public static final LinkRelation CREATE_FORM = LinkRelation.of("create-form"); /** * Refers to a resource containing the most recent item(s) in a collection of resources. * * @see https://tools.ietf.org/html/rfc5005 */ - CURRENT("current"), + public static final LinkRelation CURRENT = LinkRelation.of("current"); /** * Refers to a resource providing information about the link's context. * * @see http://www.w3.org/TR/powder-dr/#assoc-linking */ - DESCRIBED_BY("describedBy"), + public static final LinkRelation DESCRIBED_BY = LinkRelation.of("describedBy"); /** * The relationship A 'describes' B asserts that resource A provides a description of resource B. There are no - * constraints on the format or representation of either A or B, neither are there any further constraints on - * either resource. + * constraints on the format or representation of either A or B, neither are there any further constraints on either + * resource. * * @see https://tools.ietf.org/html/rfc6892 */ - DESCRIBES("describes"), + public static final LinkRelation DESCRIBES = LinkRelation.of("describes"); /** - * Refers to a list of patent disclosures made with respect to material for which 'disclosure' relation is - * specified. + * Refers to a list of patent disclosures made with respect to material for which 'disclosure' relation is specified. * * @see https://tools.ietf.org/html/rfc6579 */ - DISCLOSURE("disclosure"), + public static final LinkRelation DISCLOSURE = LinkRelation.of("disclosure"); /** - * Used to indicate an origin that will be used to fetch required resources for the link context, and that the - * user agent ought to resolve as early as possible. + * Used to indicate an origin that will be used to fetch required resources for the link context, and that the user + * agent ought to resolve as early as possible. * * @see https://www.w3.org/TR/resource-hints/ */ - DNS_PREFETCH("dns-prefetch"), + public static final LinkRelation DNS_PREFETCH = LinkRelation.of("dns-prefetch"); /** * Refers to a resource whose available representations are byte-for-byte identical with the corresponding @@ -183,100 +188,100 @@ public enum IanaLinkRelation implements LinkRelation { * * @see https://tools.ietf.org/html/rfc6249 */ - DUPLICATE("duplicate"), + public static final LinkRelation DUPLICATE = LinkRelation.of("duplicate"); /** * Refers to a resource that can be used to edit the link's context. * * @see https://tools.ietf.org/html/rfc5023 */ - EDIT("edit"), + public static final LinkRelation EDIT = LinkRelation.of("edit"); /** * The target IRI points to a resource where a submission form for editing associated resource can be obtained. * * @see https://tools.ietf.org/html/rfc6861 */ - EDIT_FORM("edit-form"), + public static final LinkRelation EDIT_FORM = LinkRelation.of("edit-form"); /** * Refers to a resource that can be used to edit media associated with the link's context. * * @see https://tools.ietf.org/html/rfc5023 */ - EDIT_MEDIA("edit-media"), + public static final LinkRelation EDIT_MEDIA = LinkRelation.of("edit-media"); /** * Identifies a related resource that is potentially large and might require special handling. * * @see https://tools.ietf.org/html/rfc4287 */ - ENCLOSURE("enclosure"), + public static final LinkRelation ENCLOSURE = LinkRelation.of("enclosure"); /** * An IRI that refers to the furthest preceding resource in a series of resources. * * @see https://tools.ietf.org/html/rfc8288 */ - FIRST("first"), + public static final LinkRelation FIRST = LinkRelation.of("first"); /** * Refers to a glossary of terms. * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - GLOSSARY("glossary"), + public static final LinkRelation GLOSSARY = LinkRelation.of("glossary"); /** * Refers to context-sensitive help. * * @see http://www.w3.org/TR/html5/links.html#link-type-help */ - HELP("help"), + public static final LinkRelation HELP = LinkRelation.of("help"); /** * Refers to a resource hosted by the server indicated by the link context. * * @see https://tools.ietf.org/html/rfc6690 */ - HOSTS("hosts"), + public static final LinkRelation HOSTS = LinkRelation.of("hosts"); /** * Refers to a hub that enables registration for notification of updates to the context. * * @see http://pubsubhubbub.googlecode.com */ - HUB("hub"), + public static final LinkRelation HUB = LinkRelation.of("hub"); /** * Refers to an icon representing the link's context. * * @see http://www.w3.org/TR/html5/links.html#link-type-icon */ - ICON("icon"), + public static final LinkRelation ICON = LinkRelation.of("icon"); /** * Refers to an index. * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - INDEX("index"), + public static final LinkRelation INDEX = LinkRelation.of("index"); /** - * refers to a resource associated with a time interval that ends before the beginning of the time interval - * associated with the context resource + * refers to a resource associated with a time interval that ends before the beginning of the time interval associated + * with the context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalAfter section 4.2.21 */ - INTERVAL_AFTER("intervalAfter"), + public static final LinkRelation INTERVAL_AFTER = LinkRelation.of("intervalAfter"); /** - * refers to a resource associated with a time interval that begins after the end of the time interval associated - * with the context resource + * refers to a resource associated with a time interval that begins after the end of the time interval associated with + * the context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalBefore section 4.2.22 */ - INTERVAL_BEFORE("intervalBefore"), + public static final LinkRelation INTERVAL_BEFORE = LinkRelation.of("intervalBefore"); /** * refers to a resource associated with a time interval that begins after the beginning of the time interval @@ -285,15 +290,15 @@ public enum IanaLinkRelation implements LinkRelation { * * @see https://www.w3.org/TR/owl-time/#time:intervalContains section 4.2.23 */ - INTERVAL_CONTAINS("intervalContains"), + public static final LinkRelation INTERVAL_CONTAINS = LinkRelation.of("intervalContains"); /** - * refers to a resource associated with a time interval that begins after the end of the time interval associated - * with the context resource, or ends before the beginning of the time interval associated with the context resource + * refers to a resource associated with a time interval that begins after the end of the time interval associated with + * the context resource, or ends before the beginning of the time interval associated with the context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalDisjoint section 4.2.24 */ - INTERVAL_DISJOINT("intervalDisjoint"), + public static final LinkRelation INTERVAL_DISJOINT = LinkRelation.of("intervalDisjoint"); /** * refers to a resource associated with a time interval that begins before the beginning of the time interval @@ -302,68 +307,68 @@ public enum IanaLinkRelation implements LinkRelation { * * @see https://www.w3.org/TR/owl-time/#time:intervalDuring section 4.2.25 */ - INTERVAL_DURING("intervalDuring"), + public static final LinkRelation INTERVAL_DURING = LinkRelation.of("intervalDuring"); /** * refers to a resource associated with a time interval whose beginning coincides with the beginning of the time - * interval associated with the context resource, and whose end coincides with the end of the time interval - * associated with the context resource + * interval associated with the context resource, and whose end coincides with the end of the time interval associated + * with the context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalEquals section 4.2.26 */ - INTERVAL_EQUALS("intervalEquals"), + public static final LinkRelation INTERVAL_EQUALS = LinkRelation.of("intervalEquals"); /** * refers to a resource associated with a time interval that begins after the beginning of the time interval - * associated with the context resource, and whose end coincides with the end of the time interval associated with - * the context resource + * associated with the context resource, and whose end coincides with the end of the time interval associated with the + * context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalFinishedBy section 4.2.27 */ - INTERVAL_FINISHED_BY("intervalFinishedBy"), + public static final LinkRelation INTERVAL_FINISHED_BY = LinkRelation.of("intervalFinishedBy"); /** * refers to a resource associated with a time interval that begins before the beginning of the time interval - * associated with the context resource, and whose end coincides with the end of the time interval associated with - * the context resource + * associated with the context resource, and whose end coincides with the end of the time interval associated with the + * context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalFinishes section 4.2.28 */ - INTERVAL_FINISHES("intervalFinishes"), + public static final LinkRelation INTERVAL_FINISHES = LinkRelation.of("intervalFinishes"); /** - * refers to a resource associated with a time interval that begins before or is coincident with the beginning - * of the time interval associated with the context resource, and ends after or is coincident with the end of the - * time interval associated with the context resource + * refers to a resource associated with a time interval that begins before or is coincident with the beginning of the + * time interval associated with the context resource, and ends after or is coincident with the end of the time + * interval associated with the context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalIn section 4.2.29 */ - INTERVAL_IN("intervalIn"), + public static final LinkRelation INTERVAL_IN = LinkRelation.of("intervalIn"); /** - * refers to a resource associated with a time interval whose beginning coincides with the end of the time - * interval associated with the context resource + * refers to a resource associated with a time interval whose beginning coincides with the end of the time interval + * associated with the context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalMeets section 4.2.30 */ - INTERVAL_MEETS("intervalMeets"), + public static final LinkRelation INTERVAL_MEETS = LinkRelation.of("intervalMeets"); /** - * refers to a resource associated with a time interval whose beginning coincides with the end of the time - * interval associated with the context resource + * refers to a resource associated with a time interval whose beginning coincides with the end of the time interval + * associated with the context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalMetBy section 4.2.31 */ - INTERVAL_MET_BY("intervalMetBy"), + public static final LinkRelation INTERVAL_MET_BY = LinkRelation.of("intervalMetBy"); /** * refers to a resource associated with a time interval that begins before the beginning of the time interval - * associated with the context resource, and ends after the beginning of the time interval associated with the - * context resource + * associated with the context resource, and ends after the beginning of the time interval associated with the context + * resource * * @see https://www.w3.org/TR/owl-time/#time:intervalOverlappedBy section 4.2.32 */ - INTERVAL_OVERLAPPED_BY("intervalOverlappedBy"), + public static final LinkRelation INTERVAL_OVERLAPPED_BY = LinkRelation.of("intervalOverlappedBy"); /** * refers to a resource associated with a time interval that begins before the end of the time interval associated @@ -371,16 +376,16 @@ public enum IanaLinkRelation implements LinkRelation { * * @see https://www.w3.org/TR/owl-time/#time:intervalOverlaps section 4.2.33 */ - INTERVAL_OVERLAPS("intervalOverlaps"), + public static final LinkRelation INTERVAL_OVERLAPS = LinkRelation.of("intervalOverlaps"); /** * refers to a resource associated with a time interval whose beginning coincides with the beginning of the time - * interval associated with the context resource, and ends before the end of the time interval associated with - * the context resource + * interval associated with the context resource, and ends before the end of the time interval associated with the + * context resource * * @see https://www.w3.org/TR/owl-time/#time:intervalStartedBy section 4.2.34 */ - INTERVAL_STARTED_BY("intervalStartedBy"), + public static final LinkRelation INTERVAL_STARTED_BY = LinkRelation.of("intervalStartedBy"); /** * refers to a resource associated with a time interval whose beginning coincides with the beginning of the time @@ -389,140 +394,140 @@ public enum IanaLinkRelation implements LinkRelation { * * @see https://www.w3.org/TR/owl-time/#time:intervalStarts section 4.2.35 */ - INTERVAL_STARTS("intervalStarts"), + public static final LinkRelation INTERVAL_STARTS = LinkRelation.of("intervalStarts"); /** * The target IRI points to a resource that is a member of the collection represented by the context IRI. * * @see https://tools.ietf.org/html/rfc6573 */ - ITEM("item"), + public static final LinkRelation ITEM = LinkRelation.of("item"); /** * An IRI that refers to the furthest following resource in a series of resources. * * @see https://tools.ietf.org/html/rfc8288 */ - LAST("last"), + public static final LinkRelation LAST = LinkRelation.of("last"); /** * Points to a resource containing the latest (e.g., current) version of the context. * * @see https://tools.ietf.org/html/rfc5829 */ - LATEST_VERSION("latest-version"), + public static final LinkRelation LATEST_VERSION = LinkRelation.of("latest-version"); /** * Refers to a license associated with this context. * * @see https://tools.ietf.org/html/rfc4946 */ - LICENSE("license"), + public static final LinkRelation LICENSE = LinkRelation.of("license"); /** * Refers to further information about the link's context, expressed as a LRDD ("Link-based Resource Descriptor - * Document") resource. See RFC6415 for information about processing this relation type in host-meta documents. - * When used elsewhere, it refers to additional links and other metadata. Multiple instances indicate additional - * LRDD resources. LRDD resources MUST have an "application/xrd+xml" representation, and MAY have others. + * Document") resource. See RFC6415 for information about processing this relation type in host-meta documents. When + * used elsewhere, it refers to additional links and other metadata. Multiple instances indicate additional LRDD + * resources. LRDD resources MUST have an "application/xrd+xml" representation, and MAY have others. * * @see https://tools.ietf.org/html/rfc6415 */ - LRDD("lrdd"), + public static final LinkRelation LRDD = LinkRelation.of("lrdd"); /** * The Target IRI points to a Memento, a fixed resource that will not change state anymore. * * @see https://tools.ietf.org/html/rfc7089 */ - MEMENTO("memento"), + public static final LinkRelation MEMENTO = LinkRelation.of("memento"); /** * Refers to a resource that can be used to monitor changes in an HTTP resource. * * @see https://tools.ietf.org/html/rfc5989 */ - MONITOR("monitor"), + public static final LinkRelation MONITOR = LinkRelation.of("monitor"); /** * Refers to a resource that can be used to monitor changes in a specified group of HTTP resources. * * @see https://tools.ietf.org/html/rfc5989 */ - MONITOR_GROUP("monitor-group"), + public static final LinkRelation MONITOR_GROUP = LinkRelation.of("monitor-group"); /** * Indicates that the link's context is a part of a series, and that the next in the series is the link target. * * @see http://www.w3.org/TR/html5/links.html#link-type-next */ - NEXT("next"), + public static final LinkRelation NEXT = LinkRelation.of("next"); /** * Refers to the immediately following archive resource. * * @see https://tools.ietf.org/html/rfc5005 */ - NEXT_ARCHIVE("next-archive"), + public static final LinkRelation NEXT_ARCHIVE = LinkRelation.of("next-archive"); /** * Indicates that the context‚Äôs original author or publisher does not endorse the link target. * * @see http://www.w3.org/TR/html5/links.html#link-type-nofollow */ - NOFOLLOW("nofollow"), + public static final LinkRelation NOFOLLOW = LinkRelation.of("nofollow"); /** * Indicates that no referrer information is to be leaked when following the link. * * @see http://www.w3.org/TR/html5/links.html#link-type-noreferrer */ - NOREFERRER("noreferrer"), + public static final LinkRelation NOREFERRER = LinkRelation.of("noreferrer"); /** * The Target IRI points to an Original Resource. * * @see https://tools.ietf.org/html/rfc7089 */ - ORIGINAL("original"), + public static final LinkRelation ORIGINAL = LinkRelation.of("original"); /** * Indicates a resource where payment is accepted. * * @see https://tools.ietf.org/html/rfc8288 */ - PAYMENT("payment"), + public static final LinkRelation PAYMENT = LinkRelation.of("payment"); /** * Gives the address of the pingback resource for the link context. * * @see http://www.hixie.ch/specs/pingback/pingback */ - PINGBACK("pingback"), + public static final LinkRelation PINGBACK = LinkRelation.of("pingback"); /** - * Used to indicate an origin that will be used to fetch required resources for the link context. Initiating an - * early connection, which includes the DNS lookup, TCP handshake, and optional TLS negotiation, allows the user - * agent to mask the high latency costs of establishing a connection. + * Used to indicate an origin that will be used to fetch required resources for the link context. Initiating an early + * connection, which includes the DNS lookup, TCP handshake, and optional TLS negotiation, allows the user agent to + * mask the high latency costs of establishing a connection. * * @see https://www.w3.org/TR/resource-hints/ */ - PRECONNECT("preconnect"), + public static final LinkRelation PRECONNECT = LinkRelation.of("preconnect"); /** * Points to a resource containing the predecessor version in the version history. * * @see https://tools.ietf.org/html/rfc5829 */ - PREDECESSOR_VERSION("predecessor-version"), + public static final LinkRelation PREDECESSOR_VERSION = LinkRelation.of("predecessor-version"); /** - * The prefetch link relation type is used to identify a resource that might be required by the next navigation - * from the link context, and that the user agent ought to fetch, such that the user agent can deliver a faster - * response once the resource is requested in the future. - * + * The prefetch link relation type is used to identify a resource that might be required by the next navigation from + * the link context, and that the user agent ought to fetch, such that the user agent can deliver a faster response + * once the resource is requested in the future. + * * @see http://www.w3.org/TR/resource-hints/ */ - PREFETCH("prefetch"), + public static final LinkRelation PREFETCH = LinkRelation.of("prefetch"); /** * Refers to a resource that should be loaded early in the processing of the link's context, without blocking @@ -530,51 +535,51 @@ public enum IanaLinkRelation implements LinkRelation { * * @see http://www.w3.org/TR/preload/ */ - PRELOAD("preload"), + public static final LinkRelation PRELOAD = LinkRelation.of("preload"); /** - * Used to identify a resource that might be required by the next navigation from the link context, and that the - * user agent ought to fetch and execute, such that the user agent can deliver a faster response once the resource - * is requested in the future. + * Used to identify a resource that might be required by the next navigation from the link context, and that the user + * agent ought to fetch and execute, such that the user agent can deliver a faster response once the resource is + * requested in the future. * * @see https://www.w3.org/TR/resource-hints/ */ - PRERENDER("prerender"), + public static final LinkRelation PRERENDER = LinkRelation.of("prerender"); /** * Indicates that the link's context is a part of a series, and that the previous in the series is the link target. * * @see http://www.w3.org/TR/html5/links.html#link-type-prev */ - PREV("prev"), + public static final LinkRelation PREV = LinkRelation.of("prev"); /** * Refers to a resource that provides a preview of the link's context. * * @see https://tools.ietf.org/html/rfc6903, section 3 */ - PREVIEW("preview"), + public static final LinkRelation PREVIEW = LinkRelation.of("preview"); /** - * Refers to the previous resource in an ordered series of resources. Synonym for "prev". + * Refers to the previous resource in an ordered series of resources. Synonym for "prev". * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - PREVIOUS("previous"), + public static final LinkRelation PREVIOUS = LinkRelation.of("previous"); /** * Refers to the immediately preceding archive resource. * * @see https://tools.ietf.org/html/rfc5005 */ - PREV_ARCHIVE("prev-archive"), + public static final LinkRelation PREV_ARCHIVE = LinkRelation.of("prev-archive"); /** * Refers to a privacy policy associated with the link's context. * * @see https://tools.ietf.org/html/rfc6903, section 4 */ - PRIVACY_POLICY("privacy-policy"), + public static final LinkRelation PRIVACY_POLICY = LinkRelation.of("privacy-policy"); /** * Identifying that a resource representation conforms to a certain profile, without affecting the non-profile @@ -582,253 +587,217 @@ public enum IanaLinkRelation implements LinkRelation { * * @see https://tools.ietf.org/html/rfc6906 */ - PROFILE("profile"), + public static final LinkRelation PROFILE = LinkRelation.of("profile"); /** * Identifies a related resource. * * @see https://tools.ietf.org/html/rfc4287 */ - RELATED("related"), + public static final LinkRelation RELATED = LinkRelation.of("related"); /** - * Identifies the root of RESTCONF API as configured on this HTTP server. The "restconf" relation defines the - * root of the API defined in RFC8040. Subsequent revisions of RESTCONF will use alternate relation values to - * support protocol versioning. + * Identifies the root of RESTCONF API as configured on this HTTP server. The "restconf" relation defines the root of + * the API defined in RFC8040. Subsequent revisions of RESTCONF will use alternate relation values to support protocol + * versioning. * * @see https://tools.ietf.org/html/rfc8040 */ - RESTCONF("restconf"), + public static final LinkRelation RESTCONF = LinkRelation.of("restconf"); /** * Identifies a resource that is a reply to the context of the link. * * @see https://tools.ietf.org/html/rfc4685 */ - REPLIES("replies"), + public static final LinkRelation REPLIES = LinkRelation.of("replies"); /** * Refers to a resource that can be used to search through the link's context and related resources. * * @see http://www.opensearch.org/Specifications/OpenSearch/1.1 */ - SEARCH("search"), + public static final LinkRelation SEARCH = LinkRelation.of("search"); /** * Refers to a section in a collection of resources. * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - SECTION("section"), + public static final LinkRelation SECTION = LinkRelation.of("section"); /** * Conveys an identifier for the link's context. * * @see https://tools.ietf.org/html/rfc4287 */ - SELF("self"), + public static final LinkRelation SELF = LinkRelation.of("self"); /** * Indicates a URI that can be used to retrieve a service document. * * @see https://tools.ietf.org/html/rfc5023 */ - SERVICE("service"), + public static final LinkRelation SERVICE = LinkRelation.of("service"); /** * Refers to the first resource in a collection of resources. * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - START("start"), + public static final LinkRelation START = LinkRelation.of("start"); /** * Refers to a stylesheet. * * @see http://www.w3.org/TR/html5/links.html#link-type-stylesheet */ - STYLESHEET("stylesheet"), + public static final LinkRelation STYLESHEET = LinkRelation.of("stylesheet"); /** * Refers to a resource serving as a subsection in a collection of resources. * * @see http://www.w3.org/TR/1999/REC-html401-19991224 */ - SUBSECTION("subsection"), + public static final LinkRelation SUBSECTION = LinkRelation.of("subsection"); /** * Points to a resource containing the successor version in the version history. * * @see https://tools.ietf.org/html/rfc5829 */ - SUCCESSOR_VERSION("successor-versions"), + public static final LinkRelation SUCCESSOR_VERSION = LinkRelation.of("successor-versions"); /** * Gives a tag (identified by the given address) that applies to the current document. * * @see http://www.w3.org/TR/html5/links.html#link-type-tag */ - TAG("tag"), + public static final LinkRelation TAG = LinkRelation.of("tag"); /** * Refers to the terms of service associated with the link's context. * * @see https://tools.ietf.org/html/rfc6903, section 5 */ - TERMS_OF_SERVICE("terms-of-service"), + public static final LinkRelation TERMS_OF_SERVICE = LinkRelation.of("terms-of-service"); /** * The Target IRI points to a TimeGate for an Original Resource. * * @see https://tools.ietf.org/html/rfc7089 */ - TIMEGATE("timegate"), + public static final LinkRelation TIMEGATE = LinkRelation.of("timegate"); /** * The Target IRI points to a TimeMap for an Original Resource. * * @see https://tools.ietf.org/html/rfc7089 */ - TIMEMAP("timemap"), + public static final LinkRelation TIMEMAP = LinkRelation.of("timemap"); /** - * Refers to a resource identifying the abstract semantic type of which the link's context is considered to be - * an instance. + * Refers to a resource identifying the abstract semantic type of which the link's context is considered to be an + * instance. * * @see https://tools.ietf.org/html/rfc6903, section 6 */ - TYPE("type"), + public static final LinkRelation TYPE = LinkRelation.of("type"); /** * Refers to a parent document in a hierarchy of documents. * * @see https://tools.ietf.org/html/rfc8288 */ - UP("up"), + public static final LinkRelation UP = LinkRelation.of("up"); /** * Points to a resource containing the version history for the context. * * @see https://tools.ietf.org/html/rfc5829 */ - VERSION_HISTORY("version-history"), + public static final LinkRelation VERSION_HISTORY = LinkRelation.of("version-history"); /** * Identifies a resource that is the source of the information in the link's context. * * @see https://tools.ietf.org/html/rfc4287 */ - VIA("via"), + public static final LinkRelation VIA = LinkRelation.of("via"); /** - * Identifies a target URI that supports the Webmention protcol. This allows clients that mention a resource in - * some form of publishing process to contact that endpoint and inform it that this resource has been mentioned. + * Identifies a target URI that supports the Webmention protcol. This allows clients that mention a resource in some + * form of publishing process to contact that endpoint and inform it that this resource has been mentioned. * * @see http://www.w3.org/TR/webmention/ */ - WEBMENTION("webmention"), + public static final LinkRelation WEBMENTION = LinkRelation.of("webmention"); /** * Points to a working copy for this resource. + * * @see https://tools.ietf.org/html/rfc5829 */ - WORKING_COPY("working-copy"), + public static final LinkRelation WORKING_COPY = LinkRelation.of("working-copy"); /** * Points to the versioned resource from which this working copy was obtained. - * + * * @see https://tools.ietf.org/html/rfc5829 */ - WORKING_COPY_OF("working-copy-of"); - - /** - * Actual IANA value for the link relation. - */ - private final String value; + public static final LinkRelation WORKING_COPY_OF = LinkRelation.of("working-copy-of"); /** - * Initialize the enum value. - * - * @param value + * Consolidated collection of {@link IanaLinkRelations}s. */ - IanaLinkRelation(String value) { - this.value = value; - } - - /** - * Return the IANA value. - */ - @Override - public String value() { - return this.value; - } - - /** - * Consolidated collection of {@link IanaLinkRelation}s. - */ - public static final Set LINK_RELATIONS; - - /** - * Consolidated collection of {@link IanaLinkRelation} values. - */ - public static final Set RELS; + private final Set LINK_RELATIONS; static { - LINK_RELATIONS = Arrays.stream(IanaLinkRelation.values()) - .collect(Collectors.toSet()); - - RELS = LINK_RELATIONS.stream() - .map(LinkRelation::value) - .collect(Collectors.toSet()); + LINK_RELATIONS = Arrays.stream(IanaLinkRelations.class.getDeclaredFields()) // + .filter(ReflectionUtils::isPublicStaticFinal) // + .map(it -> ReflectionUtils.getField(it, null)) // + .map(LinkRelation.class::cast) // + .collect(Collectors.toSet()); } /** - * Is this relation an IANA standard? + * Is this relation an IANA standard? Per RFC8288, parsing of link relations is case insensitive. * - * Per RFC8288, parsing of link relations is case insensitive. - * * @param rel * @return boolean */ public static boolean isIanaRel(String rel) { - return - rel != null - && - LINK_RELATIONS.stream().anyMatch(linkRelation -> linkRelation.value().equalsIgnoreCase(rel)); + return rel != null && LINK_RELATIONS.stream() // + .anyMatch(it -> it.value().equalsIgnoreCase(rel)); } /** - * Is this relation an IANA standard? - * - * Per RFC8288, parsing of link relations is case insensitive. + * Is this relation an IANA standard? Per RFC8288, parsing of link relations is case insensitive. * * @param rel * @return */ public static boolean isIanaRel(LinkRelation rel) { - - return - rel != null - && - LINK_RELATIONS.stream().anyMatch(linkRelation -> linkRelation.value.equalsIgnoreCase(rel.value())); + + return rel != null && LINK_RELATIONS.stream() // + .anyMatch(it -> it.value().equalsIgnoreCase(rel.value())); } /** - * Convert a string-based link relation to a {@link IanaLinkRelation}. - * - * Per RFC8288, parsing of link relations is case insensitive. + * Convert a string-based link relation to a {@link IanaLinkRelations}. Per RFC8288, parsing of link relations is case + * insensitive. * * @param rel as a string - * @return rel as a {@link IanaLinkRelation} + * @return rel as a {@link IanaLinkRelations} */ - public static IanaLinkRelation parse(String rel) { + public static LinkRelation parse(String rel) { - return LINK_RELATIONS.stream() - .filter(linkRelation -> linkRelation.value().equalsIgnoreCase(rel)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException(rel + " is not a valid IANA link relation!")); + return LINK_RELATIONS.stream() // + .filter(it -> it.value().equalsIgnoreCase(rel)) // + .findFirst() // + .orElseThrow(() -> new IllegalArgumentException(rel + " is not a valid IANA link relation!")); } } diff --git a/src/main/java/org/springframework/hateoas/IanaRels.java b/src/main/java/org/springframework/hateoas/IanaRels.java index 7f73179a..5599b929 100644 --- a/src/main/java/org/springframework/hateoas/IanaRels.java +++ b/src/main/java/org/springframework/hateoas/IanaRels.java @@ -33,10 +33,10 @@ public class IanaRels { * * @param rel the relation type to check * @return - * @deprecated Migrate to {@link IanaLinkRelation#isIanaRel(String)}. + * @deprecated Migrate to {@link IanaLinkRelations#isIanaRel(String)}. */ @Deprecated public static boolean isIanaRel(String rel) { - return IanaLinkRelation.isIanaRel(rel); + return IanaLinkRelations.isIanaRel(rel); } } diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index c3bdadad..cbac46e1 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -41,7 +41,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; /** * Value object for links. - * + * * @author Oliver Gierke * @author Greg Turnquist * @author Jens Schauder @@ -50,7 +50,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonIgnoreProperties(value = "templated", ignoreUnknown = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter -@EqualsAndHashCode(of = { "rel", "href", "hreflang", "media", "title", "type", "deprecation", "profile", "name", "affordances" }) +@EqualsAndHashCode( + of = { "rel", "href", "hreflang", "media", "title", "type", "deprecation", "profile", "name", "affordances" }) public class Link implements Serializable { private static final long serialVersionUID = -9037755944661782121L; @@ -63,36 +64,31 @@ public class Link implements Serializable { public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"; /** - * @deprecated Use {@link IanaLinkRelation#SELF} instead. + * @deprecated Use {@link IanaLinkRelations#SELF} instead. */ - @Deprecated - public static final String REL_SELF = IanaLinkRelation.SELF.value(); + public static final @Deprecated LinkRelation REL_SELF = IanaLinkRelations.SELF; /** - * @deprecated Use {@link IanaLinkRelation#FIRST} instead. + * @deprecated Use {@link IanaLinkRelations#FIRST} instead. */ - @Deprecated - public static final String REL_FIRST = IanaLinkRelation.FIRST.value(); + public static final @Deprecated LinkRelation REL_FIRST = IanaLinkRelations.FIRST; /** - * @deprecated Use {@link IanaLinkRelation#PREV} instead. + * @deprecated Use {@link IanaLinkRelations#PREV} instead. */ - @Deprecated - public static final String REL_PREVIOUS = IanaLinkRelation.PREV.value(); + public static final @Deprecated LinkRelation REL_PREVIOUS = IanaLinkRelations.PREV; /** - * @deprecated Use {@link IanaLinkRelation#NEXT} instead. + * @deprecated Use {@link IanaLinkRelations#NEXT} instead. */ - @Deprecated - public static final String REL_NEXT = IanaLinkRelation.NEXT.value(); + public static final @Deprecated LinkRelation REL_NEXT = IanaLinkRelations.NEXT; /** - * @deprecated Use {@link IanaLinkRelation#LAST} instead. + * @deprecated Use {@link IanaLinkRelations#LAST} instead. */ - @Deprecated - public static final String REL_LAST = IanaLinkRelation.LAST.value(); + public static final @Deprecated LinkRelation REL_LAST = IanaLinkRelations.LAST; - private @Wither String rel; + private LinkRelation rel; private @Wither String href; private @Wither String hreflang; private @Wither String media; @@ -106,52 +102,74 @@ public class Link implements Serializable { /** * Creates a new link to the given URI with the self rel. - * - * @see IanaLinkRelation#SELF + * + * @see IanaLinkRelations#SELF * @param href must not be {@literal null} or empty. */ public Link(String href) { - this(href, IanaLinkRelation.SELF.value()); + this(href, IanaLinkRelations.SELF); } /** * Creates a new {@link Link} to the given URI with the given rel. - * + * * @param href must not be {@literal null} or empty. * @param rel must not be {@literal null} or empty. */ public Link(String href, String rel) { + this(new UriTemplate(href), LinkRelation.of(rel)); + } + + /** + * Creates a new {@link Link} to the given URI with the given rel. + * + * @param href must not be {@literal null} or empty. + * @param rel must not be {@literal null} or empty. + */ + public Link(String href, LinkRelation rel) { this(new UriTemplate(href), rel); } /** * Creates a new Link from the given {@link UriTemplate} and rel. - * + * * @param template must not be {@literal null}. * @param rel must not be {@literal null} or empty. */ public Link(UriTemplate template, String rel) { - - Assert.notNull(template, "UriTemplate must not be null!"); - Assert.hasText(rel, "Rel must not be null or empty!"); - - this.template = template; - this.href = template.toString(); - this.rel = rel; - this.affordances = new ArrayList<>(); + this(template, LinkRelation.of(rel)); } - public Link(String href, String rel, List affordances) { + /** + * Creates a new Link from the given {@link UriTemplate} and rel. + * + * @param template must not be {@literal null}. + * @param rel must not be {@literal null} or empty. + */ + public Link(UriTemplate template, LinkRelation rel) { + this(template, rel, Collections.emptyList()); + } - this(href, rel); + /** + * Creates a new Link from the given {@link UriTemplate}, link relation and affordances. + * + * @param template must not be {@literal null}. + * @param rel must not be {@literal null} or empty. + */ + private Link(UriTemplate template, LinkRelation rel, List affordances) { - Assert.notNull(affordances, "affordances must not be null!"); + Assert.notNull(template, "UriTemplate must not be null!"); + Assert.notNull(rel, "LinkRelation must not be null!"); + Assert.notNull(affordances, "Affordances must not be null!"); + this.template = template; + this.rel = rel; + this.href = template.toString(); this.affordances = affordances; } /** - * Empty constructor required by the marshalling framework. + * Empty constructor required by the marshaling framework. */ protected Link() { this.affordances = new ArrayList<>(); @@ -159,7 +177,7 @@ public class Link implements Serializable { /** * Returns safe copy of {@link Affordance}s. - * + * * @return */ public List getAffordances() { @@ -168,11 +186,11 @@ public class Link implements Serializable { /** * Returns a {@link Link} pointing to the same URI but with the {@code self} relation. - * + * * @return */ public Link withSelfRel() { - return withRel(IanaLinkRelation.SELF.value()); + return withRel(IanaLinkRelations.SELF); } /** @@ -194,7 +212,7 @@ public class Link implements Serializable { /** * Convenience method when chaining an existing {@link Link}. - * + * * @param name * @param httpMethod * @param inputType @@ -202,13 +220,14 @@ public class Link implements Serializable { * @param outputType * @return */ - public Link andAffordance(String name, HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType) { + public Link andAffordance(String name, HttpMethod httpMethod, ResolvableType inputType, + List queryMethodParameters, ResolvableType outputType) { return andAffordance(new Affordance(name, this, httpMethod, inputType, queryMethodParameters, outputType)); } /** - * Convenience method when chaining an existing {@link Link}. Defaults the name of the affordance to verb + classname, e.g. - * {@literal } produces {@literal }. + * Convenience method when chaining an existing {@link Link}. Defaults the name of the affordance to verb + classname, + * e.g. {@literal } produces {@literal }. * * @param httpMethod * @param inputType @@ -216,13 +235,15 @@ public class Link implements Serializable { * @param outputType * @return */ - public Link andAffordance(HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType) { - return andAffordance(httpMethod.toString().toLowerCase() + inputType.resolve().getSimpleName(), httpMethod, inputType, queryMethodParameters, outputType); + public Link andAffordance(HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, + ResolvableType outputType) { + return andAffordance(httpMethod.toString().toLowerCase() + inputType.resolve().getSimpleName(), httpMethod, + inputType, queryMethodParameters, outputType); } /** - * Convenience method when chaining an existing {@link Link}. Defaults the name of the affordance to verb + classname, e.g. - * {@literal } produces {@literal }. + * Convenience method when chaining an existing {@link Link}. Defaults the name of the affordance to verb + classname, + * e.g. {@literal } produces {@literal }. * * @param httpMethod * @param inputType @@ -230,13 +251,15 @@ public class Link implements Serializable { * @param outputType * @return */ - public Link andAffordance(HttpMethod httpMethod, Class inputType, List queryMethodParameters, Class outputType) { - return andAffordance(httpMethod, ResolvableType.forClass(inputType), queryMethodParameters, ResolvableType.forClass(outputType)); + public Link andAffordance(HttpMethod httpMethod, Class inputType, List queryMethodParameters, + Class outputType) { + return andAffordance(httpMethod, ResolvableType.forClass(inputType), queryMethodParameters, + ResolvableType.forClass(outputType)); } /** * Create new {@link Link} with additional {@link Affordance}s. - * + * * @param affordances must not be {@literal null}. * @return */ @@ -251,7 +274,7 @@ public class Link implements Serializable { /** * Creats a new {@link Link} with the given {@link Affordance}s. - * + * * @param affordances must not be {@literal null}. * @return */ @@ -263,7 +286,7 @@ public class Link implements Serializable { /** * Returns the variable names contained in the template. - * + * * @return */ @JsonIgnore @@ -273,7 +296,7 @@ public class Link implements Serializable { /** * Returns all {@link TemplateVariables} contained in the {@link Link}. - * + * * @return */ @JsonIgnore @@ -283,7 +306,7 @@ public class Link implements Serializable { /** * Returns whether or not the link is templated. - * + * * @return */ public boolean isTemplated() { @@ -292,7 +315,7 @@ public class Link implements Serializable { /** * Turns the current template into a {@link Link} by expanding it using the given parameters. - * + * * @param arguments * @return */ @@ -302,7 +325,7 @@ public class Link implements Serializable { /** * Turns the current template into a {@link Link} by expanding it using the given parameters. - * + * * @param arguments must not be {@literal null}. * @return */ @@ -310,9 +333,32 @@ public class Link implements Serializable { return new Link(getUriTemplate().expand(arguments).toString(), getRel()); } + /** + * Creates a new {@link Link} with the same href but given {@link LinkRelation}. + * + * @param relation must not be {@literal null}. + * @return + */ + public Link withRel(LinkRelation relation) { + + Assert.notNull(relation, "LinkRelation must not be null!"); + + return new Link(relation, href, hreflang, media, title, type, deprecation, profile, name, template, affordances); + } + + /** + * Creates a new {@link Link} with the same href but given {@link LinkRelation}. + * + * @param relation must not be {@literal null} or empty. + * @return + */ + public Link withRel(String relation) { + return withRel(LinkRelation.of(relation)); + } + /** * Returns whether the current {@link Link} has the given link relation. - * + * * @param rel must not be {@literal null} or empty. * @return */ @@ -320,7 +366,20 @@ public class Link implements Serializable { Assert.hasText(rel, "Link relation must not be null or empty!"); - return this.rel.equals(rel); + return hasRel(LinkRelation.of(rel)); + } + + /** + * Returns whether the {@link Link} has the given {@link LinkRelation}. + * + * @param rel must not be {@literal null}. + * @return + */ + public boolean hasRel(LinkRelation rel) { + + Assert.notNull(rel, "Link relation must not be null!"); + + return this.rel.isSameAs(rel); } private UriTemplate getUriTemplate() { @@ -339,7 +398,7 @@ public class Link implements Serializable { @Override public String toString() { - String linkString = String.format("<%s>;rel=\"%s\"", href, rel); + String linkString = String.format("<%s>;rel=\"%s\"", href, rel.value()); if (hreflang != null) { linkString += ";hreflang=\"" + hreflang + "\""; @@ -375,7 +434,7 @@ 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}. - * + * * @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 no {@code rel} attribute could be found. @@ -436,7 +495,7 @@ public class Link implements Serializable { /** * Parses the links attributes from the given source {@link String}. - * + * * @param source * @return */ diff --git a/src/main/java/org/springframework/hateoas/LinkBuilder.java b/src/main/java/org/springframework/hateoas/LinkBuilder.java index f1431ce2..94f86548 100644 --- a/src/main/java/org/springframework/hateoas/LinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/LinkBuilder.java @@ -19,7 +19,7 @@ import java.net.URI; /** * Builder to ease building {@link Link} instances. - * + * * @author Ricardo Gladwell */ public interface LinkBuilder { @@ -27,7 +27,7 @@ public interface LinkBuilder { /** * Adds the given object's {@link String} representation as sub-resource to the current URI. Will unwrap * {@link Identifiable}s to their id value (see {@link Identifiable#getId()}). - * + * * @param object * @return */ @@ -36,7 +36,7 @@ public interface LinkBuilder { /** * Adds the given {@link Identifiable}'s id as sub-resource. Will simply return the {@link LinkBuilder} as is if the * given entity is {@literal null}. - * + * * @param identifiable * @return */ @@ -44,23 +44,33 @@ public interface LinkBuilder { /** * Creates a URI of the link built by the current builder instance. - * + * * @return */ URI toUri(); /** - * Creates the {@link Link} built by the current builder instance with the given rel. - * + * Creates the {@link Link} built by the current builder instance with the given link relation. + * + * @param rel must not be {@literal null}. + * @return + */ + default Link withRel(String rel) { + return withRel(LinkRelation.of(rel)); + } + + /** + * Creates the {@link Link} built by the current builder instance with the given {@link LinkRelation}. + * * @param rel must not be {@literal null} or empty. * @return */ - Link withRel(String rel); + Link withRel(LinkRelation rel); /** - * Creates the {@link Link} built by the current builder instance with the default self rel. - * - * @see IanaLinkRelation#SELF + * Creates the {@link Link} built by the current builder instance with the default self link relation. + * + * @see IanaLinkRelations#SELF * @return */ Link withSelfRel(); diff --git a/src/main/java/org/springframework/hateoas/LinkDiscoverer.java b/src/main/java/org/springframework/hateoas/LinkDiscoverer.java index 81f9c612..d552cd52 100644 --- a/src/main/java/org/springframework/hateoas/LinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/LinkDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 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. @@ -16,51 +16,88 @@ package org.springframework.hateoas; import java.io.InputStream; -import java.util.List; +import java.util.Optional; import org.springframework.http.MediaType; import org.springframework.plugin.core.Plugin; /** * Interface to allow discovering links by relation type from some source. - * + * * @author Oliver Gierke */ public interface LinkDiscoverer extends Plugin { /** * Finds a single link with the given relation type in the given {@link String} representation. - * + * * @param rel must not be {@literal null} or empty. - * @param representation must not be {@literal null} or empty. + * @param representation must not be {@literal null}. * @return the first link with the given relation type found, or {@literal null} if none was found. */ - Link findLinkWithRel(String rel, String representation); + default Optional findLinkWithRel(String rel, String representation) { + return findLinkWithRel(LinkRelation.of(rel), representation); + } + + Optional findLinkWithRel(LinkRelation rel, String representation); /** * Finds a single link with the given relation type in the given {@link InputStream} representation. - * + * * @param rel must not be {@literal null} or empty. - * @param representation must not be {@literal null} or empty. + * @param representation must not be {@literal null}. * @return the first link with the given relation type found, or {@literal null} if none was found. */ - Link findLinkWithRel(String rel, InputStream representation); + default Optional findLinkWithRel(String rel, InputStream representation) { + return findLinkWithRel(LinkRelation.of(rel), representation); + } /** - * Returns all links with the given relation type found in the given {@link String} representation. - * - * @param rel must not be {@literal null} or empty. - * @param representation must not be {@literal null} or empty. - * @return + * Finds a single link with the given {@link LinkRelation} in the given {@link InputStream} representation. + * + * @param rel must not be {@literal null}. + * @param representation must not be {@literal null}. + * @return the first link with the given relation type found, or {@literal null} if none was found. */ - List findLinksWithRel(String rel, String representation); + Optional findLinkWithRel(LinkRelation rel, InputStream representation); /** - * Returns all links with the given relation type found in the given {@link InputStream} representation. - * + * Returns all links with the given link relation found in the given {@link String} representation. + * * @param rel must not be {@literal null} or empty. - * @param representation must not be {@literal null} or empty. - * @return + * @param representation must not be {@literal null}. + * @return will never be {@literal null}. */ - List findLinksWithRel(String rel, InputStream representation); + default Links findLinksWithRel(String rel, String representation) { + return findLinksWithRel(LinkRelation.of(rel), representation); + } + + /** + * Returns all links with the given {@link LinkRelation} found in the given {@link String} representation. + * + * @param rel must not be {@literal null}. + * @param representation must not be {@literal null}. + * @return will never be {@literal null}. + */ + Links findLinksWithRel(LinkRelation rel, String representation); + + /** + * Returns all links with the given link relation found in the given {@link InputStream} representation. + * + * @param rel must not be {@literal null} or empty. + * @param representation must not be {@literal null}. + * @return will never be {@literal null}. + */ + default Links findLinksWithRel(String rel, InputStream representation) { + return findLinksWithRel(LinkRelation.of(rel), representation); + } + + /** + * Returns all links with the given {@link LinkRelation} found in the given {@link InputStream} representation. + * + * @param rel must not be {@literal null}. + * @param representation must not be {@literal null}. + * @return will never be {@literal null}. + */ + Links findLinksWithRel(LinkRelation rel, InputStream representation); } diff --git a/src/main/java/org/springframework/hateoas/LinkDiscoverers.java b/src/main/java/org/springframework/hateoas/LinkDiscoverers.java index 810e1de9..548fd040 100644 --- a/src/main/java/org/springframework/hateoas/LinkDiscoverers.java +++ b/src/main/java/org/springframework/hateoas/LinkDiscoverers.java @@ -15,6 +15,8 @@ */ package org.springframework.hateoas; +import java.util.Optional; + import org.springframework.http.MediaType; import org.springframework.plugin.core.PluginRegistry; import org.springframework.util.Assert; @@ -22,7 +24,7 @@ import org.springframework.util.Assert; /** * Value object to wrap a {@link PluginRegistry} for {@link LinkDiscoverer} so that it's easier to inject them into * clients wanting to lookup a {@link LinkDiscoverer} for a given {@link MediaTypes}. - * + * * @author Oliver Gierke */ public class LinkDiscoverers { @@ -31,7 +33,7 @@ public class LinkDiscoverers { /** * Creates a new {@link LinkDiscoverers} instance with the given {@link PluginRegistry}. - * + * * @param discoverers must not be {@literal null}. */ public LinkDiscoverers(PluginRegistry discoverers) { @@ -42,21 +44,41 @@ public class LinkDiscoverers { /** * Returns the {@link LinkDiscoverer} suitable for the given {@link MediaType}. - * + * + * @param mediaType + * @return will never be {@literal null}. + */ + public Optional getLinkDiscovererFor(MediaType mediaType) { + return discoverers.getPluginFor(mediaType); + } + + /** + * Returns the {@link LinkDiscoverer} suitable for the given media type. + * * @param mediaType * @return */ - public LinkDiscoverer getLinkDiscovererFor(MediaType mediaType) { + public Optional getLinkDiscovererFor(String mediaType) { + return getLinkDiscovererFor(MediaType.valueOf(mediaType)); + } + + /** + * Returns the {@link LinkDiscoverer} suitable for the given {@link MediaType}. + * + * @param mediaType + * @return will never be {@literal null}. + */ + public LinkDiscoverer getRequiredLinkDiscovererFor(MediaType mediaType) { return discoverers.getRequiredPluginFor(mediaType); } /** * Returns the {@link LinkDiscoverer} suitable for the given media type. - * + * * @param mediaType * @return */ - public LinkDiscoverer getLinkDiscovererFor(String mediaType) { - return getLinkDiscovererFor(MediaType.valueOf(mediaType)); + public LinkDiscoverer getRequiredLinkDiscovererFor(String mediaType) { + return getRequiredLinkDiscovererFor(MediaType.valueOf(mediaType)); } } diff --git a/src/main/java/org/springframework/hateoas/LinkRelation.java b/src/main/java/org/springframework/hateoas/LinkRelation.java index bd794671..d2d0df49 100644 --- a/src/main/java/org/springframework/hateoas/LinkRelation.java +++ b/src/main/java/org/springframework/hateoas/LinkRelation.java @@ -15,10 +15,19 @@ */ package org.springframework.hateoas; +import java.util.Arrays; +import java.util.stream.Collectors; + +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Interface for defining link relations. Can be used for implementing spec-based link relations as well as custom ones. * * @author Greg Turnquist + * @author Oliver Drotbohm * @since 1.0 */ public interface LinkRelation { @@ -26,5 +35,44 @@ public interface LinkRelation { /** * Return the link relation's value. */ + @JsonValue String value(); + + /** + * Creates a new {@link LinkRelation}. + * + * @param relation must not be {@literal null} or empty. + * @return + */ + @JsonCreator + static LinkRelation of(String relation) { + return StringLinkRelation.of(relation); + } + + /** + * Creates a new {@link Iterable} of {@link LinkRelation} for each of the given {@link String}s. + * + * @param others must not be {@literal null}. + * @return + */ + static Iterable manyOf(String... others) { + + return Arrays.stream(others) // + .map(LinkRelation::of) // + .collect(Collectors.toList()); + } + + /** + * Returns whether the given {@link LinkRelation} is logically the same as the current one, independent of + * implementation, i.e. whether the plain {@link String} values match. + * + * @param relation must not be {@literal null}. + * @return + */ + default boolean isSameAs(LinkRelation relation) { + + Assert.notNull(relation, "LinkRelation must not be null!"); + + return this.value().equals(relation.value()); + } } diff --git a/src/main/java/org/springframework/hateoas/Links.java b/src/main/java/org/springframework/hateoas/Links.java index fc2874ff..4b05d326 100644 --- a/src/main/java/org/springframework/hateoas/Links.java +++ b/src/main/java/org/springframework/hateoas/Links.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. @@ -21,12 +21,19 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Value object to represent a list of {@link Link}s. * @@ -35,75 +42,51 @@ import org.springframework.util.StringUtils; */ public class Links implements Iterable { + public static final Links NONE = new Links(Collections.emptyList()); private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("(<[^>]*>(;\\s*\\w+=\"[^\"]*\")+)"); - static final Links NO_LINKS = new Links(Collections.emptyList()); - private final List links; - /** - * Creates a new {@link Links} instance from the given {@link Link}s. - * - * @param links - */ - public Links(List links) { - this.links = links == null ? Collections.emptyList() : Collections.unmodifiableList(links); + private Links(Iterable links) { + + Assert.notNull(links, "Links must not be null!"); + + this.links = StreamSupport.stream(links.spliterator(), false) // + .collect(Collectors.toList()); } - /** - * Creates a new {@link Links} instance from the given {@link Link}s. - * - * @param links - */ - public Links(Link... links) { + private Links(Link... links) { this(Arrays.asList(links)); } /** - * Returns the {@link Link} with the given rel. + * Creates a new {@link Links} instance from the given {@link Link}s. * - * @param rel the relation type to lookup a link for. - * @return the link with the given rel or {@literal Optional#empty()} if none found. + * @param links */ - public Optional getLink(String rel) { - - return links.stream() // - .filter(link -> link.getRel().equals(rel)).findFirst(); + public static Links of(Link... links) { + return new Links(links); } /** - * Returns the {@link Link} with the given relation. + * Creates a new {@link Links} instance from the given {@link Link}s. * - * @param rel the relation type to lookup a link for. - * @return - * @throws IllegalArgumentException if no link with the given relation was present. - * @since 1.0 + * @param links */ - public Link getRequiredLink(String rel) { - - return getLink(rel) // - .orElseThrow(() -> new IllegalArgumentException(String.format("Couldn't find link with rel '%s'!", rel))); + public static Links of(Iterable links) { + return new Links(links); } /** - * Returns all {@link Links} with the given relation type. + * Creates a {@link Links} instance from the given RFC5988-compatible link format. * - * @return the links + * @param source a comma separated list of {@link Link} representations. + * @return the {@link Links} represented by the given {@link String}. + * @deprecated use {@link #parse(String)} instead */ - public List getLinks(String rel) { - - return links.stream() // - .filter(link -> link.getRel().endsWith(rel)).collect(Collectors.toList()); - } - - /** - * Returns whether the {@link Links} container contains a {@link Link} with the given rel. - * - * @param rel - * @return - */ - public boolean hasLink(String rel) { - return getLink(rel).isPresent(); + @Deprecated + public static Links valueOf(String source) { + return parse(source); } /** @@ -112,10 +95,10 @@ 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 valueOf(String source) { + public static Links parse(String source) { if (!StringUtils.hasText(source)) { - return NO_LINKS; + return NONE; } Matcher matcher = LINK_HEADER_PATTERN.matcher(source); @@ -133,6 +116,194 @@ public class Links implements Iterable { return new Links(links); } + /** + * Creates a new {@link Links} instance with all given {@link Link}s added. For conditional adding see + * {@link #merge(Link...)}. + * + * @param links must not be {@literal null}. + * @return + * @see #merge(Link...) + * @see #merge(MergeMode, Link...) + */ + public Links and(Link... links) { + + Assert.notNull(links, "Links must not be null!"); + + return and(Arrays.asList(links)); + } + + /** + * Creates a new {@link Links} instance with all given {@link Link}s added. For conditional adding see + * {@link #merge(Iterable)}. + * + * @param links must not be {@literal null}. + * @return + * @see #merge(Iterable) + * @see #merge(MergeMode, Iterable) + */ + public Links and(Iterable links) { + + List newLinks = new ArrayList<>(this.links); + links.forEach(newLinks::add); + + return Links.of(newLinks); + } + + /** + * Merges the current {@link Links} with the given ones, skipping {@link Link}s already contained in the current + * instance. For unconditional combination see {@link #and(Link...)}. + * + * @param links the {@link Link}s to be merged, must not be {@literal null}. + * @return + * @see MergeMode#SKIP_BY_EQUALITY + * @see #and(Link...) + */ + public Links merge(Link... links) { + return merge(Arrays.asList(links)); + } + + /** + * Merges the current {@link Links} with the given ones, skipping {@link Link}s already contained in the current + * instance. For unconditional combination see {@link #and(Link...)}. + * + * @param links the {@link Link}s to be merged, must not be {@literal null}. + * @return + * @see MergeMode#SKIP_BY_EQUALITY + * @see #and(Link...) + */ + public Links merge(Iterable links) { + return merge(MergeMode.SKIP_BY_EQUALITY, links); + } + + /** + * Merges the current {@link Links} with the given ones applying the given {@link MergeMode}. + * + * @param mode must not be {@literal null}. + * @param links must not be {@literal null}. + * @return + */ + public Links merge(MergeMode mode, Link... links) { + return merge(mode, Arrays.asList(links)); + } + + /** + * Merges the current {@link Links} with the given ones applying the given {@link MergeMode}. + * + * @param mode must not be {@literal null}. + * @param links must not be {@literal null}. + * @return + */ + public Links merge(MergeMode mode, Iterable links) { + + Assert.notNull(mode, "MergeMode must not be null!"); + Assert.notNull(links, "Links must not be null!"); + + List newLinks = MergeMode.REPLACE_BY_REL.equals(mode) // + ? allWithoutRels(links) + : new ArrayList<>(this.links); + + links.forEach(it -> { + + if (MergeMode.REPLACE_BY_REL.equals(mode)) { + newLinks.add(it); + } + + if (MergeMode.SKIP_BY_EQUALITY.equals(mode) && !this.links.contains(it)) { + newLinks.add(it); + } + + if (MergeMode.SKIP_BY_REL.equals(mode) && !this.hasLink(it.getRel())) { + newLinks.add(it); + } + }); + + return new Links(newLinks); + } + + /** + * Returns a {@link Links} with all {@link Link}s with the given {@link LinkRelation} removed. + * + * @param relation must not be {@literal null}. + * @return + */ + public Links without(LinkRelation relation) { + + Assert.notNull(relation, "LinkRelation must not be null!"); + + return this.links.stream() // + .filter(it -> !it.hasRel(relation)) // + .collect(Links.collector()); + } + + /** + * Returns a {@link Link} with the given relation if contained in the current {@link Links} instance, + * {@link Optional#empty()} otherwise. + * + * @param relation must not be {@literal null} or empty. + * @return + */ + public Optional getLink(String relation) { + return getLink(LinkRelation.of(relation)); + } + + /** + * Returns the {@link Link} with the given rel. + * + * @param rel the relation type to lookup a link for. + * @return the link with the given rel or {@literal Optional#empty()} if none found. + */ + public Optional getLink(LinkRelation rel) { + + return links.stream() // + .filter(it -> it.hasRel(rel)) // + .findFirst(); + } + + /** + * Returns the {@link Link} with the given relation. + * + * @param rel the relation type to lookup a link for. + * @return + * @throws IllegalArgumentException if no link with the given relation was present. + * @since 1.0 + */ + public Link getRequiredLink(String rel) { + return getRequiredLink(LinkRelation.of(rel)); + } + + /** + * Returns the {@link Link} with the given relation. + * + * @param relation the relation type to lookup a link for. + * @return + * @throws IllegalArgumentException if no link with the given relation was present. + */ + public Link getRequiredLink(LinkRelation relation) { + + return getLink(relation) // + .orElseThrow(() -> new IllegalArgumentException(String.format("Couldn't find link with rel '%s'!", relation))); + } + + /** + * Returns whether the {@link Links} container contains a {@link Link} with the given relation. + * + * @param relation must not be {@literal null} or empty. + * @return + */ + public boolean hasLink(String relation) { + return getLink(relation).isPresent(); + } + + /** + * Returns whether the current {@link Links} contains a {@link Link} with the given relation. + * + * @param relation must not be {@literal null}. + * @return + */ + public boolean hasLink(LinkRelation relation) { + return getLink(relation).isPresent(); + } + /** * Returns whether the {@link Links} container is empty. * @@ -142,6 +313,86 @@ public class Links implements Iterable { return links.isEmpty(); } + /** + * Returns whether the current {@link Links} has the given size. + * + * @param size + * @return + */ + public boolean hasSize(long size) { + return links.size() == size; + } + + /** + * Returns whether the {@link Links} contain a single {@link Link}. + * + * @return + */ + public boolean hasSingleLink() { + return hasSize(1); + } + + /** + * Creates a {@link Stream} of the current {@link Links}. + * + * @return + */ + public Stream stream() { + return this.links.stream(); + } + + /** + * Returns the current {@link Links} as {@link List}. + * + * @return + */ + @JsonValue + public List toList() { + return this.links; + } + + /** + * Returns whether the current {@link Links} contain all given {@link Link}s (but potentially others). + * + * @param links must not be {@literal null}. + * @return + */ + public boolean contains(Link... links) { + return this.links.containsAll(Links.of(links).toList()); + } + + /** + * Returns whether the current {@link Links} contain all given {@link Link}s (but potentially others). + * + * @param links must not be {@literal null}. + * @return + */ + public boolean contains(Iterable links) { + return this.links.containsAll(Links.of(links).toList()); + } + + /** + * Returns whether the current {@link Links} instance contains exactly the same {@link Link} as the given one. + * + * @param links must not be {@literal null}. + * @return + */ + public boolean containsSameLinksAs(Iterable links) { + + Links other = Links.of(links); + + return this.links.size() != other.links.size() ? false : this.links.containsAll(other.links); + } + + /** + * Creates a new {@link Collector} to collect a {@link Stream} of {@link Link}s into a {@link Links} instance. + * + * @return will never be {@literal null}. + */ + public static Collector collector() { + return Collectors.collectingAndThen(Collectors.toList(), Links::of); + } + /* * (non-Javadoc) * @see java.lang.Object#toString() @@ -188,4 +439,38 @@ public class Links implements Iterable { return result; } + + private List allWithoutRels(Iterable links) { + + Set toFilter = StreamSupport.stream(links.spliterator(), false) // + .map(Link::getRel) // + .collect(Collectors.toSet()); + + return this.links.stream() // + .filter(it -> !toFilter.contains(it.getRel())) // + .collect(Collectors.toList()); + } + + /** + * The mode how to merge two {@link Links} instances. + * + * @author Oliver Drotbohm + */ + public static enum MergeMode { + + /** + * Skips to add the same links on merge. Multiple links with the same link relation might appear. + */ + SKIP_BY_EQUALITY, + + /** + * Skips to add links with the same link relation, i.e. existing ones with the same relation are preferred. + */ + SKIP_BY_REL, + + /** + * Replaces existing links with the same link relation. + */ + REPLACE_BY_REL; + } } diff --git a/src/main/java/org/springframework/hateoas/PagedResources.java b/src/main/java/org/springframework/hateoas/PagedResources.java index 7062f11f..7f8553ea 100644 --- a/src/main/java/org/springframework/hateoas/PagedResources.java +++ b/src/main/java/org/springframework/hateoas/PagedResources.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** * DTO to implement binding response representations of pageable collections. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -46,7 +46,7 @@ public class PagedResources extends Resources { /** * Creates a new {@link PagedResources} from the given content, {@link PageMetadata} and {@link Link}s (optional). - * + * * @param content must not be {@literal null}. * @param metadata * @param links @@ -57,7 +57,7 @@ public class PagedResources extends Resources { /** * Creates a new {@link PagedResources} from the given content {@link PageMetadata} and {@link Link}s. - * + * * @param content must not be {@literal null}. * @param metadata * @param links @@ -69,7 +69,7 @@ public class PagedResources extends Resources { /** * Returns the pagination metadata. - * + * * @return the metadata */ @JsonProperty("page") @@ -79,7 +79,7 @@ public class PagedResources extends Resources { /** * Factory method to easily create a {@link PagedResources} instance from a set of entities and pagination metadata. - * + * * @param content must not be {@literal null}. * @param metadata * @return @@ -99,25 +99,25 @@ public class PagedResources extends Resources { /** * Returns the Link pointing to the next page (if set). - * + * * @return */ @JsonIgnore public Optional getNextLink() { - return getLink(IanaLinkRelation.NEXT.value()); + return getLink(IanaLinkRelations.NEXT); } /** * Returns the Link pointing to the previous page (if set). - * + * * @return */ @JsonIgnore public Optional getPreviousLink() { - return getLink(IanaLinkRelation.PREV.value()); + return getLink(IanaLinkRelations.PREV); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.ResourceSupport#toString() */ @@ -126,7 +126,7 @@ public class PagedResources extends Resources { return String.format("PagedResource { content: %s, metadata: %s, links: %s }", getContent(), metadata, getLinks()); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.Resources#equals(java.lang.Object) */ @@ -147,7 +147,7 @@ public class PagedResources extends Resources { return metadataEquals && super.equals(obj); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.Resources#hashCode() */ @@ -161,7 +161,7 @@ public class PagedResources extends Resources { /** * Value object for pagination metadata. - * + * * @author Oliver Gierke */ public static class PageMetadata { @@ -177,7 +177,7 @@ public class PagedResources extends Resources { /** * Creates a new {@link PageMetadata} from the given size, number, total elements and total pages. - * + * * @param size * @param number zero-indexed, must be less than totalPages * @param totalElements @@ -198,7 +198,7 @@ public class PagedResources extends Resources { /** * Creates a new {@link PageMetadata} from the given size, number and total elements. - * + * * @param size the size of the page * @param number the number of the page * @param totalElements the total number of elements available @@ -209,7 +209,7 @@ public class PagedResources extends Resources { /** * Returns the requested size of the page. - * + * * @return the size a positive long. */ public long getSize() { @@ -218,7 +218,7 @@ public class PagedResources extends Resources { /** * Returns the total number of elements available. - * + * * @return the totalElements a positive long. */ public long getTotalElements() { @@ -227,7 +227,7 @@ public class PagedResources extends Resources { /** * Returns how many pages are available in total. - * + * * @return the totalPages a positive long. */ public long getTotalPages() { @@ -236,14 +236,14 @@ public class PagedResources extends Resources { /** * Returns the number of the current page. - * + * * @return the number a positive long. */ public long getNumber() { return number; } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @@ -253,7 +253,7 @@ public class PagedResources extends Resources { totalElements, size); } - /* + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @@ -274,7 +274,7 @@ public class PagedResources extends Resources { && this.totalPages == that.totalPages; } - /* + /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ diff --git a/src/main/java/org/springframework/hateoas/RelProvider.java b/src/main/java/org/springframework/hateoas/RelProvider.java index 56734a7a..523cfde9 100644 --- a/src/main/java/org/springframework/hateoas/RelProvider.java +++ b/src/main/java/org/springframework/hateoas/RelProvider.java @@ -19,24 +19,24 @@ import org.springframework.plugin.core.Plugin; /** * API to provide relation types for collections and items of the given type. - * + * * @author Oliver Gierke */ public interface RelProvider extends Plugin> { /** * Returns the relation type to be used to point to an item resource of the given type. - * + * * @param type must not be {@literal null}. * @return */ - String getItemResourceRelFor(Class type); + LinkRelation getItemResourceRelFor(Class type); /** * Returns the relation type to be used to point to a collection resource of the given type. - * + * * @param type must not be {@literal null}. * @return */ - String getCollectionResourceRelFor(Class type); + LinkRelation getCollectionResourceRelFor(Class type); } diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index c11659b1..7f5a1631 100755 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -28,7 +28,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** * Base class for DTOs to collect links. - * + * * @author Oliver Gierke * @author Johhny Lim * @author Greg Turnquist @@ -42,16 +42,16 @@ public class ResourceSupport implements Identifiable { } /** - * Returns the {@link Link} with a rel of {@link IanaLinkRelation#SELF}. + * Returns the {@link Link} with a rel of {@link IanaLinkRelations#SELF}. */ @JsonIgnore public Optional getId() { - return getLink(IanaLinkRelation.SELF.value()); + return getLink(IanaLinkRelations.SELF); } /** * Adds the given link to the resource. - * + * * @param link */ public void add(Link link) { @@ -63,7 +63,7 @@ public class ResourceSupport implements Identifiable { /** * Adds all given {@link Link}s to the resource. - * + * * @param links */ public void add(Iterable links) { @@ -87,7 +87,7 @@ public class ResourceSupport implements Identifiable { /** * Returns whether the resource contains {@link Link}s at all. - * + * * @return */ public boolean hasLinks() { @@ -96,7 +96,7 @@ public class ResourceSupport implements Identifiable { /** * Returns whether the resource contains a {@link Link} with the given rel. - * + * * @param rel * @return */ @@ -104,14 +104,18 @@ public class ResourceSupport implements Identifiable { return getLink(rel).isPresent(); } + public boolean hasLink(LinkRelation rel) { + return hasLink(rel.value()); + } + /** * Returns all {@link Link}s contained in this resource. - * + * * @return */ @JsonProperty("links") - public List getLinks() { - return links; + public Links getLinks() { + return Links.of(links); } /** @@ -123,20 +127,24 @@ public class ResourceSupport implements Identifiable { /** * Returns the link with the given rel. - * + * * @param rel * @return the link with the given rel or {@link Optional#empty()} if none found. */ public Optional getLink(String rel) { + return getLink(LinkRelation.of(rel)); + } + + public Optional getLink(LinkRelation rel) { return links.stream() // - .filter(link -> link.hasRel(rel)) // + .filter(it -> it.hasRel(rel)) // .findFirst(); } /** * Returns the link with the given rel. - * + * * @param rel * @return the link with the given rel. * @throws IllegalArgumentException in case no link with the given rel can be found. @@ -147,6 +155,10 @@ public class ResourceSupport implements Identifiable { .orElseThrow(() -> new IllegalArgumentException(String.format("No link with rel %s found!", rel))); } + public Link getRequiredLink(LinkRelation rel) { + return getRequiredLink(rel.value()); + } + /** * Returns all {@link Link}s with the given relation type. * @@ -159,7 +171,11 @@ public class ResourceSupport implements Identifiable { .collect(Collectors.toList()); } - /* + public List getLinks(LinkRelation rel) { + return getLinks(rel.value()); + } + + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @@ -168,7 +184,7 @@ public class ResourceSupport implements Identifiable { return String.format("links: %s", links.toString()); } - /* + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @@ -188,7 +204,7 @@ public class ResourceSupport implements Identifiable { return this.links.equals(that.links); } - /* + /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ diff --git a/src/main/java/org/springframework/hateoas/Resources.java b/src/main/java/org/springframework/hateoas/Resources.java index 9b9882d6..61474c5d 100644 --- a/src/main/java/org/springframework/hateoas/Resources.java +++ b/src/main/java/org/springframework/hateoas/Resources.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** * General helper to easily create a wrapper for a collection of entities. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -44,7 +44,7 @@ public class Resources extends ResourceSupport implements Iterable { /** * Creates a {@link Resources} instance with the given content and {@link Link}s (optional). - * + * * @param content must not be {@literal null}. * @param links the links to be added to the {@link Resources}. */ @@ -54,7 +54,7 @@ public class Resources extends ResourceSupport implements Iterable { /** * Creates a {@link Resources} instance with the given content and {@link Link}s. - * + * * @param content must not be {@literal null}. * @param links the links to be added to the {@link Resources}. */ @@ -72,7 +72,7 @@ public class Resources extends ResourceSupport implements Iterable { /** * Creates a new {@link Resources} instance by wrapping the given domain class instances into a {@link Resource}. - * + * * @param content must not be {@literal null}. * @return */ @@ -80,6 +80,7 @@ public class Resources extends ResourceSupport implements Iterable { public static , S> Resources wrap(Iterable content) { Assert.notNull(content, "Content must not be null!"); + ArrayList resources = new ArrayList<>(); for (S element : content) { @@ -91,7 +92,7 @@ public class Resources extends ResourceSupport implements Iterable { /** * Returns the underlying elements. - * + * * @return the content will never be {@literal null}. */ @JsonProperty("content") @@ -99,7 +100,7 @@ public class Resources extends ResourceSupport implements Iterable { return Collections.unmodifiableCollection(content); } - /* + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ @@ -117,7 +118,7 @@ public class Resources extends ResourceSupport implements Iterable { return String.format("Resources { content: %s, %s }", getContent(), super.toString()); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.ResourceSupport#equals(java.lang.Object) */ @@ -138,7 +139,7 @@ public class Resources extends ResourceSupport implements Iterable { return contentEqual && super.equals(obj); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.ResourceSupport#hashCode() */ diff --git a/src/main/java/org/springframework/hateoas/StringLinkRelation.java b/src/main/java/org/springframework/hateoas/StringLinkRelation.java new file mode 100644 index 00000000..34c16f4f --- /dev/null +++ b/src/main/java/org/springframework/hateoas/StringLinkRelation.java @@ -0,0 +1,78 @@ +/* + * Copyright 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. + * 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. + */ +package org.springframework.hateoas; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Value; + +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Simple value type for a {@link String} based {@link LinkRelation}. + * + * @author Oliver Drotbohm + */ +@Value +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +class StringLinkRelation implements LinkRelation, Serializable { + + private static final long serialVersionUID = -3904935345545567957L; + private static final Map CACHE = new ConcurrentHashMap(256); + + @NonNull String relation; + + /** + * Returns a (potentially cached) {@link LinkRelation} for the given value. + * + * @param relation must not be {@literal null} or empty. + * @return + */ + @JsonCreator + public static StringLinkRelation of(String relation) { + + Assert.hasText(relation, "Relation must not be null or empty!"); + + return CACHE.computeIfAbsent(relation, it -> new StringLinkRelation(it)); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkRelation#value() + */ + @JsonValue + @Override + public String value() { + return relation; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return value(); + } +} diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index b7b02052..93298199 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -22,8 +22,6 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -36,7 +34,7 @@ import org.springframework.web.util.UriComponentsBuilder; /** * Custom URI template to support qualified URI template variables. - * + * * @author Oliver Gierke * @author JamesE Richardson * @see http://tools.ietf.org/html/rfc6570 @@ -52,7 +50,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Creates a new {@link UriTemplate} using the given template string. - * + * * @param template must not be {@literal null} or empty. */ public UriTemplate(String template) { @@ -71,7 +69,7 @@ public class UriTemplate implements Iterable, Serializable { String[] names = matcher.group(2).split(","); for (String name : names) { - + TemplateVariable variable; if (name.endsWith(VariableType.COMPOSITE_PARAM.toString())) { @@ -94,7 +92,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Creates a new {@link UriTemplate} from the given base URI and {@link TemplateVariables}. - * + * * @param baseUri must not be {@literal null} or empty. * @param variables defaults to {@link TemplateVariables#NONE}. */ @@ -108,7 +106,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Creates a new {@link UriTemplate} with the current {@link TemplateVariable}s augmented with the given ones. - * + * * @param variables can be {@literal null}. * @return will never be {@literal null}. */ @@ -142,7 +140,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Creates a new {@link UriTemplate} with a {@link TemplateVariable} with the given name and type added. - * + * * @param variableName must not be {@literal null} or empty. * @param type must not be {@literal null}. * @return will never be {@literal null}. @@ -153,7 +151,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Returns whether the given candidate is a URI template. - * + * * @param candidate * @return */ @@ -168,7 +166,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Returns the {@link TemplateVariable}s discovered. - * + * * @return */ public List getVariables() { @@ -177,7 +175,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Returns the names of the variables discovered. - * + * * @return */ public List getVariableNames() { @@ -189,7 +187,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Expands the {@link UriTemplate} using the given parameters. The values will be applied in the order of the * variables discovered. - * + * * @param parameters * @return * @see #expand(Map) @@ -215,7 +213,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Expands the {@link UriTemplate} using the given parameters. - * + * * @param parameters must not be {@literal null}. * @return */ @@ -237,7 +235,7 @@ public class UriTemplate implements Iterable, Serializable { return builder.build().toUri(); } - /* + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ @@ -246,7 +244,7 @@ public class UriTemplate implements Iterable, Serializable { return this.variables.iterator(); } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @@ -268,7 +266,7 @@ public class UriTemplate implements Iterable, Serializable { /** * Appends the value for the given {@link TemplateVariable} to the given {@link UriComponentsBuilder}. - * + * * @param builder must not be {@literal null}. * @param variable must not be {@literal null}. * @param value can be {@literal null}. @@ -311,17 +309,20 @@ public class UriTemplate implements Iterable, Serializable { * @param value * @see https://tools.ietf.org/html/rfc6570#section-2.4.2 */ + @SuppressWarnings("unchecked") private static void appendComposite(UriComponentsBuilder builder, String name, Object value) { if (value instanceof Iterable) { - for (Object valuePart : (Iterable) value) { - builder.queryParam(name, valuePart); - } + + ((Iterable) value).forEach(it -> builder.queryParam(name, it)); + } else if (value instanceof Map) { - for (Entry queryParam : (Set>) ((Map) value).entrySet()) { - builder.queryParam(queryParam.getKey().toString(), queryParam.getValue()); - } + + ((Map) value).entrySet() // + .forEach(it -> builder.queryParam(it.getKey().toString(), it.getValue())); + } else { + builder.queryParam(name, value); } } diff --git a/src/main/java/org/springframework/hateoas/VndErrors.java b/src/main/java/org/springframework/hateoas/VndErrors.java index 09774b5a..eb182fea 100644 --- a/src/main/java/org/springframework/hateoas/VndErrors.java +++ b/src/main/java/org/springframework/hateoas/VndErrors.java @@ -29,7 +29,7 @@ import com.fasterxml.jackson.annotation.JsonValue; /** * A representation model class to be rendered as specified for the media type {@code application/vnd.error}. - * + * * @see https://github.com/blongden/vnd.error * @author Oliver Gierke * @author Greg Turnquist @@ -41,7 +41,7 @@ public class VndErrors implements Iterable { /** * Creates a new {@link VndErrors} instance containing a single {@link VndError} with the given logref, message and * optional {@link Link}s. - * + * * @param logref must not be {@literal null} or empty. * @param message must not be {@literal null} or empty. * @param links @@ -52,7 +52,7 @@ public class VndErrors implements Iterable { /** * Creates a new {@link VndErrors} wrapper for at least one {@link VndError}. - * + * * @param errors must not be {@literal null}. */ public VndErrors(VndError error, VndError... errors) { @@ -66,7 +66,7 @@ public class VndErrors implements Iterable { /** * Creates a new {@link VndErrors} wrapper for the given {@link VndErrors}. - * + * * @param errors must not be {@literal null} or empty. */ @JsonCreator @@ -86,7 +86,7 @@ public class VndErrors implements Iterable { /** * Adds an additional {@link VndError} to the wrapper. - * + * * @param error */ public VndErrors add(VndError error) { @@ -96,7 +96,7 @@ public class VndErrors implements Iterable { /** * Dummy method to allow {@link JsonValue} to be configured. - * + * * @return the vndErrors */ @JsonValue @@ -113,7 +113,7 @@ public class VndErrors implements Iterable { return this.vndErrors.iterator(); } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @@ -152,7 +152,7 @@ public class VndErrors implements Iterable { /** * A single {@link VndError}. - * + * * @author Oliver Gierke */ public static class VndError extends ResourceSupport { @@ -162,7 +162,7 @@ public class VndErrors implements Iterable { /** * Creates a new {@link VndError} with the given logref, a message as well as some {@link Link}s. - * + * * @param logref must not be {@literal null} or empty. * @param message must not be {@literal null} or empty. * @param links @@ -188,7 +188,7 @@ public class VndErrors implements Iterable { /** * Returns the logref of the error. - * + * * @return the logref */ public String getLogref() { @@ -197,21 +197,20 @@ public class VndErrors implements Iterable { /** * Returns the message of the error. - * + * * @return the message */ public String getMessage() { return message; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.ResourceSupport#toString() */ @Override public String toString() { - return String.format("VndError[logref: %s, message: %s, links: [%s]]", logref, message, - StringUtils.collectionToCommaDelimitedString(getLinks())); + return String.format("VndError[logref: %s, message: %s, links: [%s]]", logref, message, getLinks().toString()); } /* diff --git a/src/main/java/org/springframework/hateoas/client/Rels.java b/src/main/java/org/springframework/hateoas/client/Rels.java index 2ec785e7..d3d3b3f0 100644 --- a/src/main/java/org/springframework/hateoas/client/Rels.java +++ b/src/main/java/org/springframework/hateoas/client/Rels.java @@ -15,6 +15,8 @@ */ package org.springframework.hateoas.client; +import java.util.Optional; + import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; import org.springframework.hateoas.LinkDiscoverers; @@ -25,7 +27,7 @@ import com.jayway.jsonpath.JsonPath; /** * Helper class to find {@link Link} instances in representations. - * + * * @author Oliver Gierke * @author Greg Turnquist * @since 0.11 @@ -34,7 +36,7 @@ class Rels { /** * Creates a new {@link Rel} for the given relation name and {@link LinkDiscoverers}. - * + * * @param rel must not be {@literal null} or empty. * @param discoverers must not be {@literal null}. * @return @@ -55,17 +57,17 @@ class Rels { /** * Returns the link contained in the given representation of the given {@link MediaType}. - * + * * @param representation * @param mediaType * @return */ - Link findInResponse(String representation, MediaType mediaType); + Optional findInResponse(String representation, MediaType mediaType); } /** * {@link Rel} to using a {@link LinkDiscoverer} based on the given {@link MediaType}. - * + * * @author Oliver Gierke */ private static class LinkDiscovererRel implements Rel { @@ -75,7 +77,7 @@ class Rels { /** * Creates a new {@link LinkDiscovererRel} for the given relation name and {@link LinkDiscoverers}. - * + * * @param rel must not be {@literal null} or empty. * @param discoverers must not be {@literal null}. */ @@ -88,21 +90,16 @@ class Rels { this.discoverers = discoverers; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.client.Rels.Rel#findInResponse(java.lang.String, org.springframework.http.MediaType) */ @Override - public Link findInResponse(String response, MediaType mediaType) { + public Optional findInResponse(String response, MediaType mediaType) { - LinkDiscoverer discoverer = discoverers.getLinkDiscovererFor(mediaType); - - if (discoverer == null) { - throw new IllegalStateException(String.format("Did not find LinkDiscoverer supporting media type %s!", - mediaType)); - } - - return discoverer.findLinkWithRel(rel, response); + return discoverers // + .getRequiredLinkDiscovererFor(mediaType) // + .findLinkWithRel(rel, response); } /* @@ -118,7 +115,7 @@ class Rels { /** * A relation that's being looked up by a JSONPath expression. - * + * * @author Oliver Gierke */ private static class JsonPathRel implements Rel { @@ -128,7 +125,7 @@ class Rels { /** * Creates a new {@link JsonPathRel} for the given JSON path. - * + * * @param jsonPath must not be {@literal null} or empty. */ private JsonPathRel(String jsonPath) { @@ -141,13 +138,13 @@ class Rels { this.rel = lastSegment.contains("[") ? lastSegment.substring(0, lastSegment.indexOf("[")) : lastSegment; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.client.Rels.Rel#findInResponse(java.lang.String, org.springframework.http.MediaType) */ @Override - public Link findInResponse(String representation, MediaType mediaType) { - return new Link(JsonPath.read(representation, jsonPath).toString(), rel); + public Optional findInResponse(String representation, MediaType mediaType) { + return Optional.of(new Link(JsonPath.read(representation, jsonPath).toString(), rel)); } } } diff --git a/src/main/java/org/springframework/hateoas/client/Traverson.java b/src/main/java/org/springframework/hateoas/client/Traverson.java index 93e1c6a0..c24c81ad 100644 --- a/src/main/java/org/springframework/hateoas/client/Traverson.java +++ b/src/main/java/org/springframework/hateoas/client/Traverson.java @@ -453,14 +453,11 @@ public class Traverson { String responseBody = responseEntity.getBody(); Hop thisHop = rels.next(); - Rel rel = Rels.getRelFor(thisHop.getRel(), discoverers); - Link link = rel.findInResponse(responseBody, contentType); - if (link == null) { - throw new IllegalStateException( - String.format("Expected to find link with rel '%s' in response %s!", rel, responseBody)); - } + Link link = rel.findInResponse(responseBody, contentType) // + .orElseThrow(() -> new IllegalStateException( + String.format("Expected to find link with rel '%s' in response %s!", rel, responseBody))); /* * Don't expand if the parameters are empty diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJson.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJson.java index a208338a..fddeed12 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJson.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJson.java @@ -19,9 +19,13 @@ import lombok.AccessLevel; import lombok.Value; import lombok.experimental.Wither; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; +import org.springframework.hateoas.Links.MergeMode; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; @@ -39,39 +43,62 @@ class CollectionJson { private String version; private String href; - - @JsonInclude(Include.NON_EMPTY) - private List links; - @JsonInclude(Include.NON_EMPTY) - private List> items; - - @JsonInclude(Include.NON_EMPTY) - private List queries; - - @JsonInclude(Include.NON_NULL) - private CollectionJsonTemplate template; - - @JsonInclude(Include.NON_NULL) - private CollectionJsonError error; + 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; @JsonCreator - CollectionJson(@JsonProperty("version") String version, @JsonProperty("href") String href, - @JsonProperty("links") List links, @JsonProperty("items") List> items, - @JsonProperty("queries") List queries, - @JsonProperty("template") CollectionJsonTemplate template, - @JsonProperty("error") CollectionJsonError error) { + CollectionJson(@JsonProperty("version") String version, // + @JsonProperty("href") String href, // + @JsonProperty("links") Links links, // + @JsonProperty("items") List> items, // + @JsonProperty("queries") List queries, // + @JsonProperty("template") CollectionJsonTemplate template, // + @JsonProperty("error") CollectionJsonError error) { this.version = version; this.href = href; - this.links = links; - this.items = items; + this.links = links == null ? Links.NONE : links; + this.items = items == null ? Collections.emptyList() : items; this.queries = queries; this.template = template; this.error = error; } CollectionJson() { - this("1.0", null, null, null, null, null, null); + this("1.0", null, Links.NONE, Collections.emptyList(), null, null, null); + } + + @SafeVarargs + final CollectionJson withItems(CollectionJsonItem... items) { + return withItems(Arrays.asList(items)); + } + + CollectionJson withItems(List> items) { + return new CollectionJson<>(version, href, links, items, queries, template, error); + } + + CollectionJson withLinks(Link... links) { + return withLinks(Links.of(links)); + } + + CollectionJson withLinks(Links links) { + return new CollectionJson<>(version, href, links, items, queries, template, error); + } + + CollectionJson withOwnSelfLink() { + + if (href == null) { + return this; + } + + return withLinks(Links.of(new Link(href)).merge(MergeMode.SKIP_BY_REL, links)); + } + + boolean hasItems() { + return !items.isEmpty(); } } diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonAffordanceModel.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonAffordanceModel.java index b9588f17..033a5777 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonAffordanceModel.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonAffordanceModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2018-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. @@ -33,17 +33,22 @@ import org.springframework.hateoas.support.PropertyUtils; import org.springframework.http.HttpMethod; /** + * {@link AffordanceModel} for Collection+JSON. + * * @author Greg Turnquist + * @author Oliver Drotbohm */ @EqualsAndHashCode(callSuper = true) -public class CollectionJsonAffordanceModel extends AffordanceModel { +class CollectionJsonAffordanceModel extends AffordanceModel { - private static final Set ENTITY_ALTERING_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH); + private static final Set ENTITY_ALTERING_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, + HttpMethod.PATCH); private final @Getter List inputProperties; private final @Getter List queryProperties; - - public CollectionJsonAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType) { + + public CollectionJsonAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, + List queryMethodParameters, ResolvableType outputType) { super(name, link, httpMethod, inputType, queryMethodParameters, outputType); @@ -52,23 +57,20 @@ public class CollectionJsonAffordanceModel extends AffordanceModel { } /** - * Look at the input's domain type to extract the {@link Affordance}'s properties. - * Then transform them into a list of {@link CollectionJsonData} objects. + * Look at the input's domain type to extract the {@link Affordance}'s properties. Then transform them into a list of + * {@link CollectionJsonData} objects. */ private List determineInputs() { - if (ENTITY_ALTERING_METHODS.contains(getHttpMethod())) { - - return PropertyUtils.findPropertyNames(getInputType()).stream() - .map(propertyName -> new CollectionJsonData() - .withName(propertyName) - .withValue("")) - .collect(Collectors.toList()); - - } else { + if (!ENTITY_ALTERING_METHODS.contains(getHttpMethod())) { return Collections.emptyList(); - } + + return PropertyUtils.findPropertyNames(getInputType()).stream() // + .map(propertyName -> new CollectionJsonData() // + .withName(propertyName) // + .withValue("")) // + .collect(Collectors.toList()); } /** @@ -78,15 +80,14 @@ public class CollectionJsonAffordanceModel extends AffordanceModel { */ private List determineQueryProperties() { - if (getHttpMethod().equals(HttpMethod.GET)) { - - return getQueryMethodParameters().stream() - .map(queryProperty -> new CollectionJsonData() - .withName(queryProperty.getName()) - .withValue("")) - .collect(Collectors.toList()); - } else { + if (!getHttpMethod().equals(HttpMethod.GET)) { return Collections.emptyList(); } + + return getQueryMethodParameters().stream() // + .map(queryProperty -> new CollectionJsonData() // + .withName(queryProperty.getName()) // + .withValue("")) // + .collect(Collectors.toList()); } } diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonDocument.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonDocument.java index 7a6a5e47..b38957e5 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonDocument.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonDocument.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -22,7 +22,7 @@ import lombok.experimental.Wither; import java.util.List; -import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -40,11 +40,13 @@ class CollectionJsonDocument { private CollectionJson collection; @JsonCreator - CollectionJsonDocument(@JsonProperty("version") String version, @JsonProperty("href") String href, - @JsonProperty("links") List links, @JsonProperty("items") List> items, - @JsonProperty("queries") List queries, - @JsonProperty("template") CollectionJsonTemplate template, - @JsonProperty("error") CollectionJsonError error) { + CollectionJsonDocument(@JsonProperty("version") String version, // + @JsonProperty("href") String href, // + @JsonProperty("links") Links links, // + @JsonProperty("items") List> items, // + @JsonProperty("queries") List queries, // + @JsonProperty("template") CollectionJsonTemplate template, // + @JsonProperty("error") CollectionJsonError error) { this.collection = new CollectionJson<>(version, href, links, items, queries, template, error); } } diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonItem.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonItem.java index cf1e78f5..f39d89e3 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonItem.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonItem.java @@ -22,11 +22,13 @@ import lombok.Value; import lombok.experimental.Wither; import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; +import org.springframework.hateoas.Links.MergeMode; import org.springframework.hateoas.support.PropertyUtils; import com.fasterxml.jackson.annotation.JsonCreator; @@ -48,20 +50,17 @@ class CollectionJsonItem { private String href; private List data; - - @JsonInclude(Include.NON_EMPTY) - private List links; - - @Getter(onMethod = @__({@JsonIgnore}), value = AccessLevel.PRIVATE) - private T rawData; + private @JsonInclude(Include.NON_EMPTY) Links links; + private @Getter(onMethod = @__({ @JsonIgnore }), value = AccessLevel.PRIVATE) T rawData; @JsonCreator - CollectionJsonItem(@JsonProperty("href") String href, @JsonProperty("data") List data, - @JsonProperty("links") List links) { + CollectionJsonItem(@JsonProperty("href") String href, // + @JsonProperty("data") List data, // + @JsonProperty("links") Links links) { this.href = href; this.data = data; - this.links = links; + this.links = links == null ? Links.NONE : links; this.rawData = null; } @@ -72,9 +71,7 @@ class CollectionJsonItem { /** * Simple scalar types that can be encoded by value, not type. */ - private final static HashSet> PRIMITIVE_TYPES = new HashSet>() {{ - add(String.class); - }}; + private final static Set> PRIMITIVE_TYPES = Collections.singleton(String.class); /** * Transform a domain object into a collection of {@link CollectionJsonData} objects to serialize properly. @@ -92,15 +89,13 @@ class CollectionJsonItem { } return PropertyUtils.findProperties(this.rawData).entrySet().stream() - .map(entry -> new CollectionJsonData() - .withName(entry.getKey()) - .withValue(entry.getValue())) - .collect(Collectors.toList()); + .map(entry -> new CollectionJsonData().withName(entry.getKey()).withValue(entry.getValue())) + .collect(Collectors.toList()); } /** * Generate an object used the deserialized properties and the provided type from the deserializer. - * + * * @param javaType - type of the object to create * @return */ @@ -111,9 +106,23 @@ class CollectionJsonItem { } return PropertyUtils.createObjectFromProperties(javaType.getRawClass(), // - this.data.stream() - .collect(Collectors.toMap( - CollectionJsonData::getName, - CollectionJsonData::getValue))); + this.data.stream().collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue))); + } + + public CollectionJsonItem withLinks(Link... links) { + return new CollectionJsonItem<>(href, data, Links.of(links), rawData); + } + + public CollectionJsonItem withLinks(Links links) { + return new CollectionJsonItem<>(href, data, links, rawData); + } + + public CollectionJsonItem withOwnSelfLink() { + + if (href == null) { + return this; + } + + return withLinks(Links.of(new Link(href)).merge(MergeMode.SKIP_BY_REL, links)); } } diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java index 79794fb7..c469198f 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java @@ -16,23 +16,23 @@ package org.springframework.hateoas.collectionjson; import java.io.InputStream; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.Optional; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; +import org.springframework.hateoas.LinkRelation; +import org.springframework.hateoas.Links; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.core.JsonPathLinkDiscoverer; +import org.springframework.util.Assert; /** - * {@link LinkDiscoverer} implementation based on JSON Collection link structure. - * - * NOTE: Since links can appear in two different places in a Collection+JSON document, this discoverer - * uses two. + * {@link LinkDiscoverer} implementation based on JSON Collection link structure. NOTE: Since links can appear in two + * different places in a Collection+JSON document, this discoverer uses two. * * @author Greg Turnquist + * @author Oliver Drotbohm */ public class CollectionJsonLinkDiscoverer extends JsonPathLinkDiscoverer { @@ -41,77 +41,94 @@ public class CollectionJsonLinkDiscoverer extends JsonPathLinkDiscoverer { public CollectionJsonLinkDiscoverer() { super("$.collection..links..[?(@.rel == '%s')].href", MediaTypes.COLLECTION_JSON); + this.selfLinkDiscoverer = new CollectionJsonSelfLinkDiscoverer(); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.JsonPathLinkDiscoverer#findLinkWithRel(org.springframework.hateoas.LinkRelation, java.lang.String) + */ @Override - public Link findLinkWithRel(String rel, String representation) { + public Optional findLinkWithRel(LinkRelation relation, String representation) { - if (rel.equals(IanaLinkRelation.SELF.value())) { - return findSelfLink(representation); - } else { - return super.findLinkWithRel(rel, representation); - } + Assert.notNull(relation, "LinkRelation must not be null!"); + Assert.notNull(representation, "Representation must not be null!"); + + return relation.isSameAs(IanaLinkRelations.SELF) // + ? findSelfLink(representation) // + : super.findLinkWithRel(relation, representation); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(java.lang.String, java.io.InputStream) + */ @Override - public Link findLinkWithRel(String rel, InputStream representation) { + public Optional findLinkWithRel(LinkRelation relation, InputStream representation) { - if (rel.equals(IanaLinkRelation.SELF.value())) { - return findSelfLink(representation); - } else { - return super.findLinkWithRel(rel, representation); - } + Assert.notNull(relation, "LinkRelation must not be null!"); + Assert.notNull(representation, "InputStream must not be null!"); + + return relation.isSameAs(IanaLinkRelations.SELF) // + ? findSelfLink(representation) // + : super.findLinkWithRel(relation, representation); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.JsonPathLinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.lang.String) + */ @Override - public List findLinksWithRel(String rel, String representation) { + public Links findLinksWithRel(LinkRelation relation, String representation) { - if (rel.equals(IanaLinkRelation.SELF.value())) { - return addSelfLink(super.findLinksWithRel(rel, representation), representation); - } else { - return super.findLinksWithRel(rel, representation); - } + Assert.notNull(relation, "LinkRelation must not be null!"); + Assert.notNull(representation, "Representation must not be null!"); + + return relation.isSameAs(IanaLinkRelations.SELF) // + ? addSelfLink(super.findLinksWithRel(relation, representation), representation) // + : super.findLinksWithRel(relation, representation); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.JsonPathLinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.io.InputStream) + */ @Override - public List findLinksWithRel(String rel, InputStream representation) { + public Links findLinksWithRel(LinkRelation relation, InputStream representation) { + + return relation.isSameAs(IanaLinkRelations.SELF) // + ? addSelfLink(super.findLinksWithRel(relation, representation), representation) // + : super.findLinksWithRel(relation, representation); - if (rel.equals(IanaLinkRelation.SELF.value())) { - return addSelfLink(super.findLinksWithRel(rel, representation), representation); - } else { - return super.findLinksWithRel(rel, representation); - } } // // Internal methods to support discovering the "self" link found at "$.collection.href". // - private Link findSelfLink(String representation) { - return this.selfLinkDiscoverer.findLinkWithRel(IanaLinkRelation.SELF.value(), representation); + private Optional findSelfLink(String representation) { + return this.selfLinkDiscoverer.findLinkWithRel(IanaLinkRelations.SELF, representation); } - private Link findSelfLink(InputStream representation) { - return this.selfLinkDiscoverer.findLinkWithRel(IanaLinkRelation.SELF.value(), representation); + private Optional findSelfLink(InputStream representation) { + return this.selfLinkDiscoverer.findLinkWithRel(IanaLinkRelations.SELF, representation); } - private List addSelfLink(List links, String representation) { + private Links addSelfLink(Links links, String representation) { - return Stream.concat( - Stream.of(findSelfLink(representation)), - links.stream() - ) - .collect(Collectors.toList()); + return findSelfLink(representation) // + .map(Links::of) // + .map(it -> it.and(links)) // + .orElseGet(() -> links); } - private List addSelfLink(List links, InputStream representation) { + private Links addSelfLink(Links links, InputStream representation) { - return Stream.concat( - Stream.of(findSelfLink(representation)), - links.stream() - ) - .collect(Collectors.toList()); + return findSelfLink(representation) // + .map(Links::of) // + .map(it -> it.and(links)) // + .orElseGet(() -> links); } /** diff --git a/src/main/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonModule.java b/src/main/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonModule.java index 1bfbaaab..7942b3e5 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonModule.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonModule.java @@ -17,20 +17,19 @@ package org.springframework.hateoas.collectionjson; import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; 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; -import java.util.stream.Stream; -import org.springframework.beans.BeanUtils; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.Affordance; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; +import org.springframework.hateoas.Links.MergeMode; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.Resource; @@ -39,19 +38,20 @@ import org.springframework.hateoas.Resources; import org.springframework.hateoas.support.JacksonHelper; import org.springframework.hateoas.support.PropertyUtils; import org.springframework.http.HttpMethod; -import org.springframework.util.ClassUtils; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; -import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase; -import com.fasterxml.jackson.databind.introspect.Annotated; -import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; -import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.ContainerSerializer; @@ -59,13 +59,15 @@ import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.type.TypeFactory; /** - * Jackson 2 module implementation to render {@link Resources}, {@link Resource}, and {@link ResourceSupport} - * instances in Collection+JSON compatible JSON. + * Jackson 2 module implementation to render {@link Resources}, {@link Resource}, and {@link ResourceSupport} instances + * in Collection+JSON compatible JSON. * * @author Greg Turnquist */ public class Jackson2CollectionJsonModule extends SimpleModule { + private static final long serialVersionUID = -6540574644565592709L; + public Jackson2CollectionJsonModule() { super("collection-json-module", new Version(1, 0, 0, null, "org.springframework.hateoas", "spring-hateoas")); @@ -79,6 +81,8 @@ public class Jackson2CollectionJsonModule extends SimpleModule { addSerializer(new CollectionJsonResourcesSerializer()); addSerializer(new CollectionJsonResourceSerializer()); addSerializer(new CollectionJsonResourceSupportSerializer()); + addSerializer(new CollectionJsonLinksSerializer()); + addDeserializer(Links.class, new CollectionJsonLinksDeserializer()); } /** @@ -87,72 +91,78 @@ public class Jackson2CollectionJsonModule extends SimpleModule { * @author Alexander Baetz * @author Oliver Gierke */ - static class CollectionJsonLinkListSerializer extends ContainerSerializer> implements ContextualSerializer { + static class CollectionJsonLinksSerializer extends ContainerSerializer { - private final BeanProperty property; - private final MessageSourceAccessor messageSource; + private static final long serialVersionUID = 5959299073301391055L; - CollectionJsonLinkListSerializer(MessageSourceAccessor messageSource) { - this(null, messageSource); + CollectionJsonLinksSerializer() { + super(Links.class); } - CollectionJsonLinkListSerializer(BeanProperty property, MessageSourceAccessor messageSource) { - - super(List.class, false); - this.property = property; - this.messageSource = messageSource; - } + /* + * (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(List value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { + public void serialize(Links links, JsonGenerator jgen, SerializerProvider provider) throws IOException { - ResourceSupport resource = new ResourceSupport(); - resource.add(value); + JavaType type = provider.getTypeFactory().constructCollectionType(List.class, Link.class); - CollectionJson collectionJson = new CollectionJson() - .withVersion("1.0") - .withHref(resource.getRequiredLink(IanaLinkRelation.SELF.value()).expand().getHref()) - .withLinks(withoutSelfLink(value)) - .withItems(Collections.EMPTY_LIST); - - provider - .findValueSerializer(CollectionJson.class, property) - .serialize(collectionJson, jgen, provider); + provider.findValueSerializer(type) // + .serialize(links.toList(), jgen, provider); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) + */ @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { - return new CollectionJsonLinkListSerializer(property, messageSource); + public boolean isEmpty(SerializerProvider provider, Links value) { + return value.isEmpty(); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType() + */ @Override public JavaType getContentType() { - return null; + return TypeFactory.defaultInstance().constructType(Link.class); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer() + */ @Override public JsonSerializer getContentSerializer() { return null; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object) + */ @Override - public boolean isEmpty(List value) { - return value.isEmpty(); - } - - @Override - public boolean hasSingleElement(List value) { - return value.size() == 1; + public boolean hasSingleElement(Links value) { + return false; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer) + */ @Override protected ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { return null; } } - static class CollectionJsonResourceSupportSerializer extends ContainerSerializer implements ContextualSerializer { + static class CollectionJsonResourceSupportSerializer extends ContainerSerializer + implements ContextualSerializer { + + private static final long serialVersionUID = 6127711241993352699L; private final BeanProperty property; @@ -169,33 +179,32 @@ public class Jackson2CollectionJsonModule extends SimpleModule { @Override public void serialize(ResourceSupport value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - String href = value.getRequiredLink(IanaLinkRelation.SELF.value()).getHref(); + String href = value.getRequiredLink(IanaLinkRelations.SELF.value()).getHref(); - CollectionJson collectionJson = new CollectionJson() - .withVersion("1.0") - .withHref(href) - .withLinks(withoutSelfLink(value.getLinks())) - .withQueries(findQueries(value)) - .withTemplate(findTemplate(value)); + CollectionJson collectionJson = new CollectionJson<>() // + .withVersion("1.0") // + .withHref(href) // + .withLinks(value.getLinks().without(IanaLinkRelations.SELF)) // + .withQueries(findQueries(value)) // + .withTemplate(findTemplate(value)); - CollectionJsonItem item = new CollectionJsonItem() - .withHref(href) - .withLinks(withoutSelfLink(value.getLinks())) - .withRawData(value); + CollectionJsonItem item = new CollectionJsonItem<>() // + .withHref(href) // + .withLinks(value.getLinks().without(IanaLinkRelations.SELF)) // + .withRawData(value); if (!item.getData().isEmpty()) { - collectionJson = collectionJson.withItems(Collections.singletonList(item)); + collectionJson = collectionJson.withItems(item); } CollectionJsonDocument doc = new CollectionJsonDocument<>(collectionJson); - provider - .findValueSerializer(CollectionJsonDocument.class, property) - .serialize(doc, jgen, provider); + provider.findValueSerializer(CollectionJsonDocument.class, property).serialize(doc, jgen, provider); } @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { return new CollectionJsonResourceSupportSerializer(property); } @@ -220,7 +229,10 @@ public class Jackson2CollectionJsonModule extends SimpleModule { } } - static class CollectionJsonResourceSerializer extends ContainerSerializer> implements ContextualSerializer { + static class CollectionJsonResourceSerializer extends ContainerSerializer> + implements ContextualSerializer { + + private static final long serialVersionUID = 2212535956767860364L; private final BeanProperty property; @@ -237,28 +249,29 @@ public class Jackson2CollectionJsonModule extends SimpleModule { @Override public void serialize(Resource value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - String href = value.getRequiredLink(IanaLinkRelation.SELF.value()).getHref(); + String href = value.getRequiredLink(IanaLinkRelations.SELF).getHref(); + Links withoutSelfLink = value.getLinks().without(IanaLinkRelations.SELF); - CollectionJson collectionJson = new CollectionJson() - .withVersion("1.0") - .withHref(href) - .withLinks(withoutSelfLink(value.getLinks())) - .withItems(Collections.singletonList(new CollectionJsonItem<>() - .withHref(href) - .withLinks(withoutSelfLink(value.getLinks())) - .withRawData(value.getContent()))) - .withQueries(findQueries(value)) - .withTemplate(findTemplate(value)); + CollectionJson collectionJson = new CollectionJson<>() // + .withVersion("1.0") // + .withHref(href) // + .withLinks(withoutSelfLink) // + .withItems(new CollectionJsonItem<>() // + .withHref(href) // + .withLinks(withoutSelfLink) // + .withRawData(value.getContent())) + .withQueries(findQueries(value)) // + .withTemplate(findTemplate(value)); CollectionJsonDocument doc = new CollectionJsonDocument<>(collectionJson); - provider - .findValueSerializer(CollectionJsonDocument.class, property) - .serialize(doc, jgen, provider); + provider.findValueSerializer(CollectionJsonDocument.class, property) // + .serialize(doc, jgen, provider); } @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { return new CollectionJsonResourceSerializer(property); } @@ -283,70 +296,85 @@ public class Jackson2CollectionJsonModule extends SimpleModule { } } - static class CollectionJsonResourcesSerializer extends ContainerSerializer> implements ContextualSerializer { + static class CollectionJsonResourcesSerializer extends ContainerSerializer> { - private final BeanProperty property; + private static final long serialVersionUID = -278986431091914402L; CollectionJsonResourcesSerializer() { - this(null); - } - - CollectionJsonResourcesSerializer(BeanProperty property) { - super(Resources.class, false); - 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 public void serialize(Resources value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - CollectionJson collectionJson = new CollectionJson() - .withVersion("1.0") - .withHref(value.getRequiredLink(IanaLinkRelation.SELF.value()).getHref()) - .withLinks(withoutSelfLink(value.getLinks())) - .withItems(resourcesToCollectionJsonItems(value)) - .withQueries(findQueries(value)) - .withTemplate(findTemplate(value)); + CollectionJson collectionJson = new CollectionJson<>() // + .withVersion("1.0") // + .withHref(value.getRequiredLink(IanaLinkRelations.SELF).getHref()) // + .withLinks(value.getLinks().without(IanaLinkRelations.SELF)) // + .withItems(resourcesToCollectionJsonItems(value)) // + .withQueries(findQueries(value)) // + .withTemplate(findTemplate(value)); CollectionJsonDocument doc = new CollectionJsonDocument<>(collectionJson); - provider - .findValueSerializer(CollectionJsonDocument.class, property) - .serialize(doc, jgen, provider); - } - - @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { - return new CollectionJsonResourcesSerializer(property); + provider.findValueSerializer(CollectionJsonDocument.class) // + .serialize(doc, jgen, provider); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType() + */ @Override public JavaType getContentType() { return null; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer() + */ @Override public JsonSerializer getContentSerializer() { return null; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) + */ @Override - public boolean isEmpty(Resources value) { - return value.getContent().size() == 0; + public boolean isEmpty(SerializerProvider provider, Resources value) { + return value.getContent().isEmpty(); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object) + */ @Override public boolean hasSingleElement(Resources value) { return value.getContent().size() == 1; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer) + */ @Override protected ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { return null; } } - static class CollectionJsonPagedResourcesSerializer extends ContainerSerializer> implements ContextualSerializer { + static class CollectionJsonPagedResourcesSerializer extends ContainerSerializer> + implements ContextualSerializer { + + private static final long serialVersionUID = -6703190072925382402L; private final BeanProperty property; @@ -363,23 +391,22 @@ public class Jackson2CollectionJsonModule extends SimpleModule { @Override public void serialize(PagedResources value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - CollectionJson collectionJson = new CollectionJson() - .withVersion("1.0") - .withHref(value.getRequiredLink(IanaLinkRelation.SELF.value()).getHref()) - .withLinks(withoutSelfLink(value.getLinks())) - .withItems(resourcesToCollectionJsonItems(value)) - .withQueries(findQueries(value)) - .withTemplate(findTemplate(value)); + CollectionJson collectionJson = new CollectionJson<>() // + .withVersion("1.0") // + .withHref(value.getRequiredLink(IanaLinkRelations.SELF).getHref()) // + .withLinks(value.getLinks().without(IanaLinkRelations.SELF)) // + .withItems(resourcesToCollectionJsonItems(value)) // + .withQueries(findQueries(value)) // + .withTemplate(findTemplate(value)); CollectionJsonDocument doc = new CollectionJsonDocument<>(collectionJson); - provider - .findValueSerializer(CollectionJsonDocument.class, property) - .serialize(doc, jgen, provider); + provider.findValueSerializer(CollectionJsonDocument.class, property).serialize(doc, jgen, provider); } @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { return new CollectionJsonPagedResourcesSerializer(property); } @@ -409,33 +436,49 @@ public class Jackson2CollectionJsonModule extends SimpleModule { } } - static class CollectionJsonLinkListDeserializer extends ContainerDeserializerBase> { + static class CollectionJsonLinksDeserializer extends ContainerDeserializerBase { - CollectionJsonLinkListDeserializer() { + private static final long serialVersionUID = 4260899521055619665L; + + CollectionJsonLinksDeserializer() { super(TypeFactory.defaultInstance().constructCollectionLikeType(List.class, Link.class)); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentType() + */ @Override public JavaType getContentType() { return null; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer() + */ @Override 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 - public List deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException { + public Links deserialize(JsonParser jp, DeserializationContext ctx) throws IOException { - CollectionJsonDocument document = jp.getCodec().readValue(jp, CollectionJsonDocument.class); + JavaType type = ctx.getTypeFactory().constructCollectionLikeType(List.class, Link.class); - return potentiallyAddSelfLink(document.getCollection().getLinks(), document.getCollection().getHref()); + return Links.of(jp.getCodec().> readValue(jp, type)); } } static class CollectionJsonResourceSupportDeserializer extends ContainerDeserializerBase - implements ContextualDeserializer { + implements ContextualDeserializer { + + private static final long serialVersionUID = 502737712634617739L; private final JavaType contentType; @@ -449,65 +492,84 @@ public 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 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 public ResourceSupport deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JavaType rootType = ctxt.getTypeFactory().constructSimpleType(Object.class, new JavaType[]{}); - JavaType wrappedType = ctxt.getTypeFactory().constructParametricType(CollectionJsonDocument.class, rootType); + TypeFactory typeFactory = ctxt.getTypeFactory(); + + JavaType rootType = typeFactory.constructSimpleType(Object.class, new JavaType[] {}); + JavaType wrappedType = typeFactory.constructParametricType(CollectionJsonDocument.class, rootType); CollectionJsonDocument document = jp.getCodec().readValue(jp, wrappedType); + CollectionJson collection = document.getCollection(); - List> items = Optional.ofNullable(document.getCollection().getItems()).orElse(new ArrayList<>()); - List links = Optional.ofNullable(document.getCollection().getLinks()).orElse(new ArrayList<>()); + List> items = collection.getItems(); + Links links = collection.getLinks(); - if (items.size() == 0) { - if (document.getCollection().getTemplate() != null) { + CollectionJson withOwnSelfLink = collection.withOwnSelfLink(); - Map properties = document.getCollection().getTemplate().getData().stream() + if (!items.isEmpty()) { + + Links merged = items.stream() // + .map(CollectionJsonItem::getLinks) // + .reduce(links, // + (left, right) -> left.merge(right), // + (left, right) -> right); + + CollectionJsonItem firstItem = items.get(0).withOwnSelfLink(); + + ResourceSupport resource = (ResourceSupport) firstItem.toRawData(this.contentType); + resource.add(firstItem.getLinks().merge(merged)); + + return resource; + } + + if (withOwnSelfLink.getTemplate() != null) { + + Map properties = withOwnSelfLink.getTemplate().getData().stream() .collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue)); - ResourceSupport obj = (ResourceSupport) PropertyUtils.createObjectFromProperties(this.contentType.getRawClass(), properties); + ResourceSupport resourceSupport = (ResourceSupport) PropertyUtils + .createObjectFromProperties(this.contentType.getRawClass(), properties); - obj.add(potentiallyAddSelfLink(links, document.getCollection().getHref())); + resourceSupport.add(withOwnSelfLink.getLinks()); - return obj; - } else { - ResourceSupport resource = new ResourceSupport(); - resource.add(potentiallyAddSelfLink(links, document.getCollection().getHref())); + return resourceSupport; - return resource; - } } else { - items.stream() - .flatMap(item -> Optional.ofNullable(item.getLinks()) - .map(Collection::stream) - .orElse(Stream.empty())) - .forEach(link -> { - if (!links.contains(link)) - links.add(link); - }); - - ResourceSupport resource = (ResourceSupport) items.get(0).toRawData(this.contentType); - resource.add(potentiallyAddSelfLink(links, items.get(0).getHref())); + ResourceSupport resource = new ResourceSupport(); + resource.add(withOwnSelfLink.getLinks()); return resource; } } @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, - BeanProperty property) throws JsonMappingException { + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { if (property != null) { return new CollectionJsonResourceSupportDeserializer(property.getType().getContentType()); @@ -520,6 +582,8 @@ public class Jackson2CollectionJsonModule extends SimpleModule { static class CollectionJsonResourceDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { + private static final long serialVersionUID = -5911687423054932523L; + private final JavaType contentType; CollectionJsonResourceDeserializer() { @@ -543,298 +607,187 @@ public class Jackson2CollectionJsonModule extends SimpleModule { } @Override - public Resource deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException { + public Resource deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JavaType rootType = JacksonHelper.findRootType(this.contentType); JavaType wrappedType = ctxt.getTypeFactory().constructParametricType(CollectionJsonDocument.class, rootType); CollectionJsonDocument document = jp.getCodec().readValue(jp, wrappedType); - List> items = Optional.ofNullable(document.getCollection().getItems()).orElse(new ArrayList<>()); - List links = Optional.ofNullable(document.getCollection().getLinks()).orElse(new ArrayList<>()); + List> items = Optional.ofNullable(document.getCollection().getItems()) + .orElse(new ArrayList<>()); + Links links = document.getCollection().withOwnSelfLink().getLinks(); if (items.size() == 0 && document.getCollection().getTemplate() != null) { Map properties = document.getCollection().getTemplate().getData().stream() - .collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue)); + .collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue)); Object obj = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties); - return new Resource<>(obj, potentiallyAddSelfLink(links, document.getCollection().getHref())); + return new Resource<>(obj, links); } else { - items.stream() - .flatMap(item -> Optional.ofNullable(item.getLinks()) - .map(Collection::stream) - .orElse(Stream.empty())) - .forEach(link -> { - if (!links.contains(link)) - links.add(link); - }); + Links merged = items.stream() // + .map(CollectionJsonItem::getLinks) // + .reduce(links, // + (left, right) -> left.merge(MergeMode.REPLACE_BY_REL, right), // + (left, right) -> right); - return new Resource<>(items.get(0).toRawData(rootType), - potentiallyAddSelfLink(links, items.get(0).getHref())); + CollectionJsonItem firstItem = items.get(0).withOwnSelfLink(); + + return new Resource<>(firstItem.toRawData(rootType), + merged.merge(MergeMode.REPLACE_BY_REL, firstItem.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) throws JsonMappingException { + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { - if (property != null) { - return new CollectionJsonResourceDeserializer(property.getType().getContentType()); - } else { - return new CollectionJsonResourceDeserializer(ctxt.getContextualType()); - } + return new CollectionJsonResourceDeserializer( + property == null ? ctxt.getContextualType() : property.getType().getContentType()); } } - static class CollectionJsonResourcesDeserializer extends ContainerDeserializerBase + static abstract class CollectionJsonDeserializerBase> extends ContainerDeserializerBase implements ContextualDeserializer { + private static final long serialVersionUID = 1007769482339850545L; + private final JavaType contentType; + private final BiFunction, Links, T> finalizer; + private final Function> creator; + + CollectionJsonDeserializerBase(BiFunction, Links, T> finalizer, + Function> creator) { + this(TypeFactory.defaultInstance().constructType(CollectionJson.class), finalizer, creator); + } + + private CollectionJsonDeserializerBase(JavaType contentType, BiFunction, Links, T> finalizer, + Function> creator) { + + super(contentType); + + this.contentType = contentType; + this.finalizer = finalizer; + this.creator = creator; + } + + /* + * (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 + 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 + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { + + JavaType contextualType = property == null // + ? ctxt.getContextualType() // + : property.getType().getContentType(); + + return creator.apply(contextualType); + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ + @Override + public T deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException { + + JavaType rootType = JacksonHelper.findRootType(contentType); + JavaType wrappedType = ctxt.getTypeFactory().constructParametricType(CollectionJsonDocument.class, rootType); + + CollectionJsonDocument document = parser.getCodec().readValue(parser, wrappedType); + CollectionJson collection = document.getCollection().withOwnSelfLink(); + + Links links = collection.getLinks(); + + if (!collection.hasItems() || !contentType.hasGenericTypes()) { + return finalizer.apply(Collections.emptyList(), links); + } + + boolean isResource = contentType.hasGenericTypes() && contentType.containedType(0).hasRawClass(Resource.class); + + return collection.getItems().stream() // + .map(CollectionJsonItem::withOwnSelfLink) // + . map(it -> isResource // + ? new Resource<>(it.toRawData(rootType), it.getLinks()) // + : it.toRawData(rootType)) // + .collect(Collectors.collectingAndThen(Collectors.toList(), it -> finalizer.apply(it, links))); + } + } + + static class CollectionJsonResourcesDeserializer extends CollectionJsonDeserializerBase> { + + private static final long serialVersionUID = 6406522912020578141L; + private static final BiFunction, Links, Resources> FINISHER = Resources::new; + private static final Function>> CONTEXTUAL_CREATOR = CollectionJsonResourcesDeserializer::new; CollectionJsonResourcesDeserializer() { - this(TypeFactory.defaultInstance().constructType(CollectionJson.class)); + super(FINISHER, CONTEXTUAL_CREATOR); } - CollectionJsonResourcesDeserializer(JavaType contentType) { - - super(contentType); - this.contentType = contentType; - } - - @Override - public JavaType getContentType() { - return this.contentType; - } - - @Override - public JsonDeserializer getContentDeserializer() { - return null; - } - - @Override - public Resources deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - - JavaType rootType = JacksonHelper.findRootType(this.contentType); - JavaType wrappedType = ctxt.getTypeFactory().constructParametricType(CollectionJsonDocument.class, rootType); - - CollectionJsonDocument document = jp.getCodec().readValue(jp, wrappedType); - - List contentList = new ArrayList<>(); - - if (document.getCollection().getItems() != null) { - for (CollectionJsonItem item : document.getCollection().getItems()) { - - Object data = item.toRawData(rootType); - - if (this.contentType.hasGenericTypes()) { - if (isResource(this.contentType)) { - contentList.add(new Resource<>(data, potentiallyAddSelfLink(item.getLinks(), item.getHref()))); - } else { - contentList.add(data); - } - } - } - } - - return new Resources(contentList, potentiallyAddSelfLink(document.getCollection().getLinks(), document.getCollection().getHref())); - } - - static boolean isResource(JavaType type) { - return type.containedType(0).hasRawClass(Resource.class); - } - - @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, - BeanProperty property) throws JsonMappingException { - - if (property != null) { - - JavaType vc = property.getType().getContentType(); - CollectionJsonResourcesDeserializer des = new CollectionJsonResourcesDeserializer(vc); - - return des; - } else { - return new CollectionJsonResourcesDeserializer(ctxt.getContextualType()); - } + private CollectionJsonResourcesDeserializer(JavaType contentType) { + super(contentType, FINISHER, CONTEXTUAL_CREATOR); } } - static class CollectionJsonPagedResourcesDeserializer extends ContainerDeserializerBase - implements ContextualDeserializer { + static class CollectionJsonPagedResourcesDeserializer extends CollectionJsonDeserializerBase> { - private final JavaType contentType; + private static final long serialVersionUID = -7465448422501330790L; + private static final BiFunction, Links, PagedResources> FINISHER = (content, + links) -> new PagedResources<>(content, null, links); + private static final Function>> CONTEXTUAL_CREATOR = CollectionJsonPagedResourcesDeserializer::new; CollectionJsonPagedResourcesDeserializer() { - this(TypeFactory.defaultInstance().constructType(CollectionJson.class)); + super(FINISHER, CONTEXTUAL_CREATOR); } - CollectionJsonPagedResourcesDeserializer(JavaType contentType) { - - super(contentType); - this.contentType = contentType; - } - - @Override - public JavaType getContentType() { - return this.contentType; - } - - @Override - public JsonDeserializer getContentDeserializer() { - return null; - } - - @Override - public PagedResources deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - - JavaType rootType = JacksonHelper.findRootType(this.contentType); - JavaType wrappedType = ctxt.getTypeFactory().constructParametricType(CollectionJsonDocument.class, rootType); - - CollectionJsonDocument document = jp.getCodec().readValue(jp, wrappedType); - - List items = new ArrayList<>(); - - document.getCollection().getItems().forEach(item -> { - - Object data = item.toRawData(rootType); - List links = item.getLinks() == null ? Collections.EMPTY_LIST : item.getLinks(); - - if (this.contentType.hasGenericTypes()) { - - if (this.contentType.containedType(0).hasRawClass(Resource.class)) { - items.add(new Resource<>(data, potentiallyAddSelfLink(links, item.getHref()))); - } else { - items.add(data); - } - } - }); - - PagedResources.PageMetadata pageMetadata = null; - - return new PagedResources(items, pageMetadata, - potentiallyAddSelfLink(document.getCollection().getLinks(), document.getCollection().getHref())); - } - - @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { - - if (property != null) { - - JavaType vc = property.getType().getContentType(); - CollectionJsonPagedResourcesDeserializer des = new CollectionJsonPagedResourcesDeserializer(vc); - - return des; - } else { - return new CollectionJsonPagedResourcesDeserializer(ctxt.getContextualType()); - } - } - - } - - public static class CollectionJsonHandlerInstantiator extends HandlerInstantiator { - - private final Map, Object> instanceMap = new HashMap<>(); - - public CollectionJsonHandlerInstantiator(MessageSourceAccessor messageSource) { - - this.instanceMap.put(CollectionJsonPagedResourcesSerializer.class, new CollectionJsonPagedResourcesSerializer()); - this.instanceMap.put(CollectionJsonResourcesSerializer.class, new CollectionJsonResourcesSerializer()); - this.instanceMap.put(CollectionJsonResourceSerializer.class, new CollectionJsonResourceSerializer()); - this.instanceMap.put(CollectionJsonResourceSupportSerializer.class, new CollectionJsonResourceSupportSerializer()); - this.instanceMap.put(CollectionJsonLinkListSerializer.class, new CollectionJsonLinkListSerializer(messageSource)); - } - - private Object findInstance(Class type) { - - Object result = instanceMap.get(type); - return result != null ? result : BeanUtils.instantiateClass(type); - } - - @Override - public JsonDeserializer deserializerInstance(DeserializationConfig config, Annotated annotated, Class deserClass) { - return (JsonDeserializer) findInstance(deserClass); - } - - @Override - public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, Class keyDeserClass) { - return (KeyDeserializer) findInstance(keyDeserClass); - } - - @Override - public JsonSerializer serializerInstance(SerializationConfig config, Annotated annotated, Class serClass) { - return (JsonSerializer) findInstance(serClass); - } - - @Override - public TypeResolverBuilder typeResolverBuilderInstance(MapperConfig config, Annotated annotated, Class builderClass) { - return (TypeResolverBuilder) findInstance(builderClass); - } - - @Override - public TypeIdResolver typeIdResolverInstance(MapperConfig config, Annotated annotated, Class resolverClass) { - return (TypeIdResolver) findInstance(resolverClass); + private CollectionJsonPagedResourcesDeserializer(JavaType contentType) { + super(contentType, FINISHER, CONTEXTUAL_CREATOR); } } - /** - * Return a list of {@link Link}s that includes a "self" link. - * - * @param links - base set of {@link Link}s. - * @param href - the URI of the "self" link - * @return - */ - private static List potentiallyAddSelfLink(List links, String href) { + private static List> resourcesToCollectionJsonItems(Resources resources) { - if (links == null) { + return resources.getContent().stream().map(content -> { - if (href == null) { - return Collections.emptyList(); + if (!Resource.class.isInstance(content)) { + return new CollectionJsonItem<>().withRawData(content); } - return Collections.singletonList(new Link(href)); - } + Resource resource = (Resource) content; - if (href == null || links.stream().map(Link::getRel).anyMatch(s -> s.equals(IanaLinkRelation.SELF.value()))) { - return links; - } + return new CollectionJsonItem<>() // + .withHref(resource.getRequiredLink(IanaLinkRelations.SELF).getHref()) + .withLinks(resource.getLinks().without(IanaLinkRelations.SELF)) // + .withRawData(resource.getContent()); - // Clone and add the self link - - List newLinks = new ArrayList<>(); - newLinks.add(new Link(href)); - newLinks.addAll(links); - - return newLinks; - } - - private static List withoutSelfLink(List links) { - - return links.stream() - .filter(link -> !link.getRel().equals(IanaLinkRelation.SELF.value())) - .collect(Collectors.toList()); - } - - private static List> resourcesToCollectionJsonItems(Resources resources) { - - return resources.getContent().stream() - .map(content -> { - if (ClassUtils.isAssignableValue(Resource.class, content)) { - - Resource resource = (Resource) content; - - return new CollectionJsonItem<>() - .withHref(resource.getRequiredLink(IanaLinkRelation.SELF.value()).getHref()) - .withLinks(withoutSelfLink(resource.getLinks())) - .withRawData(resource.getContent()); - } else { - return new CollectionJsonItem<>().withRawData(content); - } - }) - .collect(Collectors.toList()); + }).collect(Collectors.toList()); } /** @@ -845,65 +798,42 @@ public class Jackson2CollectionJsonModule extends SimpleModule { */ private static List findQueries(ResourceSupport resource) { - List queries = new ArrayList<>(); - - if (resource.hasLink(IanaLinkRelation.SELF.value())) { - Link selfLink = resource.getRequiredLink(IanaLinkRelation.SELF.value()); - - selfLink.getAffordances().forEach(affordance -> { - - CollectionJsonAffordanceModel model = affordance.getAffordanceModel(MediaTypes.COLLECTION_JSON); - - /** - * For Collection+JSON, "queries" are only collected for GET affordances where the URI is NOT a self link. - */ - if (model.getHttpMethod() == HttpMethod.GET && !model.getURI().equals(selfLink.getHref())) { - - - queries.add(new CollectionJsonQuery() - .withRel(model.getName()) - .withHref(model.getURI()) - .withData(model.getQueryProperties())); - } - }); + if (!resource.hasLink(IanaLinkRelations.SELF)) { + return Collections.emptyList(); } - return queries; + Link selfLink = resource.getRequiredLink(IanaLinkRelations.SELF); + + return selfLink.getAffordances().stream() // + .map(it -> it.getAffordanceModel(MediaTypes.COLLECTION_JSON)) // + .map(CollectionJsonAffordanceModel.class::cast) // + .filter(it -> !it.hasHttpMethod(HttpMethod.GET)) // + .filter(it -> !it.pointsToTargetOf(selfLink)) // + .map(it -> new CollectionJsonQuery() // + .withRel(it.getName()) // + .withHref(it.getURI()) // + .withData(it.getQueryProperties())) // + .collect(Collectors.toList()); } /** * Scan through the {@link Affordance}s and + * * @param resource * @return */ private static CollectionJsonTemplate findTemplate(ResourceSupport resource) { - List templates = new ArrayList<>(); - - if (resource.hasLink(IanaLinkRelation.SELF.value())) { - resource.getRequiredLink(IanaLinkRelation.SELF.value()).getAffordances().forEach(affordance -> { - - CollectionJsonAffordanceModel model = affordance.getAffordanceModel(MediaTypes.COLLECTION_JSON); - - /** - * For Collection+JSON, "templates" are made of any non-GET affordances. - */ - if (!(model.getHttpMethod() == HttpMethod.GET)) { - - CollectionJsonTemplate template = new CollectionJsonTemplate() // - .withData(model.getInputProperties()); - - templates.add(template); - } - }); + if (!resource.hasLink(IanaLinkRelations.SELF)) { + return null; } - /** - * Collection+JSON can only have one template, so grab the first one. - */ - return templates.stream() - .findFirst() - .orElse(null); + return resource.getRequiredLink(IanaLinkRelations.SELF).getAffordances() // + .stream() // + .map(it -> it.getAffordanceModel(MediaTypes.COLLECTION_JSON)) // + .map(CollectionJsonAffordanceModel.class::cast) // + .filter(it -> !it.hasHttpMethod(HttpMethod.GET)) // + .map(it -> new CollectionJsonTemplate().withData(it.getInputProperties())) // + .findFirst().orElse(null); } - } diff --git a/src/main/java/org/springframework/hateoas/collectionjson/ResourceSupportMixin.java b/src/main/java/org/springframework/hateoas/collectionjson/ResourceSupportMixin.java index 4f246807..830c35bb 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/ResourceSupportMixin.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/ResourceSupportMixin.java @@ -15,12 +15,11 @@ */ package org.springframework.hateoas.collectionjson; -import static org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule.*; - -import java.util.List; - -import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule.CollectionJsonLinksDeserializer; +import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule.CollectionJsonLinksSerializer; +import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule.CollectionJsonResourceSupportDeserializer; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -39,8 +38,8 @@ abstract class ResourceSupportMixin extends ResourceSupport { @Override @JsonProperty("collection") @JsonInclude(JsonInclude.Include.NON_EMPTY) - @JsonSerialize(using = CollectionJsonLinkListSerializer.class) - @JsonDeserialize(using = CollectionJsonLinkListDeserializer.class) - public abstract List getLinks(); + @JsonSerialize(using = CollectionJsonLinksSerializer.class) + @JsonDeserialize(using = CollectionJsonLinksDeserializer.class) + public abstract Links getLinks(); } diff --git a/src/main/java/org/springframework/hateoas/config/ConverterRegisteringWebMvcConfigurer.java b/src/main/java/org/springframework/hateoas/config/ConverterRegisteringWebMvcConfigurer.java index 39c41e8b..efa1b6ce 100644 --- a/src/main/java/org/springframework/hateoas/config/ConverterRegisteringWebMvcConfigurer.java +++ b/src/main/java/org/springframework/hateoas/config/ConverterRegisteringWebMvcConfigurer.java @@ -33,7 +33,6 @@ import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule; -import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule.CollectionJsonHandlerInstantiator; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; import org.springframework.hateoas.core.DelegatingRelProvider; import org.springframework.hateoas.hal.CurieProvider; @@ -45,7 +44,6 @@ import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule; import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule.HalFormsHandlerInstantiator; import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.hateoas.uber.Jackson2UberModule; -import org.springframework.hateoas.uber.Jackson2UberModule.UberHandlerInstantiator; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; @@ -60,7 +58,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; */ @Configuration @RequiredArgsConstructor -public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, BeanFactoryAware { +class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, BeanFactoryAware { private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource"; @@ -73,7 +71,7 @@ public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, B private BeanFactory beanFactory; private Collection hypermediaTypes; - /* + /* * (non-Javadoc) * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory) */ @@ -89,24 +87,23 @@ public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, B this.hypermediaTypes = hyperMediaTypes; } - /* + /* * (non-Javadoc) * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#extendMessageConverters(java.util.List) */ @Override public void extendMessageConverters(List> converters) { - if (converters.stream() - .filter(MappingJackson2HttpMessageConverter.class::isInstance) - .map(AbstractJackson2HttpMessageConverter.class::cast) - .map(AbstractJackson2HttpMessageConverter::getObjectMapper) - .anyMatch(Jackson2HalModule::isAlreadyRegisteredIn)) { + if (converters.stream().filter(MappingJackson2HttpMessageConverter.class::isInstance) + .map(AbstractJackson2HttpMessageConverter.class::cast) + .map(AbstractJackson2HttpMessageConverter::getObjectMapper) + .anyMatch(Jackson2HalModule::isAlreadyRegisteredIn)) { return; } ObjectMapper objectMapper = mapper.getIfAvailable(ObjectMapper::new); - + MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSourceAccessor.class); @@ -123,7 +120,7 @@ public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, B converters.add(0, createHalFormsConverter(objectMapper, curieProvider, relProvider, linkRelationMessageSource)); } } - + if (hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) { converters.add(0, createCollectionJsonConverter(objectMapper, linkRelationMessageSource)); } @@ -143,13 +140,12 @@ public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, B mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerModule(new Jackson2UberModule()); - mapper.setHandlerInstantiator(new UberHandlerInstantiator()); + // mapper.setHandlerInstantiator(new UberHandlerInstantiator()); - return new TypeConstrainedMappingJackson2HttpMessageConverter( - ResourceSupport.class, Collections.singletonList(UBER_JSON), mapper); + return new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class, + Collections.singletonList(UBER_JSON), mapper); } - /** * @param objectMapper * @param linkRelationMessageSource @@ -162,10 +158,9 @@ public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, B mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerModule(new Jackson2CollectionJsonModule()); - mapper.setHandlerInstantiator(new CollectionJsonHandlerInstantiator(linkRelationMessageSource)); - return new TypeConstrainedMappingJackson2HttpMessageConverter( - ResourceSupport.class, Collections.singletonList(COLLECTION_JSON), mapper); + return new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class, + Collections.singletonList(COLLECTION_JSON), mapper); } /** @@ -182,12 +177,11 @@ public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, B mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerModule(new Jackson2HalFormsModule()); - mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator( - relProvider, curieProvider, linkRelationMessageSource, true, - this.halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new))); + mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource, + true, this.halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new))); - return new TypeConstrainedMappingJackson2HttpMessageConverter( - ResourceSupport.class, Collections.singletonList(HAL_FORMS_JSON), mapper); + return new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class, + Collections.singletonList(HAL_FORMS_JSON), mapper); } /** @@ -201,13 +195,13 @@ public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, B RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource) { ObjectMapper mapper = objectMapper.copy(); - + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerModule(new Jackson2HalModule()); - mapper.setHandlerInstantiator(new HalHandlerInstantiator(relProvider, curieProvider, - linkRelationMessageSource, this.halConfiguration.getIfAvailable(HalConfiguration::new))); + mapper.setHandlerInstantiator(new HalHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource, + this.halConfiguration.getIfAvailable(HalConfiguration::new))); - return new TypeConstrainedMappingJackson2HttpMessageConverter( - ResourceSupport.class, Arrays.asList(HAL_JSON, HAL_JSON_UTF8), mapper); + return new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class, + Arrays.asList(HAL_JSON, HAL_JSON_UTF8), mapper); } } diff --git a/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java b/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java index aa3d59a7..ff2b623c 100644 --- a/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java @@ -20,6 +20,7 @@ import java.util.Map; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; /** @@ -41,7 +42,7 @@ public class AnnotationRelProvider implements RelProvider, Ordered { * @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class) */ @Override - public String getCollectionResourceRelFor(Class type) { + public LinkRelation getCollectionResourceRelFor(Class type) { Relation annotation = lookupAnnotation(type); @@ -49,15 +50,15 @@ public class AnnotationRelProvider implements RelProvider, Ordered { return null; } - return annotation.collectionRelation(); + return LinkRelation.of(annotation.collectionRelation()); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Object) */ @Override - public String getItemResourceRelFor(Class type) { + public LinkRelation getItemResourceRelFor(Class type) { Relation annotation = lookupAnnotation(type); @@ -65,10 +66,10 @@ public class AnnotationRelProvider implements RelProvider, Ordered { return null; } - return annotation.value(); + return LinkRelation.of(annotation.value()); } - /* + /* * (non-Javadoc) * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) */ diff --git a/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java b/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java index 81c3ac03..c0d30963 100644 --- a/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java @@ -16,13 +16,14 @@ package org.springframework.hateoas.core; import org.springframework.core.Ordered; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; import org.springframework.util.StringUtils; /** * Default implementation of {@link RelProvider} to simply use the uncapitalized version of the given type's name as * single resource rel as well as an appended {@code List} for the collection resource rel. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -42,21 +43,21 @@ public class DefaultRelProvider implements RelProvider, Ordered { return true; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class) */ @Override - public String getCollectionResourceRelFor(Class type) { - return StringUtils.uncapitalize(type.getSimpleName()) + "List"; + public LinkRelation getCollectionResourceRelFor(Class type) { + return LinkRelation.of(StringUtils.uncapitalize(type.getSimpleName()) + "List"); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class) */ @Override - public String getItemResourceRelFor(Class type) { - return StringUtils.uncapitalize(type.getSimpleName()); + public LinkRelation getItemResourceRelFor(Class type) { + return LinkRelation.of(StringUtils.uncapitalize(type.getSimpleName())); } } diff --git a/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java b/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java index a8351a32..c9e65916 100644 --- a/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java @@ -15,6 +15,7 @@ */ package org.springframework.hateoas.core; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; import org.springframework.plugin.core.PluginRegistry; import org.springframework.util.Assert; @@ -32,12 +33,12 @@ public class DelegatingRelProvider implements RelProvider { this.providers = providers; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class) */ @Override - public String getItemResourceRelFor(Class type) { + public LinkRelation getItemResourceRelFor(Class type) { return providers.getRequiredPluginFor(type).getItemResourceRelFor(type); } @@ -46,7 +47,7 @@ public class DelegatingRelProvider implements RelProvider { * @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class) */ @Override - public String getCollectionResourceRelFor(java.lang.Class type) { + public LinkRelation getCollectionResourceRelFor(java.lang.Class type) { return providers.getRequiredPluginFor(type).getCollectionResourceRelFor(type); } diff --git a/src/main/java/org/springframework/hateoas/core/EmbeddedWrapper.java b/src/main/java/org/springframework/hateoas/core/EmbeddedWrapper.java index a699b3f6..9bd69d24 100644 --- a/src/main/java/org/springframework/hateoas/core/EmbeddedWrapper.java +++ b/src/main/java/org/springframework/hateoas/core/EmbeddedWrapper.java @@ -15,11 +15,14 @@ */ package org.springframework.hateoas.core; +import java.util.Optional; + +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Resource; /** * A wrapper to handle values to be embedded into a {@link Resource}. - * + * * @author Oliver Gierke */ public interface EmbeddedWrapper { @@ -28,30 +31,30 @@ public interface EmbeddedWrapper { * Returns the rel to be used when embedding. If this returns {@literal null}, the rel will be calculated based on the * type returned by {@link #getRelTargetType()}. A wrapper returning {@literal null} for both {@link #getRel()} and * {@link #getRelTargetType()} is considered invalid. - * + * * @return * @see #getRelTargetType() */ - String getRel(); + Optional getRel(); /** * Returns whether the wrapper has the given rel. - * + * * @param rel can be {@literal null}. * @return */ - boolean hasRel(String rel); + boolean hasRel(LinkRelation rel); /** * Returns whether the wrapper is a collection value. - * + * * @return */ boolean isCollectionValue(); /** * Returns the actual value to embed. - * + * * @return */ Object getValue(); @@ -60,7 +63,7 @@ public interface EmbeddedWrapper { * Returns the type to be used to calculate a type based rel. Can return {@literal null} in case an explicit rel is * returned in {@link #getRel()}. A wrapper returning {@literal null} for both {@link #getRel()} and * {@link #getRelTargetType()} is considered invalid. - * + * * @return */ Class getRelTargetType(); diff --git a/src/main/java/org/springframework/hateoas/core/EmbeddedWrappers.java b/src/main/java/org/springframework/hateoas/core/EmbeddedWrappers.java index 90e515bc..84fe91b3 100644 --- a/src/main/java/org/springframework/hateoas/core/EmbeddedWrappers.java +++ b/src/main/java/org/springframework/hateoas/core/EmbeddedWrappers.java @@ -17,14 +17,16 @@ package org.springframework.hateoas.core; import java.util.Collection; import java.util.Collections; +import java.util.Optional; import org.springframework.aop.support.AopUtils; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Resource; import org.springframework.util.Assert; /** * Interface to mark objects that are aware of the rel they'd like to be exposed under. - * + * * @author Oliver Gierke */ public class EmbeddedWrappers { @@ -33,7 +35,7 @@ public class EmbeddedWrappers { /** * Creates a new {@link EmbeddedWrappers}. - * + * * @param preferCollections whether wrappers for single elements should rather treat the value as collection. */ public EmbeddedWrappers(boolean preferCollections) { @@ -42,7 +44,7 @@ public class EmbeddedWrappers { /** * Creates a new {@link EmbeddedWrapper} that - * + * * @param source * @return */ @@ -52,7 +54,7 @@ public class EmbeddedWrappers { /** * Creates an {@link EmbeddedWrapper} for an empty {@link Collection} with the given element type. - * + * * @param type must not be {@literal null}. * @return */ @@ -62,13 +64,13 @@ public class EmbeddedWrappers { /** * Creates a new {@link EmbeddedWrapper} with the given rel. - * + * * @param source can be {@literal null}, will return {@literal null} if so. * @param rel must not be {@literal null} or empty. * @return */ @SuppressWarnings("unchecked") - public EmbeddedWrapper wrap(Object source, String rel) { + public EmbeddedWrapper wrap(Object source, LinkRelation rel) { if (source == null) { return null; @@ -91,40 +93,42 @@ public class EmbeddedWrappers { private static abstract class AbstractEmbeddedWrapper implements EmbeddedWrapper { - private static final String NO_REL = "___norel___"; + private static final LinkRelation NO_REL = LinkRelation.of("___norel___"); - private final String rel; + private final LinkRelation rel; /** * Creates a new {@link AbstractEmbeddedWrapper} with the given rel. - * + * * @param rel must not be {@literal null} or empty. */ - public AbstractEmbeddedWrapper(String rel) { + public AbstractEmbeddedWrapper(LinkRelation rel) { - Assert.hasText(rel, "Rel must not be null or empty!"); + Assert.notNull(rel, "Rel must not be null or empty!"); this.rel = rel; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.EmbeddedWrapper#getRel() */ @Override - public String getRel() { - return NO_REL.equals(rel) ? null : rel; + public Optional getRel() { + + return Optional.ofNullable(rel) // + .filter(it -> !it.equals(NO_REL)); } - /* + /* * (non-Javadoc) - * @see org.springframework.hateoas.core.EmbeddedWrapper#hasRel(java.lang.String) + * @see org.springframework.hateoas.core.EmbeddedWrapper#hasRel(org.springframework.hateoas.LinkRelation) */ @Override - public boolean hasRel(String rel) { - return this.rel.equals(rel); + public boolean hasRel(LinkRelation rel) { + return this.rel.isSameAs(rel); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.EmbeddedWrapper#getRelTargetType() */ @@ -145,7 +149,7 @@ public class EmbeddedWrappers { /** * Peek into the wrapped element. The object returned is used to determine the actual value type of the wrapper. - * + * * @return */ protected abstract Object peek(); @@ -161,19 +165,19 @@ public class EmbeddedWrappers { private final Object value; /** - * Creates a new {@link EmbeddedElement} for the given value and rel. - * + * Creates a new {@link EmbeddedElement} for the given value and link relation. + * * @param value must not be {@literal null}. - * @param rel must not be {@literal null} or empty. + * @param relation must not be {@literal null}. */ - public EmbeddedElement(Object value, String rel) { + public EmbeddedElement(Object value, LinkRelation relation) { - super(rel); + super(relation); Assert.notNull(value, "Value must not be null!"); this.value = value; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.EmbeddedWrapper#getValue() */ @@ -182,7 +186,7 @@ public class EmbeddedWrappers { return value; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.EmbeddedWrappers.AbstractElementWrapper#peek() */ @@ -191,7 +195,7 @@ public class EmbeddedWrappers { return getValue(); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.EmbeddedWrapper#isCollectionValue() */ @@ -214,7 +218,7 @@ public class EmbeddedWrappers { * @param value must not be {@literal null} or empty. * @param rel must not be {@literal null} or empty. */ - public EmbeddedCollection(Collection value, String rel) { + public EmbeddedCollection(Collection value, LinkRelation rel) { super(rel); @@ -227,7 +231,7 @@ public class EmbeddedWrappers { this.value = value; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.EmbeddedWrapper#getValue() */ @@ -267,7 +271,7 @@ public class EmbeddedWrappers { /** * Creates a new {@link EmptyCollectionEmbeddedWrapper}. - * + * * @param type must not be {@literal null}. */ public EmptyCollectionEmbeddedWrapper(Class type) { @@ -276,16 +280,16 @@ public class EmbeddedWrappers { this.type = type; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.core.EmbeddedWrapper#getRel() */ @Override - public String getRel() { - return null; + public Optional getRel() { + return Optional.empty(); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.core.EmbeddedWrapper#getValue() */ @@ -294,7 +298,7 @@ public class EmbeddedWrappers { return Collections.emptySet(); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.core.EmbeddedWrapper#getRelTargetType() */ @@ -303,7 +307,7 @@ public class EmbeddedWrappers { return type; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.core.EmbeddedWrapper#isCollectionValue() */ @@ -312,12 +316,12 @@ public class EmbeddedWrappers { return true; } - /* + /* * (non-Javadoc) - * @see org.springframework.hateoas.core.EmbeddedWrapper#hasRel(java.lang.String) + * @see org.springframework.hateoas.core.EmbeddedWrapper#hasRel(org.springframework.hateoas.LinkRelation) */ @Override - public boolean hasRel(String rel) { + public boolean hasRel(LinkRelation rel) { return false; } } diff --git a/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java b/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java index f9b380db..679d4520 100644 --- a/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java @@ -16,12 +16,13 @@ package org.springframework.hateoas.core; import org.atteo.evo.inflector.English; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; /** * {@link RelProvider} implementation using the Evo Inflector implementation of an algorithmic approach to English * plurals. - * + * * @see http://www.csse.monash.edu.au/~damian/papers/HTML/Plurals.html * @author Oliver Gierke */ @@ -32,7 +33,7 @@ public class EvoInflectorRelProvider extends DefaultRelProvider { * @see org.springframework.hateoas.core.DefaultRelProvider#getCollectionResourceRelFor(java.lang.Class) */ @Override - public String getCollectionResourceRelFor(Class type) { - return English.plural(getItemResourceRelFor(type)); + public LinkRelation getCollectionResourceRelFor(Class type) { + return LinkRelation.of(English.plural(getItemResourceRelFor(type).value())); } } diff --git a/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java index ce8cdd4f..03910b5b 100644 --- a/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java @@ -20,14 +20,17 @@ import net.minidev.json.JSONArray; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; -import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; +import org.springframework.hateoas.LinkRelation; +import org.springframework.hateoas.Links; import org.springframework.http.MediaType; import org.springframework.util.Assert; @@ -63,51 +66,51 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { /* * (non-Javadoc) - * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(java.lang.String, java.lang.String) + * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(org.springframework.hateoas.LinkRelation, java.lang.String) */ @Override - public Link findLinkWithRel(String rel, String representation) { - - List links = findLinksWithRel(rel, representation); - return links.isEmpty() ? null : links.get(0); + public Optional findLinkWithRel(LinkRelation relation, String representation) { + return firstOrEmpty(findLinksWithRel(relation, representation)); } /* * (non-Javadoc) - * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(java.lang.String, java.io.InputStream) + * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(org.springframework.hateoas.LinkRelation, java.io.InputStream) */ @Override - public Link findLinkWithRel(String rel, InputStream representation) { - - List links = findLinksWithRel(rel, representation); - return links.isEmpty() ? null : links.get(0); + public Optional findLinkWithRel(LinkRelation relation, InputStream representation) { + return firstOrEmpty(findLinksWithRel(relation, representation)); } /* * (non-Javadoc) - * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(java.lang.String, java.lang.String) + * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.lang.String) */ @Override - public List findLinksWithRel(String rel, String representation) { + public Links findLinksWithRel(LinkRelation relation, String representation) { + + Assert.notNull(relation, "LinkRelation must not be null!"); try { - Object parseResult = getExpression(rel).read(representation); - return createLinksFrom(parseResult, rel); + Object parseResult = getExpression(relation).read(representation); + return createLinksFrom(parseResult, relation); } catch (InvalidPathException e) { - return Collections.emptyList(); + return Links.NONE; } } /* * (non-Javadoc) - * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(java.lang.String, java.io.InputStream) + * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.io.InputStream) */ @Override - public List findLinksWithRel(String rel, InputStream representation) { + public Links findLinksWithRel(LinkRelation relation, InputStream representation) { + + Assert.notNull(relation, "LinkRelation must not be null!"); try { - Object parseResult = getExpression(rel).read(representation); - return createLinksFrom(parseResult, rel); + Object parseResult = getExpression(relation).read(representation); + return createLinksFrom(parseResult, relation); } catch (IOException e) { throw new RuntimeException(e); } @@ -131,7 +134,7 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { * @param rel * @return link */ - protected Link extractLink(Object element, String rel) { + protected Link extractLink(Object element, LinkRelation rel) { return new Link(element.toString(), rel); } @@ -141,8 +144,8 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { * @param rel * @return */ - private JsonPath getExpression(String rel) { - return JsonPath.compile(String.format(pathTemplate, rel)); + private JsonPath getExpression(LinkRelation rel) { + return JsonPath.compile(String.format(pathTemplate, rel.value())); } /** @@ -152,7 +155,7 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { * @param rel the relation type that was parsed for. * @return */ - private List createLinksFrom(Object parseResult, String rel) { + private Links createLinksFrom(Object parseResult, LinkRelation rel) { if (parseResult instanceof JSONArray) { @@ -161,11 +164,18 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { return jsonArray.stream() // .flatMap(it -> JSONArray.class.isInstance(it) ? ((JSONArray) it).stream() : Stream.of(it)) // .map(it -> extractLink(it, rel)) // - .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + .collect(Collectors.collectingAndThen(Collectors.toList(), Links::of)); } - return parseResult instanceof Map // - ? Collections.singletonList(extractLink(parseResult, rel)) // - : Collections.singletonList(new Link(parseResult.toString(), rel)); + return Links.of(parseResult instanceof Map // + ? extractLink(parseResult, rel) // + : new Link(parseResult.toString(), rel)); + } + + private static Optional firstOrEmpty(Iterable source) { + + Iterator iterator = source.iterator(); + + return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(); } } diff --git a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java index cc3ed2a2..0e8a24a9 100644 --- a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java +++ b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java @@ -27,10 +27,11 @@ import java.util.List; import java.util.Optional; import org.springframework.hateoas.Affordance; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkBuilder; +import org.springframework.hateoas.LinkRelation; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; @@ -38,7 +39,7 @@ import org.springframework.web.util.UriComponentsBuilder; /** * Base class to implement {@link LinkBuilder}s based on a Spring MVC {@link UriComponentsBuilder}. - * + * * @author Ricardo Gladwell * @author Oliver Gierke * @author Kamill Sokol @@ -53,7 +54,7 @@ public abstract class LinkBuilderSupport implements LinkB /** * Creates a new {@link LinkBuilderSupport} using the given {@link UriComponentsBuilder}. - * + * * @param builder must not be {@literal null}. */ public LinkBuilderSupport(UriComponentsBuilder builder) { @@ -153,10 +154,12 @@ public abstract class LinkBuilderSupport implements LinkB /* * (non-Javadoc) - * @see org.springframework.hateoas.LinkBuilder#withRel(java.lang.String) + * @see org.springframework.hateoas.LinkBuilder#withRel(org.springframework.hateoas.LinkRelation) */ - public Link withRel(String rel) { - return new Link(toString(), rel).withAffordances(affordances); + public Link withRel(LinkRelation rel) { + + return new Link(toString(), rel) // + .withAffordances(affordances); } /* @@ -164,7 +167,7 @@ public abstract class LinkBuilderSupport implements LinkB * @see org.springframework.hateoas.LinkBuilder#withSelfRel() */ public Link withSelfRel() { - return withRel(IanaLinkRelation.SELF.value()); + return withRel(IanaLinkRelations.SELF); } /* @@ -178,14 +181,14 @@ public abstract class LinkBuilderSupport implements LinkB /** * Returns the current concrete instance. - * + * * @return */ protected abstract T getThis(); /** * Creates a new instance of the sub-class. - * + * * @param builder will never be {@literal null}. * @return */ diff --git a/src/main/java/org/springframework/hateoas/core/Objects.java b/src/main/java/org/springframework/hateoas/core/Objects.java deleted file mode 100644 index c0effa87..00000000 --- a/src/main/java/org/springframework/hateoas/core/Objects.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 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. - */ -package org.springframework.hateoas.core; - -/** - * Utilities to mimic {@link java.util.Objects} helper methods. - * - * @author Greg Turnquist - */ -public final class Objects { - - /** - * Variant of {@link java.util.Objects#requireNonNull(Object, String)} that throws an {@link IllegalArgumentException}. - */ - public static T requireNonNull(T obj, String message) { - - if (obj == null) { - throw new IllegalArgumentException(message); - } - return obj; - } -} diff --git a/src/main/java/org/springframework/hateoas/hal/CurieProvider.java b/src/main/java/org/springframework/hateoas/hal/CurieProvider.java index e06916c4..ba4225aa 100644 --- a/src/main/java/org/springframework/hateoas/hal/CurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/CurieProvider.java @@ -18,11 +18,12 @@ package org.springframework.hateoas.hal; import java.util.Collection; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; /** * API to provide HAL curie information for links. - * + * * @see http://tools.ietf.org/html/draft-kelly-json-hal#section-8.2 * @author Oliver Gierke * @author Jeff Stano @@ -33,26 +34,26 @@ public interface CurieProvider { /** * 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. - * + * * @param link * @return */ - String getNamespacedRelFrom(Link link); + HalLinkRelation getNamespacedRelFrom(Link link); /** * Returns the rel to be rendered for the given rel. Will potentially prefix the rel but also might decide not to, * depending on the actual rel. - * + * * @param rel * @return * @since 0.17 */ - String getNamespacedRelFor(String rel); + HalLinkRelation getNamespacedRelFor(LinkRelation rel); /** * Returns an object to render as the base curie information. Implementations have to make sure, the returned * instances renders as defined in the spec. - * + * * @param links the {@link Links} that have been added to the response so far. * @return must not be {@literal null}. */ diff --git a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java index 698b61a7..2d3bfaca 100644 --- a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java @@ -22,8 +22,8 @@ import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; -import org.springframework.hateoas.IanaLinkRelation; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; import org.springframework.hateoas.UriTemplate; import org.springframework.util.Assert; @@ -32,7 +32,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; /** * Default implementation of {@link CurieProvider} rendering a single configurable {@link UriTemplate} based curie. - * + * * @author Oliver Gierke * @author Jeff Stano * @author Greg Turnquist @@ -46,7 +46,7 @@ public class DefaultCurieProvider implements CurieProvider { /** * Creates a new {@link DefaultCurieProvider} for the given name and {@link UriTemplate}. The curie will be used to * expand previously unprefixed, non-IANA link relations. - * + * * @param name must not be {@literal null} or empty. * @param uriTemplate must not be {@literal null} and contain exactly one template variable. */ @@ -58,7 +58,7 @@ public class DefaultCurieProvider implements CurieProvider { * Creates a new {@link DefaultCurieProvider} for the given curies. If more than one curie is given, no default curie * will be registered. Use {@link #DefaultCurieProvider(Map, String)} to define which of the provided curies shall be * used as the default one. - * + * * @param curies must not be {@literal null}. * @see #DefaultCurieProvider(String, UriTemplate) * @since 0.19 @@ -70,7 +70,7 @@ public class DefaultCurieProvider implements CurieProvider { /** * Creates a new {@link DefaultCurieProvider} for the given curies using the one with the given name as default, which * means to expand unprefixed, non-IANA link relations. - * + * * @param curies must not be {@literal null}. * @param defaultCurieName can be {@literal null}. * @since 0.19 @@ -92,7 +92,7 @@ public class DefaultCurieProvider implements CurieProvider { this.curies = Collections.unmodifiableMap(curies); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.CurieProvider#getCurieInformation() */ @@ -104,30 +104,31 @@ public class DefaultCurieProvider implements CurieProvider { .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableCollection)); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.CurieProvider#getNamespacedRelFrom(org.springframework.hateoas.Link) */ @Override - public String getNamespacedRelFrom(Link link) { + public HalLinkRelation getNamespacedRelFrom(Link link) { return getNamespacedRelFor(link.getRel()); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.hal.CurieProvider#getNamespacedRelFrom(java.lang.String) */ @Override - public String getNamespacedRelFor(String rel) { + public HalLinkRelation getNamespacedRelFor(LinkRelation relation) { - boolean prefixingNeeded = defaultCurie != null && !IanaLinkRelation.isIanaRel(rel) && !rel.contains(":"); - return prefixingNeeded ? String.format("%s:%s", defaultCurie, rel) : rel; + HalLinkRelation result = HalLinkRelation.of(relation); + + return defaultCurie == null ? result : result.curieIfUncuried(defaultCurie); } /** * Returns the href for the {@link Curie} instance to be created. Will prepend the current application URI (servlet * mapping) in case the template is not an absolute one in the first place. - * + * * @param name will never be {@literal null} or empty. * @param template will never be {@literal null}. * @return the {@link String} to be used as href in the {@link Curie} to be created, must not be {@literal null}. @@ -144,7 +145,7 @@ public class DefaultCurieProvider implements CurieProvider { /** * Value object to get the curie {@link Link} rendered in JSON. - * + * * @author Oliver Gierke */ protected static class Curie extends Link { diff --git a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java index 81e8d310..2f8f8421 100644 --- a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java +++ b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java @@ -22,33 +22,33 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.Resource; import org.springframework.hateoas.core.EmbeddedWrapper; import org.springframework.hateoas.core.EmbeddedWrappers; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Builder class that allows collecting objects under the relation types defined for the objects but moving from the * single resource relation to the collection one, once more than one object of the same type is added. - * + * * @author Oliver Gierke * @author Dietrich Schulten */ class HalEmbeddedBuilder { - private static final String DEFAULT_REL = "content"; + 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<>(); + private final Map embeddeds = new HashMap<>(); private final RelProvider provider; private final CurieProvider curieProvider; private final EmbeddedWrappers wrappers; /** * Creates a new {@link HalEmbeddedBuilder} using the given {@link RelProvider} and prefer collection rels flag. - * + * * @param provider can be {@literal null}. * @param preferCollectionRels whether to prefer to ask the provider for collection rels. */ @@ -64,7 +64,7 @@ class HalEmbeddedBuilder { /** * Adds the given value to the embeddeds. Will skip doing so if the value is {@literal null} or the content of a * {@link Resource} is {@literal null}. - * + * * @param source can be {@literal null}. */ public void add(Object source) { @@ -75,8 +75,8 @@ class HalEmbeddedBuilder { return; } - String collectionRel = getDefaultedRelFor(wrapper, true); - String collectionOrItemRel = collectionRel; + HalLinkRelation collectionRel = getDefaultedRelFor(wrapper, true); + HalLinkRelation collectionOrItemRel = collectionRel; if (!embeddeds.containsKey(collectionRel)) { collectionOrItemRel = getDefaultedRelFor(wrapper, wrapper.isCollectionValue()); @@ -100,43 +100,47 @@ class HalEmbeddedBuilder { @SuppressWarnings("unchecked") private Collection asCollection(Object source) { - return source instanceof Collection ? (Collection) source : source == null ? Collections.emptySet() - : Collections.singleton(source); + + return source instanceof Collection // + ? (Collection) source // + : source == null ? Collections.emptySet() : Collections.singleton(source); } - private String getDefaultedRelFor(EmbeddedWrapper wrapper, boolean forCollection) { + private HalLinkRelation getDefaultedRelFor(EmbeddedWrapper wrapper, boolean forCollection) { - String valueRel = wrapper.getRel(); + return wrapper.getRel() // + .map(HalLinkRelation::of) // + .orElseGet(() -> { - if (StringUtils.hasText(valueRel)) { - return valueRel; - } + if (provider == null) { + return DEFAULT_REL; + } - if (provider == null) { - return DEFAULT_REL; - } + Class type = wrapper.getRelTargetType(); - Class type = wrapper.getRelTargetType(); + if (type == null) { + throw new IllegalStateException(String.format(INVALID_EMBEDDED_WRAPPER, wrapper)); + } - if (type == null) { - throw new IllegalStateException(String.format(INVALID_EMBEDDED_WRAPPER, wrapper)); - } + LinkRelation rel = forCollection // + ? provider.getCollectionResourceRelFor(type) // + : provider.getItemResourceRelFor(type); - String rel = forCollection ? provider.getCollectionResourceRelFor(type) : provider.getItemResourceRelFor(type); + if (curieProvider != null) { + rel = curieProvider.getNamespacedRelFor(rel); + } - if (curieProvider != null) { - rel = curieProvider.getNamespacedRelFor(rel); - } + return rel == null ? DEFAULT_REL : HalLinkRelation.of(rel); - return rel == null ? DEFAULT_REL : rel; + }); } /** * Returns the added objects keyed up by their relation types. - * + * * @return */ - public Map asMap() { + public Map asMap() { return Collections.unmodifiableMap(embeddeds); } } diff --git a/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java index 9d2515fa..20b6b34b 100644 --- a/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java @@ -19,12 +19,14 @@ import java.util.Map; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.core.JsonPathLinkDiscoverer; +import org.springframework.http.MediaType; /** * {@link LinkDiscoverer} implementation based on HAL link structure. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -34,27 +36,34 @@ public class HalLinkDiscoverer extends JsonPathLinkDiscoverer { * Constructor for {@link MediaTypes#HAL_JSON}. */ public HalLinkDiscoverer() { - super("$._links..['%s']", MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8); + this(MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8); } + protected HalLinkDiscoverer(MediaType... mediaTypes) { + super("$._links..['%s']", mediaTypes); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.JsonPathLinkDiscoverer#extractLink(java.lang.Object, org.springframework.hateoas.LinkRelation) + */ @Override @SuppressWarnings("unchecked") - protected Link extractLink(Object element, String rel) { + protected Link extractLink(Object element, LinkRelation rel) { - if (element instanceof Map) { - - Map json = (Map) element; - - return new Link(json.get("href"), rel) - .withHreflang(json.get("hreflang")) - .withMedia(json.get("media")) - .withTitle(json.get("title")) - .withType(json.get("type")) - .withDeprecation(json.get("deprecation")) - .withProfile(json.get("profile")) - .withName(json.get("name")); + if (!Map.class.isInstance(element)) { + return super.extractLink(element, rel); } - return super.extractLink(element, rel); + Map json = (Map) element; + + return new Link(json.get("href"), rel) // + .withHreflang(json.get("hreflang")) // + .withMedia(json.get("media")) // + .withTitle(json.get("title")) // + .withType(json.get("type")) // + .withDeprecation(json.get("deprecation")) // + .withProfile(json.get("profile")) // + .withName(json.get("name")); } } diff --git a/src/main/java/org/springframework/hateoas/hal/HalLinkRelation.java b/src/main/java/org/springframework/hateoas/hal/HalLinkRelation.java new file mode 100644 index 00000000..7a974e5d --- /dev/null +++ b/src/main/java/org/springframework/hateoas/hal/HalLinkRelation.java @@ -0,0 +1,173 @@ +/* + * Copyright 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. + * 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. + */ +package org.springframework.hateoas.hal; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.util.stream.Stream; + +import org.springframework.context.MessageSourceResolvable; +import org.springframework.hateoas.IanaLinkRelations; +import org.springframework.hateoas.LinkRelation; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Value object for HAL based {@link LinkRelation}, i.e. a relation that can be curied. + * + * @author Oliver Drotbohm + */ +@EqualsAndHashCode +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class HalLinkRelation implements LinkRelation, MessageSourceResolvable { + + public static final HalLinkRelation CURIES = HalLinkRelation.uncuried("curies"); + + private static final String RELATION_MESSAGE_TEMPLATE = "_links.%s.title"; + + private final String curie; + private final @NonNull @Getter String localPart; + + /** + * Returns a {@link HalLinkRelation} for the given general {@link LinkRelation}. + * + * @param relation must not be {@literal null}. + * @return + */ + public static HalLinkRelation of(LinkRelation relation) { + + Assert.notNull(relation, "LinkRelation must not be null!"); + + if (HalLinkRelation.class.isInstance(relation)) { + return HalLinkRelation.class.cast(relation); + } + + return of(relation.value()); + } + + /** + * Creates a new {@link HalLinkRelation} from the given link relation {@link String}. + * + * @param relation must not be {@literal null}. + * @return + */ + @JsonCreator + private static HalLinkRelation of(String relation) { + + String[] split = relation.split(":"); + + String curie = split.length == 1 ? null : split[0]; + String localPart = split.length == 1 ? split[0] : split[1]; + + return new HalLinkRelation(curie, localPart); + } + + /** + * Creates a new {@link HalLinkRelation} for a curied relation. + * + * @param curie the curie, must not be {@literal null} or empty. + * @param rel the link relation to be used, must not be {@literal null}. + * @return + */ + public static HalLinkRelation curied(String curie, String rel) { + + Assert.hasText(curie, "Curie must not be null or empty!"); + + return new HalLinkRelation(curie, rel); + } + + /** + * Creates a new uncuried {@link HalLinkRelation}. + * + * @param rel the link relation to be used, must not be {@literal null}. + * @return + */ + public static HalLinkRelation uncuried(String rel) { + return new HalLinkRelation(null, rel); + } + + /** + * Creates a new {@link HalLinkRelation} curied to the given value. + * + * @param curie must not be {@literal null} or empty. + * @return + */ + public HalLinkRelation curie(String curie) { + + Assert.hasText(curie, "Curie must not be null or empty!"); + + return new HalLinkRelation(curie, localPart); + } + + /** + * Returns a curied {@link HalLinkRelation} either using the existing curie or the given one if previously uncuried. + * + * @param curie must not be {@literal null} or empty. + * @return + */ + public HalLinkRelation curieIfUncuried(String curie) { + + Assert.hasText(curie, "Curie must not be null or empty!"); + + return isCuried() || IanaLinkRelations.isIanaRel(localPart) ? this : curie(curie); + } + + /** + * Returns whether the link relation is curied. + * + * @return + */ + public boolean isCuried() { + return curie != null; + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkRelation#value() + */ + @JsonValue + @Override + public String value() { + return isCuried() ? String.format("%s:%s", curie, localPart) : localPart; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.MessageSourceResolvable#getCodes() + */ + @Override + public String[] getCodes() { + + return Stream.of(value(), localPart) // + .map(it -> String.format(RELATION_MESSAGE_TEMPLATE, it)) // + .toArray(String[]::new); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return value(); + } +} diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 8f102541..a1ff290d 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -31,6 +31,7 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.NoSuchMessageException; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.Resource; @@ -103,12 +104,10 @@ public class Jackson2HalModule extends SimpleModule { * @author Alexander Baetz * @author Oliver Gierke */ - public static class HalLinkListSerializer extends ContainerSerializer> implements ContextualSerializer { + public static class HalLinkListSerializer extends ContainerSerializer implements ContextualSerializer { private static final long serialVersionUID = -1844788111509966406L; - private static final String RELATION_MESSAGE_TEMPLATE = "_links.%s.title"; - private final BeanProperty property; private final CurieProvider curieProvider; private final EmbeddedMapper mapper; @@ -116,14 +115,14 @@ public class Jackson2HalModule extends SimpleModule { private final HalConfiguration halConfiguration; public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, MessageSourceAccessor accessor, - HalConfiguration halConfiguration) { + HalConfiguration halConfiguration) { this(null, curieProvider, mapper, accessor, halConfiguration); } public HalLinkListSerializer(BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper, - MessageSourceAccessor accessor, HalConfiguration halConfiguration) { + MessageSourceAccessor accessor, HalConfiguration halConfiguration) { - super(TypeFactory.defaultInstance().constructType(List.class)); + super(TypeFactory.defaultInstance().constructType(Links.class)); this.property = property; this.curieProvider = curieProvider; @@ -144,11 +143,10 @@ 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 - public void serialize(List value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { + public void serialize(Links value, JsonGenerator jgen, SerializerProvider provider) throws IOException { // sort links according to their relation - Map> sortedLinks = new LinkedHashMap<>(); + Map> sortedLinks = new LinkedHashMap<>(); List links = new ArrayList<>(); boolean prefixingRequired = curieProvider != null; @@ -169,15 +167,15 @@ public class Jackson2HalModule extends SimpleModule { continue; } - String rel = prefixingRequired ? curieProvider.getNamespacedRelFrom(link) : link.getRel(); + LinkRelation rel = prefixingRequired ? curieProvider.getNamespacedRelFrom(link) : link.getRel(); - if (!link.getRel().equals(rel)) { + if (!link.hasRel(rel)) { curiedLinkPresent = true; } sortedLinks // - .computeIfAbsent(rel, key -> new ArrayList<>())// - .add(toHalLink(link)); + .computeIfAbsent(rel, key -> new ArrayList<>())// + .add(toHalLink(link)); links.add(link); } @@ -185,18 +183,19 @@ public class Jackson2HalModule extends SimpleModule { if (!skipCuries && prefixingRequired && curiedLinkPresent) { ArrayList curies = new ArrayList<>(); - curies.add(curieProvider.getCurieInformation(new Links(links))); + curies.add(curieProvider.getCurieInformation(Links.of(links))); - sortedLinks.put("curies", curies); + sortedLinks.put(HalLinkRelation.CURIES, curies); } TypeFactory typeFactory = provider.getConfig().getTypeFactory(); - JavaType keyType = typeFactory.constructType(String.class); + JavaType keyType = typeFactory.constructType(LinkRelation.class); JavaType valueType = typeFactory.constructCollectionType(ArrayList.class, Object.class); JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType); MapSerializer serializer = MapSerializer.construct(Collections.emptySet(), mapType, true, null, - provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property, halConfiguration), null); + provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property, halConfiguration), + null); serializer.serialize(sortedLinks, jgen, provider); } @@ -209,29 +208,24 @@ public class Jackson2HalModule extends SimpleModule { */ private HalLink toHalLink(Link link) { - String rel = link.getRel(); - String title = getTitle(rel); + HalLinkRelation rel = HalLinkRelation.of(link.getRel()); - if (title == null) { - title = getTitle(rel.contains(":") ? rel.substring(rel.indexOf(":") + 1) : rel); - } - - return new HalLink(link, title); + return new HalLink(link, getTitle(rel)); } /** * Returns the title for the given local link relation resolved through the configured {@link MessageSourceAccessor} * . * - * @param localRel must not be {@literal null} or empty. + * @param relation must not be {@literal null} or empty. * @return */ - private String getTitle(String localRel) { + private String getTitle(HalLinkRelation relation) { - Assert.hasText(localRel, "Local relation must not be null or empty!"); + Assert.notNull(relation, "Local relation must not be null or empty!"); try { - return accessor == null ? null : accessor.getMessage(String.format(RELATION_MESSAGE_TEMPLATE, localRel)); + return accessor == null ? null : accessor.getMessage(relation); } catch (NoSuchMessageException o_O) { return null; } @@ -243,7 +237,7 @@ public class Jackson2HalModule extends SimpleModule { */ @Override public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) - throws JsonMappingException { + throws JsonMappingException { return new HalLinkListSerializer(property, curieProvider, mapper, accessor, halConfiguration); } @@ -269,7 +263,8 @@ public class Jackson2HalModule extends SimpleModule { * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) */ - public boolean isEmpty(SerializerProvider provider, List value) { + @Override + public boolean isEmpty(SerializerProvider provider, Links value) { return value.isEmpty(); } @@ -278,8 +273,8 @@ public class Jackson2HalModule extends SimpleModule { * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object) */ @Override - public boolean hasSingleElement(List value) { - return value.size() == 1; + public boolean hasSingleElement(Links value) { + return value.toList().size() == 1; } /* @@ -299,7 +294,7 @@ public class Jackson2HalModule extends SimpleModule { * @author Oliver Gierke */ public static class HalResourcesSerializer extends ContainerSerializer> - implements ContextualSerializer { + implements ContextualSerializer { private static final long serialVersionUID = 8030706944344625390L; @@ -325,10 +320,9 @@ public class Jackson2HalModule extends SimpleModule { * org.codehaus.jackson.map.SerializerProvider) */ @Override - public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { + public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - Map embeddeds = embeddedMapper.map(value); + Map embeddeds = embeddedMapper.map(value); Object currentValue = jgen.getCurrentValue(); @@ -344,7 +338,7 @@ public class Jackson2HalModule extends SimpleModule { @Override public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) - throws JsonMappingException { + throws JsonMappingException { return new HalResourcesSerializer(property, embeddedMapper); } @@ -358,6 +352,7 @@ public class Jackson2HalModule extends SimpleModule { return null; } + @Override public boolean isEmpty(SerializerProvider provider, Collection value) { return value.isEmpty(); } @@ -381,7 +376,7 @@ public class Jackson2HalModule extends SimpleModule { * @author Oliver Gierke */ public static class OptionalListJackson2Serializer extends ContainerSerializer - implements ContextualSerializer { + implements ContextualSerializer { private static final long serialVersionUID = 3700806118177419817L; @@ -436,7 +431,7 @@ public class Jackson2HalModule extends SimpleModule { } private void serializeContents(Iterator value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { + throws IOException { while (value.hasNext()) { Object elem = value.next(); @@ -449,7 +444,7 @@ public class Jackson2HalModule extends SimpleModule { } private JsonSerializer getOrLookupSerializerFor(Class type, SerializerProvider provider) - throws JsonMappingException { + throws JsonMappingException { JsonSerializer serializer = serializers.get(type); @@ -493,6 +488,7 @@ public class Jackson2HalModule extends SimpleModule { * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) */ + @Override public boolean isEmpty(SerializerProvider provider, Object value) { return false; } @@ -505,7 +501,7 @@ public class Jackson2HalModule extends SimpleModule { */ @Override public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) - throws JsonMappingException { + throws JsonMappingException { return new OptionalListJackson2Serializer(property, halConfiguration); } } @@ -541,8 +537,7 @@ public class Jackson2HalModule extends SimpleModule { * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) */ @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException { + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { List result = new ArrayList<>(); String relation; @@ -561,19 +556,13 @@ public class Jackson2HalModule extends SimpleModule { if (JsonToken.START_ARRAY.equals(jp.nextToken())) { while (!JsonToken.END_ARRAY.equals(jp.nextToken())) { link = jp.readValueAs(Link.class); - result.add(new Link(link.getHref(), relation) - .withHreflang(link.getHreflang()) - .withTitle(link.getTitle()) - .withType(link.getType()) - .withDeprecation(link.getDeprecation())); + result.add(new Link(link.getHref(), relation).withHreflang(link.getHreflang()).withTitle(link.getTitle()) + .withType(link.getType()).withDeprecation(link.getDeprecation())); } } else { link = jp.readValueAs(Link.class); - result.add(new Link(link.getHref(), relation) - .withHreflang(link.getHreflang()) - .withTitle(link.getTitle()) - .withType(link.getType()) - .withDeprecation(link.getDeprecation())); + result.add(new Link(link.getHref(), relation).withHreflang(link.getHreflang()).withTitle(link.getTitle()) + .withType(link.getType()).withDeprecation(link.getDeprecation())); } } @@ -582,7 +571,7 @@ public class Jackson2HalModule extends SimpleModule { } public static class HalResourcesDeserializer extends ContainerDeserializerBase> - implements ContextualDeserializer { + implements ContextualDeserializer { private static final long serialVersionUID = 4755806754621032622L; @@ -625,8 +614,7 @@ public class Jackson2HalModule extends SimpleModule { * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) */ @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException { + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { List result = new ArrayList<>(); JsonDeserializer deser = ctxt.findRootValueDeserializer(contentType); @@ -655,7 +643,7 @@ public class Jackson2HalModule extends SimpleModule { @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) - throws JsonMappingException { + throws JsonMappingException { JavaType vc = property.getType().getContentType(); HalResourcesDeserializer des = new HalResourcesDeserializer(vc); @@ -674,7 +662,7 @@ public class Jackson2HalModule extends SimpleModule { private final AutowireCapableBeanFactory delegate; public HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, - MessageSourceAccessor messageSourceAccessor) { + MessageSourceAccessor messageSourceAccessor) { this(provider, curieProvider, messageSourceAccessor, new HalConfiguration()); } @@ -688,7 +676,7 @@ public class Jackson2HalModule extends SimpleModule { * @param messageSourceAccessor can be {@literal null}. */ public HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, - MessageSourceAccessor messageSourceAccessor, HalConfiguration halConfiguration) { + MessageSourceAccessor messageSourceAccessor, HalConfiguration halConfiguration) { this(provider, curieProvider, messageSourceAccessor, true, halConfiguration); } @@ -704,12 +692,12 @@ public class Jackson2HalModule extends SimpleModule { * @param enforceEmbeddedCollections */ public HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, MessageSourceAccessor accessor, - boolean enforceEmbeddedCollections, HalConfiguration halConfiguration) { + boolean enforceEmbeddedCollections, HalConfiguration halConfiguration) { this(provider, curieProvider, accessor, enforceEmbeddedCollections, null, halConfiguration); } private HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, MessageSourceAccessor accessor, - boolean enforceEmbeddedCollections, AutowireCapableBeanFactory delegate, HalConfiguration halConfiguration) { + boolean enforceEmbeddedCollections, AutowireCapableBeanFactory delegate, HalConfiguration halConfiguration) { Assert.notNull(provider, "RelProvider must not be null!"); @@ -719,7 +707,7 @@ public class Jackson2HalModule extends SimpleModule { this.serializers.put(HalResourcesSerializer.class, new HalResourcesSerializer(mapper)); this.serializers.put(HalLinkListSerializer.class, - new HalLinkListSerializer(curieProvider, mapper, accessor, halConfiguration)); + new HalLinkListSerializer(curieProvider, mapper, accessor, halConfiguration)); } /* @@ -728,7 +716,7 @@ public class Jackson2HalModule extends SimpleModule { */ @Override public JsonDeserializer deserializerInstance(DeserializationConfig config, Annotated annotated, - Class deserClass) { + Class deserClass) { return (JsonDeserializer) findInstance(deserClass); } @@ -738,7 +726,7 @@ public class Jackson2HalModule extends SimpleModule { */ @Override public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, - Class keyDeserClass) { + Class keyDeserClass) { return (KeyDeserializer) findInstance(keyDeserClass); } @@ -757,7 +745,7 @@ public class Jackson2HalModule extends SimpleModule { */ @Override public TypeResolverBuilder typeResolverBuilderInstance(MapperConfig config, Annotated annotated, - Class builderClass) { + Class builderClass) { return (TypeResolverBuilder) findInstance(builderClass); } @@ -796,6 +784,7 @@ public class Jackson2HalModule extends SimpleModule { * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) */ + @Override public boolean isEmpty(SerializerProvider provider, Boolean value) { return value == null || Boolean.FALSE.equals(value); } @@ -805,8 +794,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 - public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { + public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeBoolean(value); } @@ -825,7 +813,7 @@ public class Jackson2HalModule extends SimpleModule { */ @Override public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) - throws JsonMappingException { + throws JsonMappingException { if (visitor != null) { visitor.expectBooleanFormat(typeHint); } @@ -866,7 +854,7 @@ public class Jackson2HalModule extends SimpleModule { * @param source must not be {@literal null}. * @return */ - public Map map(Iterable source) { + public Map map(Iterable source) { Assert.notNull(source, "Elements must not be null!"); @@ -888,7 +876,7 @@ public class Jackson2HalModule extends SimpleModule { public boolean hasCuriedEmbed(Iterable source) { return map(source).keySet().stream() // - .anyMatch(rel -> rel.contains(":")); + .anyMatch(HalLinkRelation::isCuried); } } diff --git a/src/main/java/org/springframework/hateoas/hal/LinkMixin.java b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java index 47eba5a4..16f4e4b3 100644 --- a/src/main/java/org/springframework/hateoas/hal/LinkMixin.java +++ b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java @@ -25,7 +25,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; /** * Custom mixin to avoid rel attributes being rendered for HAL. - * + * * @author Alexander Baetz * @author Oliver Gierke * @author Greg Turnquist diff --git a/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java b/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java index b85e4182..363bad8b 100644 --- a/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java +++ b/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java @@ -15,9 +15,8 @@ */ package org.springframework.hateoas.hal; -import java.util.List; - import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.ResourceSupport; import com.fasterxml.jackson.annotation.JsonInclude; @@ -40,5 +39,5 @@ public abstract class ResourceSupportMixin extends ResourceSupport { @JsonInclude(Include.NON_EMPTY) @JsonSerialize(using = Jackson2HalModule.HalLinkListSerializer.class) @JsonDeserialize(using = Jackson2HalModule.HalLinkListDeserializer.class) - public abstract List getLinks(); + public abstract Links getLinks(); } diff --git a/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java b/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java index 17f33d3e..a00da9d1 100644 --- a/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java +++ b/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java @@ -34,7 +34,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; * @author Greg Turnquist */ @JsonPropertyOrder({ "content", "links" }) -public abstract class ResourcesMixin extends Resources { +abstract class ResourcesMixin extends Resources { @Override @JsonProperty("_embedded") diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModel.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModel.java index 39957b22..977a05fc 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModel.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModel.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,10 +15,11 @@ */ package org.springframework.hateoas.hal.forms; +import static org.springframework.http.HttpMethod.*; + import lombok.EqualsAndHashCode; import lombok.Getter; -import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -36,39 +37,40 @@ import org.springframework.http.MediaType; /** * {@link AffordanceModel} for a HAL-FORMS {@link MediaType}. - * + * * @author Greg Turnquist + * @author Oliver Gierke */ @EqualsAndHashCode(callSuper = true) -public class HalFormsAffordanceModel extends AffordanceModel { +class HalFormsAffordanceModel extends AffordanceModel { - private static final Set ENTITY_ALTERING_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH); + private static final Set ENTITY_ALTERING_METHODS = EnumSet.of(POST, PUT, PATCH); + private static final Set REQUIRED_METHODS = EnumSet.of(POST, PUT); private final @Getter List inputProperties; - public HalFormsAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType) { + public HalFormsAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, + List queryMethodParameters, ResolvableType outputType) { super(name, link, httpMethod, inputType, queryMethodParameters, outputType); - + this.inputProperties = determineInputs(); } /** - * Look at the input's domain type to extract the {@link Affordance}'s properties. - * Then transform them into a list of {@link HalFormsProperty} objects. + * Look at the input's domain type to extract the {@link Affordance}'s properties. Then transform them into a list of + * {@link HalFormsProperty} objects. */ private List determineInputs() { - if (ENTITY_ALTERING_METHODS.contains(getHttpMethod())) { - - return PropertyUtils.findPropertyNames(getInputType()).stream() - .map(propertyName -> new HalFormsProperty() - .withName(propertyName) - .withRequired(Arrays.asList(HttpMethod.POST, HttpMethod.PUT).contains(getHttpMethod()))) - .collect(Collectors.toList()); - - } else { + if (!ENTITY_ALTERING_METHODS.contains(getHttpMethod())) { return Collections.emptyList(); } + + return PropertyUtils.findPropertyNames(getInputType()).stream() // + .map(propertyName -> new HalFormsProperty() // + .withName(propertyName) // + .withRequired(REQUIRED_METHODS.contains(getHttpMethod()))) // + .collect(Collectors.toList()); } } diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModelFactory.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModelFactory.java index 91a01ade..9c381c0f 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModelFactory.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsAffordanceModelFactory.java @@ -38,8 +38,13 @@ class HalFormsAffordanceModelFactory implements AffordanceModelFactory { private final @Getter MediaType mediaType = MediaTypes.HAL_FORMS_JSON; + /* + * (non-Javadoc) + * @see org.springframework.hateoas.AffordanceModelFactory#getAffordanceModel(java.lang.String, org.springframework.hateoas.Link, org.springframework.http.HttpMethod, org.springframework.core.ResolvableType, java.util.List, org.springframework.core.ResolvableType) + */ @Override - public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType) { + public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, + List queryMethodParameters, ResolvableType outputType) { return new HalFormsAffordanceModel(name, link, httpMethod, inputType, queryMethodParameters, outputType); } } diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsDocument.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsDocument.java index e0246631..9d4fd837 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsDocument.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsDocument.java @@ -21,17 +21,17 @@ import lombok.Singular; import lombok.Value; import lombok.experimental.Wither; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; -import org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListDeserializer; +import org.springframework.hateoas.hal.HalLinkRelation; import org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListSerializer; +import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule.HalFormsLinksDeserializer; import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -45,7 +45,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; /** * Representation of a HAL-FORMS document. - * + * * @author Dietrich Schulten * @author Greg Turnquist * @author Oliver Gierke @@ -67,7 +67,7 @@ public class HalFormsDocument { @JsonProperty("_embedded") // @JsonInclude(Include.NON_EMPTY) // - private Map embedded; + private Map embedded; @JsonProperty("page") // @JsonInclude(Include.NON_NULL) // @@ -77,8 +77,8 @@ public class HalFormsDocument { @JsonProperty("_links") // @JsonInclude(Include.NON_EMPTY) // @JsonSerialize(using = HalLinkListSerializer.class) // - @JsonDeserialize(using = HalLinkListDeserializer.class) // - private List links; + @JsonDeserialize(using = HalFormsLinksDeserializer.class) // + private Links links; @Singular // @JsonProperty("_templates") // @@ -86,12 +86,12 @@ public class HalFormsDocument { private Map templates; private HalFormsDocument() { - this(null, null, Collections.emptyMap(), null, Collections.emptyList(), Collections.emptyMap()); + this(null, null, Collections.emptyMap(), null, Links.NONE, Collections.emptyMap()); } /** * Creates a new {@link HalFormsDocument} for the given resource. - * + * * @param resource can be {@literal null}. * @return */ @@ -101,7 +101,7 @@ public class HalFormsDocument { /** * returns a new {@link HalFormsDocument} for the given resources. - * + * * @param resources must not be {@literal null}. * @return */ @@ -114,7 +114,7 @@ public class HalFormsDocument { /** * Creates a new empty {@link HalFormsDocument}. - * + * * @return */ public static HalFormsDocument empty() { @@ -123,7 +123,7 @@ public class HalFormsDocument { /** * Returns the default template of the document. - * + * * @return */ @JsonIgnore @@ -133,7 +133,7 @@ public class HalFormsDocument { /** * Returns the template with the given name. - * + * * @param key must not be {@literal null}. * @return */ @@ -147,7 +147,7 @@ public class HalFormsDocument { /** * Adds the given {@link Link} to the current document. - * + * * @param link must not be {@literal null}. * @return */ @@ -155,15 +155,12 @@ public class HalFormsDocument { Assert.notNull(link, "Link must not be null!"); - List links = new ArrayList<>(this.links); - links.add(link); - - return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links, templates); + return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links.and(link), templates); } /** * Adds the given {@link HalFormsTemplate} to the current document. - * + * * @param name must not be {@literal null} or empty. * @param template must not be {@literal null}. * @return @@ -181,19 +178,23 @@ public class HalFormsDocument { /** * Adds the given value as embedded one. - * + * * @param key must not be {@literal null} or empty. * @param value must not be {@literal null}. * @return */ - public HalFormsDocument andEmbedded(String key, Object value) { + public HalFormsDocument andEmbedded(HalLinkRelation key, Object value) { Assert.notNull(key, "Embedded key must not be null!"); Assert.notNull(value, "Embedded value must not be null!"); - Map embedded = new HashMap<>(this.embedded); + Map embedded = new HashMap<>(this.embedded); embedded.put(key, value); return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links, templates); } + + HalFormsDocument withLinks(Links links) { + return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links, templates); + } } diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java index dc2e50ec..370a2e92 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java @@ -15,41 +15,19 @@ */ package org.springframework.hateoas.hal.forms; -import java.util.Map; - -import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.core.JsonPathLinkDiscoverer; +import org.springframework.hateoas.hal.HalLinkDiscoverer; /** * HAL-FORMS based {@link JsonPathLinkDiscoverer}. - * + * * @author Greg Turnquist + * @author Oliver Gierke */ -public class HalFormsLinkDiscoverer extends JsonPathLinkDiscoverer { +public class HalFormsLinkDiscoverer extends HalLinkDiscoverer { public HalFormsLinkDiscoverer() { - super("$._links..['%s']", MediaTypes.HAL_FORMS_JSON); - } - - @Override - @SuppressWarnings("unchecked") - protected Link extractLink(Object element, String rel) { - - if (element instanceof Map) { - - Map json = (Map) element; - - return new Link(json.get("href"), rel) - .withHreflang(json.get("hreflang")) - .withMedia(json.get("media")) - .withTitle(json.get("title")) - .withType(json.get("type")) - .withDeprecation(json.get("deprecation")) - .withProfile(json.get("profile")) - .withName(json.get("name")); - } - - return super.extractLink(element, rel); + super(MediaTypes.HAL_FORMS_JSON); } } diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsProperty.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsProperty.java index 86f1fd45..312b42e4 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsProperty.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsProperty.java @@ -28,7 +28,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * Describe a parameter for the associated state transition in a HAL-FORMS document. A {@link HalFormsTemplate} may * contain a list of {@link HalFormsProperty}s - * + * * @see http://mamund.site44.com/misc/hal-forms/ */ @JsonInclude(Include.NON_DEFAULT) @@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; @Wither @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(force = true) -class HalFormsProperty { +public class HalFormsProperty { private @NonNull String name; private Boolean readOnly; @@ -49,7 +49,7 @@ class HalFormsProperty { /** * Creates a new {@link HalFormsProperty} with the given name. - * + * * @param name must not be {@literal null}. * @return */ diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsSerializers.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsSerializers.java index 2eba76c8..b17279e9 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsSerializers.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsSerializers.java @@ -18,16 +18,18 @@ package org.springframework.hateoas.hal.forms; import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.springframework.hateoas.Affordance; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; +import org.springframework.hateoas.hal.HalLinkRelation; import org.springframework.hateoas.hal.Jackson2HalModule; import org.springframework.http.HttpMethod; @@ -74,9 +76,7 @@ class HalFormsSerializers { .withLinks(value.getLinks()) // .withTemplates(findTemplates(value)); - provider - .findValueSerializer(HalFormsDocument.class, property) - .serialize(doc, gen, provider); + provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider); } @Override @@ -131,7 +131,7 @@ class HalFormsSerializers { @Override public void serialize(Resources value, JsonGenerator gen, SerializerProvider provider) throws IOException { - Map embeddeds = embeddedMapper.map(value); + Map embeddeds = embeddedMapper.map(value); HalFormsDocument doc; @@ -151,9 +151,7 @@ class HalFormsSerializers { .withTemplates(findTemplates(value)); } - provider - .findValueSerializer(HalFormsDocument.class, property) - .serialize(doc, gen, provider); + provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider); } @Override @@ -191,30 +189,29 @@ class HalFormsSerializers { */ private static Map findTemplates(ResourceSupport resource) { + if (!resource.hasLink(IanaLinkRelations.SELF)) { + return Collections.emptyMap(); + } + Map templates = new HashMap<>(); + List affordances = resource.getLink(IanaLinkRelations.SELF).map(Link::getAffordances) + .orElse(Collections.emptyList()); - if (resource.hasLink(IanaLinkRelation.SELF.value())) { + affordances.stream() // + .map(it -> it.getAffordanceModel(MediaTypes.HAL_FORMS_JSON)) // + .map(HalFormsAffordanceModel.class::cast) // + .filter(it -> !it.hasHttpMethod(HttpMethod.GET)) // + .peek(it -> validate(resource, it)) // + .forEach(it -> { - - for (Affordance affordance : resource.getLink(IanaLinkRelation.SELF.value()).map(Link::getAffordances) - .orElse(Collections.emptyList())) { - - HalFormsAffordanceModel model = affordance.getAffordanceModel(MediaTypes.HAL_FORMS_JSON); - - if (!(model.getHttpMethod() == HttpMethod.GET)) { - - validate(resource, model); - - HalFormsTemplate template = HalFormsTemplate.forMethod(model.getHttpMethod()) // - .withProperties(model.getInputProperties()); + HalFormsTemplate template = HalFormsTemplate.forMethod(it.getHttpMethod()) // + .withProperties(it.getInputProperties()); /* * First template in HAL-FORMS is "default". */ - templates.put(templates.isEmpty() ? "default" : model.getName(), template); - } - } - } + templates.put(templates.isEmpty() ? "default" : it.getName(), template); + }); return templates; } @@ -228,11 +225,11 @@ class HalFormsSerializers { private static void validate(ResourceSupport resource, HalFormsAffordanceModel model) { String affordanceUri = model.getURI(); - String selfLinkUri = resource.getRequiredLink(IanaLinkRelation.SELF.value()).expand().getHref(); + String selfLinkUri = resource.getRequiredLink(IanaLinkRelations.SELF.value()).expand().getHref(); if (!affordanceUri.equals(selfLinkUri)) { - throw new IllegalStateException("Affordance's URI " + affordanceUri + " doesn't match self link " - + selfLinkUri + " as expected in HAL-FORMS"); + throw new IllegalStateException("Affordance's URI " + affordanceUri + " doesn't match self link " + selfLinkUri + + " as expected in HAL-FORMS"); } } } diff --git a/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java b/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java index 1a829a93..ae7b811d 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java @@ -15,6 +15,7 @@ */ package org.springframework.hateoas.hal.forms; +import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -22,6 +23,7 @@ import java.util.Map; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.ResourceSupport; @@ -29,6 +31,7 @@ import org.springframework.hateoas.Resources; import org.springframework.hateoas.hal.CurieProvider; import org.springframework.hateoas.hal.Jackson2HalModule.EmbeddedMapper; import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator; +import org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListDeserializer; import org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListSerializer; import org.springframework.hateoas.hal.LinkMixin; import org.springframework.hateoas.hal.ResourceSupportMixin; @@ -41,8 +44,11 @@ import org.springframework.http.MediaType; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.KeyDeserializer; @@ -50,11 +56,13 @@ import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.databind.type.TypeFactory; /** * Serialize / deserialize all the parts of HAL-FORMS documents using Jackson. @@ -78,7 +86,6 @@ public class Jackson2HalFormsModule extends SimpleModule { setMixInAnnotation(MediaType.class, MediaTypeMixin.class); addSerializer(new HalFormsResourceSerializer()); - } @JsonSerialize(using = HalFormsResourceSerializer.class) @@ -108,6 +115,35 @@ public class Jackson2HalFormsModule extends SimpleModule { @JsonDeserialize(using = MediaTypeDeserializer.class) interface MediaTypeMixin {} + static class HalFormsLinksDeserializer extends ContainerDeserializerBase { + + private static final long serialVersionUID = -848240531474910385L; + + private final HalLinkListDeserializer delegate = new HalLinkListDeserializer(); + + public HalFormsLinksDeserializer() { + super(TypeFactory.defaultInstance().constructType(Links.class)); + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer() + */ + @Override + public JsonDeserializer getContentDeserializer() { + return delegate.getContentDeserializer(); + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ + @Override + public Links deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + return Links.of(delegate.deserialize(p, ctxt)); + } + } + /** * Create new HAL-FORMS serializers based on the context. */ @@ -116,22 +152,24 @@ public class Jackson2HalFormsModule extends SimpleModule { private final Map, Object> serializers = new HashMap<>(); public HalFormsHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, - MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections, - HalFormsConfiguration halFormsConfiguration) { + MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections, + HalFormsConfiguration halFormsConfiguration) { - super(resolver, curieProvider, messageSource, enforceEmbeddedCollections, halFormsConfiguration.toHalConfiguration()); + super(resolver, curieProvider, messageSource, enforceEmbeddedCollections, + halFormsConfiguration.toHalConfiguration()); EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider, enforceEmbeddedCollections); this.serializers.put(HalFormsResourcesSerializer.class, new HalFormsResourcesSerializer(mapper)); this.serializers.put(HalLinkListSerializer.class, - new HalLinkListSerializer(curieProvider, mapper, messageSource, halFormsConfiguration.toHalConfiguration())); + new HalLinkListSerializer(curieProvider, mapper, messageSource, halFormsConfiguration.toHalConfiguration())); } public HalFormsHandlerInstantiator(RelProvider relProvider, CurieProvider curieProvider, - MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections, - AutowireCapableBeanFactory beanFactory) { - this(relProvider, curieProvider, messageSource, enforceEmbeddedCollections, beanFactory.getBean(HalFormsConfiguration.class)); + MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections, + AutowireCapableBeanFactory beanFactory) { + this(relProvider, curieProvider, messageSource, enforceEmbeddedCollections, + beanFactory.getBean(HalFormsConfiguration.class)); } private Object findInstance(Class type) { diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java b/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java index 9142bc17..dada1b2f 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java @@ -17,6 +17,7 @@ package org.springframework.hateoas.mvc; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.hateoas.ExposesResourceFor; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; import org.springframework.plugin.core.PluginRegistry; import org.springframework.util.Assert; @@ -42,25 +43,25 @@ public class ControllerRelProvider implements RelProvider { this.providers = providers; } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class) */ @Override - public String getCollectionResourceRelFor(Class resource) { + public LinkRelation getCollectionResourceRelFor(Class resource) { return providers.getRequiredPluginFor(entityType).getCollectionResourceRelFor(resource); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class) */ @Override - public String getItemResourceRelFor(Class resource) { + public LinkRelation getItemResourceRelFor(Class resource) { return providers.getRequiredPluginFor(entityType).getItemResourceRelFor(resource); } - /* + /* * (non-Javadoc) * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) */ diff --git a/src/main/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntity.java b/src/main/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntity.java index e11b3f15..4d89d595 100644 --- a/src/main/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntity.java +++ b/src/main/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntity.java @@ -15,8 +15,6 @@ */ package org.springframework.hateoas.mvc; -import java.util.List; - import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; import org.springframework.hateoas.ResourceProcessor; @@ -33,14 +31,14 @@ import org.springframework.util.Assert; * support code that will transparently invoke the header exposure. If you use this class from a controller directly, * the {@link Link}s will not be present in the {@link ResourceSupport} instance anymore when {@link ResourceProcessor}s * kick in. - * + * * @author Oliver Gierke */ public class HeaderLinksResponseEntity extends ResponseEntity { /** * Creates a new {@link HeaderLinksResponseEntity} from the given {@link ResponseEntity}. - * + * * @param entity must not be {@literal null}. */ private HeaderLinksResponseEntity(ResponseEntity entity) { @@ -52,7 +50,7 @@ public class HeaderLinksResponseEntity extends Respon /** * Creates a new {@link HeaderLinksResponseEntity} from the given {@link HttpEntity} by defaulting the status code to * {@link HttpStatus#OK}. - * + * * @param entity must not be {@literal null}. */ private HeaderLinksResponseEntity(HttpEntity entity) { @@ -62,7 +60,7 @@ public class HeaderLinksResponseEntity extends Respon /** * Wraps the given {@link HttpEntity} into a {@link HeaderLinksResponseEntity}. Will default the status code to * {@link HttpStatus#OK} if the given value is not a {@link ResponseEntity}. - * + * * @param entity must not be {@literal null}. * @return */ @@ -80,7 +78,7 @@ public class HeaderLinksResponseEntity extends Respon /** * Wraps the given {@link ResourceSupport} into a {@link HeaderLinksResponseEntity}. Will default the status code to * {@link HttpStatus#OK}. - * + * * @param entity must not be {@literal null}. * @return */ @@ -94,17 +92,17 @@ public class HeaderLinksResponseEntity extends Respon /** * Returns the {@link Link}s contained in the {@link ResourceSupport} of the given {@link ResponseEntity} as * {@link HttpHeaders}. - * + * * @param entity must not be {@literal null}. * @return */ private static HttpHeaders getHeadersWithLinks(ResponseEntity entity) { - List links = entity.getBody().getLinks(); + Links links = entity.getBody().getLinks(); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.putAll(entity.getHeaders()); - httpHeaders.add("Link", new Links(links).toString()); + httpHeaders.add("Link", links.toString()); return httpHeaders; } diff --git a/src/main/java/org/springframework/hateoas/mvc/JacksonSerializers.java b/src/main/java/org/springframework/hateoas/mvc/JacksonSerializers.java index e755e144..2cbc4017 100644 --- a/src/main/java/org/springframework/hateoas/mvc/JacksonSerializers.java +++ b/src/main/java/org/springframework/hateoas/mvc/JacksonSerializers.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. @@ -20,14 +20,13 @@ import java.io.IOException; import org.springframework.http.MediaType; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; /** * Simple Jackson serializers and deserializers. - * + * * @author Oliver Gierke */ public class JacksonSerializers { @@ -46,13 +45,12 @@ public class JacksonSerializers { super(MediaType.class); } - /* + /* * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) */ @Override - public MediaType deserialize(JsonParser p, DeserializationContext ctxt) - throws IOException { + public MediaType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return MediaType.parseMediaType(p.getText()); } } diff --git a/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java b/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java index 589b1add..53982f27 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java +++ b/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java @@ -19,17 +19,18 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.springframework.beans.BeanUtils; import org.springframework.hateoas.ResourceAssembler; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; -import org.springframework.hateoas.core.Objects; +import org.springframework.util.Assert; /** * Base class to implement {@link ResourceAssembler}s. Will automate {@link ResourceSupport} instance creation and make * sure a self-link is always added. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -40,19 +41,23 @@ public abstract class ResourceAssemblerSupport imp /** * Creates a new {@link ResourceAssemblerSupport} using the given controller class and resource type. - * + * * @param controllerClass must not be {@literal null}. * @param resourceType must not be {@literal null}. */ public ResourceAssemblerSupport(Class controllerClass, Class resourceType) { - Objects.requireNonNull(controllerClass, "ControllerClass must not be null!"); - Objects.requireNonNull(resourceType, "ResourceType must not be null!"); + Assert.notNull(controllerClass, "ControllerClass must not be null!"); + Assert.notNull(resourceType, "ResourceType must not be null!"); this.controllerClass = controllerClass; this.resourceType = resourceType; } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.ResourceAssembler#toResources(java.lang.Iterable) + */ @Override public Resources toResources(Iterable entities) { return this.map(entities).toResources(); @@ -64,7 +69,7 @@ public abstract class ResourceAssemblerSupport imp /** * Creates a new resource with a self link to the given id. - * + * * @param entity must not be {@literal null}. * @param id must not be {@literal null}. * @return @@ -75,8 +80,8 @@ public abstract class ResourceAssemblerSupport imp protected D createResourceWithId(Object id, T entity, Object... parameters) { - Objects.requireNonNull(entity, "Entity must not be null!"); - Objects.requireNonNull(id, "Id must not be null!"); + Assert.notNull(entity, "Entity must not be null!"); + Assert.notNull(id, "Id must not be null!"); D instance = instantiateResource(entity); instance.add(linkTo(this.controllerClass, parameters).slash(id).withSelfRel()); @@ -87,7 +92,7 @@ public abstract class ResourceAssemblerSupport imp * Instantiates the resource object. Default implementation will assume a no-arg constructor and use reflection but * can be overridden to manually set up the object instance initially (e.g. to improve performance if this becomes an * issue). - * + * * @param entity * @return */ @@ -110,7 +115,6 @@ public abstract class ResourceAssemblerSupport imp * Transform a list of {@code T}s into a list of {@link ResourceSupport}s. * * @see #toListOfResources() if you need this transformed list rendered as hypermedia - * * @return */ public List toListOfResources() { diff --git a/src/main/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilder.java b/src/main/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilder.java index 52f6a53b..63ea8647 100644 --- a/src/main/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilder.java @@ -24,6 +24,7 @@ import org.springframework.core.ResolvableType; import org.springframework.hateoas.Affordance; import org.springframework.hateoas.AffordanceModelFactory; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.QueryParameter; import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation; import org.springframework.hateoas.core.MappingDiscoverer; @@ -35,7 +36,7 @@ import org.springframework.web.util.UriComponents; /** * Extract information needed to assemble an {@link Affordance} from a Spring MVC web method. - * + * * @author Greg Turnquist */ class SpringMvcAffordanceBuilder { @@ -43,7 +44,7 @@ class SpringMvcAffordanceBuilder { /** * Use the attributes of the current method call along with a collection of {@link AffordanceModelFactory}'s to create * a set of {@link Affordance}s. - * + * * @param invocation * @param discoverer * @param components @@ -57,24 +58,24 @@ class SpringMvcAffordanceBuilder { for (HttpMethod requestMethod : discoverer.getRequestMethod(invocation.getTargetType(), invocation.getMethod())) { String methodName = invocation.getMethod().getName(); - - Link affordanceLink = new Link(components.toUriString()).withRel(methodName); - + Link affordanceLink = new Link(components.toUriString()).withRel(LinkRelation.of(methodName)); MethodParameters invocationMethodParameters = new MethodParameters(invocation.getMethod()); - - ResolvableType inputType = invocationMethodParameters.getParametersWith(RequestBody.class).stream() - .findFirst() - .map(ResolvableType::forMethodParameter) - .orElse(ResolvableType.NONE); - List queryMethodParameters = invocationMethodParameters.getParametersWith(RequestParam.class).stream() - .map(methodParameter -> methodParameter.getParameterAnnotation(RequestParam.class)) - .map(requestParam -> new QueryParameter(requestParam.name(), requestParam.value(), requestParam.required())) - .collect(Collectors.toList()); + ResolvableType inputType = invocationMethodParameters.getParametersWith(RequestBody.class).stream() // + .findFirst() // + .map(ResolvableType::forMethodParameter) // + .orElse(ResolvableType.NONE); + + List queryMethodParameters = invocationMethodParameters.getParametersWith(RequestParam.class) + .stream() // + .map(methodParameter -> methodParameter.getParameterAnnotation(RequestParam.class)) // + .map(requestParam -> new QueryParameter(requestParam.name(), requestParam.value(), requestParam.required())) // + .collect(Collectors.toList()); ResolvableType outputType = ResolvableType.forMethodReturnType(invocation.getMethod()); - affordances.add(new Affordance(methodName, affordanceLink, requestMethod, inputType, queryMethodParameters, outputType)); + affordances + .add(new Affordance(methodName, affordanceLink, requestMethod, inputType, queryMethodParameters, outputType)); } diff --git a/src/main/java/org/springframework/hateoas/mvc/TypeReferences.java b/src/main/java/org/springframework/hateoas/mvc/TypeReferences.java index 75adf436..00347397 100644 --- a/src/main/java/org/springframework/hateoas/mvc/TypeReferences.java +++ b/src/main/java/org/springframework/hateoas/mvc/TypeReferences.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * 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. @@ -18,7 +18,6 @@ package org.springframework.hateoas.mvc; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.util.HashMap; import org.springframework.core.GenericTypeResolver; @@ -27,8 +26,8 @@ import org.springframework.util.Assert; /** * Helper to easily create {@link ParameterizedTypeReference} instances to Spring HATEOAS resource types. They're - * basically a shortcut over using a verbose {@code new ParameterizedTypeReference>() . - * + * basically a shortcut over using a verbose {@code new ParameterizedTypeReference>()}. + * * @author Oliver Gierke * @since 0.17 */ @@ -40,8 +39,8 @@ public class TypeReferences { * @author Oliver Gierke * @since 0.17 */ - public static class ResourceType extends - SyntheticParameterizedTypeReference> {} + public static class ResourceType + extends SyntheticParameterizedTypeReference> {} /** * A {@link ParameterizedTypeReference} to return a {@link org.springframework.hateoas.Resources} of some type. @@ -49,8 +48,8 @@ public class TypeReferences { * @author Oliver Gierke * @since 0.17 */ - public static class ResourcesType extends - SyntheticParameterizedTypeReference> {} + public static class ResourcesType + extends SyntheticParameterizedTypeReference> {} /** * A {@link ParameterizedTypeReference} to return a {@link org.springframework.hateoas.PagedResources} of some type. @@ -58,12 +57,12 @@ public class TypeReferences { * @author Oliver Gierke * @since 0.17 */ - public static class PagedResourcesType extends - SyntheticParameterizedTypeReference> {} + public static class PagedResourcesType + extends SyntheticParameterizedTypeReference> {} /** - * Special {@link ParameterizedTypeReference} to customize the generic type detection and eventually return a synthetic - * {@link ParameterizedType} to represent the resource type along side its generic parameter. + * Special {@link ParameterizedTypeReference} to customize the generic type detection and eventually return a + * synthetic {@link ParameterizedType} to represent the resource type along side its generic parameter. * * @author Oliver Gierke * @since 0.17 @@ -72,7 +71,7 @@ public class TypeReferences { private final Type type; - @SuppressWarnings({ "rawtypes", "deprecation" }) + @SuppressWarnings("rawtypes") SyntheticParameterizedTypeReference() { Class foo = getClass(); @@ -84,10 +83,12 @@ public class TypeReferences { Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass(); Assert.isInstanceOf(ParameterizedType.class, type); ParameterizedType parameterizedType = (ParameterizedType) type; - Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1, String.format("Type must have exactly one generic type argument but has %s.", parameterizedType.getActualTypeArguments().length)); + Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1, + String.format("Type must have exactly one generic type argument but has %s.", + parameterizedType.getActualTypeArguments().length)); Class resourceType = GenericTypeResolver.resolveType(parameterizedType.getActualTypeArguments()[0], - new HashMap<>()); + new HashMap<>()); this.type = new SyntheticParameterizedType(resourceType, domainType); } @@ -96,6 +97,7 @@ public class TypeReferences { * (non-Javadoc) * @see org.springframework.core.ParameterizedTypeReference#getType() */ + @Override public Type getType() { return this.type; } @@ -106,8 +108,8 @@ public class TypeReferences { */ @Override public boolean equals(Object obj) { - return (this == obj || (obj instanceof SyntheticParameterizedTypeReference && this.type - .equals(((SyntheticParameterizedTypeReference) obj).type))); + return this == obj || obj instanceof SyntheticParameterizedTypeReference + && this.type.equals(((SyntheticParameterizedTypeReference) obj).type); } /* diff --git a/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java b/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java index 7f40591b..e2302827 100644 --- a/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java +++ b/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java @@ -15,7 +15,6 @@ */ package org.springframework.hateoas.uber; -import static org.springframework.hateoas.PagedResources.*; import static org.springframework.hateoas.support.JacksonHelper.*; import static org.springframework.hateoas.uber.UberData.*; @@ -27,9 +26,10 @@ import java.util.Map; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; -import org.springframework.beans.BeanUtils; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; +import org.springframework.hateoas.PagedResources.PageMetadata; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; @@ -40,52 +40,92 @@ import org.springframework.util.StringUtils; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; -import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.introspect.Annotated; -import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; -import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.ContainerSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.type.TypeFactory; /** * Jackson {@link SimpleModule} for {@literal UBER+JSON} serializers and deserializers. - * + * * @author Greg Turnquist * @author Jens Schauder * @since 1.0 */ public class Jackson2UberModule extends SimpleModule { + private static final long serialVersionUID = -2396790508486870880L; + public Jackson2UberModule() { super("uber-module", new Version(1, 0, 0, null, "org.springframework.hateoas", "spring-hateoas")); - setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class); - setMixInAnnotation(Resource.class, ResourceMixin.class); - setMixInAnnotation(Resources.class, ResourcesMixin.class); - setMixInAnnotation(PagedResources.class, PagedResourcesMixin.class); - addSerializer(new UberPagedResourcesSerializer()); addSerializer(new UberResourcesSerializer()); addSerializer(new UberResourceSerializer()); addSerializer(new UberResourceSupportSerializer()); + + setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class); + setMixInAnnotation(Resource.class, ResourceMixin.class); + setMixInAnnotation(Resources.class, ResourcesMixin.class); + setMixInAnnotation(PagedResources.class, PagedResourcesMixin.class); } + /** + * Jackson 2 mixin to handle {@link ResourceSupport} for {@literal UBER+JSON}. + * + * @author Greg Turnquist + * @since 1.0 + */ + @JsonDeserialize(using = UberResourceSupportDeserializer.class) + abstract class ResourceSupportMixin extends ResourceSupport {} + + /** + * Jackson 2 mixin to handle {@link Resource} for {@literal UBER+JSON}. + * + * @author Greg Turnquist + * @since 1.0 + */ + @JsonDeserialize(using = UberResourceDeserializer.class) + abstract class ResourceMixin extends Resource {} + + /** + * Jackson 2 mixin to handle {@link Resources} for {@literal UBER+JSON}. + * + * @author Greg Turnquist + * @since 1.0 + */ + @JsonDeserialize(using = UberResourcesDeserializer.class) + abstract class ResourcesMixin extends Resources {} + + /** + * Jackson 2 mixin to handle {@link PagedResources} for {@literal UBER+JSON}. + * + * @author Greg Turnquist + * @since 1.0 + */ + @JsonDeserialize(using = UberPagedResourcesDeserializer.class) + abstract class PagedResourcesMixin extends PagedResources {} + /** * Custom {@link JsonSerializer} to render {@link ResourceSupport} into {@literal UBER+JSON}. */ static class UberResourceSupportSerializer extends ContainerSerializer implements ContextualSerializer { + private static final long serialVersionUID = -572866287910993300L; private final BeanProperty property; UberResourceSupportSerializer(BeanProperty property) { @@ -143,6 +183,8 @@ public class Jackson2UberModule extends SimpleModule { */ static class UberResourceSerializer extends ContainerSerializer> implements ContextualSerializer { + private static final long serialVersionUID = -5538560800604582741L; + private final BeanProperty property; UberResourceSerializer(BeanProperty property) { @@ -158,10 +200,9 @@ public class Jackson2UberModule extends SimpleModule { @Override public void serialize(Resource value, JsonGenerator gen, SerializerProvider provider) throws IOException { - UberDocument doc = new UberDocument() - .withUber(new Uber() - .withVersion("1.0") - .withData(extractLinksAndContent(value))); + UberDocument doc = new UberDocument().withUber(new Uber() // + .withVersion("1.0") // + .withData(extractLinksAndContent(value))); provider // .findValueSerializer(UberDocument.class, property) // @@ -200,6 +241,8 @@ public class Jackson2UberModule extends SimpleModule { */ static class UberResourcesSerializer extends ContainerSerializer> implements ContextualSerializer { + private static final long serialVersionUID = 3422019794262694127L; + private BeanProperty property; UberResourcesSerializer(BeanProperty property) { @@ -212,6 +255,10 @@ 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 public void serialize(Resources value, JsonGenerator gen, SerializerProvider provider) throws IOException { @@ -258,6 +305,8 @@ public class Jackson2UberModule extends SimpleModule { static class UberPagedResourcesSerializer extends ContainerSerializer> implements ContextualSerializer { + private static final long serialVersionUID = -7892297813593085984L; + private BeanProperty property; UberPagedResourcesSerializer(BeanProperty property) { @@ -270,6 +319,10 @@ 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 public void serialize(PagedResources value, JsonGenerator gen, SerializerProvider provider) throws IOException { @@ -283,26 +336,46 @@ public class Jackson2UberModule extends SimpleModule { .serialize(doc, gen, provider); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType() + */ @Override public JavaType getContentType() { return null; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer() + */ @Override public JsonSerializer getContentSerializer() { return null; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object) + */ @Override public boolean hasSingleElement(PagedResources value) { return value.getContent().size() == 1; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer) + */ @Override 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 public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { @@ -310,44 +383,34 @@ public class Jackson2UberModule extends SimpleModule { } } - /** - * Custom {@link StdSerializer} to translate {@link UberAction} into the proper JSON representation. - */ - static class UberActionSerializer extends StdSerializer { - - UberActionSerializer() { - super(UberAction.class); - } - - @Override - public void serialize(UberAction value, JsonGenerator gen, SerializerProvider provider) throws IOException { - gen.writeString(value.toString()); - } - } - /** * Custom {@link StdDeserializer} to deserialize {@link ResourceSupport}. */ static class UberResourceSupportDeserializer extends ContainerDeserializerBase implements ContextualDeserializer { - private JavaType contentType; + private static final long serialVersionUID = -8738539821441549016L; + private final JavaType contentType; - UberResourceSupportDeserializer(JavaType contentType) { + UberResourceSupportDeserializer() { + this(TypeFactory.defaultInstance().constructType(ResourceSupport.class)); + } + + private UberResourceSupportDeserializer(JavaType contentType) { super(contentType); this.contentType = contentType; } - UberResourceSupportDeserializer() { - this(TypeFactory.defaultInstance().constructSimpleType(UberDocument.class, new JavaType[0])); - } - + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ @Override public ResourceSupport deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { UberDocument doc = p.getCodec().readValue(p, UberDocument.class); - List links = doc.getUber().getLinks(); + Links links = doc.getUber().getLinks(); return doc.getUber().getData().stream() // .filter(uberData -> !StringUtils.isEmpty(uberData.getName())) // @@ -363,7 +426,7 @@ public class Jackson2UberModule extends SimpleModule { } @NotNull - private ResourceSupport convertToResourceSupport(UberData uberData, List links) { + private ResourceSupport convertToResourceSupport(UberData uberData, Links links) { List data = uberData.getData(); Map properties; @@ -382,11 +445,19 @@ public class Jackson2UberModule extends SimpleModule { return resourceSupport; } + /* + * (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.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty) + */ @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { @@ -414,31 +485,36 @@ public class Jackson2UberModule extends SimpleModule { static class UberResourceDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { - private JavaType contentType; + private static final long serialVersionUID = 1776321413269082414L; - UberResourceDeserializer(JavaType contentType) { - - super(contentType); - this.contentType = contentType; - } + private final JavaType contentType; UberResourceDeserializer() { this(TypeFactory.defaultInstance().constructSimpleType(UberDocument.class, new JavaType[0])); } + private UberResourceDeserializer(JavaType contentType) { + + super(contentType); + this.contentType = contentType; + } + @Override public Resource deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { UberDocument doc = p.getCodec().readValue(p, UberDocument.class); - List links = doc.getUber().getLinks(); + Links links = doc.getUber().getLinks(); - return doc.getUber().getData().stream().filter(uberData -> !StringUtils.isEmpty(uberData.getName())).findFirst() - .map(uberData -> convertToResource(uberData, links)).orElseThrow( + return doc.getUber().getData().stream() // + .filter(uberData -> !StringUtils.isEmpty(uberData.getName())) // + .findFirst() // + .map(uberData -> convertToResource(uberData, links)) // + .orElseThrow( () -> new IllegalStateException("No data entry containing a 'value' was found in this document!")); } @NotNull - private Resource convertToResource(UberData uberData, List links) { + private Resource convertToResource(UberData uberData, Links links) { // Primitive type List data = uberData.getData(); @@ -461,11 +537,19 @@ public class Jackson2UberModule extends SimpleModule { return new Resource<>(value, links); } + /* + * (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.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty) + */ @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { @@ -493,7 +577,9 @@ public class Jackson2UberModule extends SimpleModule { static class UberResourcesDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { - private JavaType contentType; + private static final long serialVersionUID = 8722467561709171145L; + + private final JavaType contentType; UberResourcesDeserializer(JavaType contentType) { @@ -505,6 +591,10 @@ public class Jackson2UberModule extends SimpleModule { this(TypeFactory.defaultInstance().constructSimpleType(UberDocument.class, new JavaType[0])); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ @Override public Resources deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { @@ -550,6 +640,8 @@ public class Jackson2UberModule extends SimpleModule { static class UberPagedResourcesDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { + private static final long serialVersionUID = 4123359694609188745L; + private JavaType contentType; UberPagedResourcesDeserializer(JavaType contentType) { @@ -562,6 +654,10 @@ public class Jackson2UberModule extends SimpleModule { this(TypeFactory.defaultInstance().constructSimpleType(UberDocument.class, new JavaType[0])); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ @Override public PagedResources deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { @@ -602,12 +698,11 @@ public class Jackson2UberModule extends SimpleModule { public JsonDeserializer getContentDeserializer() { return null; } - } /** * Convert an {@link UberDocument} into a {@link Resources}. - * + * * @param doc * @param rootType * @param contentType @@ -681,9 +776,7 @@ public class Jackson2UberModule extends SimpleModule { * ...or return a Resources */ - List resourceLessContent = content.stream() - .map(item -> (Resource) item) - .map(Resource::getContent) + List resourceLessContent = content.stream().map(item -> (Resource) item).map(Resource::getContent) .collect(Collectors.toList()); return new Resources<>(resourceLessContent, doc.getUber().getLinks()); @@ -697,10 +790,8 @@ 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")) - .findFirst() - .map(Jackson2UberModule::convertUberDataToPageMetaData) - .orElse(null); + .filter(uberData -> uberData.getName() != null && uberData.getName().equals("page")).findFirst() + .map(Jackson2UberModule::convertUberDataToPageMetaData).orElse(null); } @NotNull @@ -747,60 +838,19 @@ public class Jackson2UberModule extends SimpleModule { */ static class UberActionDeserializer extends StdDeserializer { + private static final long serialVersionUID = -6198451472474285487L; + UberActionDeserializer() { super(UberAction.class); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ @Override public UberAction deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return UberAction.valueOf(p.getText().toUpperCase()); } } - - public static class UberHandlerInstantiator extends HandlerInstantiator { - - private final Map, Object> serializers = new HashMap<>(); - - public UberHandlerInstantiator() { - - this.serializers.put(UberResourceSupportSerializer.class, new UberResourceSupportSerializer()); - this.serializers.put(UberResourceSerializer.class, new UberResourceSerializer()); - this.serializers.put(UberResourcesSerializer.class, new UberResourcesSerializer()); - this.serializers.put(UberPagedResourcesSerializer.class, new UberPagedResourcesSerializer()); - } - - @Override - public JsonDeserializer deserializerInstance(DeserializationConfig config, Annotated annotated, - Class deserClass) { - return (JsonDeserializer) findInstance(deserClass); - } - - @Override - public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, - Class keyDeserClass) { - return (KeyDeserializer) findInstance(keyDeserClass); - } - - @Override - public JsonSerializer serializerInstance(SerializationConfig config, Annotated annotated, Class serClass) { - return (JsonSerializer) findInstance(serClass); - } - - @Override - public TypeResolverBuilder typeResolverBuilderInstance(MapperConfig config, Annotated annotated, - Class builderClass) { - return (TypeResolverBuilder) findInstance(builderClass); - } - - @Override - public TypeIdResolver typeIdResolverInstance(MapperConfig config, Annotated annotated, Class resolverClass) { - return (TypeIdResolver) findInstance(resolverClass); - } - - private Object findInstance(Class type) { - - Object result = this.serializers.get(type); - return result != null ? result : BeanUtils.instantiateClass(type); - } - } } diff --git a/src/main/java/org/springframework/hateoas/uber/PagedResourcesMixin.java b/src/main/java/org/springframework/hateoas/uber/PagedResourcesMixin.java deleted file mode 100644 index c2b9d9ee..00000000 --- a/src/main/java/org/springframework/hateoas/uber/PagedResourcesMixin.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018-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. - * 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. - */ -package org.springframework.hateoas.uber; - -import org.springframework.hateoas.PagedResources; -import org.springframework.hateoas.uber.Jackson2UberModule.UberPagedResourcesDeserializer; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -/** - * Jackson 2 mixin to handle {@link PagedResources} for {@literal UBER+JSON}. - * - * @author Greg Turnquist - * @since 1.0 - */ -@JsonDeserialize(using = UberPagedResourcesDeserializer.class) -abstract class PagedResourcesMixin extends PagedResources { - -} \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/uber/ResourceMixin.java b/src/main/java/org/springframework/hateoas/uber/ResourceMixin.java deleted file mode 100644 index 8c2e1605..00000000 --- a/src/main/java/org/springframework/hateoas/uber/ResourceMixin.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018-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. - * 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. - */ -package org.springframework.hateoas.uber; - -import org.springframework.hateoas.Resource; -import org.springframework.hateoas.uber.Jackson2UberModule.UberResourceDeserializer; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -/** - * Jackson 2 mixin to handle {@link Resource} for {@literal UBER+JSON}. - * - * @author Greg Turnquist - * @since 1.0 - */ -@JsonDeserialize(using = UberResourceDeserializer.class) -abstract class ResourceMixin extends Resource { - -} \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/uber/ResourceSupportMixin.java b/src/main/java/org/springframework/hateoas/uber/ResourceSupportMixin.java deleted file mode 100644 index 4a378486..00000000 --- a/src/main/java/org/springframework/hateoas/uber/ResourceSupportMixin.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018-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. - * 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. - */ -package org.springframework.hateoas.uber; - -import org.springframework.hateoas.ResourceSupport; -import org.springframework.hateoas.uber.Jackson2UberModule.UberResourceSupportDeserializer; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -/** - * Jackson 2 mixin to handle {@link ResourceSupport} for {@literal UBER+JSON}. - * - * @author Greg Turnquist - * @since 1.0 - */ -@JsonDeserialize(using = UberResourceSupportDeserializer.class) -abstract class ResourceSupportMixin extends ResourceSupport { - -} \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/uber/ResourcesMixin.java b/src/main/java/org/springframework/hateoas/uber/ResourcesMixin.java deleted file mode 100644 index 53be5cda..00000000 --- a/src/main/java/org/springframework/hateoas/uber/ResourcesMixin.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018-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. - * 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. - */ -package org.springframework.hateoas.uber; - -import org.springframework.hateoas.Resources; -import org.springframework.hateoas.uber.Jackson2UberModule.UberResourcesDeserializer; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -/** - * Jackson 2 mixin to handle {@link Resources} for {@literal UBER+JSON}. - * - * @author Greg Turnquist - * @since 1.0 - */ -@JsonDeserialize(using = UberResourcesDeserializer.class) -abstract class ResourcesMixin extends Resources { - -} \ No newline at end of file diff --git a/src/main/java/org/springframework/hateoas/uber/Uber.java b/src/main/java/org/springframework/hateoas/uber/Uber.java index 57971dd6..1487d81c 100644 --- a/src/main/java/org/springframework/hateoas/uber/Uber.java +++ b/src/main/java/org/springframework/hateoas/uber/Uber.java @@ -20,9 +20,8 @@ import lombok.Value; import lombok.experimental.Wither; import java.util.List; -import java.util.stream.Collectors; -import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -47,7 +46,7 @@ class Uber { @JsonCreator Uber(@JsonProperty("version") String version, @JsonProperty("data") List data, - @JsonProperty("error") UberError error) { + @JsonProperty("error") UberError error) { this.version = version; this.data = data; @@ -64,11 +63,11 @@ class Uber { * @return */ @JsonIgnore - List getLinks() { + Links getLinks() { - return this.data.stream() - .flatMap(uberData -> uberData.getLinks().stream()) - .collect(Collectors.toList()); + return data.stream() // + .flatMap(uberData -> uberData.getLinks().stream()) // + .collect(Links.collector()); } -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/hateoas/uber/UberAction.java b/src/main/java/org/springframework/hateoas/uber/UberAction.java index 868a0b10..3f15d290 100644 --- a/src/main/java/org/springframework/hateoas/uber/UberAction.java +++ b/src/main/java/org/springframework/hateoas/uber/UberAction.java @@ -16,14 +16,13 @@ package org.springframework.hateoas.uber; -import static org.springframework.hateoas.uber.Jackson2UberModule.*; - import java.util.Arrays; +import org.springframework.hateoas.uber.Jackson2UberModule.UberActionDeserializer; import org.springframework.http.HttpMethod; +import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; /** * Embodies possible actions for an {@literal UBER+JSON} representation, mapped onto {@link HttpMethod}s. @@ -32,7 +31,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; * @author Greg Turnquist * @since 1.0 */ -@JsonSerialize(using = UberActionSerializer.class) @JsonDeserialize(using = UberActionDeserializer.class) enum UberAction { @@ -55,7 +53,7 @@ enum UberAction { * DELETE */ REMOVE(HttpMethod.DELETE), - + /** * PUT */ @@ -76,23 +74,24 @@ enum UberAction { return this.httpMethod; } + @JsonValue @Override public String toString() { return this.name().toLowerCase(); } - /** * Convert an {@link HttpMethod} into an {@link UberAction}. + * * @param method * @return */ static UberAction fromMethod(HttpMethod method) { - return Arrays.stream(UberAction.values()) - .filter(action -> action.httpMethod == method) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Unsupported method: " + method)); + return Arrays.stream(UberAction.values()) // + .filter(action -> action.httpMethod == method) // + .findFirst() // + .orElseThrow(() -> new IllegalArgumentException("Unsupported method: " + method)); } /** @@ -104,4 +103,4 @@ enum UberAction { static UberAction forRequestMethod(HttpMethod method) { return HttpMethod.GET == method ? null : fromMethod(method); } -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/hateoas/uber/UberAffordanceModelFactory.java b/src/main/java/org/springframework/hateoas/uber/UberAffordanceModelFactory.java index a19cf97c..649af54b 100644 --- a/src/main/java/org/springframework/hateoas/uber/UberAffordanceModelFactory.java +++ b/src/main/java/org/springframework/hateoas/uber/UberAffordanceModelFactory.java @@ -32,14 +32,19 @@ import org.springframework.http.MediaType; * {@link AffordanceModelFactory} for {@literal UBER+JSON}. * * @author Greg Turnquist - * @since 1.0 + * @author Oliver Drotbohm */ public class UberAffordanceModelFactory implements AffordanceModelFactory { private final @Getter MediaType mediaType = MediaTypes.UBER_JSON; + /* + * (non-Javadoc) + * @see org.springframework.hateoas.AffordanceModelFactory#getAffordanceModel(java.lang.String, org.springframework.hateoas.Link, org.springframework.http.HttpMethod, org.springframework.core.ResolvableType, java.util.List, org.springframework.core.ResolvableType) + */ @Override - public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, List queryMethodParameters, ResolvableType outputType) { + public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, + List queryMethodParameters, ResolvableType outputType) { return new UberAffordanceModel(name, link, httpMethod, inputType, queryMethodParameters, outputType); } } diff --git a/src/main/java/org/springframework/hateoas/uber/UberData.java b/src/main/java/org/springframework/hateoas/uber/UberData.java index 07289f95..bb83c54d 100644 --- a/src/main/java/org/springframework/hateoas/uber/UberData.java +++ b/src/main/java/org/springframework/hateoas/uber/UberData.java @@ -15,8 +15,6 @@ */ package org.springframework.hateoas.uber; -import static com.fasterxml.jackson.annotation.JsonInclude.*; - import lombok.AccessLevel; import lombok.Data; import lombok.Value; @@ -34,6 +32,8 @@ import java.util.stream.Collectors; import org.springframework.hateoas.Affordance; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; +import org.springframework.hateoas.Links; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.Resource; @@ -47,6 +47,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -64,7 +65,7 @@ class UberData { private String id; private String name; private String label; - private List rel; + private List rel; private String url; private UberAction action; private boolean transclude; @@ -75,12 +76,12 @@ class UberData { private List data; @JsonCreator - UberData(@JsonProperty("id") String id, @JsonProperty("name") String name, - @JsonProperty("label") String label, @JsonProperty("rel") List rel, - @JsonProperty("url") String url, @JsonProperty("action") UberAction action, - @JsonProperty("transclude") boolean transclude, @JsonProperty("model") String model, - @JsonProperty("sending") List sending, @JsonProperty("accepting") List accepting, - @JsonProperty("value") Object value, @JsonProperty("data") List data) { + UberData(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("label") String label, + @JsonProperty("rel") List rel, @JsonProperty("url") String url, + @JsonProperty("action") UberAction action, @JsonProperty("transclude") boolean transclude, + @JsonProperty("model") String model, @JsonProperty("sending") List sending, + @JsonProperty("accepting") List accepting, @JsonProperty("value") Object value, + @JsonProperty("data") List data) { this.id = id; this.name = name; @@ -104,11 +105,7 @@ class UberData { * Don't render if it's {@link UberAction#READ}. */ public UberAction getAction() { - - if (this.action == UberAction.READ) { - return null; - } - return this.action; + return action == UberAction.READ ? null : action; } /* @@ -116,9 +113,9 @@ class UberData { */ public Boolean isTemplated() { - return Optional.ofNullable(this.url) - .map(s -> s.contains("{?") ? true : null) - .orElse(null); + return Optional.ofNullable(this.url) // + .map(s -> s.contains("{?") ? true : null) // + .orElse(null); } /* @@ -134,29 +131,23 @@ class UberData { @JsonIgnore public List getLinks() { - return Optional.ofNullable(this.rel) - .map(rels -> rels.stream() - .map(rel -> new Link(this.url, rel)) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()); + return Optional.ofNullable(this.rel) // + .map(rels -> rels.stream() // + .map(rel -> new Link(this.url, rel)) // + .collect(Collectors.toList())) // + .orElse(Collections.emptyList()); } /** * Simple scalar types that can be encoded by value, not type. */ - private final static HashSet> PRIMITIVE_TYPES = new HashSet<>(Arrays.asList( - String.class - )); + private final static HashSet> PRIMITIVE_TYPES = new HashSet<>(Arrays.asList(String.class)); /** * Set of all Spring HATEOAS resource types. */ - private static final HashSet> RESOURCE_TYPES = new HashSet<>(Arrays.asList( - ResourceSupport.class, - Resource.class, - Resources.class, - PagedResources.class - )); + private static final HashSet> RESOURCE_TYPES = new HashSet<>( + Arrays.asList(ResourceSupport.class, Resource.class, Resources.class, PagedResources.class)); /** * Convert a {@link ResourceSupport} into a list of {@link UberData}s, containing links and content. @@ -198,10 +189,8 @@ class UberData { List data = extractLinks(resources); - data.addAll(resources.getContent().stream() - .map(UberData::doExtractLinksAndContent) - .map(uberData -> new UberData().withData(uberData)) - .collect(Collectors.toList())); + data.addAll(resources.getContent().stream().map(UberData::doExtractLinksAndContent) + .map(uberData -> new UberData().withData(uberData)).collect(Collectors.toList())); return data; } @@ -210,23 +199,13 @@ class UberData { List collectionOfResources = extractLinksAndContent((Resources) resources); - if (resources.getMetadata() != null ) { + if (resources.getMetadata() != null) { - collectionOfResources.add(new UberData() - .withName("page") - .withData(Arrays.asList( - new UberData() - .withName("number") - .withValue(resources.getMetadata().getNumber()), - new UberData() - .withName("size") - .withValue(resources.getMetadata().getSize()), - new UberData() - .withName("totalElements") - .withValue(resources.getMetadata().getTotalElements()), - new UberData() - .withName("totalPages") - .withValue(resources.getMetadata().getTotalPages())))); + collectionOfResources.add(new UberData().withName("page") + .withData(Arrays.asList(new UberData().withName("number").withValue(resources.getMetadata().getNumber()), + new UberData().withName("size").withValue(resources.getMetadata().getSize()), + new UberData().withName("totalElements").withValue(resources.getMetadata().getTotalElements()), + new UberData().withName("totalPages").withValue(resources.getMetadata().getTotalPages())))); } return collectionOfResources; @@ -238,13 +217,13 @@ class UberData { * @param links * @return */ - private static List extractLinks(List links) { + private static List extractLinks(Links links) { - return urlRelMap(links).entrySet().stream() - .map(entry -> new UberData() - .withUrl(entry.getKey()) - .withRel(entry.getValue().getRels())) - .collect(Collectors.toList()); + return urlRelMap(links).entrySet().stream() // + .map(entry -> new UberData() // + .withUrl(entry.getKey()) // + .withRel(entry.getValue().getRels())) // + .collect(Collectors.toList()); } /** @@ -277,14 +256,11 @@ class UberData { */ private static Optional extractContent(Object content) { - if (!RESOURCE_TYPES.contains(content.getClass())) { - - return Optional.of(new UberData() - .withName(StringUtils.uncapitalize(content.getClass().getSimpleName())) - .withData(extractProperties(content))); - } - - return Optional.empty(); + return Optional.of(content) // + .filter(it -> !RESOURCE_TYPES.contains(content.getClass())) // + .map(it -> new UberData() // + .withName(StringUtils.uncapitalize(it.getClass().getSimpleName())) // + .withData(extractProperties(it))); } /** @@ -304,13 +280,12 @@ class UberData { } /** - * Turn a {@list List} of {@link Link}s into a {@link Map}, where you can see ALL the rels of a given - * link. + * Turn a {@list List} of {@link Link}s into a {@link Map}, where you can see ALL the rels of a given link. * * @param links * @return a map with links mapping onto a {@link List} of rels */ - private static Map urlRelMap(List links) { + private static Map urlRelMap(Links links) { Map urlRelMap = new LinkedHashMap<>(); @@ -329,43 +304,41 @@ class UberData { * @param links * @return */ - private static List extractAffordances(List links) { + private static List extractAffordances(Links links) { - return links.stream() - .flatMap(link -> link.getAffordances().stream()) - .map(affordance -> (UberAffordanceModel) affordance.getAffordanceModel(MediaTypes.UBER_JSON)) - .map(model -> { + return links.stream() // + .flatMap(it -> it.getAffordances().stream()) // + .map(it -> it.getAffordanceModel(MediaTypes.UBER_JSON)) // + .map(UberAffordanceModel.class::cast) // + .map(it -> { - if (model.getHttpMethod().equals(HttpMethod.GET)) { + if (it.hasHttpMethod(HttpMethod.GET)) { - String suffix = model.getQueryProperties().stream() - .map(UberData::getName) - .collect(Collectors.joining(",")); + String suffix = it.getQueryProperties().stream() // + .map(UberData::getName) // + .collect(Collectors.joining(",")); - if (!model.getQueryMethodParameters().isEmpty()) { - suffix = "{?" + suffix + "}"; + if (!it.getQueryMethodParameters().isEmpty()) { + suffix = "{?" + suffix + "}"; + } + + return new UberData() // + .withName(it.getName()) // + .withRel(Collections.singletonList(LinkRelation.of(it.getName()))) + .withUrl(it.getLink().expand().getHref() + suffix) // + .withAction(it.getAction()); + + } else { + + return new UberData() // + .withName(it.getName()) // + .withRel(Collections.singletonList(LinkRelation.of(it.getName()))) + .withUrl(it.getLink().expand().getHref()).withModel(it.getInputProperties().stream() // + .map(UberData::getName).map(property -> property + "={" + property + "}") // + .collect(Collectors.joining("&"))) + .withAction(it.getAction()); } - - return new UberData() - .withName(model.getName()) - .withRel(Collections.singletonList(model.getName())) - .withUrl(model.getLink().expand().getHref() + suffix) - .withAction(model.getAction()); - - } else { - - return new UberData() - .withName(model.getName()) - .withRel(Collections.singletonList(model.getName())) - .withUrl(model.getLink().expand().getHref()) - .withModel(model.getInputProperties().stream() - .map(UberData::getName) - .map(property -> property + "={" + property + "}") - .collect(Collectors.joining("&"))) - .withAction(model.getAction()); - } - }) - .collect(Collectors.toList()); + }).collect(Collectors.toList()); } /** @@ -375,27 +348,26 @@ class UberData { * @param links * @return */ - private static List mergeDeclaredLinksIntoAffordanceLinks(List affordanceBasedLinks, List links) { + private static List mergeDeclaredLinksIntoAffordanceLinks(List affordanceBasedLinks, + List links) { - return affordanceBasedLinks.stream() - .flatMap(affordance -> links.stream() - .filter(link -> link.getUrl().equals(affordance.getUrl())) - .map(link -> { + return affordanceBasedLinks.stream() // + .flatMap(affordance -> links.stream() // + .filter(link -> link.getUrl().equals(affordance.getUrl())) // + .map(link -> { - if (link.getAction() == affordance.getAction()) { + if (link.getAction() == affordance.getAction()) { - List rels = new ArrayList<>(link.getRel()); + List rels = new ArrayList<>(link.getRel()); + rels.addAll(affordance.getRel()); - rels.addAll(affordance.getRel()); - - return affordance - .withName(rels.get(0)) - .withRel(rels); - } else { - return affordance; - } - })) - .collect(Collectors.toList()); + return affordance.withName(rels.get(0).value()) // + .withRel(rels); + } else { + return affordance; + } + })) + .collect(Collectors.toList()); } /** @@ -407,26 +379,20 @@ class UberData { private static List extractProperties(Object obj) { if (PRIMITIVE_TYPES.contains(obj.getClass())) { - return Collections.singletonList(new UberData() - .withValue(obj)); + return Collections.singletonList(new UberData().withValue(obj)); } return PropertyUtils.findProperties(obj).entrySet().stream() - .map(entry -> new UberData() - .withName(entry.getKey()) - .withValue(entry.getValue())) - .collect(Collectors.toList()); + .map(entry -> new UberData().withName(entry.getKey()).withValue(entry.getValue())).collect(Collectors.toList()); } - /** * Holds both a {@link Link} and related {@literal rels}. - * */ @Data private static class LinkAndRels { private Link link; - private List rels = new ArrayList<>(); + private List rels = new ArrayList<>(); } -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/hateoas/uber/UberLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/uber/UberLinkDiscoverer.java index ba44e1ce..6e5e8118 100644 --- a/src/main/java/org/springframework/hateoas/uber/UberLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/uber/UberLinkDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 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. @@ -13,27 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.hateoas.uber; import java.io.IOException; import java.io.InputStream; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; +import org.springframework.hateoas.LinkRelation; +import org.springframework.hateoas.Links; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; /** - * Find links by rel in an {@literal UBER+JSON} representation. - * - * TODO: Pending https://github.com/json-path/JsonPath/issues/429, replace deserializing solution with JsonPath-based expression "$.uber.data[?(@.rel.indexOf('%s') != -1)].url" + * Find links by rel in an {@literal UBER+JSON} representation. TODO: Pending + * https://github.com/json-path/JsonPath/issues/429, replace deserializing solution with JsonPath-based expression + * "$.uber.data[?(@.rel.indexOf('%s') != -1)].url" * * @author Greg Turnquist + * @author Oliver Drotbohm * @since 1.0 */ public class UberLinkDiscoverer implements LinkDiscoverer { @@ -46,40 +47,57 @@ public class UberLinkDiscoverer implements LinkDiscoverer { this.mapper.registerModules(new Jackson2UberModule()); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(org.springframework.hateoas.LinkRelation, java.lang.String) + */ @Override - public Link findLinkWithRel(String rel, String representation) { + public Optional findLinkWithRel(LinkRelation rel, String representation) { - return getLinks(representation).stream() - .filter(link -> link.getRel().equals(rel)) - .findFirst() - .orElse(null); + return getLinks(representation).stream() // + .filter(it -> it.hasRel(rel)) // + .findFirst(); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(org.springframework.hateoas.LinkRelation, java.io.InputStream) + */ @Override - public Link findLinkWithRel(String rel, InputStream representation) { + public Optional findLinkWithRel(LinkRelation rel, InputStream representation) { - return getLinks(representation).stream() - .filter(link -> link.getRel().equals(rel)) - .findFirst() - .orElse(null); + return getLinks(representation).stream() // + .filter(it -> it.hasRel(rel)) // + .findFirst(); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.lang.String) + */ @Override - public List findLinksWithRel(String rel, String representation) { + public Links findLinksWithRel(LinkRelation rel, String representation) { - return getLinks(representation).stream() - .filter(link -> link.getRel().equals(rel)) - .collect(Collectors.toList()); + return getLinks(representation).stream().filter(it -> it.hasRel(rel)) // + .collect(Links.collector()); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.io.InputStream) + */ @Override - public List findLinksWithRel(String rel, InputStream representation) { + public Links findLinksWithRel(LinkRelation rel, InputStream representation) { - return getLinks(representation).stream() - .filter(link -> link.getRel().equals(rel)) - .collect(Collectors.toList()); + return getLinks(representation).stream() // + .filter(it -> it.hasRel(rel)) // + .collect(Links.collector()); } + /* + * (non-Javadoc) + * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) + */ @Override public boolean supports(MediaType delimiter) { return delimiter.isCompatibleWith(MediaTypes.UBER_JSON); @@ -91,7 +109,7 @@ public class UberLinkDiscoverer implements LinkDiscoverer { * @param json * @return */ - private List getLinks(String json) { + private Links getLinks(String json) { try { return this.mapper.readValue(json, UberDocument.class).getUber().getLinks(); @@ -106,7 +124,7 @@ public class UberLinkDiscoverer implements LinkDiscoverer { * @param stream * @return */ - private List getLinks(InputStream stream) { + private Links getLinks(InputStream stream) { try { return this.mapper.readValue(stream, UberDocument.class).getUber().getLinks(); @@ -114,4 +132,4 @@ public class UberLinkDiscoverer implements LinkDiscoverer { throw new RuntimeException(e); } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/springframework/hateoas/mvc/LinkBuilderDsl.kt b/src/main/kotlin/org/springframework/hateoas/mvc/LinkBuilderDsl.kt index 63afd382..02738339 100644 --- a/src/main/kotlin/org/springframework/hateoas/mvc/LinkBuilderDsl.kt +++ b/src/main/kotlin/org/springframework/hateoas/mvc/LinkBuilderDsl.kt @@ -17,8 +17,8 @@ package org.springframework.hateoas.mvc import org.springframework.hateoas.Link +import org.springframework.hateoas.LinkRelation import org.springframework.hateoas.ResourceSupport -import org.springframework.hateoas.mvc.ControllerLinkBuilder import org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo import org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn @@ -28,6 +28,7 @@ import kotlin.reflect.KClass * Create a [ControllerLinkBuilder] pointing to a [func] method. * * @author Roland Kulcsár + * @author Oliver Drotbohm * @since 1.0 */ inline fun linkTo(func: C.() -> Unit): ControllerLinkBuilder = linkTo(methodOn(C::class.java).apply(func)) @@ -38,6 +39,7 @@ inline fun linkTo(func: C.() -> Unit): ControllerLinkBuilder = linkT * @author Roland Kulcsár * @since 1.0 */ +infix fun ControllerLinkBuilder.withRel(rel: LinkRelation): Link = withRel(rel) infix fun ControllerLinkBuilder.withRel(rel: String): Link = withRel(rel) /** @@ -81,6 +83,13 @@ open class LinkBuilderDsl(val controller: Class, val * Add a link with the given [rel] to the [resource]. */ infix fun ControllerLinkBuilder.withRel(rel: String): Link { + return this withRel(LinkRelation.of(rel)) + } + + /** + * Add a link with the given [rel] to the [resource]. + */ + infix fun ControllerLinkBuilder.withRel(rel: LinkRelation): Link { val link = withRel(rel) resource.add(link) diff --git a/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java b/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java index 7e0eb067..3ba1463d 100644 --- a/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java +++ b/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java @@ -31,7 +31,7 @@ public class IanaLinkRelationUnitTest { */ @Test public void extractingValueOfIanaLinkRelationShouldWork() { - assertThat(IanaLinkRelation.ABOUT.value()).isEqualTo("about"); + assertThat(IanaLinkRelations.ABOUT.value()).isEqualTo("about"); } /** @@ -40,13 +40,13 @@ public class IanaLinkRelationUnitTest { @Test public void testingForOfficialIanaLinkRelation() { - assertThat(IanaLinkRelation.isIanaRel((String) null)).isFalse(); - assertThat(IanaLinkRelation.isIanaRel((LinkRelation) null)).isFalse(); - assertThat(IanaLinkRelation.isIanaRel("")).isFalse(); - assertThat(IanaLinkRelation.isIanaRel("foo-bar")).isFalse(); + assertThat(IanaLinkRelations.isIanaRel((String) null)).isFalse(); + assertThat(IanaLinkRelations.isIanaRel((LinkRelation) null)).isFalse(); + assertThat(IanaLinkRelations.isIanaRel("")).isFalse(); + assertThat(IanaLinkRelations.isIanaRel("foo-bar")).isFalse(); - assertThat(IanaLinkRelation.isIanaRel("about")).isTrue(); - assertThat(IanaLinkRelation.isIanaRel("ABOUT")).isTrue(); + assertThat(IanaLinkRelations.isIanaRel("about")).isTrue(); + assertThat(IanaLinkRelations.isIanaRel("ABOUT")).isTrue(); } /** @@ -55,26 +55,26 @@ public class IanaLinkRelationUnitTest { @Test public void parsingIanaLinkRelationsShouldWork() { - assertThat(IanaLinkRelation.parse("about")).isEqualTo(IanaLinkRelation.ABOUT); - assertThat(IanaLinkRelation.parse("ABOUT")).isEqualTo(IanaLinkRelation.ABOUT); + assertThat(IanaLinkRelations.parse("about")).isEqualTo(IanaLinkRelations.ABOUT); + assertThat(IanaLinkRelations.parse("ABOUT")).isEqualTo(IanaLinkRelations.ABOUT); - assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelation.parse(null)); - assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelation.parse("")); - assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelation.parse("faulty")); - assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelation.parse("FAULTY")); + assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelations.parse(null)); + assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelations.parse("")); + assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelations.parse("faulty")); + assertThatIllegalArgumentException().isThrownBy(() -> IanaLinkRelations.parse("FAULTY")); } @Test public void testIanaLinkRelationShouldPass() { - assertThat(IanaLinkRelation.isIanaRel(IanaLinkRelation.ABOUT)).isTrue(); + assertThat(IanaLinkRelations.isIanaRel(IanaLinkRelations.ABOUT)).isTrue(); } @Test public void comparingNonIanaLinkRelationsToIanaLinkRelationsDontGuaranteeAMatch() { - assertThat(IanaLinkRelation.isIanaRel(new CustomLinkRelation("about"))).isTrue(); - assertThat(IanaLinkRelation.isIanaRel(new CustomLinkRelation("ABOUT"))).isTrue(); - assertThat(IanaLinkRelation.isIanaRel(new CustomLinkRelation("something-new"))).isFalse(); + assertThat(IanaLinkRelations.isIanaRel(new CustomLinkRelation("about"))).isTrue(); + assertThat(IanaLinkRelations.isIanaRel(new CustomLinkRelation("ABOUT"))).isTrue(); + assertThat(IanaLinkRelations.isIanaRel(new CustomLinkRelation("something-new"))).isFalse(); } /** diff --git a/src/test/java/org/springframework/hateoas/Jackson2LinkIntegrationTest.java b/src/test/java/org/springframework/hateoas/Jackson2LinkIntegrationTest.java index c2f8c354..888d6bf3 100755 --- a/src/test/java/org/springframework/hateoas/Jackson2LinkIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/Jackson2LinkIntegrationTest.java @@ -21,7 +21,7 @@ import org.junit.Test; /** * Integration tests for {@link org.springframework.hateoas.Link} marshaling. - * + * * @author Oliver Gierke * @author Jon Brisbin */ @@ -44,6 +44,6 @@ public class Jackson2LinkIntegrationTest extends AbstractJackson2MarshallingInte public void readsLinkCorrectly() throws Exception { Link result = read(REFERENCE, Link.class); assertThat(result.getHref()).isEqualTo("location"); - assertThat(result.getRel()).isEqualTo("something"); + assertThat(result.getRel()).isEqualTo(LinkRelation.of("something")); } } diff --git a/src/test/java/org/springframework/hateoas/LinkIntegrationTest.java b/src/test/java/org/springframework/hateoas/LinkIntegrationTest.java index 1d28bf29..59e3f091 100755 --- a/src/test/java/org/springframework/hateoas/LinkIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/LinkIntegrationTest.java @@ -21,7 +21,7 @@ import org.junit.Test; /** * Integration tests for {@link Link} marshaling. - * + * * @author Oliver Gierke */ public class LinkIntegrationTest extends AbstractJackson2MarshallingIntegrationTest { @@ -44,7 +44,7 @@ public class LinkIntegrationTest extends AbstractJackson2MarshallingIntegrationT Link result = read(REFERENCE, Link.class); assertThat(result.getHref()).isEqualTo("location"); - assertThat(result.getRel()).isEqualTo("something"); + assertThat(result.getRel()).isEqualTo(LinkRelation.of("something")); assertThat(result.getAffordances()).hasSize(0); } } diff --git a/src/test/java/org/springframework/hateoas/LinkRelationUnitTest.java b/src/test/java/org/springframework/hateoas/LinkRelationUnitTest.java index 66732db4..13b9b65a 100644 --- a/src/test/java/org/springframework/hateoas/LinkRelationUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinkRelationUnitTest.java @@ -15,6 +15,7 @@ */ package org.springframework.hateoas; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import lombok.Value; @@ -43,11 +44,9 @@ public class LinkRelationUnitTest { assertThat(MyOwnLinkRelation.FOO.value()).isEqualTo("foo"); assertThat(MyOwnLinkRelation.BAR.value()).isEqualTo("bar"); - Set myOwnLinkRelations = Arrays.stream(MyOwnLinkRelation.values()) - .collect(Collectors.toSet()); + Set myOwnLinkRelations = Arrays.stream(MyOwnLinkRelation.values()).collect(Collectors.toSet()); - assertThat(myOwnLinkRelations) - .containsExactlyInAnyOrder(MyOwnLinkRelation.FOO, MyOwnLinkRelation.BAR); + assertThat(myOwnLinkRelations).containsExactlyInAnyOrder(MyOwnLinkRelation.FOO, MyOwnLinkRelation.BAR); } /** @@ -58,9 +57,13 @@ public class LinkRelationUnitTest { private String value; + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkRelation#value() + */ @Override public String value() { - return this.value; + return value; } } @@ -69,8 +72,7 @@ public class LinkRelationUnitTest { */ enum MyOwnLinkRelation implements LinkRelation { - FOO("foo"), - BAR("bar"); + FOO("foo"), BAR("bar"); private final String value; @@ -78,9 +80,13 @@ public class LinkRelationUnitTest { this.value = value; } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkRelation#value() + */ @Override public String value() { - return this.value; + return value; } } diff --git a/src/test/java/org/springframework/hateoas/LinkUnitTest.java b/src/test/java/org/springframework/hateoas/LinkUnitTest.java index 94a2deab..f4402f39 100755 --- a/src/test/java/org/springframework/hateoas/LinkUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinkUnitTest.java @@ -21,44 +21,39 @@ import static org.assertj.core.api.SoftAssertions.*; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.Collections; -import java.util.List; -import java.util.Optional; import org.apache.commons.io.output.ByteArrayOutputStream; import org.junit.Test; -import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.hateoas.support.Employee; import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; /** * Unit tests for {@link Link}. - * + * * @author Oliver Gierke * @author Greg Turnquist * @author Jens Schauder */ public class LinkUnitTest { - private static final Affordance TEST_AFFORDANCE = new Affordance(null, null, HttpMethod.GET, null, Collections.emptyList(), null); + private static final Affordance TEST_AFFORDANCE = new Affordance(null, null, HttpMethod.GET, null, + Collections.emptyList(), null); @Test public void linkWithHrefOnlyBecomesSelfLink() { - - Link link = new Link("foo"); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(new Link("foo").hasRel(IanaLinkRelations.SELF)).isTrue(); } @Test public void createsLinkFromRelAndHref() { - Link link = new Link("foo", IanaLinkRelation.SELF.value()); + Link link = new Link("foo", IanaLinkRelations.SELF); assertSoftly(softly -> { softly.assertThat(link.getHref()).isEqualTo("foo"); - softly.assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + softly.assertThat(link.hasRel(IanaLinkRelations.SELF)).isTrue(); }); } @@ -69,7 +64,7 @@ public class LinkUnitTest { @Test(expected = IllegalArgumentException.class) public void rejectsNullRel() { - new Link("foo", null); + new Link("foo", (String) null); } @Test(expected = IllegalArgumentException.class) @@ -85,8 +80,8 @@ public class LinkUnitTest { @Test public void sameRelAndHrefMakeSameLink() { - Link left = new Link("foo", IanaLinkRelation.SELF.value()); - Link right = new Link("foo", IanaLinkRelation.SELF.value()); + Link left = new Link("foo", IanaLinkRelations.SELF); + Link right = new Link("foo", IanaLinkRelations.SELF); TestUtils.assertEqualAndSameHashCode(left, right); } @@ -94,8 +89,8 @@ public class LinkUnitTest { @Test public void differentRelMakesDifferentLink() { - Link left = new Link("foo", IanaLinkRelation.PREV.value()); - Link right = new Link("foo", IanaLinkRelation.NEXT.value()); + Link left = new Link("foo", IanaLinkRelations.PREV); + Link right = new Link("foo", IanaLinkRelations.NEXT); TestUtils.assertNotEqualAndDifferentHashCode(left, right); } @@ -103,8 +98,8 @@ public class LinkUnitTest { @Test public void differentHrefMakesDifferentLink() { - Link left = new Link("foo", IanaLinkRelation.SELF.value()); - Link right = new Link("bar", IanaLinkRelation.SELF.value()); + Link left = new Link("foo", IanaLinkRelations.SELF); + Link right = new Link("bar", IanaLinkRelations.SELF); TestUtils.assertNotEqualAndDifferentHashCode(left, right); } @@ -162,12 +157,13 @@ public class LinkUnitTest { */ @Test public void ignoresUnrecognizedAttributes() { + Link link = Link.valueOf(";rel=\"foo\";unknown=\"should fail\""); assertSoftly(softly -> { softly.assertThat(link.getHref()).isEqualTo("/something"); - softly.assertThat(link.getRel()).isEqualTo("foo"); + softly.assertThat(link.hasRel("foo")).isTrue(); }); } @@ -247,8 +243,8 @@ public class LinkUnitTest { @Test public void parsesLinkRelationWithDotAndMinus() { - assertThat(Link.valueOf("; rel=\"rel-with-minus-and-.\"").getRel()) - .isEqualTo("rel-with-minus-and-."); + assertThat(Link.valueOf("; rel=\"rel-with-minus-and-.\"").hasRel("rel-with-minus-and-.")) + .isTrue(); } /** @@ -258,7 +254,7 @@ public class LinkUnitTest { public void parsesUriLinkRelations() { assertThat(Link.valueOf("; rel=\"http://acme.com/rels/foo-bar\"").getRel()) // - .isEqualTo("http://acme.com/rels/foo-bar"); + .isEqualTo(LinkRelation.of("http://acme.com/rels/foo-bar")); } /** @@ -305,66 +301,95 @@ public class LinkUnitTest { Link link = new Link("/"); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> link.hasRel(null)); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> link.hasRel((String) null)); assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> link.hasRel("")); } @Test public void affordanceConvenienceMethodChainsExistingLink() { - Link link = new Link("/").andAffordance("name", HttpMethod.POST, ResolvableType.forClass(Employee.class), Collections.emptyList(), ResolvableType.forClass(Employee.class)); + Link link = new Link("/").andAffordance("name", HttpMethod.POST, ResolvableType.forClass(Employee.class), + Collections.emptyList(), ResolvableType.forClass(Employee.class)); assertThat(link.getHref()).isEqualTo("/"); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.hasRel(IanaLinkRelations.SELF)).isTrue(); assertThat(link.getAffordances()).hasSize(1); assertThat(link.getAffordances().get(0).getAffordanceModels()).hasSize(3); - + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getName()).isEqualTo("name"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getName()).isEqualTo("name"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getName()).isEqualTo("name"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); } @Test public void affordanceConvenienceMethodDefaultsNameBasedOnHttpVerb() { - Link link = new Link("/").andAffordance(HttpMethod.POST, ResolvableType.forClass(Employee.class), Collections.emptyList(), ResolvableType.forClass(Employee.class)); + Link link = new Link("/").andAffordance(HttpMethod.POST, ResolvableType.forClass(Employee.class), + Collections.emptyList(), ResolvableType.forClass(Employee.class)); assertThat(link.getHref()).isEqualTo("/"); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.hasRel(IanaLinkRelations.SELF)).isTrue(); assertThat(link.getAffordances()).hasSize(1); assertThat(link.getAffordances().get(0).getAffordanceModels()).hasSize(3); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getName()).isEqualTo("postEmployee"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getName()) + .isEqualTo("postEmployee"); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getName()).isEqualTo("postEmployee"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getName()) + .isEqualTo("postEmployee"); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getName()).isEqualTo("postEmployee"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getName()) + .isEqualTo("postEmployee"); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); } @Test @@ -373,26 +398,41 @@ public class LinkUnitTest { Link link = new Link("/").andAffordance(HttpMethod.POST, Employee.class, Collections.emptyList(), Employee.class); assertThat(link.getHref()).isEqualTo("/"); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.hasRel(IanaLinkRelations.SELF)).isTrue(); assertThat(link.getAffordances()).hasSize(1); assertThat(link.getAffordances().get(0).getAffordanceModels()).hasSize(3); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getName()).isEqualTo("postEmployee"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getName()) + .isEqualTo("postEmployee"); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.COLLECTION_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getName()).isEqualTo("postEmployee"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getName()) + .isEqualTo("postEmployee"); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.HAL_FORMS_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getName()).isEqualTo("postEmployee"); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getHttpMethod()).isEqualTo(HttpMethod.POST); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getInputType().resolve()).isEqualTo(Employee.class); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getQueryMethodParameters()).hasSize(0); - assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getOutputType().resolve()).isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getName()) + .isEqualTo("postEmployee"); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getHttpMethod()) + .isEqualTo(HttpMethod.POST); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getInputType().resolve()) + .isEqualTo(Employee.class); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getQueryMethodParameters()) + .hasSize(0); + assertThat(link.getAffordances().get(0).getAffordanceModel(MediaTypes.UBER_JSON).getOutputType().resolve()) + .isEqualTo(Employee.class); } } diff --git a/src/test/java/org/springframework/hateoas/LinksUnitTest.java b/src/test/java/org/springframework/hateoas/LinksUnitTest.java index 26e926cd..8db3d660 100755 --- a/src/test/java/org/springframework/hateoas/LinksUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinksUnitTest.java @@ -43,30 +43,30 @@ public class LinksUnitTest { static final String LINKS2 = StringUtils.collectionToCommaDelimitedString(Arrays.asList(THIRD, FOURTH)); - static final Links reference = new Links(new Link("/something", "foo"), new Link("/somethingElse", "bar")); - static final Links reference2 = new Links(new Link("/something", "foo").withHreflang("en"), + static final Links reference = Links.of(new Link("/something", "foo"), new Link("/somethingElse", "bar")); + static final Links reference2 = Links.of(new Link("/something", "foo").withHreflang("en"), new Link("/somethingElse", "bar").withHreflang("de")); @Test public void parsesLinkHeaderLinks() { - assertThat(Links.valueOf(LINKS)).isEqualTo(reference); - assertThat(Links.valueOf(LINKS2)).isEqualTo(reference2); + assertThat(Links.parse(LINKS)).isEqualTo(reference); + assertThat(Links.parse(LINKS2)).isEqualTo(reference2); assertThat(reference.toString()).isEqualTo(LINKS); assertThat(reference2.toString()).isEqualTo(LINKS2); } @Test public void skipsEmptyLinkElements() { - assertThat(Links.valueOf(LINKS + ",,,")).isEqualTo(reference); - assertThat(Links.valueOf(LINKS2 + ",,,")).isEqualTo(reference2); + assertThat(Links.parse(LINKS + ",,,")).isEqualTo(reference); + assertThat(Links.parse(LINKS2 + ",,,")).isEqualTo(reference2); } @Test public void returnsNullForNullOrEmptySource() { - assertThat(Links.valueOf(null)).isEqualTo(Links.NO_LINKS); - assertThat(Links.valueOf("")).isEqualTo(Links.NO_LINKS); + assertThat(Links.parse(null)).isEqualTo(Links.NONE); + assertThat(Links.parse("")).isEqualTo(Links.NONE); } /** @@ -87,9 +87,9 @@ public class LinksUnitTest { Link withComma = new Link("http://localhost:8080/test?page=0&filter=foo,bar", "foo"); - assertThat(Links.valueOf(WITH_COMMA).getLink("foo")).isEqualTo(Optional.of(withComma)); + assertThat(Links.parse(WITH_COMMA).getLink("foo")).isEqualTo(Optional.of(withComma)); - Links twoWithCommaInFirst = Links.valueOf(WITH_COMMA.concat(",").concat(SECOND)); + Links twoWithCommaInFirst = Links.parse(WITH_COMMA.concat(",").concat(SECOND)); assertThat(twoWithCommaInFirst.getLink("foo")).hasValue(withComma); assertThat(twoWithCommaInFirst.getLink("bar")).hasValue(new Link("/somethingElse", "bar")); @@ -100,14 +100,14 @@ public class LinksUnitTest { */ @Test public void parsesLinksWithWhitespace() { - assertThat(Links.valueOf(WITH_WHITESPACE)).isEqualTo(reference); + assertThat(Links.parse(WITH_WHITESPACE)).isEqualTo(reference); } @Test // #805 public void returnsRequiredLink() { Link reference = new Link("http://localhost", "someRel"); - Links links = new Links(reference); + Links links = Links.of(reference); assertThat(links.getRequiredLink("someRel")).isEqualTo(reference); } @@ -115,10 +115,24 @@ public class LinksUnitTest { @Test // #805 public void rejectsMissingLinkWithIllegalArgumentException() { - Links links = new Links(); + Links links = Links.of(); assertThatExceptionOfType(IllegalArgumentException.class) // .isThrownBy(() -> links.getRequiredLink("self")) // .withMessageContaining("self"); } + + @Test + public void detectsContainedLinks() { + + Link first = new Link("http://localhost", "someRel"); + Link second = new Link("http://localhost", "someOtherRel"); + + assertThat(Links.of(first).contains(first)).isTrue(); + assertThat(Links.of(first).contains(second)).isFalse(); + + assertThat(Links.of(first).containsSameLinksAs(Links.of(first, second))).isFalse(); + assertThat(Links.of(first, second).containsSameLinksAs(Links.of(first))).isFalse(); + assertThat(Links.of(first, second).containsSameLinksAs(Links.of(first, second))).isTrue(); + } } diff --git a/src/test/java/org/springframework/hateoas/PagedResourcesUnitTest.java b/src/test/java/org/springframework/hateoas/PagedResourcesUnitTest.java index f3ad9786..07e8e3c1 100755 --- a/src/test/java/org/springframework/hateoas/PagedResourcesUnitTest.java +++ b/src/test/java/org/springframework/hateoas/PagedResourcesUnitTest.java @@ -42,7 +42,7 @@ public class PagedResourcesUnitTest { @Test public void discoversNextLink() { - resources.add(new Link("foo", IanaLinkRelation.NEXT.value())); + resources.add(new Link("foo", IanaLinkRelations.NEXT.value())); assertThat(resources.getNextLink()).isNotNull(); } @@ -50,7 +50,7 @@ public class PagedResourcesUnitTest { @Test public void discoversPreviousLink() { - resources.add(new Link("custom", IanaLinkRelation.PREV.value())); + resources.add(new Link("custom", IanaLinkRelations.PREV.value())); assertThat(resources.getPreviousLink()).isNotNull(); } diff --git a/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java b/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java index 35c8d011..d3793453 100755 --- a/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java +++ b/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java @@ -33,15 +33,15 @@ public class ResourceSupportUnitTest { ResourceSupport support = new ResourceSupport(); assertThat(support.hasLinks()).isFalse(); - assertThat(support.hasLink(IanaLinkRelation.SELF.value())).isFalse(); + assertThat(support.hasLink(IanaLinkRelations.SELF.value())).isFalse(); assertThat(support.getLinks().isEmpty()).isTrue(); - assertThat(support.getLinks(IanaLinkRelation.SELF.value()).isEmpty()).isTrue(); + assertThat(support.getLinks(IanaLinkRelations.SELF.value()).isEmpty()).isTrue(); } @Test public void addsLinkCorrectly() { - Link link = new Link("foo", IanaLinkRelation.NEXT.value()); + Link link = new Link("foo", IanaLinkRelations.NEXT.value()); ResourceSupport support = new ResourceSupport(); support.add(link); @@ -49,7 +49,7 @@ public class ResourceSupportUnitTest { assertThat(support.hasLinks()).isTrue(); assertThat(support.hasLink(link.getRel())).isTrue(); assertThat(support.getLink(link.getRel())).hasValue(link); - assertThat(support.getLinks(IanaLinkRelation.NEXT.value())).contains(link); + assertThat(support.getLinks(IanaLinkRelations.NEXT.value())).contains(link); } @Test @@ -69,8 +69,8 @@ public class ResourceSupportUnitTest { @Test public void addsLinksCorrectly() { - Link first = new Link("foo", IanaLinkRelation.PREV.value()); - Link second = new Link("bar", IanaLinkRelation.NEXT.value()); + Link first = new Link("foo", IanaLinkRelations.PREV.value()); + Link second = new Link("bar", IanaLinkRelations.NEXT.value()); ResourceSupport support = new ResourceSupport(); support.add(Arrays.asList(first, second)); @@ -79,8 +79,8 @@ public class ResourceSupportUnitTest { assertThat(support.hasLinks()).isTrue(); assertThat(support.getLinks()).contains(first, second); assertThat(support.getLinks()).hasSize(2); - assertThat(support.getLinks(IanaLinkRelation.PREV.value())).contains(first); - assertThat(support.getLinks(IanaLinkRelation.NEXT.value())).contains(second); + assertThat(support.getLinks(IanaLinkRelations.PREV.value())).contains(first); + assertThat(support.getLinks(IanaLinkRelations.NEXT.value())).contains(second); } @Test diff --git a/src/test/java/org/springframework/hateoas/StringLinkRelationUnitTests.java b/src/test/java/org/springframework/hateoas/StringLinkRelationUnitTests.java new file mode 100644 index 00000000..f6cda4ec --- /dev/null +++ b/src/test/java/org/springframework/hateoas/StringLinkRelationUnitTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 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. + * 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. + */ +package org.springframework.hateoas; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Unit tests for {@link StringLinkRelation}. + * + * @author Oliver Gierke + */ +public class StringLinkRelationUnitTests { + + @Test + public void serializesAsPlainString() throws Exception { + + Sample sample = new Sample(); + sample.relation = StringLinkRelation.of("foo"); + + ObjectMapper mapper = new ObjectMapper(); + + assertThat(mapper.writeValueAsString(sample)).isEqualTo("{\"relation\":\"foo\"}"); + } + + @Test + public void deserializesUsingFactoryMethod() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + Sample result = mapper.readValue("{\"relation\":\"foo\"}", Sample.class); + + assertThat(result.relation).isEqualTo(StringLinkRelation.of("foo")); + } + + @JsonAutoDetect(fieldVisibility = Visibility.ANY) + static class Sample { + StringLinkRelation relation; + } +} diff --git a/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java b/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java index 62746923..4757f07f 100755 --- a/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java +++ b/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java @@ -31,7 +31,7 @@ import org.springframework.hateoas.TemplateVariable.VariableType; /** * Unit tests for {@link UriTemplate}. - * + * * @author Oliver Gierke * @author JamesE Richardson */ @@ -270,26 +270,30 @@ public class UriTemplateUnitTest { UriTemplate template = new UriTemplate("/foo{&bar,foobar*}"); - assertVariables(template, - new TemplateVariable("bar", VariableType.REQUEST_PARAM_CONTINUED), - new TemplateVariable("foobar", VariableType.COMPOSITE_PARAM)); + assertVariables(template, new TemplateVariable("bar", VariableType.REQUEST_PARAM_CONTINUED), + new TemplateVariable("foobar", VariableType.COMPOSITE_PARAM)); } /** * @see #483 */ @Test + @SuppressWarnings("serial") public void expandsCompositeValueAsAssociativeArray() { UriTemplate template = new UriTemplate("/foo{&bar,foobar*}"); - String expandedTemplate = template.expand(new HashMap(){{ - put("bar", "barExpanded"); - put("foobar", new HashMap(){{ - put("city", "Clarksville"); - put("state", "TN"); - }}); - }}).toString(); + String expandedTemplate = template.expand(new HashMap() { + { + put("bar", "barExpanded"); + put("foobar", new HashMap() { + { + put("city", "Clarksville"); + put("state", "TN"); + } + }); + } + }).toString(); assertThat(expandedTemplate).isEqualTo("/foo?bar=barExpanded&city=Clarksville&state=TN"); } @@ -298,14 +302,17 @@ public class UriTemplateUnitTest { * @see #483 */ @Test + @SuppressWarnings("serial") public void expandsCompositeValueAsList() { UriTemplate template = new UriTemplate("/foo{&bar,foobar*}"); - String expandedTemplate = template.expand(new HashMap(){{ - put("bar", "barExpanded"); - put("foobar", Arrays.asList("foo1", "foo2")); - }}).toString(); + String expandedTemplate = template.expand(new HashMap() { + { + put("bar", "barExpanded"); + put("foobar", Arrays.asList("foo1", "foo2")); + } + }).toString(); assertThat(expandedTemplate).isEqualTo("/foo?bar=barExpanded&foobar=foo1&foobar=foo2"); } @@ -314,14 +321,17 @@ public class UriTemplateUnitTest { * @see #483 */ @Test + @SuppressWarnings("serial") public void handlesCompositeValueAsSingleValue() { UriTemplate template = new UriTemplate("/foo{&bar,foobar*}"); - String expandedTemplate = template.expand(new HashMap(){{ - put("bar", "barExpanded"); - put("foobar", "singleValue"); - }}).toString(); + String expandedTemplate = template.expand(new HashMap() { + { + put("bar", "barExpanded"); + put("foobar", "singleValue"); + } + }).toString(); assertThat(expandedTemplate).isEqualTo("/foo?bar=barExpanded&foobar=singleValue"); } diff --git a/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java index a1f86c12..56c1929b 100755 --- a/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java +++ b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java @@ -73,7 +73,9 @@ public class VndErrorsMarshallingTest { */ @Test public void jackson2Marshalling() throws Exception { - assertThat(jackson2Mapper.writeValueAsString(errors)).isEqualToIgnoringWhitespace(json2Reference); + + assertThat(jackson2Mapper.writeValueAsString(errors)) // + .isEqualToIgnoringWhitespace(json2Reference); } /** diff --git a/src/test/java/org/springframework/hateoas/alps/AlpsLinkDiscoverUnitTest.java b/src/test/java/org/springframework/hateoas/alps/AlpsLinkDiscoverUnitTest.java index a43e9935..bc78cdd6 100755 --- a/src/test/java/org/springframework/hateoas/alps/AlpsLinkDiscoverUnitTest.java +++ b/src/test/java/org/springframework/hateoas/alps/AlpsLinkDiscoverUnitTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*; import java.io.IOException; import java.nio.charset.Charset; +import java.util.Optional; import org.junit.Test; import org.springframework.core.io.ClassPathResource; @@ -29,7 +30,7 @@ import org.springframework.util.StreamUtils; /** * Unit tests for {@link AlpsLinkDiscoverer}. - * + * * @author Greg Turnquist * @author Oliver Gierke */ @@ -40,10 +41,11 @@ public class AlpsLinkDiscoverUnitTest extends AbstractLinkDiscovererUnitTest { @Test public void discoversFullyQualifiedRel() { - Link link = getDiscoverer().findLinkWithRel("http://foo.com/bar", getInputString()); + Optional link = getDiscoverer().findLinkWithRel("http://foo.com/bar", getInputString()); - assertThat(link).isNotNull(); - assertThat(link.getHref()).isEqualTo("fullRelHref"); + assertThat(link) // + .map(Link::getHref) // + .hasValue("fullRelHref"); } /** diff --git a/src/test/java/org/springframework/hateoas/client/Server.java b/src/test/java/org/springframework/hateoas/client/Server.java index 76180eb5..0974b297 100644 --- a/src/test/java/org/springframework/hateoas/client/Server.java +++ b/src/test/java/org/springframework/hateoas/client/Server.java @@ -27,6 +27,7 @@ import java.util.UUID; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.Resource; @@ -43,7 +44,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** * Helper class for integration tests. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -172,10 +173,10 @@ public class Server implements Closeable { Object content = resource.getContent(); Class type = content.getClass(); - String collectionRel = relProvider.getCollectionResourceRelFor(type); - String singleRel = relProvider.getItemResourceRelFor(type); + LinkRelation collectionRel = relProvider.getCollectionResourceRelFor(type); + LinkRelation singleRel = relProvider.getItemResourceRelFor(type); - String baseResourceUri = String.format("%s/%s", rootResource(), collectionRel); + String baseResourceUri = String.format("%s/%s", rootResource(), collectionRel.value()); String resourceUri = String.format("%s/%s", baseResourceUri, UUID.randomUUID().toString()); baseResources.add(new Link(baseResourceUri, collectionRel), new Link(resourceUri, singleRel)); @@ -217,7 +218,7 @@ public class Server implements Closeable { } } - /* + /* * (non-Javadoc) * @see java.io.Closeable#close() */ diff --git a/src/test/java/org/springframework/hateoas/client/TraversonTest.java b/src/test/java/org/springframework/hateoas/client/TraversonTest.java index 6841a97e..7935867c 100755 --- a/src/test/java/org/springframework/hateoas/client/TraversonTest.java +++ b/src/test/java/org/springframework/hateoas/client/TraversonTest.java @@ -28,8 +28,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.springframework.core.ParameterizedTypeReference; import org.springframework.hateoas.Link; @@ -50,34 +51,39 @@ import org.springframework.web.client.RestTemplate; /** * Integration tests for {@link Traverson}. - * + * * @author Oliver Gierke * @author Greg Turnquist * @since 0.11 */ public class TraversonTest { - URI baseUri; + static URI baseUri; + static Server server; - Server server; - Traverson traverson; - @Before - public void setUp() { + @BeforeClass + public static void setUpClass() { - this.server = new Server(); - this.baseUri = URI.create(this.server.rootResource()); - this.traverson = new Traverson(this.baseUri, MediaTypes.HAL_JSON_UTF8, MediaTypes.HAL_JSON); + server = new Server(); + baseUri = URI.create(server.rootResource()); setUpActors(); } - @After - public void tearDown() throws IOException { + @Before + public void setUp() { - if (this.server != null) { - this.server.close(); + this.traverson = new Traverson(baseUri, MediaTypes.HAL_JSON_UTF8, MediaTypes.HAL_JSON); + + } + + @AfterClass + public static void tearDown() throws IOException { + + if (server != null) { + server.close(); } } @@ -94,7 +100,7 @@ public class TraversonTest { */ @Test(expected = IllegalArgumentException.class) public void rejectsEmptyMediaTypes() { - new Traverson(this.baseUri); + new Traverson(baseUri); } /** @@ -107,8 +113,7 @@ public class TraversonTest { verifyThatRequest() // .havingPathEqualTo("/") // - .havingHeader("Accept", contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // - .receivedOnce(); + .havingHeader("Accept", contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)); // } /** @@ -193,7 +198,7 @@ public class TraversonTest { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Arrays.asList(interceptor)); - this.traverson = new Traverson(this.baseUri, MediaTypes.HAL_JSON); + this.traverson = new Traverson(baseUri, MediaTypes.HAL_JSON); this.traverson.setRestOperations(restTemplate); traverson.follow("movies", "movie", "actor"). toObject("$.name"); @@ -206,7 +211,7 @@ public class TraversonTest { @Test public void usesCustomLinkDiscoverer() { - this.traverson = new Traverson(URI.create(this.server.rootResource() + "/github"), MediaType.APPLICATION_JSON); + this.traverson = new Traverson(URI.create(server.rootResource() + "/github"), MediaType.APPLICATION_JSON); this.traverson.setLinkDiscoverers(Arrays.asList(new GitHubLinkDiscoverer())); String value = this.traverson.follow("foo").toObject("$.key"); @@ -222,7 +227,7 @@ public class TraversonTest { Link result = traverson.follow("movies").asLink(); assertThat(result.getHref()).endsWith("/movies"); - assertThat(result.getRel()).isEqualTo("movies"); + assertThat(result.hasRel("movies")).isTrue(); } /** @@ -231,7 +236,7 @@ public class TraversonTest { @Test public void returnsTemplatedLinkIfRequested() { - TraversalBuilder follow = new Traverson(URI.create(this.server.rootResource().concat("/link")), MediaTypes.HAL_JSON) + TraversalBuilder follow = new Traverson(URI.create(server.rootResource().concat("/link")), MediaTypes.HAL_JSON) .follow("self"); Link link = follow.asTemplatedLink(); @@ -269,8 +274,7 @@ public class TraversonTest { @Test public void returnsDefaultMessageConverters() { - List> converters = Traverson - .getDefaultMessageConverters(Collections.emptyList()); + List> converters = Traverson.getDefaultMessageConverters(Collections.emptyList()); assertThat(converters).hasSize(1); assertThat(converters.get(0)).isInstanceOf(StringHttpMessageConverter.class); @@ -310,7 +314,7 @@ public class TraversonTest { .isEqualTo(server.rootResource() + "/springagram/items/1"); final Item item = itemResource.getContent(); - assertThat(item.image).isEqualTo(this.server.rootResource() + "/springagram/file/cat"); + assertThat(item.image).isEqualTo(server.rootResource() + "/springagram/file/cat"); assertThat(item.description).isEqualTo("cat"); } @@ -320,7 +324,7 @@ public class TraversonTest { @Test public void allowAlteringTheDetailsOfASingleHopByMapOperations() { - this.traverson = new Traverson(URI.create(this.server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); + this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); // tag::hop-put[] ParameterizedTypeReference> resourceParameterizedTypeReference = new ParameterizedTypeReference>() {}; @@ -335,10 +339,10 @@ public class TraversonTest { assertThat(itemResource.hasLink("self")).isTrue(); assertThat(itemResource.getRequiredLink("self").expand().getHref()) - .isEqualTo(this.server.rootResource() + "/springagram/items/1"); + .isEqualTo(server.rootResource() + "/springagram/items/1"); final Item item = itemResource.getContent(); - assertThat(item.image).isEqualTo(this.server.rootResource() + "/springagram/file/cat"); + assertThat(item.image).isEqualTo(server.rootResource() + "/springagram/file/cat"); assertThat(item.description).isEqualTo("cat"); } @@ -348,7 +352,7 @@ public class TraversonTest { @Test public void allowGlobalsToImpactSingleHops() { - this.traverson = new Traverson(URI.create(this.server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); + this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); Map params = new HashMap<>(); params.put("projection", "thisShouldGetOverwrittenByLocalHop"); @@ -360,10 +364,10 @@ public class TraversonTest { assertThat(itemResource.hasLink("self")).isTrue(); assertThat(itemResource.getRequiredLink("self").expand().getHref()) - .isEqualTo(this.server.rootResource() + "/springagram/items/1"); + .isEqualTo(server.rootResource() + "/springagram/items/1"); final Item item = itemResource.getContent(); - assertThat(item.image).isEqualTo(this.server.rootResource() + "/springagram/file/cat"); + assertThat(item.image).isEqualTo(server.rootResource() + "/springagram/file/cat"); assertThat(item.description).isEqualTo("cat"); } @@ -373,7 +377,7 @@ public class TraversonTest { @Test public void doesNotDoubleEncodeURI() { - this.traverson = new Traverson(URI.create(this.server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); + this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); Resource itemResource = traverson.// follow(rel("items").withParameters(Collections.singletonMap("projection", "no images"))).// @@ -381,7 +385,7 @@ public class TraversonTest { assertThat(itemResource.hasLink("self")).isTrue(); assertThat(itemResource.getRequiredLink("self").expand().getHref()) - .isEqualTo(this.server.rootResource() + "/springagram/items"); + .isEqualTo(server.rootResource() + "/springagram/items"); } @Test @@ -390,50 +394,40 @@ public class TraversonTest { String customHeaderName = "X-CustomHeader"; traverson - .follow(rel("movies") - .header(customHeaderName, "alpha") - .header(HttpHeaders.LOCATION, "http://localhost:8080/my/custom/location")) - .follow(rel("movie").header(customHeaderName, "bravo")) - .follow(rel("actor").header(customHeaderName, "charlie")) - .toObject("$.name"); + .follow(rel("movies").header(customHeaderName, "alpha").header(HttpHeaders.LOCATION, + "http://localhost:8080/my/custom/location")) + .follow(rel("movie").header(customHeaderName, "bravo")).follow(rel("actor").header(customHeaderName, "charlie")) + .toObject("$.name"); verifyThatRequest() // - .havingPathEqualTo("/") // - .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // - .receivedOnce(); + .havingPathEqualTo("/") // + .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)); // - verifyThatRequest() - .havingPathEqualTo("/movies") // aggregate root movies - .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // - .havingHeader(customHeaderName, contains("alpha")) // - .havingHeader(HttpHeaders.LOCATION, contains("http://localhost:8080/my/custom/location")) // - .receivedOnce(); + verifyThatRequest().havingPathEqualTo("/movies") // aggregate root movies + .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // + .havingHeader(customHeaderName, contains("alpha")) // + .havingHeader(HttpHeaders.LOCATION, contains("http://localhost:8080/my/custom/location")); // - verifyThatRequest() - .havingPath(startsWith("/movies/")) // single movie - .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // - .havingHeader(customHeaderName, contains("bravo")) // - .receivedOnce(); + verifyThatRequest().havingPath(startsWith("/movies/")) // single movie + .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // + .havingHeader(customHeaderName, contains("bravo")); // - verifyThatRequest() - .havingPath(startsWith("/actors/")) // single actor - .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // - .havingHeader(customHeaderName, contains("charlie")) // - .receivedOnce(); + verifyThatRequest().havingPath(startsWith("/actors/")) // single actor + .havingHeader(HttpHeaders.ACCEPT, contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) // + .havingHeader(customHeaderName, contains("charlie")); // } - - private void setUpActors() { + private static void setUpActors() { Resource actor = new Resource<>(new Actor("Keanu Reaves")); - String actorUri = this.server.mockResourceFor(actor); + String actorUri = server.mockResourceFor(actor); Movie movie = new Movie("The Matrix"); Resource resource = new Resource<>(movie); resource.add(new Link(actorUri, "actor")); - this.server.mockResourceFor(resource); - this.server.finishMocking(); + server.mockResourceFor(resource); + server.finishMocking(); } static class CountingInterceptor implements ClientHttpRequestInterceptor { diff --git a/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscovererUnitTest.java index 12825c02..e1e8c600 100644 --- a/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscovererUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2018-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. @@ -18,7 +18,7 @@ package org.springframework.hateoas.collectionjson; import static org.assertj.core.api.Assertions.*; import java.io.IOException; -import java.util.List; +import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -31,6 +31,7 @@ import org.springframework.hateoas.support.MappingUtils; * Unit tests for {@link CollectionJsonLinkDiscoverer}. * * @author Greg Turnquist + * @author Oliver Drotbohm */ public class CollectionJsonLinkDiscovererUnitTest { @@ -46,10 +47,11 @@ public class CollectionJsonLinkDiscovererUnitTest { String specBasedJson = MappingUtils.read(new ClassPathResource("spec-part1.json", getClass())); - Link link = this.discoverer.findLinkWithRel("self", specBasedJson); + Optional link = this.discoverer.findLinkWithRel("self", specBasedJson); - assertThat(link).isNotNull(); - assertThat(link.getHref()).isEqualTo("http://example.org/friends/"); + assertThat(link) // + .map(Link::getHref) // + .hasValue("http://example.org/friends/"); } @Test @@ -57,32 +59,22 @@ public class CollectionJsonLinkDiscovererUnitTest { String specBasedJson = MappingUtils.read(new ClassPathResource("spec-part2.json", getClass())); - Link selfLink = this.discoverer.findLinkWithRel("self", specBasedJson); + assertThat(this.discoverer.findLinkWithRel("self", specBasedJson)) // + .map(Link::getHref) // + .hasValue("http://example.org/friends/"); - assertThat(selfLink).isNotNull(); - assertThat(selfLink.getHref()).isEqualTo("http://example.org/friends/"); + assertThat(this.discoverer.findLinkWithRel("feed", specBasedJson)) // + .map(Link::getHref) // + .hasValue("http://example.org/friends/rss"); - Link feedLink = this.discoverer.findLinkWithRel("feed", specBasedJson); + assertThat(this.discoverer.findLinksWithRel("blog", specBasedJson)) // + .extracting("href") // + .containsExactlyInAnyOrder("http://examples.org/blogs/jdoe", "http://examples.org/blogs/msmith", + "http://examples.org/blogs/rwilliams"); - assertThat(feedLink).isNotNull(); - assertThat(feedLink.getHref()).isEqualTo("http://example.org/friends/rss"); - - List links = this.discoverer.findLinksWithRel("blog", specBasedJson); - - assertThat(links) - .extracting("href") - .containsExactlyInAnyOrder( - "http://examples.org/blogs/jdoe", - "http://examples.org/blogs/msmith", - "http://examples.org/blogs/rwilliams"); - - links = this.discoverer.findLinksWithRel("avatar", specBasedJson); - - assertThat(links) - .extracting("href") - .containsExactlyInAnyOrder( - "http://examples.org/images/jdoe", - "http://examples.org/images/msmith", - "http://examples.org/images/rwilliams"); + assertThat(this.discoverer.findLinksWithRel("avatar", specBasedJson)) // + .extracting("href") // + .containsExactlyInAnyOrder("http://examples.org/images/jdoe", "http://examples.org/images/msmith", + "http://examples.org/images/rwilliams"); } } diff --git a/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonSpecTest.java b/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonSpecTest.java index 700c5d88..3a8b22f8 100644 --- a/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonSpecTest.java +++ b/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonSpecTest.java @@ -26,7 +26,7 @@ import java.util.List; import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; @@ -37,11 +37,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; /** - * Unit tests leveraging spec fragments of JSON. + * Unit tests leveraging spec fragments of JSON. NOTE: Fields that don't map into Java property names (e.g. + * {@literal full-name}) are altered in the JSON to work properly. Alternative is to have some sort of injectable + * converter. * - * NOTE: Fields that don't map into Java property names (e.g. {@literal full-name}) are altered in the JSON to work properly. - * Alternative is to have some sort of injectable converter. - * * @author Greg Turnquist */ public class CollectionJsonSpecTest { @@ -53,7 +52,6 @@ public class CollectionJsonSpecTest { mapper = new ObjectMapper(); mapper.registerModule(new Jackson2CollectionJsonModule()); - mapper.setHandlerInstantiator(new Jackson2CollectionJsonModule.CollectionJsonHandlerInstantiator(null)); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); } @@ -69,7 +67,7 @@ public class CollectionJsonSpecTest { ResourceSupport resource = mapper.readValue(specBasedJson, ResourceSupport.class); assertThat(resource.getLinks()).hasSize(1); - assertThat(resource.getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/")); + assertThat(resource.getRequiredLink(IanaLinkRelations.SELF)).isEqualTo(new Link("http://example.org/friends/")); } /** @@ -78,15 +76,15 @@ public class CollectionJsonSpecTest { */ @Test public void specPart2() throws IOException { - + String specBasedJson = MappingUtils.read(new ClassPathResource("spec-part2.json", getClass())); Resources> resources = mapper.readValue(specBasedJson, - mapper.getTypeFactory().constructParametricType(Resources.class, - mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); + mapper.getTypeFactory().constructParametricType(Resources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); assertThat(resources.getLinks()).hasSize(2); - assertThat(resources.getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/")); + assertThat(resources.getRequiredLink(IanaLinkRelations.SELF)).isEqualTo(new Link("http://example.org/friends/")); assertThat(resources.getRequiredLink("feed")).isEqualTo(new Link("http://example.org/friends/rss", "feed")); assertThat(resources.getContent()).hasSize(3); @@ -94,21 +92,28 @@ public class CollectionJsonSpecTest { assertThat(friends.get(0).getContent().getEmail()).isEqualTo("jdoe@example.org"); assertThat(friends.get(0).getContent().getFullname()).isEqualTo("J. Doe"); - assertThat(friends.get(0).getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/jdoe")); + assertThat(friends.get(0).getRequiredLink(IanaLinkRelations.SELF)) + .isEqualTo(new Link("http://example.org/friends/jdoe")); assertThat(friends.get(0).getRequiredLink("blog")).isEqualTo(new Link("http://examples.org/blogs/jdoe", "blog")); - assertThat(friends.get(0).getRequiredLink("avatar")).isEqualTo(new Link("http://examples.org/images/jdoe", "avatar")); + assertThat(friends.get(0).getRequiredLink("avatar")) + .isEqualTo(new Link("http://examples.org/images/jdoe", "avatar")); assertThat(friends.get(1).getContent().getEmail()).isEqualTo("msmith@example.org"); assertThat(friends.get(1).getContent().getFullname()).isEqualTo("M. Smith"); - assertThat(friends.get(1).getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/msmith")); + assertThat(friends.get(1).getRequiredLink(IanaLinkRelations.SELF.value())) + .isEqualTo(new Link("http://example.org/friends/msmith")); assertThat(friends.get(1).getRequiredLink("blog")).isEqualTo(new Link("http://examples.org/blogs/msmith", "blog")); - assertThat(friends.get(1).getRequiredLink("avatar")).isEqualTo(new Link("http://examples.org/images/msmith", "avatar")); + assertThat(friends.get(1).getRequiredLink("avatar")) + .isEqualTo(new Link("http://examples.org/images/msmith", "avatar")); assertThat(friends.get(2).getContent().getEmail()).isEqualTo("rwilliams@example.org"); assertThat(friends.get(2).getContent().getFullname()).isEqualTo("R. Williams"); - assertThat(friends.get(2).getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/rwilliams")); - assertThat(friends.get(2).getRequiredLink("blog")).isEqualTo(new Link("http://examples.org/blogs/rwilliams", "blog")); - assertThat(friends.get(2).getRequiredLink("avatar")).isEqualTo(new Link("http://examples.org/images/rwilliams", "avatar")); + assertThat(friends.get(2).getRequiredLink(IanaLinkRelations.SELF.value())) + .isEqualTo(new Link("http://example.org/friends/rwilliams")); + assertThat(friends.get(2).getRequiredLink("blog")) + .isEqualTo(new Link("http://examples.org/blogs/rwilliams", "blog")); + assertThat(friends.get(2).getRequiredLink("avatar")) + .isEqualTo(new Link("http://examples.org/images/rwilliams", "avatar")); } /** @@ -124,10 +129,12 @@ public class CollectionJsonSpecTest { mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class)); assertThat(resource.getLinks()).hasSize(6); - assertThat(resource.getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/jdoe")); + assertThat(resource.getRequiredLink(IanaLinkRelations.SELF)).isEqualTo(new Link("http://example.org/friends/jdoe")); assertThat(resource.getRequiredLink("feed")).isEqualTo(new Link("http://example.org/friends/rss", "feed")); - assertThat(resource.getRequiredLink("queries")).isEqualTo(new Link("http://example.org/friends/?queries", "queries")); - assertThat(resource.getRequiredLink("template")).isEqualTo(new Link("http://example.org/friends/?template", "template")); + assertThat(resource.getRequiredLink("queries")) + .isEqualTo(new Link("http://example.org/friends/?queries", "queries")); + assertThat(resource.getRequiredLink("template")) + .isEqualTo(new Link("http://example.org/friends/?template", "template")); assertThat(resource.getRequiredLink("blog")).isEqualTo(new Link("http://examples.org/blogs/jdoe", "blog")); assertThat(resource.getRequiredLink("avatar")).isEqualTo(new Link("http://examples.org/images/jdoe", "avatar")); @@ -145,12 +152,14 @@ public class CollectionJsonSpecTest { String specBasedJson = MappingUtils.read(new ClassPathResource("spec-part4.json", getClass())); Resources> resources = mapper.readValue(specBasedJson, - mapper.getTypeFactory().constructParametricType(Resources.class, - mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); + mapper.getTypeFactory().constructParametricType(Resources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); assertThat(resources.getContent()).hasSize(0); - assertThat(resources.getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/")); + assertThat(resources.getRequiredLink(IanaLinkRelations.SELF.value())) + .isEqualTo(new Link("http://example.org/friends/")); } + /** * @see http://amundsen.com/media-types/collection/examples/ - Section 5. Template Representation * @throws IOException @@ -161,12 +170,14 @@ public class CollectionJsonSpecTest { String specBasedJson = MappingUtils.read(new ClassPathResource("spec-part5.json", getClass())); Resources> resources = mapper.readValue(specBasedJson, - mapper.getTypeFactory().constructParametricType(Resources.class, - mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); - + mapper.getTypeFactory().constructParametricType(Resources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); + assertThat(resources.getContent()).hasSize(0); - assertThat(resources.getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/")); + assertThat(resources.getRequiredLink(IanaLinkRelations.SELF.value())) + .isEqualTo(new Link("http://example.org/friends/")); } + /** * @see http://amundsen.com/media-types/collection/examples/ - Section 6. Error Representation * @throws IOException @@ -177,12 +188,14 @@ public class CollectionJsonSpecTest { String specBasedJson = MappingUtils.read(new ClassPathResource("spec-part6.json", getClass())); Resources> resources = mapper.readValue(specBasedJson, - mapper.getTypeFactory().constructParametricType(Resources.class, - mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); + mapper.getTypeFactory().constructParametricType(Resources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, Friend.class))); assertThat(resources.getContent()).hasSize(0); - assertThat(resources.getRequiredLink(IanaLinkRelation.SELF.value())).isEqualTo(new Link("http://example.org/friends/")); + assertThat(resources.getRequiredLink(IanaLinkRelations.SELF.value())) + .isEqualTo(new Link("http://example.org/friends/")); } + /** * @see http://amundsen.com/media-types/collection/examples/ - Section 7. Write Representation * @throws IOException diff --git a/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonWebMvcIntegrationTest.java b/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonWebMvcIntegrationTest.java index f03236e0..6f14e232 100644 --- a/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonWebMvcIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/collectionjson/CollectionJsonWebMvcIntegrationTest.java @@ -37,7 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; @@ -166,7 +166,8 @@ public class CollectionJsonWebMvcIntegrationTest { .andExpect(status().isCreated()) // .andExpect(header().stringValues(HttpHeaders.LOCATION, "http://localhost/employees/2")); - this.mockMvc.perform(get("/employees/2").accept(MediaTypes.COLLECTION_JSON)).andExpect(status().isOk()) // + this.mockMvc.perform(get("/employees/2").accept(MediaTypes.COLLECTION_JSON)) // + .andExpect(status().isOk()) // .andExpect(jsonPath("$.collection.version", is("1.0"))) .andExpect(jsonPath("$.collection.href", is("http://localhost/employees/2"))) @@ -270,12 +271,10 @@ public class CollectionJsonWebMvcIntegrationTest { EMPLOYEES.put(newEmployeeId, employee.getContent()); try { - return ResponseEntity - .created( - new URI(findOne(newEmployeeId) // - .getLink(IanaLinkRelation.SELF.value()) // - .map(link -> link.expand().getHref()) // - .orElse(""))) // + return ResponseEntity.created(new URI(findOne(newEmployeeId) // + .getLink(IanaLinkRelations.SELF.value()) // + .map(link -> link.expand().getHref()) // + .orElse(""))) // .build(); } catch (URISyntaxException e) { return ResponseEntity.badRequest().body(e.getMessage()); @@ -290,7 +289,7 @@ public class CollectionJsonWebMvcIntegrationTest { try { return ResponseEntity.noContent() // .location(new URI(findOne(id) // - .getLink(IanaLinkRelation.SELF.value()) // + .getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse(""))) // .build(); @@ -319,7 +318,7 @@ public class CollectionJsonWebMvcIntegrationTest { try { return ResponseEntity.noContent() // .location(new URI(findOne(id) // - .getLink(IanaLinkRelation.SELF.value()) // + .getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse(""))) // .build(); diff --git a/src/test/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonIntegrationTest.java b/src/test/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonIntegrationTest.java index 4d2a4415..76196a21 100644 --- a/src/test/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/collectionjson/Jackson2CollectionJsonIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * 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. @@ -29,7 +29,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; @@ -45,19 +45,19 @@ import com.fasterxml.jackson.databind.SerializationFeature; * Integration test for Jackson 2 JSON+Collection * * @author Greg Turnquist + * @author Oliver Drotbohm */ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2MarshallingIntegrationTest { - static final Links PAGINATION_LINKS = new Links( - new Link("localhost", IanaLinkRelation.SELF.value()), - new Link("foo", IanaLinkRelation.NEXT.value()), - new Link("bar", IanaLinkRelation.PREV.value())); + static final Links PAGINATION_LINKS = Links.of( // + new Link("localhost", IanaLinkRelations.SELF), // + new Link("foo", IanaLinkRelations.NEXT), // + new Link("bar", IanaLinkRelations.PREV)); @Before public void setUpModule() { mapper.registerModule(new Jackson2CollectionJsonModule()); - mapper.setHandlerInstantiator(new Jackson2CollectionJsonModule.CollectionJsonHandlerInstantiator(null)); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); } @@ -67,7 +67,8 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh ResourceSupport resourceSupport = new ResourceSupport(); resourceSupport.add(new Link("localhost").withSelfRel()); - assertThat(write(resourceSupport)).isEqualTo(MappingUtils.read(new ClassPathResource("resource-support.json", getClass()))); + assertThat(write(resourceSupport)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resource-support.json", getClass()))); } @Test @@ -76,8 +77,9 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh ResourceSupport expected = new ResourceSupport(); expected.add(new Link("localhost")); - assertThat(read(MappingUtils.read(new ClassPathResource("resource-support.json", getClass())), ResourceSupport.class)) - .isEqualTo(expected); + assertThat( + read(MappingUtils.read(new ClassPathResource("resource-support.json", getClass())), ResourceSupport.class)) + .isEqualTo(expected); } @Test @@ -87,7 +89,8 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh resourceSupport.add(new Link("localhost")); resourceSupport.add(new Link("localhost2").withRel("orders")); - assertThat(write(resourceSupport)).isEqualTo(MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass()))); + assertThat(write(resourceSupport)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass()))); } @Test @@ -96,7 +99,8 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh ResourceWithAttributes resource = new ResourceWithAttributes("test value"); resource.add(new Link("localhost").withSelfRel()); - assertThat(write(resource)).isEqualTo(MappingUtils.read(new ClassPathResource("resource-support-3.json", getClass()))); + assertThat(write(resource)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resource-support-3.json", getClass()))); } @Test @@ -105,8 +109,8 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh ResourceWithAttributes expected = new ResourceWithAttributes("test value"); expected.add(new Link("localhost").withSelfRel()); - assertThat(read(MappingUtils.read(new ClassPathResource("resource-support-3.json", getClass())), ResourceWithAttributes.class)) - .isEqualTo(expected); + assertThat(read(MappingUtils.read(new ClassPathResource("resource-support-3.json", getClass())), + ResourceWithAttributes.class)).isEqualTo(expected); } @Test @@ -116,8 +120,10 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh expected.add(new Link("localhost")); expected.add(new Link("localhost2").withRel("orders")); - assertThat(read(MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass())), ResourceSupport.class)) - .isEqualTo(expected); + String read = MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass())); + ResourceSupport readResourceSupport = read(read, ResourceSupport.class); + + assertThat(readResourceSupport.getLinks()).containsAll(expected.getLinks()); } @Test @@ -149,7 +155,6 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh assertThat(result).isEqualTo(expected); } - @Test public void renderResource() throws Exception { @@ -161,11 +166,12 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh @Test public void deserializeResource() throws Exception { - Resource expected = new Resource<>("first", new Link("localhost")); + Resource expected = new Resource<>("first", new Link("localhost")); String source = MappingUtils.read(new ClassPathResource("resource.json", getClass())); - Resource actual = mapper.readValue(source, mapper.getTypeFactory().constructParametricType(Resource.class, String.class)); - + Resource actual = mapper.readValue(source, + mapper.getTypeFactory().constructParametricType(Resource.class, String.class)); + assertThat(actual).isEqualTo(expected); } @@ -180,7 +186,8 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh resources.add(new Link("localhost")); resources.add(new Link("/page/2").withRel("next")); - assertThat(write(resources)).isEqualTo(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass()))); + assertThat(write(resources)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass()))); } @Test @@ -190,11 +197,12 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh data.add(new Resource<>("first", new Link("localhost"), new Link("orders").withRel("orders"))); data.add(new Resource<>("second", new Link("remotehost"), new Link("order").withRel("orders"))); - Resources expected = new Resources<>(data); + Resources expected = new Resources<>(data); expected.add(new Link("localhost")); expected.add(new Link("/page/2").withRel("next")); - Resources> actual = mapper.readValue(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass())), + Resources> actual = mapper.readValue( + MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass())), mapper.getTypeFactory().constructParametricType(Resources.class, mapper.getTypeFactory().constructParametricType(Resource.class, String.class))); @@ -213,7 +221,8 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh resources.add(new Link("localhost")); resources.add(new Link("/page/2").withRel("next")); - assertThat(write(resources)).isEqualTo(MappingUtils.read(new ClassPathResource("resources-simple-pojos.json", getClass()))); + assertThat(write(resources)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resources-simple-pojos.json", getClass()))); } @Test @@ -226,7 +235,8 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh @Test public void deserializesPagedResource() throws Exception { - PagedResources> result = mapper.readValue(MappingUtils.read(new ClassPathResource("paged-resources.json", getClass())), + PagedResources> result = mapper.readValue( + MappingUtils.read(new ClassPathResource("paged-resources.json", getClass())), mapper.getTypeFactory().constructParametricType(PagedResources.class, mapper.getTypeFactory().constructParametricType(Resource.class, SimplePojo.class))); @@ -247,7 +257,7 @@ public class Jackson2CollectionJsonIntegrationTest extends AbstractJackson2Marsh @NoArgsConstructor @AllArgsConstructor public static class ResourceWithAttributes extends ResourceSupport { - + private String attribute; } diff --git a/src/test/java/org/springframework/hateoas/collectionjson/JacksonSerializationTest.java b/src/test/java/org/springframework/hateoas/collectionjson/JacksonSerializationTest.java index d3eba47b..12e24b5f 100644 --- a/src/test/java/org/springframework/hateoas/collectionjson/JacksonSerializationTest.java +++ b/src/test/java/org/springframework/hateoas/collectionjson/JacksonSerializationTest.java @@ -19,15 +19,14 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import java.io.IOException; -import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.support.MappingUtils; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -42,28 +41,26 @@ public class JacksonSerializationTest { public void setUp() { mapper = new ObjectMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.registerModule(new Jackson2CollectionJsonModule()); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); } @Test public void createSimpleCollection() throws IOException { - CollectionJson collection = new CollectionJson<>() - .withVersion("1.0") - .withHref("localhost") - .withLinks(Arrays.asList(new Link("foo").withSelfRel())) - .withItems(Arrays.asList( - new CollectionJsonItem<>() - .withHref("localhost") - .withRawData("Greetings programs") - .withLinks(Arrays.asList(new Link("localhost").withSelfRel())), - new CollectionJsonItem<>() - .withHref("localhost") - .withRawData("Yo") - .withLinks(Arrays.asList(new Link("localhost/orders").withRel("orders"))))); + CollectionJson collection = new CollectionJson<>().withVersion("1.0").withHref("localhost") + .withLinks(Links.of(new Link("foo").withSelfRel())) // + .withItems(new CollectionJsonItem<>() // + .withHref("localhost") // + .withRawData("Greetings programs") // + .withLinks(new Link("localhost").withSelfRel()), // + new CollectionJsonItem<>() // + .withHref("localhost") // + .withRawData("Yo") // + .withLinks(new Link("localhost/orders").withRel("orders"))); String actual = mapper.writeValueAsString(collection); + assertThat(actual, is(MappingUtils.read(new ClassPathResource("reference.json", getClass())))); } } diff --git a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java index 61788894..45b23735 100755 --- a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java @@ -69,7 +69,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** * Integration tests for {@link EnableHypermediaSupport}. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -106,8 +106,10 @@ public class EnableHypermediaSupportIntegrationTest { LinkDiscoverers discoverers = context.getBean(LinkDiscoverers.class); assertThat(discoverers).isNotNull(); - assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON)).isInstanceOf(HalLinkDiscoverer.class); - assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON_UTF8)).isInstanceOf(HalLinkDiscoverer.class); + assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON)) + .hasValueSatisfying(HalLinkDiscoverer.class::isInstance); + assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON_UTF8)) + .hasValueSatisfying(HalLinkDiscoverer.class::isInstance); assertRelProvidersSetUp(context); }); } @@ -121,7 +123,7 @@ public class EnableHypermediaSupportIntegrationTest { assertThat(discoverers).isNotNull(); assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_FORMS_JSON)) - .isInstanceOf(HalFormsLinkDiscoverer.class); + .hasValueSatisfying(HalFormsLinkDiscoverer.class::isInstance); assertRelProvidersSetUp(context); }); } @@ -135,7 +137,7 @@ public class EnableHypermediaSupportIntegrationTest { assertThat(discoverers).isNotNull(); assertThat(discoverers.getLinkDiscovererFor(MediaTypes.COLLECTION_JSON)) - .isInstanceOf(CollectionJsonLinkDiscoverer.class); + .hasValueSatisfying(CollectionJsonLinkDiscoverer.class::isInstance); assertRelProvidersSetUp(context); }); } @@ -148,7 +150,8 @@ public class EnableHypermediaSupportIntegrationTest { LinkDiscoverers discoverers = context.getBean(LinkDiscoverers.class); assertThat(discoverers).isNotNull(); - assertThat(discoverers.getLinkDiscovererFor(MediaTypes.UBER_JSON)).isInstanceOf(UberLinkDiscoverer.class); + assertThat(discoverers.getLinkDiscovererFor(MediaTypes.UBER_JSON)) + .hasValueSatisfying(UberLinkDiscoverer.class::isInstance); assertRelProvidersSetUp(context); }); } @@ -298,8 +301,8 @@ public class EnableHypermediaSupportIntegrationTest { assertThat(converters.get(0)).isInstanceOf(TypeConstrainedMappingJackson2HttpMessageConverter.class); assertThat(converters.get(0).getSupportedMediaTypes()) // - .hasSize(1) // - .contains(MediaTypes.UBER_JSON); + .hasSize(1) // + .contains(MediaTypes.UBER_JSON); } } @@ -393,8 +396,8 @@ public class EnableHypermediaSupportIntegrationTest { RestTemplate template = context.getBean(RestTemplate.class); assertThat(template.getMessageConverters().get(0).getSupportedMediaTypes()) // - .hasSize(1) // - .contains(MediaTypes.UBER_JSON); + .hasSize(1) // + .contains(MediaTypes.UBER_JSON); }); } @@ -480,7 +483,7 @@ public class EnableHypermediaSupportIntegrationTest { assertThatCode(() -> { assertThat(it.writeValueAsString(resourceSupport)) // - .isEqualTo("{\"_links\":{\"self\":{\"href\":\"localhost\"}}}"); + .isEqualTo("{\"_links\":{\"self\":{\"href\":\"localhost\"}}}"); }).doesNotThrowAnyException(); }); }); @@ -581,7 +584,7 @@ public class EnableHypermediaSupportIntegrationTest { /** * Method to mitigate API changes between Spring 3.2 and 4.0. - * + * * @param adapter * @return */ diff --git a/src/test/java/org/springframework/hateoas/core/AbstractLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/core/AbstractLinkDiscovererUnitTest.java index 4fa3cb43..17ef6b0c 100755 --- a/src/test/java/org/springframework/hateoas/core/AbstractLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/AbstractLinkDiscovererUnitTest.java @@ -19,15 +19,15 @@ import static org.assertj.core.api.Assertions.*; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.util.List; import org.junit.Test; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; +import org.springframework.hateoas.Links; /** * Base class for unit tests for {@link LinkDiscoverer} implementations. - * + * * @author Oliver Gierke */ public abstract class AbstractLinkDiscovererUnitTest { @@ -35,9 +35,11 @@ public abstract class AbstractLinkDiscovererUnitTest { @Test public void findsSingleLink() { - assertThat(getDiscoverer().findLinkWithRel("self", getInputString())).isEqualTo(new Link("selfHref")); + assertThat(getDiscoverer().findLinkWithRel("self", getInputString())) // + .hasValue(new Link("selfHref")); + + Links links = getDiscoverer().findLinksWithRel("self", getInputString()); - List links = getDiscoverer().findLinksWithRel("self", getInputString()); assertThat(links).hasSize(1); assertThat(links).contains(new Link("selfHref")); } @@ -46,46 +48,47 @@ public abstract class AbstractLinkDiscovererUnitTest { public void findsFirstLink() { assertThat(getDiscoverer().findLinkWithRel("relation", getInputString())) - .isEqualTo(new Link("firstHref", "relation")); + .hasValue(new Link("firstHref", "relation")); } @Test public void findsAllLinks() { - List links = getDiscoverer().findLinksWithRel("relation", getInputString()); + Links links = getDiscoverer().findLinksWithRel("relation", getInputString()); + assertThat(links).hasSize(2); assertThat(links).contains(new Link("firstHref", "relation"), new Link("secondHref", "relation")); } @Test public void returnsForInexistingLink() { - assertThat(getDiscoverer().findLinkWithRel("something", getInputString())).isNull(); + assertThat(getDiscoverer().findLinkWithRel("something", getInputString())).isEmpty(); } @Test public void returnsForInexistingLinkFromInputStream() throws Exception { InputStream inputStream = new ByteArrayInputStream(getInputString().getBytes("UTF-8")); - assertThat(getDiscoverer().findLinkWithRel("something", inputStream)).isNull(); + assertThat(getDiscoverer().findLinkWithRel("something", inputStream)).isEmpty(); } @Test public void returnsNullForNonExistingLinkContainer() { assertThat(getDiscoverer().findLinksWithRel("something", getInputStringWithoutLinkContainer())).isEmpty(); - assertThat(getDiscoverer().findLinkWithRel("something", getInputStringWithoutLinkContainer())).isNull(); + assertThat(getDiscoverer().findLinkWithRel("something", getInputStringWithoutLinkContainer())).isEmpty(); } /** * Return the {@link LinkDiscoverer} to be tested. - * + * * @return */ protected abstract LinkDiscoverer getDiscoverer(); /** * Return the JSON structure we expect to find the links in. - * + * * @return */ protected abstract String getInputString(); diff --git a/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java b/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java index 8db546df..0a74427f 100755 --- a/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java @@ -18,6 +18,7 @@ package org.springframework.hateoas.core; import static org.assertj.core.api.Assertions.*; import org.junit.Test; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; import org.springframework.plugin.core.OrderAwarePluginRegistry; import org.springframework.plugin.core.PluginRegistry; @@ -38,12 +39,12 @@ public class DelegatingRelProviderUnitTest { RelProvider delegatingProvider = new DelegatingRelProvider(registry); assertThat(delegatingProvider.supports(Sample.class)).isTrue(); - assertThat(delegatingProvider.getItemResourceRelFor(Sample.class)).isEqualTo("foo"); - assertThat(delegatingProvider.getCollectionResourceRelFor(Sample.class)).isEqualTo("bar"); + 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.getItemResourceRelFor(String.class)).isEqualTo("string"); - assertThat(delegatingProvider.getCollectionResourceRelFor(String.class)).isEqualTo("stringList"); + assertThat(delegatingProvider.getItemResourceRelFor(String.class)).isEqualTo(LinkRelation.of("string")); + assertThat(delegatingProvider.getCollectionResourceRelFor(String.class)).isEqualTo(LinkRelation.of("stringList")); } @Relation(value = "foo", collectionRelation = "bar") diff --git a/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTest.java b/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTest.java index 6d6b55cd..bbe99b0f 100755 --- a/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTest.java @@ -21,10 +21,11 @@ import java.util.Collection; import java.util.Collections; import org.junit.Test; +import org.springframework.hateoas.LinkRelation; /** * Unit tests for {@link EmbeddedWrappers}. - * + * * @author Oliver Gierke */ public class EmbeddedWrappersUnitTest { @@ -35,13 +36,12 @@ public class EmbeddedWrappersUnitTest { * @see #286 */ @Test - @SuppressWarnings("rawtypes") public void createsWrapperForEmptyCollection() { EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(String.class); assertEmptyCollectionValue(wrapper); - assertThat(wrapper.getRel()).isNull(); + assertThat(wrapper.getRel()).isEmpty(); assertThat(wrapper.getRelTargetType()).isEqualTo(String.class); } @@ -51,10 +51,10 @@ public class EmbeddedWrappersUnitTest { @Test public void createsWrapperForEmptyCollectionAndExplicitRel() { - EmbeddedWrapper wrapper = wrappers.wrap(Collections.emptySet(), "rel"); + EmbeddedWrapper wrapper = wrappers.wrap(Collections.emptySet(), LinkRelation.of("rel")); assertEmptyCollectionValue(wrapper); - assertThat(wrapper.getRel()).isEqualTo("rel"); + assertThat(wrapper.getRel()).hasValue(LinkRelation.of("rel")); assertThat(wrapper.getRelTargetType()).isNull(); } @@ -68,6 +68,8 @@ public class EmbeddedWrappersUnitTest { @SuppressWarnings("unchecked") private static void assertEmptyCollectionValue(EmbeddedWrapper wrapper) { - assertThat(wrapper.getValue()).isInstanceOfSatisfying(Collection.class, it -> assertThat(it).isEmpty()); + + assertThat(wrapper.getValue()) // + .isInstanceOfSatisfying(Collection.class, it -> assertThat(it).isEmpty()); } } diff --git a/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java b/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java index 96bb055d..8073077c 100755 --- a/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java @@ -18,11 +18,12 @@ package org.springframework.hateoas.core; import static org.assertj.core.api.Assertions.*; import org.junit.Test; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; /** * Unit tests for {@link EvoInflectorRelProvider}. - * + * * @author Oliver Gierke */ public class EvoInflectorRelProviderUnitTest { @@ -36,8 +37,9 @@ public class EvoInflectorRelProviderUnitTest { } private void assertRels(Class type, String singleRel, String collectionRel) { - assertThat(provider.getItemResourceRelFor(type)).isEqualTo(singleRel); - assertThat(provider.getCollectionResourceRelFor(type)).isEqualTo(collectionRel); + + assertThat(provider.getItemResourceRelFor(type)).isEqualTo(LinkRelation.of(singleRel)); + assertThat(provider.getCollectionResourceRelFor(type)).isEqualTo(LinkRelation.of(collectionRel)); } static class Person { diff --git a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java index 5d915507..bf7173af 100755 --- a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java @@ -22,7 +22,9 @@ import java.util.HashMap; import java.util.Map; import org.junit.Test; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.hal.DefaultCurieProvider.Curie; @@ -32,7 +34,7 @@ import org.springframework.web.context.request.ServletRequestAttributes; /** * Unit tests for {@link DefaultCurieProvider}. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -69,17 +71,23 @@ public class DefaultCurieProviderUnitTest { @Test public void doesNotPrefixIanaRels() { - assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com"))).isEqualTo("self"); + + assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com"))) // + .isEqualTo(HalLinkRelation.of(IanaLinkRelations.SELF)); } @Test public void prefixesNormalRels() { - assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "book"))).isEqualTo("acme:book"); + + assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "book"))) // + .isEqualTo(HalLinkRelation.curied("acme", "book")); } @Test public void doesNotPrefixQualifiedRels() { - assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "custom:rel"))).isEqualTo("custom:rel"); + + assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "custom:rel"))) + .isEqualTo(HalLinkRelation.curied("custom", "rel")); } /** @@ -95,7 +103,8 @@ public class DefaultCurieProviderUnitTest { .withType("the type") // .withDeprecation("http://example.com/custom/deprecated"); - assertThat(provider.getNamespacedRelFrom(link)).isEqualTo("custom:rel"); + assertThat(provider.getNamespacedRelFrom(link)) // + .isEqualTo(HalLinkRelation.curied("custom", "rel")); } /** @@ -103,7 +112,9 @@ public class DefaultCurieProviderUnitTest { */ @Test public void doesNotPrefixIanaRelsForRelAsString() { - assertThat(provider.getNamespacedRelFor("self")).isEqualTo("self"); + + assertThat(provider.getNamespacedRelFor(IanaLinkRelations.SELF)) // + .isEqualTo(HalLinkRelation.uncuried("self")); } /** @@ -111,7 +122,9 @@ public class DefaultCurieProviderUnitTest { */ @Test public void prefixesNormalRelsForRelAsString() { - assertThat(provider.getNamespacedRelFor("book")).isEqualTo("acme:book"); + + assertThat(provider.getNamespacedRelFor(LinkRelation.of("book"))) // + .isEqualTo(HalLinkRelation.curied("acme", "book")); } /** @@ -119,7 +132,9 @@ public class DefaultCurieProviderUnitTest { */ @Test public void doesNotPrefixQualifiedRelsForRelAsString() { - assertThat(provider.getNamespacedRelFor("custom:rel")).isEqualTo("custom:rel"); + + assertThat(provider.getNamespacedRelFor(HalLinkRelation.curied("custom", "rel"))) + .isEqualTo(HalLinkRelation.curied("custom", "rel")); } /** @@ -130,8 +145,8 @@ public class DefaultCurieProviderUnitTest { DefaultCurieProvider provider = new DefaultCurieProvider(getCuries()); - assertThat(provider.getCurieInformation(new Links())).hasSize(2); - assertThat(provider.getNamespacedRelFor("some")).isEqualTo("some"); + assertThat(provider.getCurieInformation(Links.of())).hasSize(2); + assertThat(provider.getNamespacedRelFor(LinkRelation.of("some"))).isEqualTo(HalLinkRelation.uncuried("some")); } /** @@ -142,8 +157,9 @@ public class DefaultCurieProviderUnitTest { DefaultCurieProvider provider = new DefaultCurieProvider(getCuries(), "foo"); - assertThat(provider.getCurieInformation(new Links())).hasSize(2); - assertThat(provider.getNamespacedRelFor("some")).isEqualTo("foo:some"); + assertThat(provider.getCurieInformation(Links.of())).hasSize(2); + assertThat(provider.getNamespacedRelFor(LinkRelation.of("some"))) // + .isEqualTo(HalLinkRelation.curied("foo", "some")); } /** @@ -158,7 +174,7 @@ public class DefaultCurieProviderUnitTest { ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); RequestContextHolder.setRequestAttributes(requestAttributes); - Links links = new Links(new Link("http://localhost", "name:foo")); + Links links = Links.of(new Link("http://localhost", "name:foo")); Collection curies = provider.getCurieInformation(links); assertThat(curies).hasSize(1); diff --git a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java index b0fcfd92..682c4424 100755 --- a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 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. @@ -17,12 +17,14 @@ package org.springframework.hateoas.hal; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import static org.springframework.hateoas.hal.HalLinkRelation.*; import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.core.EmbeddedWrapper; @@ -49,20 +51,20 @@ public class HalEmbeddedBuilderUnitTest { @Test public void rendersSingleElementsWithSingleEntityRel() { - Map map = setUpBuilder(null, "foo", 1L); + Map map = setUpBuilder(null, "foo", 1L); - assertThat(map.get("string")).isEqualTo("foo"); - assertThat(map.get("long")).isEqualTo(1L); + assertThat(map.get(uncuried("string"))).isEqualTo("foo"); + assertThat(map.get(uncuried("long"))).isEqualTo(1L); } @Test public void rendersMultipleElementsWithCollectionResourceRel() { - Map map = setUpBuilder(null, "foo", "bar", 1L); + Map map = setUpBuilder(null, "foo", "bar", 1L); - assertThat(map.containsKey("string")).isFalse(); - assertThat(map.get("long")).isEqualTo(1L); - assertHasValues(map, "strings", "foo", "bar"); + assertThat(map.containsKey(uncuried("string"))).isFalse(); + assertThat(map.get(uncuried("long"))).isEqualTo(1L); + assertHasValues(map, uncuried("strings"), "foo", "bar"); } /** @@ -71,11 +73,11 @@ public class HalEmbeddedBuilderUnitTest { @Test public void correctlyPilesUpResourcesInCollectionRel() { - Map map = setUpBuilder(null, "foo", "bar", "foobar", 1L); + Map map = setUpBuilder(null, "foo", "bar", "foobar", 1L); - assertThat(map.containsKey("string")).isFalse(); - assertHasValues(map, "strings", "foo", "bar", "foobar"); - assertThat(map.get("long")).isEqualTo(1L); + assertThat(map.containsKey(uncuried("string"))).isFalse(); + assertHasValues(map, uncuried("strings"), "foo", "bar", "foobar"); + assertThat(map.get(uncuried("long"))).isEqualTo(1L); } /** @@ -87,8 +89,8 @@ public class HalEmbeddedBuilderUnitTest { HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, null, true); builder.add("Sample"); - assertThat(builder.asMap().get("string")).isNull(); - assertHasValues(builder.asMap(), "strings", "Sample"); + assertThat(builder.asMap().get(uncuried("string"))).isNull(); + assertHasValues(builder.asMap(), uncuried("strings"), "Sample"); } /** @@ -100,9 +102,9 @@ public class HalEmbeddedBuilderUnitTest { EmbeddedWrappers wrappers = new EmbeddedWrappers(false); HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, null, true); - builder.add(wrappers.wrap("MyValue", "foo")); + builder.add(wrappers.wrap("MyValue", LinkRelation.of("foo"))); - assertThat(builder.asMap().get("foo")).isInstanceOf(String.class); + assertThat(builder.asMap().get(uncuried("foo"))).isInstanceOf(String.class); } /** @@ -119,10 +121,10 @@ public class HalEmbeddedBuilderUnitTest { @Test public void rendersSingleElementsWithSingleEntityRelWithCurieProvider() { - Map map = setUpBuilder(curieProvider, "foo", 1L); + Map map = setUpBuilder(curieProvider, "foo", 1L); - assertThat(map.get("curie:string")).isEqualTo("foo"); - assertThat(map.get("curie:long")).isEqualTo(1L); + assertThat(map.get(curied("curie", "string"))).isEqualTo("foo"); + assertThat(map.get(curied("curie", "long"))).isEqualTo(1L); } /** @@ -131,11 +133,11 @@ public class HalEmbeddedBuilderUnitTest { @Test public void rendersMultipleElementsWithCollectionResourceRelWithCurieProvider() { - Map map = setUpBuilder(curieProvider, "foo", "bar", 1L); + Map map = setUpBuilder(curieProvider, "foo", "bar", 1L); - assertThat(map.containsKey("curie:string")).isFalse(); - assertThat(map.get("curie:long")).isEqualTo(1L); - assertHasValues(map, "curie:strings", "foo", "bar"); + assertThat(map.containsKey(curied("curie", "string"))).isFalse(); + assertThat(map.get(curied("curie", "long"))).isEqualTo(1L); + assertHasValues(map, curied("curie", "strings"), "foo", "bar"); } /** @@ -144,11 +146,11 @@ public class HalEmbeddedBuilderUnitTest { @Test public void correctlyPilesUpResourcesInCollectionRelWithCurieprovider() { - Map map = setUpBuilder(curieProvider, "foo", "bar", "foobar", 1L); + Map map = setUpBuilder(curieProvider, "foo", "bar", "foobar", 1L); - assertThat(map.containsKey("curie:string")).isFalse(); - assertHasValues(map, "curie:strings", "foo", "bar", "foobar"); - assertThat(map.get("curie:long")).isEqualTo(1L); + assertThat(map.containsKey(curied("curie", "string"))).isFalse(); + assertHasValues(map, curied("curie", "strings"), "foo", "bar", "foobar"); + assertThat(map.get(curied("curie", "long"))).isEqualTo(1L); } /** @@ -160,8 +162,8 @@ public class HalEmbeddedBuilderUnitTest { HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, curieProvider, true); builder.add("Sample"); - assertThat(builder.asMap().get("curie:string")).isNull(); - assertHasValues(builder.asMap(), "curie:strings", "Sample"); + assertThat(builder.asMap().get(curied("curie", "string"))).isNull(); + assertHasValues(builder.asMap(), curied("curie", "strings"), "Sample"); } /** @@ -173,7 +175,7 @@ public class HalEmbeddedBuilderUnitTest { } @SuppressWarnings("unchecked") - private static void assertHasValues(Map source, String rel, Object... values) { + private static void assertHasValues(Map source, HalLinkRelation rel, Object... values) { Object value = source.get(rel); @@ -183,7 +185,7 @@ public class HalEmbeddedBuilderUnitTest { }); } - private Map setUpBuilder(CurieProvider curieProvider, Object... values) { + private Map setUpBuilder(CurieProvider curieProvider, Object... values) { HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, curieProvider, false); diff --git a/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java index 106424f1..924da7a5 100755 --- a/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java @@ -16,22 +16,21 @@ package org.springframework.hateoas.hal; import static org.assertj.core.api.Assertions.*; -import static org.springframework.hateoas.support.MappingUtils.read; +import static org.springframework.hateoas.support.MappingUtils.*; import java.io.IOException; import org.junit.Test; import org.springframework.core.io.ClassPathResource; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.core.AbstractLinkDiscovererUnitTest; -import org.springframework.hateoas.support.MappingUtils; /** * Unit tests for {@link HalLinkDiscoverer}. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -45,10 +44,9 @@ public class HalLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest { @Test public void discoversFullyQualifiedRel() { - Link link = getDiscoverer().findLinkWithRel("http://foo.com/bar", getInputString()); - - assertThat(link).isNotNull(); - assertThat(link.getHref()).isEqualTo("fullRelHref"); + assertThat(getDiscoverer().findLinkWithRel("http://foo.com/bar", getInputString())) // + .map(Link::getHref) // + .hasValue("fullRelHref"); } /** @@ -59,19 +57,18 @@ public class HalLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest { String linkText = read(new ClassPathResource("hal-link.json", getClass())); - Link actual = getDiscoverer().findLinkWithRel(IanaLinkRelation.SELF.value(), linkText); - Link expected = Link.valueOf(";" // - + "rel=\"self\";" // - + "hreflang=\"en\";" // - + "media=\"pdf\";" // - + "title=\"pdf customer copy\";" // - + "type=\"portable document\";" // - + "deprecation=\"http://example.com/customers/deprecated\";" // - + "profile=\"my-profile\"" // - + "name=\"my-name\""); - - assertThat(actual).isEqualTo(expected); + + "rel=\"self\";" // + + "hreflang=\"en\";" // + + "media=\"pdf\";" // + + "title=\"pdf customer copy\";" // + + "type=\"portable document\";" // + + "deprecation=\"http://example.com/customers/deprecated\";" // + + "profile=\"my-profile\"" // + + "name=\"my-name\""); + + assertThat(getDiscoverer().findLinkWithRel(IanaLinkRelations.SELF.value(), linkText)) // + .hasValue(expected); } /** diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java index d21a2cb2..8642c59a 100755 --- a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java @@ -32,7 +32,7 @@ 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.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; @@ -70,7 +70,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final String ANNOTATED_PAGED_RESOURCES = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"next\":{\"href\":\"foo\"},\"prev\":{\"href\":\"bar\"}},\"page\":{\"size\":2,\"totalElements\":4,\"totalPages\":2,\"number\":0}}"; - static final Links PAGINATION_LINKS = new Links(new Link("foo", IanaLinkRelation.NEXT.value()), new Link("bar", IanaLinkRelation.PREV.value())); + static final Links PAGINATION_LINKS = Links.of(new Link("foo", IanaLinkRelations.NEXT.value()), new Link("bar", IanaLinkRelations.PREV.value())); static final String CURIED_DOCUMENT = "{\"_links\":{\"self\":{\"href\":\"foo\"},\"foo:myrel\":{\"href\":\"bar\"},\"curies\":[{\"href\":\"http://localhost:8080/rels/{rel}\",\"name\":\"foo\",\"templated\":true}]}}"; static final String MULTIPLE_CURIES_DOCUMENT = "{\"_links\":{\"default:myrel\":{\"href\":\"foo\"},\"curies\":[{\"href\":\"bar\",\"name\":\"foo\"},{\"href\":\"foo\",\"name\":\"bar\"}]}}"; diff --git a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java index 788fce77..6e70ed5f 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java @@ -15,22 +15,21 @@ */ package org.springframework.hateoas.hal.forms; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.hateoas.support.MappingUtils.read; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.hateoas.support.MappingUtils.*; import java.io.IOException; import org.junit.Test; - import org.springframework.core.io.ClassPathResource; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; import org.springframework.hateoas.core.AbstractLinkDiscovererUnitTest; /** * Unit tests for {@link HalFormsLinkDiscoverer}. - * + * * @author Greg Turnquist */ public class HalFormsLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest { @@ -53,19 +52,18 @@ public class HalFormsLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTe String linkText = read(new ClassPathResource("hal-forms-link.json", getClass())); - Link actual = getDiscoverer().findLinkWithRel(IanaLinkRelation.SELF.value(), linkText); - Link expected = Link.valueOf(";" // - + "rel=\"self\";" // - + "hreflang=\"en\";" // - + "media=\"pdf\";" // - + "title=\"pdf customer copy\";" // - + "type=\"portable document\";" // - + "deprecation=\"http://example.com/customers/deprecated\";" // - + "profile=\"my-profile\"" // - + "name=\"my-name\""); + + "rel=\"self\";" // + + "hreflang=\"en\";" // + + "media=\"pdf\";" // + + "title=\"pdf customer copy\";" // + + "type=\"portable document\";" // + + "deprecation=\"http://example.com/customers/deprecated\";" // + + "profile=\"my-profile\"" // + + "name=\"my-name\""); - assertThat(actual).isEqualTo(expected); + assertThat(getDiscoverer().findLinkWithRel(IanaLinkRelations.SELF, linkText)) // + .hasValue(expected); } @Override diff --git a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java index 6ce2ed52..48eb74fe 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java @@ -15,10 +15,11 @@ */ package org.springframework.hateoas.hal.forms; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.hasItems; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -83,9 +84,8 @@ public class HalFormsMessageConverterUnitTest { HalFormsDocument halFormsDocument = (HalFormsDocument) convertedMessage; - assertThat(halFormsDocument.getLinks().size(), is(2)); - assertThat(halFormsDocument.getLinks().get(0).getHref(), is("/employees")); - assertThat(halFormsDocument.getLinks().get(1).getHref(), is("/employees/1")); + assertThat(halFormsDocument.getLinks()).hasSize(2); + assertThat(halFormsDocument.getLinks()).extracting(Link::getHref).containsExactly("/employees", "/employees/1"); assertThat(halFormsDocument.getTemplates().size(), is(1)); assertThat(halFormsDocument.getTemplates().keySet(), hasItems("default")); diff --git a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java index 6fa9fddb..fc0665a2 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java @@ -35,7 +35,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; @@ -156,7 +156,7 @@ public class HalFormsValidationIntegrationTest { EMPLOYEES.put(newEmployeeId, employee); try { - return ResponseEntity.noContent().location(new URI(findOne(newEmployeeId).getLink(IanaLinkRelation.SELF.value()) + return ResponseEntity.noContent().location(new URI(findOne(newEmployeeId).getLink(IanaLinkRelations.SELF.value()) .map(link -> link.expand().getHref()).orElse(""))).build(); } catch (URISyntaxException e) { return ResponseEntity.badRequest().body(e.getMessage()); @@ -172,7 +172,7 @@ public class HalFormsValidationIntegrationTest { return ResponseEntity // .noContent() // .location( // - new URI(findOne(id).getLink(IanaLinkRelation.SELF.value()) // + new URI(findOne(id).getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse("")) // ).build(); @@ -201,7 +201,7 @@ public class HalFormsValidationIntegrationTest { .noContent() // .location( // new URI(findOne(id) // - .getLink(IanaLinkRelation.SELF.value()) // + .getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse(""))) // .build(); diff --git a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsWebMvcIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsWebMvcIntegrationTest.java index e51dba8d..75b72d92 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsWebMvcIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsWebMvcIntegrationTest.java @@ -36,7 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; @@ -233,7 +233,7 @@ public class HalFormsWebMvcIntegrationTest { private URI toUri(Integer id) throws URISyntaxException { - String uri = findOne(id).getLink(IanaLinkRelation.SELF.value()) // + String uri = findOne(id).getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse(""); diff --git a/src/test/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsIntegrationTest.java index 1ff9a7d2..7384211b 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsIntegrationTest.java @@ -33,7 +33,7 @@ import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.StaticMessageSource; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; @@ -59,9 +59,9 @@ import com.fasterxml.jackson.databind.SerializationFeature; */ public class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegrationTest { - static final Links PAGINATION_LINKS = new Links( // - new Link("foo", IanaLinkRelation.NEXT.value()), // - new Link("bar", IanaLinkRelation.PREV.value()) // + static final Links PAGINATION_LINKS = Links.of( // + new Link("foo", IanaLinkRelations.NEXT), // + new Link("bar", IanaLinkRelations.PREV) // ); @Before diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java index b5a5b688..9f7da7e6 100755 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java @@ -29,7 +29,7 @@ import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.TestUtils; import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.ControllerWithMethods; @@ -45,7 +45,7 @@ import org.springframework.web.util.UriComponentsBuilder; /** * Unit tests for {@link ControllerLinkBuilderFactory}. - * + * * @author Ricardo Gladwell * @author Oliver Gierke * @author Kamill Sokol @@ -61,7 +61,7 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { Link link = factory.linkTo(PersonControllerImpl.class).withSelfRel(); assertPointsToMockServer(link); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people"); } @@ -71,7 +71,7 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { Link link = factory.linkTo(PersonsAddressesController.class, 15).withSelfRel(); assertPointsToMockServer(link); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people/15/addresses"); } @@ -109,7 +109,7 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { public void linksToMethodWithPathVariableContainingBlank() { Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithPathVariable("with blank")).withSelfRel(); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/something/with%20blank/foo"); } @@ -122,7 +122,7 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { Link link = factory.linkTo(PersonsAddressesController.class, "with blank").withSelfRel(); assertPointsToMockServer(link); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people/with%20blank/addresses"); } @@ -139,7 +139,7 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { Link link = factory.linkTo(methodOn(SampleController.class).sampleMethodWithMap(queryParams)).withSelfRel(); assertPointsToMockServer(link); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/sample/mapsupport?firstKey=firstValue&secondKey=secondValue"); } @@ -156,7 +156,7 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { Link link = factory.linkTo(methodOn(SampleController.class).sampleMethodWithMap(queryParams)).withSelfRel(); assertPointsToMockServer(link); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()) // .endsWith("/sample/multivaluemapsupport?key1=value1a&key1=value1b&key2=value2a&key2=value2b"); } @@ -170,7 +170,7 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { Link link = factory.linkTo(PersonsAddressesController.class, Collections.singletonMap("id", "17")).withSelfRel(); assertPointsToMockServer(link); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people/17/addresses"); } diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java index 083b437e..77858835 100755 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java @@ -20,19 +20,16 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; -import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Optional; -import javax.servlet.ServletException; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mockito; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; import org.springframework.hateoas.TemplateVariable; @@ -49,7 +46,7 @@ import org.springframework.web.util.UriComponentsBuilder; /** * Unit tests for {@link ControllerLinkBuilder}. - * + * * @author Oliver Gierke * @author Dietrich Schulten * @author Kamill Sokol @@ -67,7 +64,7 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { public void createsLinkToControllerRoot() { Link link = linkTo(PersonControllerImpl.class).withSelfRel(); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people"); } @@ -75,7 +72,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { public void createsLinkToParameterizedControllerRoot() { Link link = linkTo(PersonsAddressesController.class, 15).withSelfRel(); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people/15/addresses"); } @@ -86,7 +84,7 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { public void createsLinkToMethodOnParameterizedControllerRoot() { Link link = linkTo(methodOn(PersonsAddressesController.class, 15).getAddressesForCountry("DE")).withSelfRel(); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people/15/addresses/DE"); } @@ -94,15 +92,16 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { public void createsLinkToSubResource() { Link link = linkTo(PersonControllerImpl.class).slash("something").withSelfRel(); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/people/something"); } @Test public void createsLinkWithCustomRel() { - Link link = linkTo(PersonControllerImpl.class).withRel(IanaLinkRelation.NEXT.value()); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.NEXT.value()); + Link link = linkTo(PersonControllerImpl.class).withRel(IanaLinkRelations.NEXT); + + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.NEXT); assertThat(link.getHref()).endsWith("/people"); } @@ -400,7 +399,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { public void linksToMethodWithPathVariableContainingBlank() { Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithPathVariable("with blank")).withSelfRel(); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/something/with%20blank/foo"); } @@ -410,7 +410,10 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { @Test public void usesRootMappingOfTargetClassForMethodsOfParentClass() { - Link link = linkTo(methodOn(ChildControllerWithRootMapping.class).someEmptyMappedMethod()).withSelfRel(); + Link link = linkTo(methodOn(ChildControllerWithRootMapping.class) // + .someEmptyMappedMethod()) // + .withSelfRel(); + assertThat(link.getHref()).endsWith("/root"); } @@ -496,7 +499,7 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithRequestParam("Spring#\n")).withSelfRel(); - assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF.value()); + assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF); assertThat(link.getHref()).endsWith("/something/foo?id=Spring%23%0A"); } diff --git a/src/test/java/org/springframework/hateoas/mvc/ForwardedHeaderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ForwardedHeaderUnitTest.java index ec77e9a7..b3f5923a 100755 --- a/src/test/java/org/springframework/hateoas/mvc/ForwardedHeaderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ForwardedHeaderUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 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. @@ -21,9 +21,10 @@ import org.junit.Test; /** * Unit tests for {@link ForwardedHeader}. - * + * * @author Oliver Gierke */ +@SuppressWarnings("deprecation") public class ForwardedHeaderUnitTest { /** diff --git a/src/test/java/org/springframework/hateoas/mvc/IdentifiableResourceAssemblerSupportUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/IdentifiableResourceAssemblerSupportUnitTest.java index da27afe5..8b54d63f 100755 --- a/src/test/java/org/springframework/hateoas/mvc/IdentifiableResourceAssemblerSupportUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/IdentifiableResourceAssemblerSupportUnitTest.java @@ -24,7 +24,7 @@ import java.util.Optional; import org.junit.Before; import org.junit.Test; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkBuilder; @@ -57,7 +57,7 @@ public class IdentifiableResourceAssemblerSupportUnitTest extends TestUtils { public void createsInstanceWithSelfLinkToController() { PersonResource resource = assembler.createResource(person); - Link link = resource.getRequiredLink(IanaLinkRelation.SELF.value()); + Link link = resource.getRequiredLink(IanaLinkRelations.SELF.value()); assertThat(link).isNotNull(); assertThat(resource.getLinks()).hasSize(1); diff --git a/src/test/java/org/springframework/hateoas/mvc/MultiMediaTypeWebMvcIntegrationTest.java b/src/test/java/org/springframework/hateoas/mvc/MultiMediaTypeWebMvcIntegrationTest.java index e807d798..e85a2142 100644 --- a/src/test/java/org/springframework/hateoas/mvc/MultiMediaTypeWebMvcIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/MultiMediaTypeWebMvcIntegrationTest.java @@ -37,7 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; @@ -549,7 +549,7 @@ public class MultiMediaTypeWebMvcIntegrationTest { private URI toUri(Integer id) throws URISyntaxException { String uri = findOne(id) // - .getLink(IanaLinkRelation.SELF.value()) // + .getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse(""); diff --git a/src/test/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilderUnitTest.java deleted file mode 100644 index fac42d86..00000000 --- a/src/test/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilderUnitTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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. - * 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. - */ -package org.springframework.hateoas.mvc; - -import static org.assertj.core.api.Assertions.*; - -import java.util.List; - -import org.junit.Test; -import org.springframework.core.Ordered; -import org.springframework.core.ResolvableType; -import org.springframework.hateoas.AffordanceModel; -import org.springframework.hateoas.AffordanceModelFactory; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.QueryParameter; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.plugin.core.OrderAwarePluginRegistry; -import org.springframework.plugin.core.PluginRegistry; - -/** - * @author Greg Turnquist - * @author Oliver Gierke - */ -public class SpringMvcAffordanceBuilderUnitTest { - - @Test - public void favorsCustomLinkDiscovererOverDefault() { - - AffordanceModelFactory low = new LowPriorityModelFactory(); - AffordanceModelFactory high = new HighPriorityModelFactory(); - - PluginRegistry registry = OrderAwarePluginRegistry.of(low, high); - - assertThat(registry.getPluginFor(MediaType.APPLICATION_JSON).get()).isEqualTo(high); - } - - static class LowPriorityModelFactory implements AffordanceModelFactory, Ordered { - - @Override - public int getOrder() { - return 20; - } - - @Override - public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, - List queryMethodParameters, ResolvableType outputType) { - return null; - } - - @Override - public boolean supports(MediaType delimiter) { - return true; - } - } - - static class HighPriorityModelFactory implements AffordanceModelFactory, Ordered { - - @Override - public int getOrder() { - return 10; - } - - @Override - public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, ResolvableType inputType, - List queryMethodParameters, ResolvableType outputType) { - return null; - } - - @Override - public boolean supports(MediaType delimiter) { - return true; - } - } -} diff --git a/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java b/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java index b8f12ed6..0f011a68 100644 --- a/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import java.io.IOException; @@ -30,7 +31,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; @@ -38,7 +39,6 @@ import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; import org.springframework.hateoas.support.MappingUtils; -import org.springframework.hateoas.uber.Jackson2UberModule.UberHandlerInstantiator; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.SerializationFeature; @@ -49,17 +49,16 @@ import com.fasterxml.jackson.databind.SerializationFeature; */ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingIntegrationTest { - static final Links PAGINATION_LINKS = new Links( // - new Link("localhost", IanaLinkRelation.SELF.value()), // - new Link("foo", IanaLinkRelation.NEXT.value()), // - new Link("bar", IanaLinkRelation.PREV.value())// + static final Links PAGINATION_LINKS = Links.of( // + new Link("localhost", IanaLinkRelations.SELF), // + new Link("foo", IanaLinkRelations.NEXT), // + new Link("bar", IanaLinkRelations.PREV) // ); @Before public void setUpModule() { this.mapper.registerModule(new Jackson2UberModule()); - this.mapper.setHandlerInstantiator(new UberHandlerInstantiator()); this.mapper.enable(SerializationFeature.INDENT_OUTPUT); } @@ -233,7 +232,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte JavaType resourceStringType = mapper.getTypeFactory().constructParametricType(Resource.class, String.class); - Resource expected = new Resource<>("first", new Link("localhost")); + Resource expected = new Resource<>("first", new Link("localhost")); Resource actual = mapper.readValue(MappingUtils.read(new ClassPathResource("resource.json", getClass())), resourceStringType); @@ -289,7 +288,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte data.add(new Resource<>("first", new Link("localhost"), new Link("orders").withRel("orders"))); data.add(new Resource<>("second", new Link("remotehost"), new Link("order").withRel("orders"))); - Resources expected = new Resources<>(data); + Resources expected = new Resources<>(data); expected.add(new Link("localhost")); expected.add(new Link("/page/2").withRel("next")); @@ -311,7 +310,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte data.add(new Resource<>("", new Link("localhost"), new Link("orders").withRel("orders"))); data.add(new Resource<>("second", new Link("remotehost"), new Link("order").withRel("orders"))); - Resources expected = new Resources<>(data); + Resources expected = new Resources<>(data); expected.add(new Link("localhost")); expected.add(new Link("/page/2").withRel("next")); @@ -333,12 +332,12 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte data.add(new Resource<>("first", new Link("localhost"), new Link("orders").withRel("orders"))); data.add(new Resource<>("second", new Link("remotehost"), new Link("order").withRel("orders"))); - Resources source = new Resources<>(data); + Resources source = new Resources<>(data); source.add(new Link("localhost")); source.add(new Link("/page/2").withRel("next")); assertThat(write(source)) - .isEqualTo(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass()))); + .isEqualTo(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass()))); } /** @@ -351,7 +350,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte data.add(new Resource<>("first", new Link("localhost"), new Link("orders").withRel("orders"))); data.add(new Resource<>("second", new Link("remotehost"), new Link("order").withRel("orders"))); - Resources expected = new Resources<>(data); + Resources expected = new Resources<>(data); expected.add(new Link("localhost")); expected.add(new Link("/page/2").withRel("next")); @@ -375,7 +374,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte data.add("first"); data.add("second"); - Resources expected = new Resources<>(data); + Resources expected = new Resources<>(data); expected.add(new Link("localhost")); expected.add(new Link("/page/2").withRel("next")); @@ -515,7 +514,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte mapper.getTypeFactory().constructParametricType(PagedResources.class, mapper.getTypeFactory().constructParametricType(Resource.class, Employee.class))); - assertThat(result).isEqualTo(setupAnnotatedPagedResources(0,0)); + assertThat(result).isEqualTo(setupAnnotatedPagedResources(0, 0)); } /** @@ -566,6 +565,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte @Data @AllArgsConstructor @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) static class EmployeeResource extends ResourceSupport { private String name; diff --git a/src/test/java/org/springframework/hateoas/uber/UberWebMvcIntegrationTest.java b/src/test/java/org/springframework/hateoas/uber/UberWebMvcIntegrationTest.java index 2cfca4f3..15ad4847 100644 --- a/src/test/java/org/springframework/hateoas/uber/UberWebMvcIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/uber/UberWebMvcIntegrationTest.java @@ -38,7 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; @@ -344,7 +344,7 @@ public class UberWebMvcIntegrationTest { try { return ResponseEntity.created( // new URI(findOne(newEmployeeId) // - .getLink(IanaLinkRelation.SELF.value()) // + .getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse("") // ) // @@ -364,7 +364,7 @@ public class UberWebMvcIntegrationTest { .noContent() // .location( // new URI(findOne(id) // - .getLink(IanaLinkRelation.SELF.value()) // + .getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse("") // ) // @@ -396,7 +396,7 @@ public class UberWebMvcIntegrationTest { .noContent() // .location( // new URI(findOne(id) // - .getLink(IanaLinkRelation.SELF.value()) // + .getLink(IanaLinkRelations.SELF.value()) // .map(link -> link.expand().getHref()) // .orElse("") // ) // diff --git a/src/test/kotlin/org/springframework/hateoas/mvc/AffordanceBuilderDslUnitTest.kt b/src/test/kotlin/org/springframework/hateoas/mvc/AffordanceBuilderDslUnitTest.kt index 81a12dd5..983cd2e8 100644 --- a/src/test/kotlin/org/springframework/hateoas/mvc/AffordanceBuilderDslUnitTest.kt +++ b/src/test/kotlin/org/springframework/hateoas/mvc/AffordanceBuilderDslUnitTest.kt @@ -50,13 +50,13 @@ class AffordanceBuilderDslUnitTest : TestUtils() { fun `creates link to controller method with affordances`() { val id = "15" - val self = linkTo { findById(id) } withRel IanaLinkRelation.SELF.value() + val self = linkTo { findById(id) } withRel IanaLinkRelations.SELF val selfWithAffordances = self andAffordances { afford { update(id, CustomerDTO("John Doe")) } afford { delete(id) } } - assertThat(selfWithAffordances.rel).isEqualTo(IanaLinkRelation.SELF.value()) + assertThat(selfWithAffordances.rel).isEqualTo(IanaLinkRelations.SELF) assertThat(selfWithAffordances.href).isEqualTo("http://localhost/customers/15") assertThat(selfWithAffordances.affordances).hasSize(3) diff --git a/src/test/kotlin/org/springframework/hateoas/mvc/LinkBuilderDslUnitTest.kt b/src/test/kotlin/org/springframework/hateoas/mvc/LinkBuilderDslUnitTest.kt index 68dc7d73..78109c8c 100644 --- a/src/test/kotlin/org/springframework/hateoas/mvc/LinkBuilderDslUnitTest.kt +++ b/src/test/kotlin/org/springframework/hateoas/mvc/LinkBuilderDslUnitTest.kt @@ -36,10 +36,10 @@ class LinkBuilderDslUnitTest : TestUtils() { @Test fun `creates link to controller method`() { - val self = linkTo { findById("15") } withRel IanaLinkRelation.SELF.value() + val self = linkTo { findById("15") } withRel IanaLinkRelations.SELF assertPointsToMockServer(self) - assertThat(self.rel).isEqualTo(IanaLinkRelation.SELF.value()) + assertThat(self.rel).isEqualTo(IanaLinkRelations.SELF) assertThat(self.href).endsWith("/customers/15") } @@ -52,12 +52,12 @@ class LinkBuilderDslUnitTest : TestUtils() { val customer = Resource(Customer("15", "John Doe")) customer.add(CustomerController::class) { - linkTo { findById(it.content.id) } withRel IanaLinkRelation.SELF.value() + linkTo { findById(it.content.id) } withRel IanaLinkRelations.SELF linkTo { findProductsById(it.content.id) } withRel REL_PRODUCTS } customer.links.forEach { assertPointsToMockServer(it) } - assertThat(customer.hasLink(IanaLinkRelation.SELF.value())).isTrue() + assertThat(customer.hasLink(IanaLinkRelations.SELF)).isTrue() assertThat(customer.hasLink(REL_PRODUCTS)).isTrue() } @@ -70,12 +70,12 @@ class LinkBuilderDslUnitTest : TestUtils() { val customer = CustomerResource("15", "John Doe") customer.add(CustomerController::class) { - linkTo { findById(it.id) } withRel IanaLinkRelation.SELF.value() + linkTo { findById(it.id) } withRel IanaLinkRelations.SELF linkTo { findProductsById(it.id) } withRel REL_PRODUCTS } customer.links.forEach { assertPointsToMockServer(it) } - assertThat(customer.hasLink(IanaLinkRelation.SELF.value())).isTrue() + assertThat(customer.hasLink(IanaLinkRelations.SELF)).isTrue() assertThat(customer.hasLink(REL_PRODUCTS)).isTrue() } diff --git a/src/test/resources/org/springframework/hateoas/collectionjson/resource-template.json b/src/test/resources/org/springframework/hateoas/collectionjson/resource-template.json deleted file mode 100644 index e5d8017c..00000000 --- a/src/test/resources/org/springframework/hateoas/collectionjson/resource-template.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "collection" : { - "version" : "1.0", - "href" : "localhost", - "links" : [ { - "rel" : "self", - "href" : "localhost" - } ], - "items" : [ { - "href" : null, - "data" : [ "first" ], - "links" : null - } ], - "template" : { - "data" : [ - "firstName" : - ] - } - } -}