diff --git a/.gitignore b/.gitignore
index f95ebd1d8..89216b362 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ target/
*.iml
.idea
*.orig
+.springBeans
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
diff --git a/pom.xml b/pom.xml
index 445f17398..981727ba7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,12 +25,22 @@
ogierkeOliver Gierkeogierke(at)pivotal.io
- Pivotal, Inc.
+ Pivotal Software, Inc.Project lead+1
+
+ gturnquist
+ Greg Turnquist
+ gturnquist(at)pivotal.io
+ Pivotal Software, Inc.
+
+ Contributor
+
+ -6
+
@@ -77,7 +87,7 @@
spring43-next
- 4.3.5.BUILD-SNAPSHOT
+ 4.3.8.BUILD-SNAPSHOT
@@ -90,7 +100,7 @@
spring5
- 5.0.0.M3
+ 5.0.0.M5
@@ -218,7 +228,7 @@
org.springframework.data
- spring-data-build-resources
+ spring-data-build-resourcesziptrue${shared.resources}
diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc
index 89eccaa3e..d7e5baa7e 100644
--- a/src/main/asciidoc/index.adoc
+++ b/src/main/asciidoc/index.adoc
@@ -18,9 +18,9 @@ toc::[]
== Fundmentals
[[fundamentals.jaxb-json]]
-=== JAXB / JSON integration
+=== Jackson / JAXB integration
-As representations for REST web services are usually rendered in either XML or JSON the natural choice of technology to achieve this is either JAXB, JSON or both in combination. To follow HATEOAS principles you need to incorporate links into those representation. Spring HATEOAS provides a set of useful types to ease working with those.
+As representations for REST web services are usually rendered in either XML or JSON the natural choice of technology to achieve this is either Jackson, JAXB, or both in combination. To follow HATEOAS principles you need to incorporate links into those representation. Spring HATEOAS provides a set of useful types to ease working with those.
[[fundamentals.links]]
=== Links
@@ -30,7 +30,7 @@ The `Link` value object follows the Atom link definition and consists of a `rel`
----
Link link = new Link("http://localhost:8080/something");
assertThat(link.getHref(), is("http://localhost:8080/something"));
-assertThat(link.getRel(), is(Link.SELF));
+assertThat(link.getRel(), is(Link.REL_SELF));
Link link = new Link("http://localhost:8080/something", "my-rel");
assertThat(link.getHref(), is("http://localhost:8080/something"));
@@ -62,11 +62,18 @@ resource.add(new Link("http://myhost/people"));
This would render as follows in JSON:
-[source, xml]
+[source, json]
----
-{ firstname : "Dave",
- lastname : "Matthews",
- links : [ { rel : "self", href : "http://myhost/people" } ] }
+{
+ "firstname" : "Dave",
+ "lastname" : "Matthews",
+ _"links" : [
+ {
+ "rel" : "self",
+ "href" : "http://myhost/people"
+ }
+ ]
+}
----
… or slightly more verbose in XML …
@@ -88,7 +95,7 @@ You can also easily access links contained in that resource:
----
Link selfLink = new Link("http://myhost/people");
assertThat(resource.getId(), is(selfLink));
-assertThat(resource.getLink(Link.SELF), is(selfLink));
+assertThat(resource.getLink(Link.REL_SELF), is(selfLink));
----
[[fundamentals.obtaining-links]]
=== Obtaining links
@@ -136,7 +143,7 @@ The `ControllerLinkBuilder` uses Spring's `ServletUriComponentsBuilder` under th
Person person = new Person(1L, "Dave", "Matthews");
// /person / 1
Link link = linkTo(PersonController.class).slash(person.getId()).withSelfRel();
-assertThat(link.getRel(), is(Link.SELF));
+assertThat(link.getRel(), is(Link.REL_SELF));
assertThat(link.getHref(), endsWith("/people/1"));
----
@@ -169,7 +176,7 @@ As of version 0.4 you can even easily build links pointing to methods or creatin
Method method = PersonController.class.getMethod("show", Long.class);
Link link = linkTo(method, 2L).withSelfRel();
-assertThat(link.getHref(), is("/people/2")));
+assertThat(link.getHref(), endsWith("/people/2")));
----
This is still a bit dissatisfying as we have to get a `Method` instance first, which throws an exception and is generally quite cumbersome. At least we don't repeat the mapping. An even better approach is to have a dummy method invocation of the target method on a controller proxy we can create easily using the `methodOn(…)` helper.
@@ -177,10 +184,10 @@ This is still a bit dissatisfying as we have to get a `Method` instance first, w
[source, java]
----
Link link = linkTo(methodOn(PersonController.class).show(2L)).withSelfRel();
-assertThat(link.getHref(), is("/people/2")));
+assertThat(link.getHref(), endsWith("/people/2")));
----
-`methodOn(…)` creates a proxy of the controller class that is recording the method invocation and exposed it in a proxy created for the return type of the method. This allows the fluent expression of the method we want to obtain the mapping for. However there are a few constraints on the methods that can be obtained using this technique:
+`methodOn(…)` creates a proxy of the controller class that is recording the method invocation and exposes it in a proxy created for the return type of the method. This allows the fluent expression of the method we want to obtain the mapping for. However there are a few constraints on the methods that can be obtained using this technique:
1. The return type has to be capable of proxying as we need to expose the method invocation on it.
2. The parameters handed into the methods are generally neglected, except the ones referred to through `@PathVariable` as they make up the URI.
@@ -192,6 +199,7 @@ So far we have created links by pointing to the web-framework implementations (i
The `EntityLinks` interface now exposes an API to lookup a `Link` or `LinkBuilder` based on the model types. The methods essentially return links to either point to the collection resource (e.g. `/people`) or a single resource (e.g. `/people/1`).
+[source, java]
----
EntityLinks links = …;
LinkBuilder builder = links.linkFor(CustomerResource.class);
@@ -330,20 +338,20 @@ public class Config {
Note that now the prefix `ex:` automatically appears before all rels which are not registered with IANA, as in `ex:orders`. Clients can use the `curies` link to resolve a curie to its full form:
-[source, java]
+[source, json]
----
{
- _links : {
- self: { href: "http://myhost/person/1" },
- curies: {
- name: "ex",
- href: "http://example.com/rels/{rel}",
- templated: true
+ _"links" : {
+ "self" : { href: "http://myhost/person/1" },
+ "curies" : {
+ "name" : "ex",
+ "href" : "http://example.com/rels/{rel}",
+ "templated" : true
},
- "ex:orders" : { href: "http://myhost/person/1/orders" }
+ "ex:orders" : { href : "http://myhost/person/1/orders" }
},
- firstname : "Dave",
- lastname : "Matthews"
+ "firstname" : "Dave",
+ "lastname" : "Matthews"
}
----
diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java
index 11ac811d6..7a24c140c 100755
--- a/src/main/java/org/springframework/hateoas/Link.java
+++ b/src/main/java/org/springframework/hateoas/Link.java
@@ -1,295 +1,523 @@
-/*
- * Copyright 2012-2016 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 java.io.Serializable;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlTransient;
-import javax.xml.bind.annotation.XmlType;
-
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-/**
- * Value object for links.
- *
- * @author Oliver Gierke
- */
-@XmlType(name = "link", namespace = Link.ATOM_NAMESPACE)
-@JsonIgnoreProperties("templated")
-public class Link implements Serializable {
-
- private static final long serialVersionUID = -9037755944661782121L;
- private static final String URI_PATTERN = "(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
-
- public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
-
- public static final String REL_SELF = "self";
- public static final String REL_FIRST = "first";
- public static final String REL_PREVIOUS = "prev";
- public static final String REL_NEXT = "next";
- public static final String REL_LAST = "last";
-
- @XmlAttribute private String rel;
- @XmlAttribute private String href;
- @XmlTransient @JsonIgnore private UriTemplate template;
-
- /**
- * Creates a new link to the given URI with the self rel.
- *
- * @see #REL_SELF
- * @param href must not be {@literal null} or empty.
- */
- public Link(String href) {
- this(href, REL_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), 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, "UriTempalte must not be null!");
- Assert.hasText(rel, "Rel must not be null or empty!");
-
- this.template = template;
- this.href = template.toString();
- this.rel = rel;
- }
-
- /**
- * Empty constructor required by the marshalling framework.
- */
- protected Link() {
-
- }
-
- /**
- * Returns the actual URI the link is pointing to.
- *
- * @return
- */
- public String getHref() {
- return href;
- }
-
- /**
- * Returns the rel of the link.
- *
- * @return
- */
- public String getRel() {
- return rel;
- }
-
- /**
- * Returns a {@link Link} pointing to the same URI but with the given relation.
- *
- * @param rel must not be {@literal null} or empty.
- * @return
- */
- public Link withRel(String rel) {
- return new Link(href, rel);
- }
-
- /**
- * Returns a {@link Link} pointing to the same URI but with the {@code self} relation.
- *
- * @return
- */
- public Link withSelfRel() {
- return withRel(Link.REL_SELF);
- }
-
- /**
- * Returns the variable names contained in the template.
- *
- * @return
- */
- @JsonIgnore
- public List getVariableNames() {
- return getUriTemplate().getVariableNames();
- }
-
- /**
- * Returns all {@link TemplateVariables} contained in the {@link Link}.
- *
- * @return
- */
- @JsonIgnore
- public List getVariables() {
- return getUriTemplate().getVariables();
- }
-
- /**
- * Returns whether the link is templated.
- *
- * @return
- */
- public boolean isTemplated() {
- return !getUriTemplate().getVariables().isEmpty();
- }
-
- /**
- * Turns the current template into a {@link Link} by expanding it using the given parameters.
- *
- * @param arguments
- * @return
- */
- public Link expand(Object... arguments) {
- return new Link(getUriTemplate().expand(arguments).toString(), getRel());
- }
-
- /**
- * Turns the current template into a {@link Link} by expanding it using the given parameters.
- *
- * @param arguments must not be {@literal null}.
- * @return
- */
- public Link expand(Map arguments) {
- return new Link(getUriTemplate().expand(arguments).toString(), getRel());
- }
-
- private UriTemplate getUriTemplate() {
-
- if (template == null) {
- this.template = new UriTemplate(href);
- }
-
- return template;
- }
-
- /*
- * (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(Object obj) {
-
- if (this == obj) {
- return true;
- }
-
- if (!(obj instanceof Link)) {
- return false;
- }
-
- Link that = (Link) obj;
-
- return this.href.equals(that.href) && this.rel.equals(that.rel);
- }
-
- /*
- * (non-Javadoc)
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
-
- int result = 17;
- result += 31 * href.hashCode();
- result += 31 * rel.hashCode();
- return result;
- }
-
- /*
- * (non-Javadoc)
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- return String.format("<%s>;rel=\"%s\"", href, rel);
- }
-
- /**
- * Factory method to easily create {@link Link} instances from RFC-5988 compatible {@link String} representations of a
- * link. Will return {@literal null} if an empty or {@literal null} {@link String} is given.
- *
- * @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.
- * @return
- */
- public static Link valueOf(String element) {
-
- if (!StringUtils.hasText(element)) {
- return null;
- }
-
- Pattern uriAndAttributes = Pattern.compile("<(.*)>;(.*)");
- Matcher matcher = uriAndAttributes.matcher(element);
-
- if (matcher.find()) {
-
- Map attributes = getAttributeMap(matcher.group(2));
-
- if (!attributes.containsKey("rel")) {
- throw new IllegalArgumentException("Link does not provide a rel attribute!");
- }
-
- return new Link(matcher.group(1), attributes.get("rel"));
-
- } else {
- throw new IllegalArgumentException(String.format("Given link header %s is not RFC5988 compliant!", element));
- }
- }
-
- /**
- * Parses the links attributes from the given source {@link String}.
- *
- * @param source
- * @return
- */
- private static Map getAttributeMap(String source) {
-
- if (!StringUtils.hasText(source)) {
- return Collections.emptyMap();
- }
-
- Map attributes = new HashMap();
- Pattern keyAndValue = Pattern.compile("(\\w+)=\"(\\p{Lower}[\\p{Lower}\\p{Digit}\\.\\-]*|" + URI_PATTERN + ")\"");
- Matcher matcher = keyAndValue.matcher(source);
-
- while (matcher.find()) {
- attributes.put(matcher.group(1), matcher.group(2));
- }
-
- return attributes;
- }
-}
+/*
+ * Copyright 2012-2017 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 java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * Value object for links.
+ *
+ * @author Oliver Gierke
+ * @author Greg Turnquist
+ */
+@XmlType(name = "link", namespace = Link.ATOM_NAMESPACE)
+@JsonIgnoreProperties("templated")
+public class Link implements Serializable {
+
+ private static final long serialVersionUID = -9037755944661782121L;
+ private static final String URI_PATTERN = "(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
+
+ public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
+
+ public static final String REL_SELF = "self";
+ public static final String REL_FIRST = "first";
+ public static final String REL_PREVIOUS = "prev";
+ public static final String REL_NEXT = "next";
+ public static final String REL_LAST = "last";
+
+ @XmlAttribute private String rel;
+ @XmlAttribute private String href;
+ @XmlAttribute private String hreflang;
+ @XmlAttribute private String media;
+ @XmlAttribute private String title;
+ @XmlAttribute private String type;
+ @XmlAttribute private String deprecation;
+ @XmlTransient @JsonIgnore private UriTemplate template;
+
+ /**
+ * Creates a new link to the given URI with the self rel.
+ *
+ * @see #REL_SELF
+ * @param href must not be {@literal null} or empty.
+ */
+ public Link(String href) {
+ this(href, REL_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), 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;
+ }
+
+ /**
+ * Creates a new {@link Link} to the given URI with the given rel, hreflang, media, title, and type.
+ *
+ * @param href must not be {@literal null} or empty.
+ * @param rel must not be {@literal null} or empty.
+ * @param hreflang
+ * @param media
+ * @param title
+ * @param type
+ * @param deprecation
+ */
+ public Link(String href, String rel, String hreflang, String media, String title, String type, String deprecation) {
+
+ this(href, rel);
+ this.hreflang = hreflang;
+ this.media = media;
+ this.title = title;
+ this.type = type;
+ this.deprecation = deprecation;
+ }
+
+ /**
+ * Empty constructor required by the marshalling framework.
+ */
+ protected Link() {
+
+ }
+
+ /**
+ * Returns the actual URI the link is pointing to.
+ *
+ * @return
+ */
+ public String getHref() {
+ return href;
+ }
+
+ /**
+ * Returns the rel of the link.
+ *
+ * @return
+ */
+ public String getRel() {
+ return rel;
+ }
+
+ /**
+ * Returns the hreflang of the link.
+ *
+ * @return
+ */
+ public String getHreflang() {
+ return hreflang;
+ }
+
+ /**
+ * Returns the media of the link.
+ *
+ * @return
+ */
+ public String getMedia() {
+ return media;
+ }
+
+ /**
+ * Returns the title of the link.
+ *
+ * @return
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Returns the type of the link
+ *
+ * @return
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Returns link about deprecation of this link.
+ *
+ * @return
+ */
+ public String getDeprecation() {
+ return deprecation;
+ }
+
+ /**
+ * Returns a {@link Link} pointing to the same URI but with the given relation.
+ *
+ * @param rel must not be {@literal null} or empty.
+ * @return
+ */
+ public Link withRel(String rel) {
+ return new Link(href, rel);
+ }
+
+ /**
+ * Returns a {@link Link} pointing to the same URI but with the {@code self} relation.
+ *
+ * @return
+ */
+ public Link withSelfRel() {
+ return withRel(Link.REL_SELF);
+ }
+
+ /**
+ * Returns a {@link Link} with the {@code hreflang} attribute filled out.
+ *
+ * @param hreflang
+ * @return
+ */
+ public Link withHreflang(String hreflang) {
+ Assert.hasText(hreflang, "hreflang must not be null or empty!");
+ return new Link(this.href, this.rel, hreflang, this.media, this.title, this.type, this.deprecation);
+ }
+
+ /**
+ * Returns a {@link Link} with the {@code media} attribute filled out.
+ *
+ * @param media
+ * @return
+ */
+ public Link withMedia(String media) {
+ Assert.hasText(media, "media must not be null or empty!");
+ return new Link(this.href, this.rel, this.hreflang, media, this.title, this.type, this.deprecation);
+ }
+
+ /**
+ * Returns a {@link Link} with the {@code title} attribute filled out.
+ *
+ * @param title
+ * @return
+ */
+ public Link withTitle(String title) {
+ Assert.hasText(title, "title must not be null or empty!");
+ return new Link(this.href, this.rel, this.hreflang, this.media, title, this.type, this.deprecation);
+ }
+
+ /**
+ * Returns a {@link Link} with the {@code type} attribute filled out.
+ *
+ * @param type
+ * @return
+ */
+ public Link withType(String type) {
+ Assert.hasText(type, "type must not be null or empty!");
+ return new Link(this.href, this.rel, this.hreflang, this.media, this.title, type, this.deprecation);
+ }
+
+ /**
+ * Returns a {@link Link} with the {@code deprecation} attribute filled out.
+ *
+ * @param deprecation
+ * @return
+ */
+ public Link withDeprecation(String deprecation) {
+ Assert.hasText(deprecation, "deprecation must not be null or empty!");
+ return new Link(this.href, this.rel, this.hreflang, this.media, this.title, this.type, deprecation);
+ }
+
+ /**
+ * Returns the variable names contained in the template.
+ *
+ * @return
+ */
+ @JsonIgnore
+ public List getVariableNames() {
+ return getUriTemplate().getVariableNames();
+ }
+
+ /**
+ * Returns all {@link TemplateVariables} contained in the {@link Link}.
+ *
+ * @return
+ */
+ @JsonIgnore
+ public List getVariables() {
+ return getUriTemplate().getVariables();
+ }
+
+ /**
+ * Returns whether the link is templated.
+ *
+ * @return
+ */
+ public boolean isTemplated() {
+ return !getUriTemplate().getVariables().isEmpty();
+ }
+
+ /**
+ * Turns the current template into a {@link Link} by expanding it using the given parameters.
+ *
+ * @param arguments
+ * @return
+ */
+ public Link expand(Object... arguments) {
+ return new Link(getUriTemplate().expand(arguments).toString(), getRel());
+ }
+
+ /**
+ * Turns the current template into a {@link Link} by expanding it using the given parameters.
+ *
+ * @param arguments must not be {@literal null}.
+ * @return
+ */
+ public Link expand(Map arguments) {
+ return new Link(getUriTemplate().expand(arguments).toString(), getRel());
+ }
+
+ private UriTemplate getUriTemplate() {
+
+ if (template == null) {
+ this.template = new UriTemplate(href);
+ }
+
+ return template;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof Link)) {
+ return false;
+ }
+
+ Link that = (Link) obj;
+
+ return
+ this.href.equals(that.href)
+ &&
+ this.rel.equals(that.rel)
+ &&
+ (this.hreflang != null ? this.hreflang.equals(that.hreflang) : this.hreflang == that.hreflang)
+ &&
+ (this.media != null ? this.media.equals(that.media) : this.media == that.media)
+ &&
+ (this.title != null ? this.title.equals(that.title) : this.title == that.title)
+ &&
+ (this.type != null ? this.type.equals(that.type) : this.type == that.type)
+ &&
+ (this.deprecation != null ? this.deprecation.equals(that.deprecation) : this.deprecation == that.deprecation);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+ result += 31 * href.hashCode();
+ result += 31 * rel.hashCode();
+ if (hreflang != null) {
+ result += 31 * hreflang.hashCode();
+ }
+ if (media != null) {
+ result += 31 * media.hashCode();
+ }
+ if (title != null) {
+ result += 31 * title.hashCode();
+ }
+ if (type != null) {
+ result += 31 * type.hashCode();
+ }
+ if (deprecation != null) {
+ result += 31 * deprecation.hashCode();
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ String linkString = String.format("<%s>;rel=\"%s\"", href, rel);
+
+ if (hreflang != null) {
+ linkString += ";hreflang=\"" + hreflang + "\"";
+ }
+
+ if (media != null) {
+ linkString += ";media=\"" + media + "\"";
+ }
+
+ if (title != null) {
+ linkString += ";title=\"" + title + "\"";
+ }
+
+ if (type != null) {
+ linkString += ";type=\"" + type + "\"";
+ }
+
+ if (deprecation != null) {
+ linkString += ";deprecation=\"" + deprecation + "\"";
+ }
+
+
+ return linkString;
+ }
+
+ /**
+ * Factory method to easily create {@link Link} instances from RFC-5988 compatible {@link String} representations of a
+ * link. Will return {@literal null} if an empty or {@literal null} {@link String} is given.
+ *
+ * @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.
+ * @return
+ */
+ public static Link valueOf(String element) {
+
+ if (!StringUtils.hasText(element)) {
+ return null;
+ }
+
+ Pattern uriAndAttributes = Pattern.compile("<(.*)>;(.*)");
+ Matcher matcher = uriAndAttributes.matcher(element);
+
+ if (matcher.find()) {
+
+ Map attributes = getAttributeMap(matcher.group(2));
+
+ if (!attributes.containsKey("rel")) {
+ throw new IllegalArgumentException("Link does not provide a rel attribute!");
+ }
+
+ Set unrecognizedHeaders = unrecognizedHeaders(attributes);
+ if (!unrecognizedHeaders.isEmpty()) {
+ throw new IllegalArgumentException("Link contains invalid RFC5988 headers! => " + unrecognizedHeaders);
+ }
+
+ Link link = new Link(matcher.group(1), attributes.get("rel"));
+
+ if (attributes.containsKey("hreflang")) {
+ link = link.withHreflang(attributes.get("hreflang"));
+ }
+
+ if (attributes.containsKey("media")) {
+ link = link.withMedia(attributes.get("media"));
+ }
+
+ if (attributes.containsKey("title")) {
+ link = link.withTitle(attributes.get("title"));
+ }
+
+ if (attributes.containsKey("type")) {
+ link = link.withType(attributes.get("type"));
+ }
+
+ if (attributes.containsKey("deprecation")) {
+ link = link.withDeprecation(attributes.get("deprecation"));
+ }
+
+ return link;
+
+ } else {
+ throw new IllegalArgumentException(String.format("Given link header %s is not RFC5988 compliant!", element));
+ }
+ }
+
+ /**
+ * Parses the links attributes from the given source {@link String}.
+ *
+ * @param source
+ * @return
+ */
+ private static Map getAttributeMap(String source) {
+
+ if (!StringUtils.hasText(source)) {
+ return Collections.emptyMap();
+ }
+
+ Map attributes = new HashMap();
+ Pattern keyAndValue = Pattern.compile("(\\w+)=\"(\\p{Lower}[\\p{Lower}\\p{Digit}\\.\\-\\s]*|" + URI_PATTERN + ")\"");
+ Matcher matcher = keyAndValue.matcher(source);
+
+ while (matcher.find()) {
+ attributes.put(matcher.group(1), matcher.group(2));
+ }
+
+ return attributes;
+ }
+
+ /**
+ * Scan for any headers not recognized.
+ *
+ * @param attributes
+ * @return
+ */
+ private static Set unrecognizedHeaders(final Map attributes) {
+
+ // Copy the existing keys to avoid damaging the original.
+ Set unrecognizedHeaders = new HashSet() {{
+ addAll(attributes.keySet());
+ }};
+
+ // Remove all recognized headers
+ unrecognizedHeaders.removeAll(Arrays.asList("href", "rel", "hreflang", "media", "title", "type", "deprecation"));
+
+ return unrecognizedHeaders;
+ }
+}
diff --git a/src/main/java/org/springframework/hateoas/Links.java b/src/main/java/org/springframework/hateoas/Links.java
index f58554b46..bf8b6903d 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-2016 the original author or authors.
+ * Copyright 2013-2017 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,10 +29,11 @@
* Value object to represent a list of {@link Link}s.
*
* @author Oliver Gierke
+ * @author Greg Turnquist
*/
public class Links implements Iterable {
- private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("(<[^>]*>;rel=\"[^\"]*\")");
+ private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("(<[^>]*>(;\\w+=\"[^\"]*\")+)");
static final Links NO_LINKS = new Links(Collections. emptyList());
diff --git a/src/main/java/org/springframework/hateoas/PagedResources.java b/src/main/java/org/springframework/hateoas/PagedResources.java
index 8cf0c126a..a2116c3ec 100644
--- a/src/main/java/org/springframework/hateoas/PagedResources.java
+++ b/src/main/java/org/springframework/hateoas/PagedResources.java
@@ -102,7 +102,6 @@ public static , S> PagedResources wrap(Iterable cont
/**
* Returns the Link pointing to the next page (if set).
*
- * @see #addPaginationLinks(Link)
* @return
*/
@JsonIgnore
@@ -113,7 +112,6 @@ public Link getNextLink() {
/**
* Returns the Link pointing to the previous page (if set).
*
- * @see #addPaginationLinks(Link)
* @return
*/
@JsonIgnore
diff --git a/src/main/java/org/springframework/hateoas/ResourceAssembler.java b/src/main/java/org/springframework/hateoas/ResourceAssembler.java
index 132341d28..0b4b93739 100755
--- a/src/main/java/org/springframework/hateoas/ResourceAssembler.java
+++ b/src/main/java/org/springframework/hateoas/ResourceAssembler.java
@@ -1,32 +1,32 @@
-/*
- * Copyright 2012 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;
-
-/**
- * Interface for components that convert a domain type into an {@link ResourceSupport}.
- *
- * @author Oliver Gierke
- */
-public interface ResourceAssembler {
-
- /**
- * Converts the given entity into an {@link ResourceSupport}.
- *
- * @param entity
- * @return
- */
- D toResource(T entity);
-}
+/*
+ * Copyright 2012 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;
+
+/**
+ * Interface for components that convert a domain type into an {@link ResourceSupport}.
+ *
+ * @author Oliver Gierke
+ */
+public interface ResourceAssembler {
+
+ /**
+ * Converts the given entity into an {@link ResourceSupport}.
+ *
+ * @param entity
+ * @return
+ */
+ D toResource(T entity);
+}
diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java
index 64de72076..1d99ab14c 100755
--- a/src/main/java/org/springframework/hateoas/ResourceSupport.java
+++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java
@@ -1,173 +1,191 @@
-/*
- * Copyright 2012-2015 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 java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.xml.bind.annotation.XmlElement;
-
-import org.springframework.util.Assert;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * Base class for DTOs to collect links.
- *
- * @author Oliver Gierke
- */
-public class ResourceSupport implements Identifiable {
-
- private final List links;
-
- public ResourceSupport() {
- this.links = new ArrayList();
- }
-
- /**
- * Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
- */
- @JsonIgnore
- public Link getId() {
- return getLink(Link.REL_SELF);
- }
-
- /**
- * Adds the given link to the resource.
- *
- * @param link
- */
- public void add(Link link) {
- Assert.notNull(link, "Link must not be null!");
- this.links.add(link);
- }
-
- /**
- * Adds all given {@link Link}s to the resource.
- *
- * @param links
- */
- public void add(Iterable links) {
- Assert.notNull(links, "Given links must not be null!");
- for (Link candidate : links) {
- add(candidate);
- }
- }
-
- /**
- * Adds all given {@link Link}s to the resource.
- *
- * @param links must not be {@literal null}.
- */
- public void add(Link... links) {
- Assert.notNull(links, "Given links must not be null!");
- add(Arrays.asList(links));
- }
-
- /**
- * Returns whether the resource contains {@link Link}s at all.
- *
- * @return
- */
- public boolean hasLinks() {
- return !this.links.isEmpty();
- }
-
- /**
- * Returns whether the resource contains a {@link Link} with the given rel.
- *
- * @param rel
- * @return
- */
- public boolean hasLink(String rel) {
- return getLink(rel) != null;
- }
-
- /**
- * Returns all {@link Link}s contained in this resource.
- *
- * @return
- */
- @XmlElement(name = "link", namespace = Link.ATOM_NAMESPACE)
- @JsonProperty("links")
- public List getLinks() {
- return links;
- }
-
- /**
- * Removes all {@link Link}s added to the resource so far.
- */
- public void removeLinks() {
- this.links.clear();
- }
-
- /**
- * Returns the link with the given rel.
- *
- * @param rel
- * @return the link with the given rel or {@literal null} if none found.
- */
- public Link getLink(String rel) {
-
- for (Link link : links) {
- if (link.getRel().equals(rel)) {
- return link;
- }
- }
-
- return null;
- }
-
- /*
- * (non-Javadoc)
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- return String.format("links: %s", links.toString());
- }
-
- /*
- * (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(Object obj) {
-
- if (this == obj) {
- return true;
- }
-
- if (obj == null || !obj.getClass().equals(this.getClass())) {
- return false;
- }
-
- ResourceSupport that = (ResourceSupport) obj;
-
- return this.links.equals(that.links);
- }
-
- /*
- * (non-Javadoc)
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
- return this.links.hashCode();
- }
-}
+/*
+ * Copyright 2012-2015 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElement;
+
+import org.springframework.util.Assert;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Base class for DTOs to collect links.
+ *
+ * @author Oliver Gierke
+ */
+public class ResourceSupport implements Identifiable {
+
+ private final List links;
+
+ public ResourceSupport() {
+ this.links = new ArrayList();
+ }
+
+ /**
+ * Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
+ */
+ @JsonIgnore
+ public Link getId() {
+ return getLink(Link.REL_SELF);
+ }
+
+ /**
+ * Adds the given link to the resource.
+ *
+ * @param link
+ */
+ public void add(Link link) {
+ Assert.notNull(link, "Link must not be null!");
+ this.links.add(link);
+ }
+
+ /**
+ * Adds all given {@link Link}s to the resource.
+ *
+ * @param links
+ */
+ public void add(Iterable links) {
+ Assert.notNull(links, "Given links must not be null!");
+ for (Link candidate : links) {
+ add(candidate);
+ }
+ }
+
+ /**
+ * Adds all given {@link Link}s to the resource.
+ *
+ * @param links must not be {@literal null}.
+ */
+ public void add(Link... links) {
+ Assert.notNull(links, "Given links must not be null!");
+ add(Arrays.asList(links));
+ }
+
+ /**
+ * Returns whether the resource contains {@link Link}s at all.
+ *
+ * @return
+ */
+ public boolean hasLinks() {
+ return !this.links.isEmpty();
+ }
+
+ /**
+ * Returns whether the resource contains a {@link Link} with the given rel.
+ *
+ * @param rel
+ * @return
+ */
+ public boolean hasLink(String rel) {
+ return getLink(rel) != null;
+ }
+
+ /**
+ * Returns all {@link Link}s contained in this resource.
+ *
+ * @return
+ */
+ @XmlElement(name = "link", namespace = Link.ATOM_NAMESPACE)
+ @JsonProperty("links")
+ public List getLinks() {
+ return links;
+ }
+
+ /**
+ * Removes all {@link Link}s added to the resource so far.
+ */
+ public void removeLinks() {
+ this.links.clear();
+ }
+
+ /**
+ * Returns the link with the given rel.
+ *
+ * @param rel
+ * @return the link with the given rel or {@literal null} if none found.
+ */
+ public Link getLink(String rel) {
+
+ for (Link link : links) {
+ if (link.getRel().equals(rel)) {
+ return link;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns all {@link Link}s with the given relation type.
+ *
+ * @return the links in a {@link List}
+ */
+ public List getLinks(String rel) {
+
+ List relatedLinks = new ArrayList();
+
+ for (Link link : links) {
+ if (link.getRel().equals(rel)) {
+ relatedLinks.add(link);
+ }
+ }
+
+ return relatedLinks;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return String.format("links: %s", links.toString());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null || !obj.getClass().equals(this.getClass())) {
+ return false;
+ }
+
+ ResourceSupport that = (ResourceSupport) obj;
+
+ return this.links.equals(that.links);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return this.links.hashCode();
+ }
+}
diff --git a/src/main/java/org/springframework/hateoas/client/Traverson.java b/src/main/java/org/springframework/hateoas/client/Traverson.java
index 8f0c61194..9f9d22578 100644
--- a/src/main/java/org/springframework/hateoas/client/Traverson.java
+++ b/src/main/java/org/springframework/hateoas/client/Traverson.java
@@ -82,7 +82,7 @@ public class Traverson {
* interact with the service.
*
* @param baseUri must not be {@literal null}.
- * @param mediaType must not be {@literal null} or empty.
+ * @param mediaTypes must not be {@literal null} or empty.
*/
public Traverson(URI baseUri, MediaType... mediaTypes) {
this(baseUri, Arrays.asList(mediaTypes));
@@ -93,7 +93,7 @@ public Traverson(URI baseUri, MediaType... mediaTypes) {
* interact with the service.
*
* @param baseUri must not be {@literal null}.
- * @param mediaType must not be {@literal null} or empty.
+ * @param mediaTypes must not be {@literal null} or empty.
*/
public Traverson(URI baseUri, List mediaTypes) {
diff --git a/src/main/java/org/springframework/hateoas/hal/LinkMixin.java b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java
index 53f796eb9..aceef016d 100644
--- a/src/main/java/org/springframework/hateoas/hal/LinkMixin.java
+++ b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2014 the original author or authors.
+ * Copyright 2012-2017 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.
@@ -28,13 +28,46 @@
*
* @author Alexander Baetz
* @author Oliver Gierke
+ * @author Greg Turnquist
*/
-@JsonIgnoreProperties(value = "rel")
+@JsonIgnoreProperties({"rel", "media"})
abstract class LinkMixin extends Link {
private static final long serialVersionUID = 4720588561299667409L;
- /*
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.Link#getHreflang()
+ */
+ @Override
+ @JsonInclude(Include.NON_NULL)
+ public abstract String getHreflang();
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.Link#getTitle()
+ */
+ @Override
+ @JsonInclude(Include.NON_NULL)
+ public abstract String getTitle();
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.Link#getType()
+ */
+ @Override
+ @JsonInclude(Include.NON_NULL)
+ public abstract String getType();
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.Link#getDeprecation()
+ */
+ @Override
+ @JsonInclude(Include.NON_NULL)
+ public abstract String getDeprecation();
+
+ /*
* (non-Javadoc)
* @see org.springframework.hateoas.Link#isTemplate()
*/
diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java
index 21ef76ea9..eb32d6abe 100755
--- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java
+++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java
@@ -1,343 +1,343 @@
-/*
- * Copyright 2012-2016 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.springframework.util.StringUtils.*;
-
-import lombok.RequiredArgsConstructor;
-import lombok.experimental.Delegate;
-
-import java.lang.reflect.Method;
-import java.net.URI;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.springframework.hateoas.Link;
-import org.springframework.hateoas.TemplateVariables;
-import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
-import org.springframework.hateoas.core.DummyInvocationUtils;
-import org.springframework.hateoas.core.LinkBuilderSupport;
-import org.springframework.hateoas.core.MappingDiscoverer;
-import org.springframework.util.Assert;
-import org.springframework.util.ConcurrentReferenceHashMap;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.context.request.RequestAttributes;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
-import org.springframework.web.util.DefaultUriTemplateHandler;
-import org.springframework.web.util.UriComponents;
-import org.springframework.web.util.UriComponentsBuilder;
-import org.springframework.web.util.UriTemplate;
-
-/**
- * Builder to ease building {@link Link} instances pointing to Spring MVC controllers.
- *
- * @author Oliver Gierke
- * @author Kamill Sokol
- * @author Greg Turnquist
- * @author Kevin Conaway
- * @author Andrew Naydyonock
- * @author Oliver Trosien
- */
-public class ControllerLinkBuilder extends LinkBuilderSupport {
-
- private static final String REQUEST_ATTRIBUTES_MISSING = "Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?";
- private static final CachingAnnotationMappingDiscoverer DISCOVERER = new CachingAnnotationMappingDiscoverer(
- new AnnotationMappingDiscoverer(RequestMapping.class));
- private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory();
- private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
-
- private final TemplateVariables variables;
-
- /**
- * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}.
- *
- * @param builder must not be {@literal null}.
- */
- ControllerLinkBuilder(UriComponentsBuilder builder) {
-
- super(builder);
-
- this.variables = TemplateVariables.NONE;
- }
-
- /**
- * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponents}.
- *
- * @param uriComponents must not be {@literal null}.
- */
- ControllerLinkBuilder(UriComponents uriComponents) {
- this(uriComponents, TemplateVariables.NONE);
- }
-
- ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables) {
-
- super(uriComponents);
-
- this.variables = variables;
- }
-
- /**
- * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class.
- *
- * @param controller the class to discover the annotation on, must not be {@literal null}.
- * @return
- */
- public static ControllerLinkBuilder linkTo(Class> controller) {
- return linkTo(controller, new Object[0]);
- }
-
- /**
- * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class. The
- * additional parameters are used to fill up potentially available path variables in the class scop request mapping.
- *
- * @param controller the class to discover the annotation on, must not be {@literal null}.
- * @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
- * {@literal null}.
- * @return
- */
- public static ControllerLinkBuilder linkTo(Class> controller, Object... parameters) {
-
- Assert.notNull(controller, "Controller must not be null!");
- Assert.notNull(parameters, "Parameters must not be null!");
-
- String mapping = DISCOVERER.getMapping(controller);
-
- UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
- UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
-
- return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
- }
-
- /**
- * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class.
- * Parameter map is used to fill up potentially available path variables in the class scope request mapping.
- *
- * @param controller the class to discover the annotation on, must not be {@literal null}.
- * @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
- * {@literal null}.
- * @return
- */
- public static ControllerLinkBuilder linkTo(Class> controller, Map parameters) {
-
- Assert.notNull(controller, "Controller must not be null!");
- Assert.notNull(parameters, "Parameters must not be null!");
-
- String mapping = DISCOVERER.getMapping(controller);
-
- UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
- UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
-
- return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
- }
-
- /*
- * @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
- */
- public static ControllerLinkBuilder linkTo(Method method, Object... parameters) {
- return linkTo(method.getDeclaringClass(), method, parameters);
- }
-
- /*
- * @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class>, Method, Object...)
- */
- public static ControllerLinkBuilder linkTo(Class> controller, Method method, Object... parameters) {
-
- Assert.notNull(controller, "Controller type must not be null!");
- Assert.notNull(method, "Method must not be null!");
-
- UriTemplate template = DISCOVERER.getMappingAsUriTemplate(controller, method);
- URI uri = template.expand(parameters);
-
- return new ControllerLinkBuilder(getBuilder()).slash(uri);
- }
-
- /**
- * Creates a {@link ControllerLinkBuilder} pointing to a controller method. Hand in a dummy method invocation result
- * you can create via {@link #methodOn(Class, Object...)} or {@link DummyInvocationUtils#methodOn(Class, Object...)}.
- *
- *
- * @RequestMapping("/customers")
- * class CustomerController {
- *
- * @RequestMapping("/{id}/addresses")
- * HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … }
- * }
- *
- * Link link = linkTo(methodOn(CustomerController.class).showAddresses(2L)).withRel("addresses");
- *
- *
- * The resulting {@link Link} instance will point to {@code /customers/2/addresses} and have a rel of
- * {@code addresses}. For more details on the method invocation constraints, see
- * {@link DummyInvocationUtils#methodOn(Class, Object...)}.
- *
- * @param invocationValue
- * @return
- */
- public static ControllerLinkBuilder linkTo(Object invocationValue) {
- return FACTORY.linkTo(invocationValue);
- }
-
- /**
- * Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
- * imports of {@link ControllerLinkBuilder}.
- *
- * @param controller must not be {@literal null}.
- * @param parameters parameters to extend template variables in the type level mapping.
- * @return
- */
- public static T methodOn(Class controller, Object... parameters) {
- return DummyInvocationUtils.methodOn(controller, parameters);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis()
- */
- @Override
- protected ControllerLinkBuilder getThis() {
- return this;
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.hateoas.UriComponentsLinkBuilder#createNewInstance(org.springframework.web.util.UriComponentsBuilder)
- */
- @Override
- protected ControllerLinkBuilder createNewInstance(UriComponentsBuilder builder) {
- return new ControllerLinkBuilder(builder);
- }
-
- /**
- * Returns a {@link UriComponentsBuilder} to continue to build the already built URI in a more fine grained way.
- *
- * @return
- */
- public UriComponentsBuilder toUriComponentsBuilder() {
- return UriComponentsBuilder.fromUri(toUri());
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.hateoas.core.LinkBuilderSupport#toString()
- */
- @Override
- public String toString() {
-
- String result = super.toString();
-
- if (variables == TemplateVariables.NONE) {
- return result;
- }
-
- if (!result.contains("#")) {
- return result.concat(variables.toString());
- }
-
- String[] parts = result.split("#");
- return parts[0].concat(variables.toString()).concat("#").concat(parts[0]);
- }
-
- /**
- * Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping with scheme tweaked in case the
- * request contains an {@code X-Forwarded-Ssl} header, which is not (yet) supported by the underlying
- * {@link UriComponentsBuilder}.
- *
- * @return
- */
- static UriComponentsBuilder getBuilder() {
-
- HttpServletRequest request = getCurrentRequest();
- UriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);
-
- // special case handling for X-Forwarded-Ssl:
- // apply it, but only if X-Forwarded-Proto is unset.
-
- String forwardedSsl = request.getHeader("X-Forwarded-Ssl");
- ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME));
- String proto = hasText(forwarded.getProto()) ? forwarded.getProto() : request.getHeader("X-Forwarded-Proto");
-
- if (!hasText(proto) && hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on")) {
- builder.scheme("https");
- }
-
- return builder;
- }
-
- /**
- * Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110 gets fixed.
- *
- * @return
- */
- @SuppressWarnings("null")
- private static HttpServletRequest getCurrentRequest() {
-
- RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
- Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING);
- Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
- HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
- Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
- return servletRequest;
- }
-
- @RequiredArgsConstructor
- private static class CachingAnnotationMappingDiscoverer implements MappingDiscoverer {
-
- private final @Delegate AnnotationMappingDiscoverer delegate;
- private final Map templates = new ConcurrentReferenceHashMap();
-
- public UriTemplate getMappingAsUriTemplate(Class> type, Method method) {
-
- String mapping = delegate.getMapping(type, method);
-
- UriTemplate template = templates.get(mapping);
-
- if (template == null) {
- template = new UriTemplate(mapping);
- templates.put(mapping, template);
- }
-
- return template;
- }
- }
-
- private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
-
- public CustomUriTemplateHandler() {
- setStrictEncoding(true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
- */
- @Override
- public UriComponents expandAndEncode(UriComponentsBuilder builder, Map uriVariables) {
- return super.expandAndEncode(builder, uriVariables);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
- */
- @Override
- public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
- return super.expandAndEncode(builder, uriVariables);
- }
- }
-}
+/*
+ * Copyright 2012-2016 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.springframework.util.StringUtils.*;
+
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.Delegate;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.TemplateVariables;
+import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
+import org.springframework.hateoas.core.DummyInvocationUtils;
+import org.springframework.hateoas.core.LinkBuilderSupport;
+import org.springframework.hateoas.core.MappingDiscoverer;
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import org.springframework.web.util.DefaultUriTemplateHandler;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UriTemplate;
+
+/**
+ * Builder to ease building {@link Link} instances pointing to Spring MVC controllers.
+ *
+ * @author Oliver Gierke
+ * @author Kamill Sokol
+ * @author Greg Turnquist
+ * @author Kevin Conaway
+ * @author Andrew Naydyonock
+ * @author Oliver Trosien
+ */
+public class ControllerLinkBuilder extends LinkBuilderSupport {
+
+ private static final String REQUEST_ATTRIBUTES_MISSING = "Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?";
+ private static final CachingAnnotationMappingDiscoverer DISCOVERER = new CachingAnnotationMappingDiscoverer(
+ new AnnotationMappingDiscoverer(RequestMapping.class));
+ private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory();
+ private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
+
+ private final TemplateVariables variables;
+
+ /**
+ * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}.
+ *
+ * @param builder must not be {@literal null}.
+ */
+ ControllerLinkBuilder(UriComponentsBuilder builder) {
+
+ super(builder);
+
+ this.variables = TemplateVariables.NONE;
+ }
+
+ /**
+ * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponents}.
+ *
+ * @param uriComponents must not be {@literal null}.
+ */
+ ControllerLinkBuilder(UriComponents uriComponents) {
+ this(uriComponents, TemplateVariables.NONE);
+ }
+
+ ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables) {
+
+ super(uriComponents);
+
+ this.variables = variables;
+ }
+
+ /**
+ * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class.
+ *
+ * @param controller the class to discover the annotation on, must not be {@literal null}.
+ * @return
+ */
+ public static ControllerLinkBuilder linkTo(Class> controller) {
+ return linkTo(controller, new Object[0]);
+ }
+
+ /**
+ * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class. The
+ * additional parameters are used to fill up potentially available path variables in the class scop request mapping.
+ *
+ * @param controller the class to discover the annotation on, must not be {@literal null}.
+ * @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
+ * {@literal null}.
+ * @return
+ */
+ public static ControllerLinkBuilder linkTo(Class> controller, Object... parameters) {
+
+ Assert.notNull(controller, "Controller must not be null!");
+ Assert.notNull(parameters, "Parameters must not be null!");
+
+ String mapping = DISCOVERER.getMapping(controller);
+
+ UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
+ UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
+
+ return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
+ }
+
+ /**
+ * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class.
+ * Parameter map is used to fill up potentially available path variables in the class scope request mapping.
+ *
+ * @param controller the class to discover the annotation on, must not be {@literal null}.
+ * @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
+ * {@literal null}.
+ * @return
+ */
+ public static ControllerLinkBuilder linkTo(Class> controller, Map parameters) {
+
+ Assert.notNull(controller, "Controller must not be null!");
+ Assert.notNull(parameters, "Parameters must not be null!");
+
+ String mapping = DISCOVERER.getMapping(controller);
+
+ UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
+ UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
+
+ return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
+ }
+
+ /*
+ * @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
+ */
+ public static ControllerLinkBuilder linkTo(Method method, Object... parameters) {
+ return linkTo(method.getDeclaringClass(), method, parameters);
+ }
+
+ /*
+ * @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class>, Method, Object...)
+ */
+ public static ControllerLinkBuilder linkTo(Class> controller, Method method, Object... parameters) {
+
+ Assert.notNull(controller, "Controller type must not be null!");
+ Assert.notNull(method, "Method must not be null!");
+
+ UriTemplate template = DISCOVERER.getMappingAsUriTemplate(controller, method);
+ URI uri = template.expand(parameters);
+
+ return new ControllerLinkBuilder(getBuilder()).slash(uri);
+ }
+
+ /**
+ * Creates a {@link ControllerLinkBuilder} pointing to a controller method. Hand in a dummy method invocation result
+ * you can create via {@link #methodOn(Class, Object...)} or {@link DummyInvocationUtils#methodOn(Class, Object...)}.
+ *
+ *
+ * @RequestMapping("/customers")
+ * class CustomerController {
+ *
+ * @RequestMapping("/{id}/addresses")
+ * HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … }
+ * }
+ *
+ * Link link = linkTo(methodOn(CustomerController.class).showAddresses(2L)).withRel("addresses");
+ *
+ *
+ * The resulting {@link Link} instance will point to {@code /customers/2/addresses} and have a rel of
+ * {@code addresses}. For more details on the method invocation constraints, see
+ * {@link DummyInvocationUtils#methodOn(Class, Object...)}.
+ *
+ * @param invocationValue
+ * @return
+ */
+ public static ControllerLinkBuilder linkTo(Object invocationValue) {
+ return FACTORY.linkTo(invocationValue);
+ }
+
+ /**
+ * Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
+ * imports of {@link ControllerLinkBuilder}.
+ *
+ * @param controller must not be {@literal null}.
+ * @param parameters parameters to extend template variables in the type level mapping.
+ * @return
+ */
+ public static T methodOn(Class controller, Object... parameters) {
+ return DummyInvocationUtils.methodOn(controller, parameters);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis()
+ */
+ @Override
+ protected ControllerLinkBuilder getThis() {
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.UriComponentsLinkBuilder#createNewInstance(org.springframework.web.util.UriComponentsBuilder)
+ */
+ @Override
+ protected ControllerLinkBuilder createNewInstance(UriComponentsBuilder builder) {
+ return new ControllerLinkBuilder(builder);
+ }
+
+ /**
+ * Returns a {@link UriComponentsBuilder} to continue to build the already built URI in a more fine grained way.
+ *
+ * @return
+ */
+ public UriComponentsBuilder toUriComponentsBuilder() {
+ return UriComponentsBuilder.fromUri(toUri());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.core.LinkBuilderSupport#toString()
+ */
+ @Override
+ public String toString() {
+
+ String result = super.toString();
+
+ if (variables == TemplateVariables.NONE) {
+ return result;
+ }
+
+ if (!result.contains("#")) {
+ return result.concat(variables.toString());
+ }
+
+ String[] parts = result.split("#");
+ return parts[0].concat(variables.toString()).concat("#").concat(parts[0]);
+ }
+
+ /**
+ * Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping with scheme tweaked in case the
+ * request contains an {@code X-Forwarded-Ssl} header, which is not (yet) supported by the underlying
+ * {@link UriComponentsBuilder}.
+ *
+ * @return
+ */
+ static UriComponentsBuilder getBuilder() {
+
+ HttpServletRequest request = getCurrentRequest();
+ UriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);
+
+ // special case handling for X-Forwarded-Ssl:
+ // apply it, but only if X-Forwarded-Proto is unset.
+
+ String forwardedSsl = request.getHeader("X-Forwarded-Ssl");
+ ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME));
+ String proto = hasText(forwarded.getProto()) ? forwarded.getProto() : request.getHeader("X-Forwarded-Proto");
+
+ if (!hasText(proto) && hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on")) {
+ builder.scheme("https");
+ }
+
+ return builder;
+ }
+
+ /**
+ * Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110 gets fixed.
+ *
+ * @return
+ */
+ @SuppressWarnings("null")
+ private static HttpServletRequest getCurrentRequest() {
+
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING);
+ Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
+ HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
+ Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
+ return servletRequest;
+ }
+
+ @RequiredArgsConstructor
+ private static class CachingAnnotationMappingDiscoverer implements MappingDiscoverer {
+
+ private final @Delegate AnnotationMappingDiscoverer delegate;
+ private final Map templates = new ConcurrentReferenceHashMap();
+
+ public UriTemplate getMappingAsUriTemplate(Class> type, Method method) {
+
+ String mapping = delegate.getMapping(type, method);
+
+ UriTemplate template = templates.get(mapping);
+
+ if (template == null) {
+ template = new UriTemplate(mapping);
+ templates.put(mapping, template);
+ }
+
+ return template;
+ }
+ }
+
+ private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
+
+ public CustomUriTemplateHandler() {
+ setStrictEncoding(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
+ */
+ @Override
+ public UriComponents expandAndEncode(UriComponentsBuilder builder, Map uriVariables) {
+ return super.expandAndEncode(builder, uriVariables);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
+ */
+ @Override
+ public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
+ return super.expandAndEncode(builder, uriVariables);
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java b/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java
index 2d51ab2c9..c097b3f84 100755
--- a/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java
+++ b/src/main/java/org/springframework/hateoas/mvc/ResourceAssemblerSupport.java
@@ -1,105 +1,105 @@
-/*
- * Copyright 2012-2013 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.springframework.hateoas.mvc.ControllerLinkBuilder.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.springframework.beans.BeanUtils;
-import org.springframework.hateoas.ResourceAssembler;
-import org.springframework.hateoas.ResourceSupport;
-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
- */
-public abstract class ResourceAssemblerSupport implements ResourceAssembler {
-
- private final Class> controllerClass;
- private final Class resourceType;
-
- /**
- * 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) {
-
- Assert.notNull(controllerClass, "ControllerClass must not be null!");
- Assert.notNull(resourceType, "ResourceType must not be null!");
-
- this.controllerClass = controllerClass;
- this.resourceType = resourceType;
- }
-
- /**
- * Converts all given entities into resources.
- *
- * @see #toResource(Object)
- * @param entities must not be {@literal null}.
- * @return
- */
- public List toResources(Iterable extends T> entities) {
-
- Assert.notNull(entities, "Entities must not be null!");
- List result = new ArrayList();
-
- for (T entity : entities) {
- result.add(toResource(entity));
- }
-
- return result;
- }
-
- /**
- * 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
- */
- protected D createResourceWithId(Object id, T entity) {
- return createResourceWithId(id, entity, new Object[0]);
- }
-
- protected D createResourceWithId(Object id, T entity, Object... parameters) {
-
- Assert.notNull(entity, "Entity must not be null!");
- Assert.notNull(id, "Id must not be null!");
-
- D instance = instantiateResource(entity);
- instance.add(linkTo(controllerClass, parameters).slash(id).withSelfRel());
- return instance;
- }
-
- /**
- * 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
- */
- protected D instantiateResource(T entity) {
- return BeanUtils.instantiateClass(resourceType);
- }
-}
+/*
+ * Copyright 2012-2013 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.springframework.hateoas.mvc.ControllerLinkBuilder.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.hateoas.ResourceAssembler;
+import org.springframework.hateoas.ResourceSupport;
+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
+ */
+public abstract class ResourceAssemblerSupport implements ResourceAssembler {
+
+ private final Class> controllerClass;
+ private final Class resourceType;
+
+ /**
+ * 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) {
+
+ Assert.notNull(controllerClass, "ControllerClass must not be null!");
+ Assert.notNull(resourceType, "ResourceType must not be null!");
+
+ this.controllerClass = controllerClass;
+ this.resourceType = resourceType;
+ }
+
+ /**
+ * Converts all given entities into resources.
+ *
+ * @see #toResource(Object)
+ * @param entities must not be {@literal null}.
+ * @return
+ */
+ public List toResources(Iterable extends T> entities) {
+
+ Assert.notNull(entities, "Entities must not be null!");
+ List result = new ArrayList();
+
+ for (T entity : entities) {
+ result.add(toResource(entity));
+ }
+
+ return result;
+ }
+
+ /**
+ * 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
+ */
+ protected D createResourceWithId(Object id, T entity) {
+ return createResourceWithId(id, entity, new Object[0]);
+ }
+
+ protected D createResourceWithId(Object id, T entity, Object... parameters) {
+
+ Assert.notNull(entity, "Entity must not be null!");
+ Assert.notNull(id, "Id must not be null!");
+
+ D instance = instantiateResource(entity);
+ instance.add(linkTo(controllerClass, parameters).slash(id).withSelfRel());
+ return instance;
+ }
+
+ /**
+ * 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
+ */
+ protected D instantiateResource(T entity) {
+ return BeanUtils.instantiateClass(resourceType);
+ }
+}
diff --git a/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTest.java b/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTest.java
index df0133d6d..c774dd29d 100644
--- a/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTest.java
+++ b/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2013-2017 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 java.io.StringWriter;
@@ -5,6 +20,7 @@
import org.junit.Before;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
@@ -12,6 +28,7 @@
*
* @author Oliver Gierke
* @author Jon Brisbin
+ * @author Greg Turnquist
*/
public abstract class AbstractJackson2MarshallingIntegrationTest {
@@ -20,6 +37,7 @@ public abstract class AbstractJackson2MarshallingIntegrationTest {
@Before
public void setUp() {
mapper = new ObjectMapper();
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
protected String write(Object object) throws Exception {
diff --git a/src/test/java/org/springframework/hateoas/LinkUnitTest.java b/src/test/java/org/springframework/hateoas/LinkUnitTest.java
index 727822f2a..e1a93373b 100644
--- a/src/test/java/org/springframework/hateoas/LinkUnitTest.java
+++ b/src/test/java/org/springframework/hateoas/LinkUnitTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2016 the original author or authors.
+ * Copyright 2012-2017 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.
@@ -28,6 +28,7 @@
* Unit tests for {@link Link}.
*
* @author Oliver Gierke
+ * @author Greg Turnquist
*/
public class LinkUnitTest {
@@ -103,11 +104,35 @@ public void returnsNullForNullOrEmptyLink() {
assertThat(Link.valueOf(""), is(nullValue()));
}
+ /**
+ * @see #54
+ * @see #100
+ */
@Test
public void parsesRFC5988HeaderIntoLink() {
assertThat(Link.valueOf(";rel=\"foo\""), is(new Link("/something", "foo")));
assertThat(Link.valueOf(";rel=\"foo\";title=\"Some title\""), is(new Link("/something", "foo")));
+ assertThat(Link.valueOf(";rel=\"self\";hreflang=\"en\";media=\"pdf\";title=\"pdf customer copy\";type=\"portable document\";deprecation=\"http://example.com/customers/deprecated\""),
+ is(new Link("/customer/1")
+ .withHreflang("en")
+ .withMedia("pdf")
+ .withTitle("pdf customer copy")
+ .withType("portable document")
+ .withDeprecation("http://example.com/customers/deprecated")));
+ }
+
+ /**
+ * @see #100
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void rejectsUnrecognizedAttributes() {
+ try {
+ Link.valueOf(";rel=\"foo\";unknown=\"should fail\"");
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), is("Link contains invalid RFC5988 headers! => [unknown]"));
+ throw e;
+ }
}
@Test(expected = IllegalArgumentException.class)
diff --git a/src/test/java/org/springframework/hateoas/LinksUnitTest.java b/src/test/java/org/springframework/hateoas/LinksUnitTest.java
index b16004afa..cb6b38b53 100644
--- a/src/test/java/org/springframework/hateoas/LinksUnitTest.java
+++ b/src/test/java/org/springframework/hateoas/LinksUnitTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2016 the original author or authors.
+ * Copyright 2013-2017 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.
@@ -27,6 +27,7 @@
* Unit test for {@link Links}.
*
* @author Oliver Gierke
+ * @author Greg Turnquist
*/
public class LinksUnitTest {
@@ -36,20 +37,40 @@ public class LinksUnitTest {
static final String LINKS = StringUtils.collectionToCommaDelimitedString(Arrays.asList(FIRST, SECOND));
+ static final String THIRD = ";rel=\"foo\";hreflang=\"en\"";
+ static final String FOURTH = ";rel=\"bar\";hreflang=\"de\"";
+
+ 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"), new Link("/somethingElse", "bar").withHreflang("de"));
+ /**
+ * @see #54
+ * @ssee #100
+ */
@Test
public void parsesLinkHeaderLinks() {
assertThat(Links.valueOf(LINKS), is(reference));
+ assertThat(Links.valueOf(LINKS2), is(reference2));
assertThat(reference.toString(), is(LINKS));
+ assertThat(reference2.toString(), is(LINKS2));
}
+ /**
+ * @see #54
+ * @ssee #100
+ */
@Test
public void skipsEmptyLinkElements() {
assertThat(Links.valueOf(LINKS + ",,,"), is(reference));
+ assertThat(Links.valueOf(LINKS2 + ",,,"), is(reference2));
}
+ /**
+ * @see #54
+ */
@Test
public void returnsNullForNullOrEmptySource() {
@@ -57,9 +78,14 @@ public void returnsNullForNullOrEmptySource() {
assertThat(Links.valueOf(""), is(Links.NO_LINKS));
}
+ /**
+ * @see #54
+ * @ssee #100
+ */
@Test
public void getSingleLinkByRel() {
assertThat(reference.getLink("bar"), is(new Link("/somethingElse", "bar")));
+ assertThat(reference2.getLink("bar"), is(new Link("/somethingElse", "bar").withHreflang("de")));
}
/**
diff --git a/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java b/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java
index a30cc72af..06877c4c2 100644
--- a/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java
+++ b/src/test/java/org/springframework/hateoas/ResourceSupportUnitTest.java
@@ -20,6 +20,7 @@
import java.util.Arrays;
+import org.hamcrest.Matchers;
import org.junit.Test;
/**
@@ -36,6 +37,7 @@ public void setsUpWithEmptyLinkList() {
assertThat(support.hasLinks(), is(false));
assertThat(support.hasLink(Link.REL_SELF), is(false));
assertThat(support.getLinks().isEmpty(), is(true));
+ assertThat(support.getLinks(Link.REL_SELF).isEmpty(), is(true));
}
@Test
@@ -49,6 +51,21 @@ public void addsLinkCorrectly() {
assertThat(support.hasLinks(), is(true));
assertThat(support.hasLink(link.getRel()), is(true));
assertThat(support.getLink(link.getRel()), is(link));
+ assertThat(support.getLinks(Link.REL_NEXT), contains(link));
+ }
+
+ @Test
+ public void addsMultipleLinkRelationsCorrectly() {
+
+ Link link = new Link("/customers/1", "customers");
+ Link link2 = new Link("/orders/1/customer", "customers");
+ ResourceSupport support = new ResourceSupport();
+ support.add(link, link2);
+
+ assertThat(support.getLinks("customers").size(), is(2));
+ assertThat(support.getLinks("customers"), contains(link, link2));
+ assertThat(support.getLinks("non-existent").size(), is(0));
+ assertThat(support.getLinks("non-existent"), is(Matchers.empty()));
}
@Test
@@ -64,6 +81,8 @@ public void addsLinksCorrectly() {
assertThat(support.hasLinks(), is(true));
assertThat(support.getLinks(), hasItems(first, second));
assertThat(support.getLinks().size(), is(2));
+ assertThat(support.getLinks(Link.REL_PREVIOUS), contains(first));
+ assertThat(support.getLinks(Link.REL_NEXT), contains(second));
}
@Test
diff --git a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java
index 5e20279dc..ed9327f91 100644
--- a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java
+++ b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2016 the original author or authors.
+ * Copyright 2013-2017 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.
@@ -35,6 +35,7 @@
* Unit tests for {@link DefaultCurieProvider}.
*
* @author Oliver Gierke
+ * @author Greg Turnquist
*/
public class DefaultCurieProviderUnitTest {
@@ -82,6 +83,19 @@ public void doesNotPrefixQualifiedRels() {
assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "custom:rel")), is("custom:rel"));
}
+ /**
+ * @see #100
+ */
+ @Test
+ public void prefixesNormalRelsThatHaveExtraRFC5988Attributes() {
+ assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "custom:rel")
+ .withHreflang("en")
+ .withTitle("the title")
+ .withMedia("the media")
+ .withType("the type")
+ .withDeprecation("http://example.com/custom/deprecated")), is("custom:rel"));
+ }
+
/**
* @see #229
*/
diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java
index df858c8df..0ae161f23 100644
--- a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java
+++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2015 the original author or authors.
+ * Copyright 2012-2017 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.
@@ -27,6 +27,7 @@
import org.junit.Before;
import org.junit.Test;
+
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.MessageSourceAccessor;
@@ -51,6 +52,7 @@
*
* @author Alexander Baetz
* @author Oliver Gierke
+ * @author Greg Turnquist
*/
public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingIntegrationTest {
@@ -77,6 +79,9 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg
static final String LINK_WITH_TITLE = "{\"_links\":{\"ns:foobar\":{\"href\":\"target\",\"title\":\"Foobar's title!\"}}}";
+ static final String SINGLE_WITH_ONE_EXTRA_ATTRIBUTES = "{\"_links\":{\"self\":{\"href\":\"localhost\",\"title\":\"the title\"}}}";
+ static final String SINGLE_WITH_ALL_EXTRA_ATTRIBUTES = "{\"_links\":{\"self\":{\"href\":\"localhost\",\"hreflang\":\"en\",\"title\":\"the title\",\"type\":\"the type\",\"deprecation\":\"/customers/deprecated\"}}}";
+
@Before
public void setUpModule() {
@@ -96,6 +101,33 @@ public void rendersSingleLinkAsObject() throws Exception {
assertThat(write(resourceSupport), is(SINGLE_LINK_REFERENCE));
}
+ /**
+ * @see #100
+ */
+ @Test
+ public void rendersAllExtraRFC5988Attributes() throws Exception {
+
+ ResourceSupport resourceSupport = new ResourceSupport();
+ resourceSupport.add(new Link("localhost", "self")
+ .withHreflang("en")
+ .withTitle("the title")
+ .withType("the type")
+ .withMedia("the media")
+ .withDeprecation("/customers/deprecated"));
+
+ assertThat(write(resourceSupport), is(SINGLE_WITH_ALL_EXTRA_ATTRIBUTES));
+ }
+
+ @Test
+ public void rendersWithOneExtraRFC5988Attributes() throws Exception {
+
+ ResourceSupport resourceSupport = new ResourceSupport();
+ resourceSupport.add(new Link("localhost", "self")
+ .withTitle("the title"));
+
+ assertThat(write(resourceSupport), is(SINGLE_WITH_ONE_EXTRA_ATTRIBUTES));
+ }
+
@Test
public void deserializeSingleLink() throws Exception {
ResourceSupport expected = new ResourceSupport();