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" : - ] - } - } -}