From 0fdf6633f4b600d2f22cf721a3e3c7c913bfa889 Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Sat, 13 Jun 2015 00:08:17 +0200 Subject: [PATCH 01/27] Allow for links property to be rendered to JSON when filtered by providing a tooling interface. Signed-off-by: Karsten Tinnefeld --- .../java/org/springframework/hateoas/ResourceSupport.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index 64de72076..36a03197f 100755 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonView; /** * Base class for DTOs to collect links. @@ -33,6 +34,8 @@ */ public class ResourceSupport implements Identifiable { + public interface ResourceSupportView {} + private final List links; public ResourceSupport() { @@ -105,6 +108,7 @@ public boolean hasLink(String rel) { */ @XmlElement(name = "link", namespace = Link.ATOM_NAMESPACE) @JsonProperty("links") + @JsonView(ResourceSupportView.class) public List getLinks() { return links; } From 376740fed0f1d73a0919928ee64a1c28f8101058 Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Sat, 13 Jun 2015 15:50:34 +0200 Subject: [PATCH 02/27] Explain bundlor to eclipse. Signed-off-by: Karsten Tinnefeld --- pom.xml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pom.xml b/pom.xml index 0571be5f4..38ca397a5 100644 --- a/pom.xml +++ b/pom.xml @@ -580,6 +580,36 @@ + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + [1.0,2.0) + + bundlor + + + + + true + + + + + + + + + @@ -590,6 +620,7 @@ + org.apache.maven.plugins maven-compiler-plugin From d2f28342221e65a36120451e319e80c1fc687372 Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Sat, 13 Jun 2015 19:28:08 +0200 Subject: [PATCH 03/27] Revert "Allow for links property to be rendered to JSON when filtered by providing a tooling interface." This reverts commit 0fdf6633f4b600d2f22cf721a3e3c7c913bfa889. --- .../java/org/springframework/hateoas/ResourceSupport.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index 36a03197f..64de72076 100755 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -25,7 +25,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonView; /** * Base class for DTOs to collect links. @@ -34,8 +33,6 @@ */ public class ResourceSupport implements Identifiable { - public interface ResourceSupportView {} - private final List links; public ResourceSupport() { @@ -108,7 +105,6 @@ public boolean hasLink(String rel) { */ @XmlElement(name = "link", namespace = Link.ATOM_NAMESPACE) @JsonProperty("links") - @JsonView(ResourceSupportView.class) public List getLinks() { return links; } From e1623fbac6d88f19542b6d6a50defc204656be86 Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Sat, 13 Jun 2015 19:34:27 +0200 Subject: [PATCH 04/27] Allow for links property to be rendered to JSON when filtered by providing a tooling interface, now complete. Signed-off-by: Karsten Tinnefeld --- src/main/java/org/springframework/hateoas/Link.java | 5 +++++ .../java/org/springframework/hateoas/ResourceSupport.java | 1 + 2 files changed, 6 insertions(+) mode change 100755 => 100644 src/main/java/org/springframework/hateoas/ResourceSupport.java diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index 95457ae1c..e512690b1 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonView; /** * Value object for links. @@ -42,6 +43,8 @@ @JsonIgnoreProperties("templated") public class Link implements Serializable { + public interface LinkView {} + private static final long serialVersionUID = -9037755944661782121L; public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"; @@ -104,6 +107,7 @@ protected Link() { * * @return */ + @JsonView(LinkView.class) public String getHref() { return href; } @@ -113,6 +117,7 @@ public String getHref() { * * @return */ + @JsonView(LinkView.class) public String getRel() { return rel; } diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java old mode 100755 new mode 100644 index 64de72076..fe574126f --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -105,6 +105,7 @@ public boolean hasLink(String rel) { */ @XmlElement(name = "link", namespace = Link.ATOM_NAMESPACE) @JsonProperty("links") + @JsonView(Link.LinkView.class) public List getLinks() { return links; } From 8da55e125687de45f1e2153ee885bc5e5137137a Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Sun, 14 Jun 2015 13:02:37 +0200 Subject: [PATCH 05/27] Missing import added (merge problem). Signed-off-by: Karsten Tinnefeld --- src/main/java/org/springframework/hateoas/ResourceSupport.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index fe574126f..e8d63a83e 100644 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonView; /** * Base class for DTOs to collect links. From af813ca1063543f4bf8099ce431c04e804e75284 Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Wed, 17 Jun 2015 23:45:02 +0200 Subject: [PATCH 06/27] Support views for Resource and Resources as well. Signed-off-by: Karsten Tinnefeld --- src/main/java/org/springframework/hateoas/Resource.java | 2 ++ src/main/java/org/springframework/hateoas/Resources.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/org/springframework/hateoas/Resource.java b/src/main/java/org/springframework/hateoas/Resource.java index 17aa48ae7..2237f4c9e 100644 --- a/src/main/java/org/springframework/hateoas/Resource.java +++ b/src/main/java/org/springframework/hateoas/Resource.java @@ -24,6 +24,7 @@ import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonView; /** * A simple {@link Resource} wrapping a domain object and adding links to it. @@ -73,6 +74,7 @@ public Resource(T content, Iterable links) { */ @JsonUnwrapped @XmlAnyElement + @JsonView(Link.LinkView.class) public T getContent() { return content; } diff --git a/src/main/java/org/springframework/hateoas/Resources.java b/src/main/java/org/springframework/hateoas/Resources.java index 04f4142a9..712f4c4c7 100644 --- a/src/main/java/org/springframework/hateoas/Resources.java +++ b/src/main/java/org/springframework/hateoas/Resources.java @@ -28,6 +28,8 @@ import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonView; /** * General helper to easily create a wrapper for a collection of entities. @@ -101,6 +103,7 @@ public static , S> Resources wrap(Iterable content) @XmlAnyElement @XmlElementWrapper @JsonProperty("content") + @JsonView(Link.LinkView.class) public Collection getContent() { return Collections.unmodifiableCollection(content); } From 57d2be043b3a4350ee274c2cced9c5d5131dd37c Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Fri, 19 Jun 2015 22:05:31 +0200 Subject: [PATCH 07/27] Move marker interface to separate module. Signed-off-by: Karsten Tinnefeld --- .../org/springframework/hateoas/Link.java | 6 ++-- .../org/springframework/hateoas/Resource.java | 2 +- .../hateoas/ResourceSupport.java | 2 +- .../springframework/hateoas/Resources.java | 3 +- .../hateoas/ResourcesLinksVisible.java | 32 +++++++++++++++++++ .../hateoas/mvc/ControllerLinkBuilder.java | 1 - 6 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/springframework/hateoas/ResourcesLinksVisible.java diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index e512690b1..4ffd5a590 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -43,8 +43,6 @@ @JsonIgnoreProperties("templated") public class Link implements Serializable { - public interface LinkView {} - private static final long serialVersionUID = -9037755944661782121L; public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"; @@ -107,7 +105,7 @@ protected Link() { * * @return */ - @JsonView(LinkView.class) + @JsonView(ResourcesLinksVisible.class) public String getHref() { return href; } @@ -117,7 +115,7 @@ public String getHref() { * * @return */ - @JsonView(LinkView.class) + @JsonView(ResourcesLinksVisible.class) public String getRel() { return rel; } diff --git a/src/main/java/org/springframework/hateoas/Resource.java b/src/main/java/org/springframework/hateoas/Resource.java index 2237f4c9e..36089ca0a 100644 --- a/src/main/java/org/springframework/hateoas/Resource.java +++ b/src/main/java/org/springframework/hateoas/Resource.java @@ -74,7 +74,7 @@ public Resource(T content, Iterable links) { */ @JsonUnwrapped @XmlAnyElement - @JsonView(Link.LinkView.class) + @JsonView(ResourcesLinksVisible.class) public T getContent() { return content; } diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index e8d63a83e..40cd2ce0a 100644 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -106,7 +106,7 @@ public boolean hasLink(String rel) { */ @XmlElement(name = "link", namespace = Link.ATOM_NAMESPACE) @JsonProperty("links") - @JsonView(Link.LinkView.class) + @JsonView(ResourcesLinksVisible.class) public List getLinks() { return links; } diff --git a/src/main/java/org/springframework/hateoas/Resources.java b/src/main/java/org/springframework/hateoas/Resources.java index 712f4c4c7..e43e60664 100644 --- a/src/main/java/org/springframework/hateoas/Resources.java +++ b/src/main/java/org/springframework/hateoas/Resources.java @@ -28,7 +28,6 @@ import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.annotation.JsonView; /** @@ -103,7 +102,7 @@ public static , S> Resources wrap(Iterable content) @XmlAnyElement @XmlElementWrapper @JsonProperty("content") - @JsonView(Link.LinkView.class) + @JsonView(ResourcesLinksVisible.class) public Collection getContent() { return Collections.unmodifiableCollection(content); } diff --git a/src/main/java/org/springframework/hateoas/ResourcesLinksVisible.java b/src/main/java/org/springframework/hateoas/ResourcesLinksVisible.java new file mode 100644 index 000000000..4e348dc6a --- /dev/null +++ b/src/main/java/org/springframework/hateoas/ResourcesLinksVisible.java @@ -0,0 +1,32 @@ +/* + * Copyright 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; + +/** + * Parent interface to mark {@link Resource}(s) beans that {@link Link}s and Resource + * {@code content} are part of a JSON view. + * + * Usage: subinterface and mark all view relevant properties of your bean + * {@code @JSonView(SubInterface.class)}. Then serialize bean of {@code new Resource<>(bean)} to + * JSON. Your ({@code content} with) relevant properties and all associated {@code labels} will be + * visible. + * + * @see com.fasterxml.jackson.annotation.JsonView + * @since 0.18.0 + * + * @author Karsten Tinnefeld + */ +public interface ResourcesLinksVisible {} diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index 1c760e9ff..80c00824c 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -235,7 +235,6 @@ static UriComponentsBuilder getBuilder() { * * @return */ - @SuppressWarnings("null") private static HttpServletRequest getCurrentRequest() { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); From d4bba8fa898b47d67df4ab8bfdb53d9760f2e5f1 Mon Sep 17 00:00:00 2001 From: Karsten Tinnefeld Date: Fri, 19 Jun 2015 22:13:52 +0200 Subject: [PATCH 08/27] general contribution clean-up --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 38ca397a5..c9e0adea7 100644 --- a/pom.xml +++ b/pom.xml @@ -620,7 +620,6 @@ - org.apache.maven.plugins maven-compiler-plugin From 2222a40cb3406738d92efa30e7ad0dd967394c7f Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 4 Aug 2015 10:48:07 +0200 Subject: [PATCH 09/27] #353 - Dependency upgrades. Spring Framework 4.0.9 -> 4.1.7 Minidev JSON 2.1.1 -> 2.2 JSONPath 0.9.1 -> 2.0.0 Spring Data Build Resources 1.6.0 -> 1.6.2 Lombok 1.14.4 -> 1.16.4 JUnit 4.11 -> 4.12 Mockito 1.9.5 -> 1.10.19 JodaTime 2.3 -> 2.8.1 XMLUnit 1.5 -> 1.6 Asciidoctor Maven Plugin 1.5.2 -> 1.5.2.1 AsciidoctorJ PDF 1.5.0-alpha.6 -> 1.5.0-alpha.8 Maven Assembly Plugin 2.5.4 -> 2.5.5 Updated available build profiles. Update Javadoc reference to link to used Spring Framework version. --- .travis.yml | 5 ++--- pom.xml | 58 +++++++++++++++-------------------------------------- 2 files changed, 18 insertions(+), 45 deletions(-) diff --git a/.travis.yml b/.travis.yml index 853adf560..c005f2430 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,11 @@ jdk: env: matrix: - PROFILE=non-existant - - PROFILE=spring41 - - PROFILE=spring41-next + - PROFILE=spring42 - PROFILE=spring42-next cache: directories: - $HOME/.m2 sudo: false install: true -script: "mvn clean dependency:list test -P${PROFILE} -Dsort" \ No newline at end of file +script: "mvn clean dependency:list test -P${PROFILE} -Dsort" diff --git a/pom.xml b/pom.xml index 7780a3c5f..1899e1301 100644 --- a/pom.xml +++ b/pom.xml @@ -58,12 +58,12 @@ UTF-8 - 4.0.9.RELEASE + 4.1.7.RELEASE 1.1.3 2.5.4 1.0 - 2.1.1 - 0.9.1 + 2.2 + 2.0.0 1.7.12 1.2.1 true @@ -73,42 +73,16 @@ - spring40-next + spring42 - 4.0.10.BUILD-SNAPSHOT + 4.2.0.RELEASE - - - spring-libs-snapshot - http://repo.spring.io/libs-snapshot - - - - - - spring41 - - 4.1.6.RELEASE - - - - - spring41-next - - 4.1.6.BUILD-SNAPSHOT - - - - spring-libs-snapshot - http://repo.spring.io/libs-snapshot - - spring42-next - 4.2.0.BUILD-SNAPSHOT + 4.2.1.BUILD-SNAPSHOT @@ -167,7 +141,7 @@ org.springframework.data.build spring-data-build-resources - 1.6.0.RELEASE + 1.6.2.RELEASE provided zip @@ -223,12 +197,12 @@ org.asciidoctor asciidoctor-maven-plugin - 1.5.2 + 1.5.2.1 org.asciidoctor asciidoctorj-pdf - 1.5.0-alpha.6 + 1.5.0-alpha.8 org.asciidoctor @@ -355,7 +329,7 @@ org.apache.maven.plugins maven-assembly-plugin - 2.5.4 + 2.5.5 static @@ -495,7 +469,7 @@ org.projectlombok lombok - 1.14.4 + 1.16.4 provided @@ -529,7 +503,7 @@ junit junit - 4.11 + 4.12 test @@ -543,21 +517,21 @@ org.mockito mockito-all - 1.9.5 + 1.10.19 test joda-time joda-time - 2.3 + 2.8.1 test xmlunit xmlunit - 1.5 + 1.6 test @@ -652,7 +626,7 @@ -Xdoclint:none ${shared.resources}/javadoc/spring-javadoc.css - http://static.springframework.org/spring/docs/3.2.x/javadoc-api + http://static.springframework.org/spring/docs/4.1.x/javadoc-api http://docs.oracle.com/javase/6/docs/api From c73dde68f4b08bc3a36355ea4f77e986a5418d69 Mon Sep 17 00:00:00 2001 From: Angel Aguilera Date: Mon, 13 Jul 2015 11:26:27 +0200 Subject: [PATCH 10/27] #364 - Add missing parentheses in reference documentation. Original pull request: #364. --- src/main/asciidoc/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 1930df8a9..a58f25e1d 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -87,7 +87,7 @@ You can also easily access links contained in that resource: [source, java] ---- Link selfLink = new Link("http://myhost/people"); -assertThat(resource.getId(), is(selfLink); +assertThat(resource.getId(), is(selfLink)); assertThat(resource.getLink(Link.SELF), is(selfLink)); ---- [[fundamentals.obtaining-links]] From 2cec9982b5d63f3e7dd7b975af093bf5e31bb689 Mon Sep 17 00:00:00 2001 From: Przemek Nowak Date: Sat, 4 Apr 2015 17:57:00 +0200 Subject: [PATCH 11/27] #247 - Added HAL_JSON_VALUE in MediaTypes. Original pull request: #329. --- .../org/springframework/hateoas/MediaTypes.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/MediaTypes.java b/src/main/java/org/springframework/hateoas/MediaTypes.java index 0b7699880..6e020bdab 100644 --- a/src/main/java/org/springframework/hateoas/MediaTypes.java +++ b/src/main/java/org/springframework/hateoas/MediaTypes.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -21,8 +21,17 @@ * Constants for well-known hypermedia types. * * @author Oliver Gierke + * @author Przemek Nowak */ public class MediaTypes { - public static final MediaType HAL_JSON = MediaType.valueOf("application/hal+json"); + /** + * A String equivalent of {@link MediaTypes#HAL_JSON}. + */ + public static final String HAL_JSON_VALUE = "application/hal+json"; + + /** + * Public constant media type for {@code application/hal+json}. + */ + public static final MediaType HAL_JSON = MediaType.valueOf(HAL_JSON_VALUE); } From d0691ab51b1ae7842f2989d76aae26aef9ddd49f Mon Sep 17 00:00:00 2001 From: Spring Buildmaster Date: Tue, 4 Aug 2015 02:04:10 -0700 Subject: [PATCH 12/27] #353 - Release version 0.18.0.RELEASE. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1899e1301..e3276c68d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.springframework.hateoas spring-hateoas - 0.18.0.BUILD-SNAPSHOT + 0.18.0.RELEASE Spring HATEOAS http://github.com/SpringSource/spring-hateoas From 01d24895049f8bcd12b254140563431e3c347a3a Mon Sep 17 00:00:00 2001 From: Spring Buildmaster Date: Tue, 4 Aug 2015 02:04:13 -0700 Subject: [PATCH 13/27] #353 - Prepare next development iteration. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3276c68d..259e393ea 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.springframework.hateoas spring-hateoas - 0.18.0.RELEASE + 0.19.0.BUILD-SNAPSHOT Spring HATEOAS http://github.com/SpringSource/spring-hateoas From e6e23b4330e9454254792d2d0b7f8b561238fd4f Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 4 Aug 2015 11:09:43 +0200 Subject: [PATCH 14/27] #353 - After release cleanups. Updated changelog. --- src/main/resources/changelog.txt | 20 +++++++++++++++++++ .../hateoas/support/ChangelogCreator.java | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index b317ae905..59c3694f1 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,26 @@ Spring HATEOAS Changelog ======================== +Changes in version 0.18.0.RELEASE (2015-08-04) +---------------------------------------------- +- #369 - Traverson's getAndFindLinkWithRel(…) needs to expand given URI defensively. +- #368 - Assert Jackson 2.6 compatibility. +- #364 - Added missing parentheses in reference documentation. +- #360 - TypeConstrainedMappingJackson2HttpMessageConverter needs to override canRead(Type, Class, MediaType). +- #356 - #355 - Clean up Traverson section and explain Resources vs. Resource. +- #355 - Traverson docs need more cleanup. +- #353 - Release 0.18.0. +- #347 - Augment .gitignore to contain exclusions for Intellij. +- #342 - Augment error message when building links outside of an MVC request. +- #341 - ObjectMapper used for default HAL converter should not fail on unknown properties. +- #332 - Inconsistent behavior when using linkTo(methodOn()) on handler methods. +- #331 - Inconsistent behavior when using linkTo(methodOn()) on handler methods with defaultValue attribute set on @RequestParam. +- #329 - #247 - Added HAL_JSON_VALUE in MediaTypes class. +- #322 - Upgrade to Spring Plugin 1.2.0. +- #321 - Remove dependency to Objenesis. +- #314 - JSON-Path in HalLinkDiscoverer doesn't work for URI rels. +- #247 - Add HAL_JSON_VALUE in MediaTypes class. + Changes in version 0.17.0.RELEASE (2015-03-05) ---------------------------------------------- - #313 - Assert compatibility with both JSONPath 0.9 and 1.2. diff --git a/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java b/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java index 7662c9826..470b5231c 100644 --- a/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java +++ b/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java @@ -15,10 +15,10 @@ */ package org.springframework.hateoas.support; -import java.util.Iterator; - import net.minidev.json.JSONArray; +import java.util.Iterator; + import org.springframework.web.client.RestTemplate; import com.jayway.jsonpath.JsonPath; @@ -30,7 +30,7 @@ */ class ChangelogCreator { - private static final int MILESTONE_ID = 17; + private static final int MILESTONE_ID = 18; private static final String URI_TEMPLATE = "https://api.github.com/repos/spring-projects/spring-hateoas/issues?milestone={id}&state=closed"; public static void main(String... args) throws Exception { From dcf692cbdb43d804aa01d6e3342d088d6f63c1b2 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 11 Aug 2015 21:17:20 +0200 Subject: [PATCH 15/27] #375 - Fixed is-empty evaluation for TrueOnlyBooleanSerializer on Jackson 2.6. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added missing override for isEmpty(…) that gets used by Jackson 2.6+ to prevent { templated : false } get rendered in HAL link representations. Polished Javadoc on the way. --- .../hateoas/hal/Jackson2HalModule.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 17730b47f..4fe4a0585 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -675,22 +675,47 @@ public TrueOnlyBooleanSerializer() { super(Boolean.class); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(java.lang.Object) + */ @Override public boolean isEmpty(Boolean value) { + return isEmpty(null, value); + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) + */ + @Override + public boolean isEmpty(SerializerProvider provider, Boolean value) { return value == null || Boolean.FALSE.equals(value); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider) + */ @Override - public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) throws IOException, - JsonGenerationException { + public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonGenerationException { jgen.writeBoolean(value.booleanValue()); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.std.StdScalarSerializer#getSchema(com.fasterxml.jackson.databind.SerializerProvider, java.lang.reflect.Type) + */ @Override public JsonNode getSchema(SerializerProvider provider, Type typeHint) { return createSchemaNode("boolean", true); } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.std.StdScalarSerializer#acceptJsonFormatVisitor(com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper, com.fasterxml.jackson.databind.JavaType) + */ @Override public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { From d8cc66670365459225307d92a5637acc5beac5ea Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 13 Aug 2015 21:31:17 +0200 Subject: [PATCH 16/27] #363 - Extended DefaultCurieProvider to be able to configure multiple curies. DefaultCurieProvider now also takes a Map to expose multiple curies. The default curie to be used to expand unprefixed, non-IANA link relations has to be defined explicitly in case more than one curie is if given. If there's only one, that becomes the default, too. --- .../hateoas/hal/DefaultCurieProvider.java | 65 ++++++++++++++++--- .../hal/DefaultCurieProviderUnitTest.java | 41 +++++++++++- 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java index ee6be5053..63ee94dc7 100644 --- a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java @@ -17,12 +17,16 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; import org.springframework.hateoas.IanaRels; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; import org.springframework.hateoas.UriTemplate; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Default implementation of {@link CurieProvider} rendering a single configurable {@link UriTemplate} based curie. @@ -33,22 +37,63 @@ */ public class DefaultCurieProvider implements CurieProvider { - private final Curie curie; + private final Map curies; + private final Curie defaultCurie; /** - * Creates a new {@link DefaultCurieProvider} for the given name and {@link UriTemplate}. + * Creates a new {@link DefaultCurieProvider} for the given name and {@link UriTemplate}. The curie will be used to + * expand previously unprefixed, non-IANA link relations. * * @param name must not be {@literal null} or empty. * @param uriTemplate must not be {@literal null} and contain exactly one template variable. */ public DefaultCurieProvider(String name, UriTemplate uriTemplate) { + this(Collections.singletonMap(name, uriTemplate)); + } + + /** + * Creates a new {@link DefaultCurieProvider} for the given curies. If more than one curie is given, no default curie + * will be registered. Use {@link #DefaultCurieProvider(Map, String)} to define which of the provided curies shall be + * used as the default one. + * + * @param curies must not be {@literal null}. + * @see #DefaultCurieProvider(String, UriTemplate) + * @since 0.19 + */ + public DefaultCurieProvider(Map curies) { + this(curies, null); + } + + /** + * Creates a new {@link DefaultCurieProvider} for the given curies using the one with the given name as default, which + * means to expand unprefixed, non-IANA link relations. + * + * @param curies must not be {@literal null}. + * @param defaultCurieName can be {@literal null}. + * @since 0.19 + */ + public DefaultCurieProvider(Map curies, String defaultCurieName) { + + Assert.notNull(curies, "Curies must not be null!"); + + Map map = new HashMap(curies.size()); - Assert.hasText(name, "Name must not be null or empty!"); - Assert.notNull(uriTemplate, "UriTemplate must not be null!"); - Assert.isTrue(uriTemplate.getVariableNames().size() == 1, - String.format("Expected a single template variable in the UriTemplate %s!", uriTemplate.toString())); + for (Entry entry : curies.entrySet()) { + + String name = entry.getKey(); + UriTemplate template = entry.getValue(); + + Assert.hasText(name, "Curie name must not be null or empty!"); + Assert.notNull(template, "UriTemplate must not be null!"); + Assert.isTrue(template.getVariableNames().size() == 1, + String.format("Expected a single template variable in the UriTemplate %s!", template.toString())); + + map.put(name, new Curie(name, template.toString())); + } - this.curie = new Curie(name, uriTemplate.toString()); + this.defaultCurie = StringUtils.hasText(defaultCurieName) ? map.get(defaultCurieName) + : map.size() == 1 ? map.values().iterator().next() : null; + this.curies = Collections.unmodifiableMap(map); } /* @@ -57,7 +102,7 @@ public DefaultCurieProvider(String name, UriTemplate uriTemplate) { */ @Override public Collection getCurieInformation(Links links) { - return Collections.singleton(curie); + return Collections.unmodifiableCollection(curies.values()); } /* @@ -76,8 +121,8 @@ public String getNamespacedRelFrom(Link link) { @Override public String getNamespacedRelFor(String rel) { - boolean prefixingNeeded = !IanaRels.isIanaRel(rel) && !rel.contains(":"); - return prefixingNeeded ? String.format("%s:%s", curie.name, rel) : rel; + boolean prefixingNeeded = defaultCurie != null && !IanaRels.isIanaRel(rel) && !rel.contains(":"); + return prefixingNeeded ? String.format("%s:%s", defaultCurie.name, rel) : rel; } /** diff --git a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java index d22ec15e2..2c880beb8 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-2014 the original author or authors. + * Copyright 2013-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. @@ -15,11 +15,15 @@ */ package org.springframework.hateoas.hal; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.util.HashMap; +import java.util.Map; + import org.junit.Test; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.UriTemplate; /** @@ -96,4 +100,37 @@ public void prefixesNormalRelsForRelAsString() { public void doesNotPrefixQualifiedRelsForRelAsString() { assertThat(provider.getNamespacedRelFor("custom:rel"), is("custom:rel")); } + + /** + * @see #363 + */ + @Test + public void configuresMultipleCuriesWithoutDefaultCorrectly() { + + DefaultCurieProvider provider = new DefaultCurieProvider(getCuries()); + + assertThat(provider.getCurieInformation(new Links()), hasSize(2)); + assertThat(provider.getNamespacedRelFor("some"), is("some")); + } + + /** + * @see #363 + */ + @Test + public void configuresMultipleCuriesWithDefaultCorrectly() { + + DefaultCurieProvider provider = new DefaultCurieProvider(getCuries(), "foo"); + + assertThat(provider.getCurieInformation(new Links()), hasSize(2)); + assertThat(provider.getNamespacedRelFor("some"), is("foo:some")); + } + + private static Map getCuries() { + + Map curies = new HashMap(2); + curies.put("foo", new UriTemplate("/foo/{rel}")); + curies.put("bar", new UriTemplate("/bar/{rel}")); + + return curies; + } } From d77a4b65f5f670340de4e106e755b456c421e8bc Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 19 Aug 2015 16:35:04 +0200 Subject: [PATCH 17/27] #320 - Overhaul in curie rendering. The curie rendering has been improved in the following ways: - Resources now get their embeds inspected for namespaced keys and curies are added if at least one is found. This makes sure curies are also rendered if no namespaced link is registered. - Nested resources skip curies entirely, assuming they have been added to the root correctly. Had to change the order of _embedded and _links to make sure the embedded elements are processed first to tweak the links to indicate the need for curies. --- .../hateoas/hal/Jackson2HalModule.java | 146 ++++++++++++++---- .../hateoas/hal/ResourcesMixin.java | 2 + .../hal/Jackson2HalIntegrationTest.java | 35 ++--- 3 files changed, 131 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 4fe4a0585..87b5ebd36 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * 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. @@ -78,6 +78,7 @@ public class Jackson2HalModule extends SimpleModule { private static final long serialVersionUID = 7806951456457932384L; + private static final Link CURIES_REQUIRED_DUE_TO_EMBEDS = new Link("__rel__", "¯\\_(ツ)_/¯"); public Jackson2HalModule() { @@ -106,20 +107,22 @@ public static boolean isAlreadyRegisteredIn(ObjectMapper mapper) { * @author Alexander Baetz * @author Oliver Gierke */ - public static class HalLinkListSerializer extends ContainerSerializer> implements ContextualSerializer { + public static class HalLinkListSerializer extends ContainerSerializer>implements ContextualSerializer { private final BeanProperty property; private final CurieProvider curieProvider; + private final EmbeddedMapper mapper; - public HalLinkListSerializer(CurieProvider curieProvider) { - this(null, curieProvider); + public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper) { + this(null, curieProvider, mapper); } - public HalLinkListSerializer(BeanProperty property, CurieProvider curieProvider) { + public HalLinkListSerializer(BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper) { super(List.class, false); this.property = property; this.curieProvider = curieProvider; + this.mapper = mapper; } /* @@ -136,9 +139,22 @@ public void serialize(List value, JsonGenerator jgen, SerializerProvider p boolean prefixingRequired = curieProvider != null; boolean curiedLinkPresent = false; + boolean skipCuries = !jgen.getOutputContext().getParent().inRoot(); + + Object currentValue = jgen.getCurrentValue(); + + if (currentValue instanceof Resources) { + if (mapper.hasCuriedEmbed((Resources) currentValue)) { + curiedLinkPresent = true; + } + } for (Link link : value) { + if (link.equals(CURIES_REQUIRED_DUE_TO_EMBEDS)) { + continue; + } + String rel = prefixingRequired ? curieProvider.getNamespacedRelFrom(link) : link.getRel(); if (!link.getRel().equals(rel)) { @@ -153,7 +169,7 @@ public void serialize(List value, JsonGenerator jgen, SerializerProvider p sortedLinks.get(rel).add(link); } - if (prefixingRequired && curiedLinkPresent) { + if (!skipCuries && prefixingRequired && curiedLinkPresent) { ArrayList curies = new ArrayList(); curies.add(curieProvider.getCurieInformation(new Links(links))); @@ -179,7 +195,7 @@ public void serialize(List value, JsonGenerator jgen, SerializerProvider p @Override public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { - return new HalLinkListSerializer(property, curieProvider); + return new HalLinkListSerializer(property, curieProvider, mapper); } /* @@ -234,27 +250,21 @@ protected ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { * @author Alexander Baetz * @author Oliver Gierke */ - public static class HalResourcesSerializer extends ContainerSerializer> implements ContextualSerializer { + public static class HalResourcesSerializer extends ContainerSerializer>implements ContextualSerializer { private final BeanProperty property; - private final RelProvider relProvider; - private final CurieProvider curieProvider; - private final boolean enforceEmbeddedCollections; + private final EmbeddedMapper embeddedMapper; - public HalResourcesSerializer(RelProvider relPorvider, CurieProvider curieProvider, - boolean enforceEmbeddedCollections) { - this(null, relPorvider, curieProvider, enforceEmbeddedCollections); + public HalResourcesSerializer(EmbeddedMapper embeddedMapper) { + this(null, embeddedMapper); } - public HalResourcesSerializer(BeanProperty property, RelProvider relProvider, CurieProvider curieProvider, - boolean enforceEmbeddedCollections) { + public HalResourcesSerializer(BeanProperty property, EmbeddedMapper embeddedMapper) { super(Collection.class, false); this.property = property; - this.relProvider = relProvider; - this.curieProvider = curieProvider; - this.enforceEmbeddedCollections = enforceEmbeddedCollections; + this.embeddedMapper = embeddedMapper; } /* @@ -264,22 +274,27 @@ public HalResourcesSerializer(BeanProperty property, RelProvider relProvider, Cu * org.codehaus.jackson.map.SerializerProvider) */ @Override - public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider) throws IOException, - JsonGenerationException { + public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonGenerationException { - HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider, curieProvider, enforceEmbeddedCollections); + Map embeddeds = embeddedMapper.map(value); - for (Object resource : value) { - builder.add(resource); + Object currentValue = jgen.getCurrentValue(); + + if (currentValue instanceof ResourceSupport) { + + if (embeddedMapper.hasCuriedEmbed(value)) { + ((ResourceSupport) currentValue).add(CURIES_REQUIRED_DUE_TO_EMBEDS); + } } - provider.findValueSerializer(Map.class, property).serialize(builder.asMap(), jgen, provider); + provider.findValueSerializer(Map.class, property).serialize(embeddeds, jgen, provider); } @Override public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { - return new HalResourcesSerializer(property, relProvider, curieProvider, enforceEmbeddedCollections); + return new HalResourcesSerializer(property, embeddedMapper); } @Override @@ -554,8 +569,8 @@ public JsonDeserializer getContentDeserializer() { * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) */ @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, - JsonProcessingException { + public List deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { List result = new ArrayList(); JsonDeserializer deser = ctxt.findRootValueDeserializer(contentType); @@ -600,12 +615,14 @@ public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider) this(resolver, curieProvider, true); } - public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, boolean enforceEmbeddedCollections) { + public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, + boolean enforceEmbeddedCollections) { + + EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider, enforceEmbeddedCollections); Assert.notNull(resolver, "RelProvider must not be null!"); - this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(resolver, curieProvider, - enforceEmbeddedCollections)); - this.instanceMap.put(HalLinkListSerializer.class, new HalLinkListSerializer(curieProvider)); + this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(mapper)); + this.instanceMap.put(HalLinkListSerializer.class, new HalLinkListSerializer(curieProvider, mapper)); } private Object findInstance(Class type) { @@ -724,4 +741,69 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t } } } + + /** + * Helper to easily map embedded resources and find out whether they were curied. + * + * @author Oliver Gierke + */ + private static class EmbeddedMapper { + + private RelProvider relProvider; + private CurieProvider curieProvider; + private boolean preferCollectionRels; + + /** + * Creates a new {@link EmbeddedMapper} for the given {@link RelProvider}, {@link CurieProvider} and flag whether to + * prefer collection relations. + * + * @param relProvider must not be {@literal null}. + * @param curieProvider can be {@literal null}. + * @param preferCollectionRels + */ + public EmbeddedMapper(RelProvider relProvider, CurieProvider curieProvider, boolean preferCollectionRels) { + + Assert.notNull(relProvider, "RelProvider must not be null!"); + + this.relProvider = relProvider; + this.curieProvider = curieProvider; + this.preferCollectionRels = preferCollectionRels; + } + + /** + * Maps the given source elements as embedded values. + * + * @param source must not be {@literal null}. + * @return + */ + public Map map(Iterable source) { + + Assert.notNull(source, "Elements must not be null!"); + + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider, curieProvider, preferCollectionRels); + + for (Object resource : source) { + builder.add(resource); + } + + return builder.asMap(); + } + + /** + * Returns whether the given source elements will be namespaced. + * + * @param source must not be {@literal null}. + * @return + */ + public boolean hasCuriedEmbed(Iterable source) { + + for (String rel : map(source).keySet()) { + if (rel.contains(":")) { + return true; + } + } + + return false; + } + } } diff --git a/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java b/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java index e95d07355..941ec600c 100644 --- a/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java +++ b/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java @@ -22,9 +22,11 @@ import org.springframework.hateoas.Resources; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +@JsonPropertyOrder({ "content", "links" }) public abstract class ResourcesMixin extends Resources { @Override diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java index 1ff09e28c..9ef8d1471 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-2014 the original author or authors. + * 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. @@ -52,14 +52,14 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final String SINGLE_LINK_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}}}"; static final String LIST_LINK_REFERENCE = "{\"_links\":{\"self\":[{\"href\":\"localhost\"},{\"href\":\"localhost2\"}]}}"; - static final String SIMPLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"content\":[\"first\",\"second\"]}}"; - static final String SINGLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; - static final String LIST_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; + static final String SIMPLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"content\":[\"first\",\"second\"]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}"; + static final String SINGLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}"; + static final String LIST_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}"; - static final String ANNOTATED_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; + static final String ANNOTATED_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}"; static final String ANNOTATED_EMBEDDED_RESOURCES_REFERENCE = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; - static final String ANNOTATED_PAGED_RESOURCES = "{\"_links\":{\"next\":{\"href\":\"foo\"},\"prev\":{\"href\":\"bar\"}},\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"page\":{\"size\":2,\"totalElements\":4,\"totalPages\":2,\"number\":0}}"; + static final String ANNOTATED_PAGED_RESOURCES = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"next\":{\"href\":\"foo\"},\"prev\":{\"href\":\"bar\"}},\"page\":{\"size\":2,\"totalElements\":4,\"totalPages\":2,\"number\":0}}"; static final Links PAGINATION_LINKS = new Links(new Link("foo", Link.REL_NEXT), new Link("bar", Link.REL_PREVIOUS)); @@ -142,8 +142,8 @@ public void deserializesSimpleResourcesAsEmbedded() throws Exception { Resources expected = new Resources(content); expected.add(new Link("localhost")); - Resources result = mapper.readValue(SIMPLE_EMBEDDED_RESOURCE_REFERENCE, mapper.getTypeFactory() - .constructParametricType(Resources.class, String.class)); + Resources result = mapper.readValue(SIMPLE_EMBEDDED_RESOURCE_REFERENCE, + mapper.getTypeFactory().constructParametricType(Resources.class, String.class)); assertThat(result, is(expected)); @@ -170,8 +170,7 @@ public void deserializesSingleResourceResourcesAsEmbedded() throws Exception { Resources> expected = new Resources>(content); expected.add(new Link("localhost")); - Resources> result = mapper.readValue( - SINGLE_EMBEDDED_RESOURCE_REFERENCE, + Resources> result = mapper.readValue(SINGLE_EMBEDDED_RESOURCE_REFERENCE, mapper.getTypeFactory().constructParametricType(Resources.class, mapper.getTypeFactory().constructParametricType(Resource.class, SimplePojo.class))); @@ -194,8 +193,7 @@ public void deserializesMultipleResourceResourcesAsEmbedded() throws Exception { Resources> expected = setupResources(); expected.add(new Link("localhost")); - Resources> result = mapper.readValue( - LIST_EMBEDDED_RESOURCE_REFERENCE, + Resources> result = mapper.readValue(LIST_EMBEDDED_RESOURCE_REFERENCE, mapper.getTypeFactory().constructParametricType(Resources.class, mapper.getTypeFactory().constructParametricType(Resource.class, SimplePojo.class))); @@ -229,8 +227,7 @@ public void deserializesAnnotatedResourceResourcesAsEmbedded() throws Exception Resources> expected = new Resources>(content); expected.add(new Link("localhost")); - Resources> result = mapper.readValue( - ANNOTATED_EMBEDDED_RESOURCE_REFERENCE, + Resources> result = mapper.readValue(ANNOTATED_EMBEDDED_RESOURCE_REFERENCE, mapper.getTypeFactory().constructParametricType(Resources.class, mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class))); @@ -251,8 +248,7 @@ public void serializesMultipleAnnotatedResourceResourcesAsEmbedded() throws Exce @Test public void deserializesMultipleAnnotatedResourceResourcesAsEmbedded() throws Exception { - Resources> result = mapper.readValue( - ANNOTATED_EMBEDDED_RESOURCES_REFERENCE, + Resources> result = mapper.readValue(ANNOTATED_EMBEDDED_RESOURCES_REFERENCE, mapper.getTypeFactory().constructParametricType(Resources.class, mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class))); @@ -272,8 +268,7 @@ public void serializesPagedResource() throws Exception { */ @Test public void deserializesPagedResource() throws Exception { - PagedResources> result = mapper.readValue( - ANNOTATED_PAGED_RESOURCES, + PagedResources> result = mapper.readValue(ANNOTATED_PAGED_RESOURCES, mapper.getTypeFactory().constructParametricType(PagedResources.class, mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class))); @@ -286,8 +281,8 @@ public void deserializesPagedResource() throws Exception { @Test public void rendersCuriesCorrectly() throws Exception { - Resources resources = new Resources(Collections.emptySet(), new Link("foo"), new Link("bar", - "myrel")); + Resources resources = new Resources(Collections.emptySet(), new Link("foo"), + new Link("bar", "myrel")); assertThat(getCuriedObjectMapper().writeValueAsString(resources), is(CURIED_DOCUMENT)); } From 71ec26fc8d31f1c0982ac1cdc61b83f5d424f3b8 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 19 Aug 2015 19:07:07 +0200 Subject: [PATCH 18/27] #378 - HAL links get title attributes rendered resolved through a resource bundle. HalLinkListSerializer now tries to obtain a link title by looking up key _links.$rel.title for both the namespaced (curied) and local link relation if the former doesn't resolve into a message. --- .../config/EnableHypermediaSupport.java | 7 +- .../hateoas/config/HateoasConfiguration.java | 53 +++++++++++ ...ermediaSupportBeanDefinitionRegistrar.java | 7 +- .../hateoas/hal/Jackson2HalModule.java | 90 +++++++++++++++++-- .../hateoas/VndErrorsMarshallingTest.java | 4 +- .../hateoas/client/Server.java | 2 +- .../hal/Jackson2HalIntegrationTest.java | 50 +++++++++-- 7 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java diff --git a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java index 468795553..d5feb4a09 100644 --- a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java +++ b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -33,8 +33,7 @@ * components will be registered: *
    *
  • {@link LinkDiscoverer}
  • - *
  • a Jackson (1 or 2, dependning on what is on the classpath) module to correctly marshal the resource model classes - * into the appropriate representation. + *
  • a Jackson 2 module to correctly marshal the resource model classes into the appropriate representation. *
* * @see LinkDiscoverer @@ -44,7 +43,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented -@Import(HypermediaSupportBeanDefinitionRegistrar.class) +@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class }) public @interface EnableHypermediaSupport { /** diff --git a/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java new file mode 100644 index 000000000..83607bf4a --- /dev/null +++ b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 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.config; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; + +/** + * Common HATOEAS specific configuration. + * + * @author Oliver Gierke + * @soundtrack Elephants Crossing - Wait (Live at Stadtfest Dresden) + * @since 0.19 + */ +@Configuration +class HateoasConfiguration { + + /** + * The {@link MessageSourceAccessor} to provide messages for {@link ResourceDescription}s being rendered. + * + * @return + */ + @Bean + public MessageSourceAccessor linkRelationMessageSource() { + + try { + + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + messageSource.setBasename("classpath:rest-messages"); + + return new MessageSourceAccessor(messageSource); + + } catch (Exception o_O) { + throw new BeanCreationException("resourceDescriptionMessageSourceAccessor", "", o_O); + } + } +} diff --git a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java index 0a3075181..071c769b1 100644 --- a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java +++ b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java @@ -39,6 +39,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.context.support.MessageSourceAccessor; import org.springframework.core.type.AnnotationMetadata; import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.LinkDiscoverer; @@ -80,6 +81,7 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe private static final String DELEGATING_REL_PROVIDER_BEAN_NAME = "_relProvider"; private static final String LINK_DISCOVERER_REGISTRY_BEAN_NAME = "_linkDiscovererRegistry"; private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper"; + private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource"; private static final boolean JACKSON2_PRESENT = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null); @@ -288,9 +290,12 @@ private List> potentiallyRegisterModule(List>implements ContextualSerializer { + private static final String RELATION_MESSAGE_TEMPLATE = "_links.%s.title"; + private final BeanProperty property; private final CurieProvider curieProvider; private final EmbeddedMapper mapper; + private final MessageSourceAccessor messageSource; - public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper) { - this(null, curieProvider, mapper); + public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, + MessageSourceAccessor messageSource) { + this(null, curieProvider, mapper, messageSource); } - public HalLinkListSerializer(BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper) { + public HalLinkListSerializer(BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper, + MessageSourceAccessor messageSource) { super(List.class, false); this.property = property; this.curieProvider = curieProvider; this.mapper = mapper; + this.messageSource = messageSource; } /* @@ -166,7 +177,8 @@ public void serialize(List value, JsonGenerator jgen, SerializerProvider p } links.add(link); - sortedLinks.get(rel).add(link); + + sortedLinks.get(rel).add(toHalLink(link)); } if (!skipCuries && prefixingRequired && curiedLinkPresent) { @@ -188,6 +200,43 @@ public void serialize(List value, JsonGenerator jgen, SerializerProvider p serializer.serialize(sortedLinks, jgen, provider); } + /** + * Wraps the given link into a HAL specifc extension. + * + * @param link must not be {@literal null}. + * @return + */ + private HalLink toHalLink(Link link) { + + String rel = link.getRel(); + String title = getTitle(rel); + + if (title == null) { + title = getTitle(rel.contains(":") ? rel.substring(rel.indexOf(":") + 1) : rel); + } + + return new HalLink(link, title); + } + + /** + * Returns the title for the given local link relation resolved through the configured {@link MessageSourceAccessor} + * . + * + * @param localRel must not be {@literal null} or empty. + * @return + */ + private String getTitle(String localRel) { + + Assert.hasText(localRel, "Local relation must not be null or empty!"); + + try { + return messageSource == null ? null + : messageSource.getMessage(String.format(RELATION_MESSAGE_TEMPLATE, localRel)); + } catch (NoSuchMessageException o_O) { + return null; + } + } + /* * (non-Javadoc) * @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty) @@ -195,7 +244,7 @@ public void serialize(List value, JsonGenerator jgen, SerializerProvider p @Override public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { - return new HalLinkListSerializer(property, curieProvider, mapper); + return new HalLinkListSerializer(property, curieProvider, mapper, messageSource); } /* @@ -611,18 +660,20 @@ public static class HalHandlerInstantiator extends HandlerInstantiator { private final Map, Object> instanceMap = new HashMap, Object>(); - public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider) { - this(resolver, curieProvider, true); + public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, + MessageSourceAccessor messageSource) { + this(resolver, curieProvider, messageSource, true); } public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, - boolean enforceEmbeddedCollections) { + MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections) { EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider, enforceEmbeddedCollections); Assert.notNull(resolver, "RelProvider must not be null!"); this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(mapper)); - this.instanceMap.put(HalLinkListSerializer.class, new HalLinkListSerializer(curieProvider, mapper)); + this.instanceMap.put(HalLinkListSerializer.class, + new HalLinkListSerializer(curieProvider, mapper, messageSource)); } private Object findInstance(Class type) { @@ -806,4 +857,25 @@ public boolean hasCuriedEmbed(Iterable source) { return false; } } + + static class HalLink { + + private final Link link; + private final String title; + + public HalLink(Link link, String title) { + this.link = link; + this.title = title; + } + + @JsonUnwrapped + public Link getLink() { + return link; + } + + @JsonInclude(Include.NON_NULL) + public String getTitle() { + return title; + } + } } diff --git a/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java index 42e7c4187..473cee227 100644 --- a/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java +++ b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-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. @@ -72,7 +72,7 @@ public void setUp() throws Exception { jackson2Mapper = new com.fasterxml.jackson.databind.ObjectMapper(); jackson2Mapper.registerModule(new Jackson2HalModule()); - jackson2Mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null)); + jackson2Mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null, null)); jackson2Mapper.configure(SerializationFeature.INDENT_OUTPUT, true); JAXBContext context = JAXBContext.newInstance(VndErrors.class); diff --git a/src/test/java/org/springframework/hateoas/client/Server.java b/src/test/java/org/springframework/hateoas/client/Server.java index b6f991582..580b0eae6 100644 --- a/src/test/java/org/springframework/hateoas/client/Server.java +++ b/src/test/java/org/springframework/hateoas/client/Server.java @@ -61,7 +61,7 @@ public Server() { this.mapper = new ObjectMapper(); this.mapper.registerModule(new Jackson2HalModule()); - this.mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null)); + this.mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null, null)); initJadler(). // that().// diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java index 9ef8d1471..df858c8df 100644 --- a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java @@ -23,9 +23,14 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Locale; 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; +import org.springframework.context.support.StaticMessageSource; import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; @@ -70,11 +75,13 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final String LINK_TEMPLATE = "{\"_links\":{\"search\":{\"href\":\"/foo{?bar}\",\"templated\":true}}}"; + static final String LINK_WITH_TITLE = "{\"_links\":{\"ns:foobar\":{\"href\":\"target\",\"title\":\"Foobar's title!\"}}}"; + @Before public void setUpModule() { mapper.registerModule(new Jackson2HalModule()); - mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), null)); + mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), null, null)); } /** @@ -337,7 +344,7 @@ public Collection getCurieInformation(Links links) { } }; - assertThat(getCuriedObjectMapper(provider).writeValueAsString(resources), is(MULTIPLE_CURIES_DOCUMENT)); + assertThat(getCuriedObjectMapper(provider, null).writeValueAsString(resources), is(MULTIPLE_CURIES_DOCUMENT)); } /** @@ -356,6 +363,37 @@ public void rendersEmptyEmbeddedCollections() throws Exception { assertThat(write(resources), is("{\"_embedded\":{\"pojos\":[]}}")); } + /** + * @see #378 + */ + @Test + public void rendersTitleIfMessageSourceResolvesNamespacedKey() throws Exception { + verifyResolvedTitle("_links.ns:foobar.title"); + } + + /** + * @see #378 + */ + @Test + public void rendersTitleIfMessageSourceResolvesLocalKey() throws Exception { + verifyResolvedTitle("_links.foobar.title"); + } + + private static void verifyResolvedTitle(String resourceBundleKey) throws Exception { + + LocaleContextHolder.setLocale(Locale.US); + + StaticMessageSource messageSource = new StaticMessageSource(); + messageSource.addMessage(resourceBundleKey, Locale.US, "Foobar's title!"); + + ObjectMapper objectMapper = getCuriedObjectMapper(null, messageSource); + + ResourceSupport resource = new ResourceSupport(); + resource.add(new Link("target", "ns:foobar")); + + assertThat(objectMapper.writeValueAsString(resource), is(LINK_WITH_TITLE)); + } + private static Resources> setupAnnotatedPagedResources() { List> content = new ArrayList>(); @@ -385,14 +423,16 @@ private static Resources> setupResources() { private static ObjectMapper getCuriedObjectMapper() { - return getCuriedObjectMapper(new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}"))); + return getCuriedObjectMapper(new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}")), + null); } - private static ObjectMapper getCuriedObjectMapper(CurieProvider provider) { + private static ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jackson2HalModule()); - mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), provider)); + mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), provider, + messageSource == null ? null : new MessageSourceAccessor(messageSource))); return mapper; } From 790e10be2588261def49b5038bf76820708c34ff Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 12 Aug 2015 10:28:13 +0100 Subject: [PATCH 19/27] #376 - Compile against latest JAX-RS API. This commit updates Spring HATEOAS' optional dependency on the JAX-RS API to use the latest version (which uses a different artifact ID). This change will make things a little easier for the Spring IO Platform which currently provides dependency management for Jersey 2.x (which uses JAX-RS 2.0), by allowing it to stop providing dependency management for the JAX-RS 1.0 API. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 259e393ea..94d532885 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 4.1.7.RELEASE 1.1.3 2.5.4 - 1.0 + 2.0.1 2.2 2.0.0 1.7.12 @@ -447,7 +447,7 @@ javax.ws.rs - jsr311-api + javax.ws.rs-api ${jaxrs.version} true From 1ecedaa4b3a8b5300e5efdd7920eb382cc2a726a Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 12 Aug 2015 15:19:44 +0200 Subject: [PATCH 20/27] #379 - Added test case that shows AbstractJackson2HttpMessageConverter drop information on rendering on Spring 4.2 and Jackson 2.6. Related tickets: spring-projects/spring-boot#3731, SPR-13318. --- pom.xml | 2 + ...Jackson2PagedResourcesIntegrationTest.java | 93 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java diff --git a/pom.xml b/pom.xml index 94d532885..dcdb0603f 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,7 @@ spring42 4.2.0.RELEASE + 2.6.1 @@ -83,6 +84,7 @@ spring42-next 4.2.1.BUILD-SNAPSHOT + 2.6.1 diff --git a/src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java b/src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java new file mode 100644 index 000000000..e1bc0feec --- /dev/null +++ b/src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 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 static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collections; + +import org.apache.commons.io.output.WriterOutputStream; +import org.junit.Assume; +import org.junit.Test; +import org.springframework.hateoas.PagedResources.PageMetadata; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.util.ReflectionUtils; + +/** + * Integration tests for serialization of {@link PagedResources}. + * + * @author Oliver Gierke + */ +public class Jackson2PagedResourcesIntegrationTest { + + private static String REFERENCE = "{\"links\":[],\"content\":[{\"firstname\":\"Dave\",\"lastname\":\"Matthews\"}],\"page\":{\"size\":1,\"totalElements\":2,\"totalPages\":2,\"number\":0}}"; + + private static Method SPRING_4_2_WRITE_METHOD; + + static { + + try { + SPRING_4_2_WRITE_METHOD = MappingJackson2HttpMessageConverter.class.getMethod("write", Object.class, Type.class, + MediaType.class, HttpOutputMessage.class); + } catch (Exception e) {} + } + + /** + * @see SPR-13318 + */ + @Test + public void serializesPagedResourcesCorrectly() throws Exception { + + Assume.assumeThat(SPRING_4_2_WRITE_METHOD, is(notNullValue())); + + User user = new User(); + user.firstname = "Dave"; + user.lastname = "Matthews"; + + PageMetadata metadata = new PagedResources.PageMetadata(1, 0, 2); + PagedResources resources = new PagedResources(Collections.singleton(user), metadata); + + Method method = Sample.class.getMethod("someMethod"); + StringWriter writer = new StringWriter(); + + HttpOutputMessage outputMessage = mock(HttpOutputMessage.class); + when(outputMessage.getBody()).thenReturn(new WriterOutputStream(writer)); + when(outputMessage.getHeaders()).thenReturn(new HttpHeaders()); + + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + + ReflectionUtils.invokeMethod(SPRING_4_2_WRITE_METHOD, converter, resources, method.getGenericReturnType(), + MediaType.APPLICATION_JSON, outputMessage); + + assertThat(writer.toString(), is(REFERENCE)); + } + + interface Sample { + Resources someMethod(); + } + + static class User { + public String firstname, lastname; + } +} From d4ac71a9fdb22ca4dbc7b0a7ee1675bfc13ec7d8 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 1 Sep 2015 09:02:18 +0200 Subject: [PATCH 21/27] #379 - Updated changelog. --- src/main/resources/changelog.txt | 9 +++++++++ .../hateoas/support/ChangelogCreator.java | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 59c3694f1..355b71505 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,15 @@ Spring HATEOAS Changelog ======================== +Changes in version 0.19.0.RELEASE (2015-09-01) +---------------------------------------------- +- #379 - Release 0.19.0. +- #378 - Add title attributes for HAL links derived from resource bundle. +- #376 - Compile against latest JAX-RS API. +- #375 - TrueOnlyBooleanSerializer.isEmpty(…) not honored on Jackson 2.6. +- #363 - Support for multiple Curie namespaces in the same application. +- #320 - Improve curie rendering in case no namespaced links are present. + Changes in version 0.18.0.RELEASE (2015-08-04) ---------------------------------------------- - #369 - Traverson's getAndFindLinkWithRel(…) needs to expand given URI defensively. diff --git a/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java b/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java index 470b5231c..80e6ec3b2 100644 --- a/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java +++ b/src/test/java/org/springframework/hateoas/support/ChangelogCreator.java @@ -30,7 +30,7 @@ */ class ChangelogCreator { - private static final int MILESTONE_ID = 18; + private static final int MILESTONE_ID = 19; private static final String URI_TEMPLATE = "https://api.github.com/repos/spring-projects/spring-hateoas/issues?milestone={id}&state=closed"; public static void main(String... args) throws Exception { From e538dd31be2c3197a087042bbedb9286fb1e2703 Mon Sep 17 00:00:00 2001 From: Spring Buildmaster Date: Tue, 1 Sep 2015 00:10:00 -0700 Subject: [PATCH 22/27] #379 - Release version 0.19.0.RELEASE. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dcdb0603f..ef6695e50 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.springframework.hateoas spring-hateoas - 0.19.0.BUILD-SNAPSHOT + 0.19.0.RELEASE Spring HATEOAS http://github.com/SpringSource/spring-hateoas From b41402f195cec46e8f2c0fe42a200309bb54180d Mon Sep 17 00:00:00 2001 From: Spring Buildmaster Date: Tue, 1 Sep 2015 00:10:02 -0700 Subject: [PATCH 23/27] #379 - Prepare next development iteration. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ef6695e50..9789d3a02 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.springframework.hateoas spring-hateoas - 0.19.0.RELEASE + 0.20.0.BUILD-SNAPSHOT Spring HATEOAS http://github.com/SpringSource/spring-hateoas From 9d35670e77167c4becfa2af6bf5ca7f7c8b909e8 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 2 Sep 2015 08:28:05 +0200 Subject: [PATCH 24/27] #380 - Renamed *Tests classes to *Test to make sure the Maven build runs them. --- ...sts.java => JacksonSerializationTest.java} | 2 +- .../{HopUnitTests.java => HopUnitTest.java} | 2 +- ...TraversonTests.java => TraversonTest.java} | 2 +- ...a => XmlConfigurationIntegrationTest.java} | 2 +- ...sts.java => EmbeddedWrappersUnitTest.java} | 2 +- .../core/MethodParametersUnitTests.java | 65 ------------------- ...ava => TypeReferencesIntegrationTest.java} | 2 +- .../hateoas/config/application-context.xml | 2 +- 8 files changed, 7 insertions(+), 72 deletions(-) rename src/test/java/org/springframework/hateoas/alps/{JacksonSerializationTests.java => JacksonSerializationTest.java} (98%) rename src/test/java/org/springframework/hateoas/client/{HopUnitTests.java => HopUnitTest.java} (98%) rename src/test/java/org/springframework/hateoas/client/{TraversonTests.java => TraversonTest.java} (99%) rename src/test/java/org/springframework/hateoas/config/{XmlConfigurationIntegrationTests.java => XmlConfigurationIntegrationTest.java} (97%) rename src/test/java/org/springframework/hateoas/core/{EmbeddedWrappersUnitTests.java => EmbeddedWrappersUnitTest.java} (98%) delete mode 100644 src/test/java/org/springframework/hateoas/core/MethodParametersUnitTests.java rename src/test/java/org/springframework/hateoas/mvc/{TypeReferencesIntegrationTests.java => TypeReferencesIntegrationTest.java} (99%) diff --git a/src/test/java/org/springframework/hateoas/alps/JacksonSerializationTests.java b/src/test/java/org/springframework/hateoas/alps/JacksonSerializationTest.java similarity index 98% rename from src/test/java/org/springframework/hateoas/alps/JacksonSerializationTests.java rename to src/test/java/org/springframework/hateoas/alps/JacksonSerializationTest.java index 33c0d2a8a..44e47a922 100644 --- a/src/test/java/org/springframework/hateoas/alps/JacksonSerializationTests.java +++ b/src/test/java/org/springframework/hateoas/alps/JacksonSerializationTest.java @@ -37,7 +37,7 @@ * * @author Oliver Gierke */ -public class JacksonSerializationTests { +public class JacksonSerializationTest { ObjectMapper mapper; diff --git a/src/test/java/org/springframework/hateoas/client/HopUnitTests.java b/src/test/java/org/springframework/hateoas/client/HopUnitTest.java similarity index 98% rename from src/test/java/org/springframework/hateoas/client/HopUnitTests.java rename to src/test/java/org/springframework/hateoas/client/HopUnitTest.java index 8385da558..78f76d0bf 100644 --- a/src/test/java/org/springframework/hateoas/client/HopUnitTests.java +++ b/src/test/java/org/springframework/hateoas/client/HopUnitTest.java @@ -29,7 +29,7 @@ * @author Oliver Gierke * @soundtrack Dave Matthews Band - The Stone (Before These Crowded Streets) */ -public class HopUnitTests { +public class HopUnitTest { /** * @see #346 diff --git a/src/test/java/org/springframework/hateoas/client/TraversonTests.java b/src/test/java/org/springframework/hateoas/client/TraversonTest.java similarity index 99% rename from src/test/java/org/springframework/hateoas/client/TraversonTests.java rename to src/test/java/org/springframework/hateoas/client/TraversonTest.java index 7119d918f..5e9b1c9b9 100644 --- a/src/test/java/org/springframework/hateoas/client/TraversonTests.java +++ b/src/test/java/org/springframework/hateoas/client/TraversonTest.java @@ -55,7 +55,7 @@ * @author Greg Turnquist * @since 0.11 */ -public class TraversonTests { +public class TraversonTest { URI baseUri; Server server; diff --git a/src/test/java/org/springframework/hateoas/config/XmlConfigurationIntegrationTests.java b/src/test/java/org/springframework/hateoas/config/XmlConfigurationIntegrationTest.java similarity index 97% rename from src/test/java/org/springframework/hateoas/config/XmlConfigurationIntegrationTests.java rename to src/test/java/org/springframework/hateoas/config/XmlConfigurationIntegrationTest.java index 99b0b3fde..e23aca011 100644 --- a/src/test/java/org/springframework/hateoas/config/XmlConfigurationIntegrationTests.java +++ b/src/test/java/org/springframework/hateoas/config/XmlConfigurationIntegrationTest.java @@ -30,7 +30,7 @@ * * @author Oliver Gierke */ -public class XmlConfigurationIntegrationTests { +public class XmlConfigurationIntegrationTest { /** * @see #259 diff --git a/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTests.java b/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTest.java similarity index 98% rename from src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTests.java rename to src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTest.java index 968e11712..dc68693a1 100644 --- a/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTests.java +++ b/src/test/java/org/springframework/hateoas/core/EmbeddedWrappersUnitTest.java @@ -28,7 +28,7 @@ * * @author Oliver Gierke */ -public class EmbeddedWrappersUnitTests { +public class EmbeddedWrappersUnitTest { EmbeddedWrappers wrappers = new EmbeddedWrappers(false); diff --git a/src/test/java/org/springframework/hateoas/core/MethodParametersUnitTests.java b/src/test/java/org/springframework/hateoas/core/MethodParametersUnitTests.java deleted file mode 100644 index e7d1664cf..000000000 --- a/src/test/java/org/springframework/hateoas/core/MethodParametersUnitTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.hateoas.core; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.lang.reflect.Method; -import java.util.List; - -import org.junit.Test; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.core.MethodParameter; - -/** - * Unit tests for {@link MethodParameters}. - * - * @author Oliver Gierke - */ -public class MethodParametersUnitTests { - - @Test - public void prefersAnnotatedParameterOverDiscovered() throws Exception { - - Method method = Sample.class.getMethod("method", String.class, String.class, Object.class); - MethodParameters parameters = new MethodParameters(method, new AnnotationAttribute(Qualifier.class)); - - assertThat(parameters.getParameter("param"), is(notNullValue())); - assertThat(parameters.getParameter("foo"), is(notNullValue())); - assertThat(parameters.getParameter("another"), is(nullValue())); - } - - /** - * @see #138 - */ - @Test - public void returnsParametersOfAGivenType() throws Exception { - - Method method = Sample.class.getMethod("method", String.class, String.class, Object.class); - MethodParameters methodParameters = new MethodParameters(method); - - List objectParameters = methodParameters.getParametersOfType(Object.class); - assertThat(objectParameters, hasSize(1)); - assertThat(objectParameters.get(0).getParameterIndex(), is(2)); - } - - static class Sample { - - public void method(String param, @Qualifier("foo") String another, Object object) {} - } - -} diff --git a/src/test/java/org/springframework/hateoas/mvc/TypeReferencesIntegrationTests.java b/src/test/java/org/springframework/hateoas/mvc/TypeReferencesIntegrationTest.java similarity index 99% rename from src/test/java/org/springframework/hateoas/mvc/TypeReferencesIntegrationTests.java rename to src/test/java/org/springframework/hateoas/mvc/TypeReferencesIntegrationTest.java index 4cd59890d..185f05d42 100644 --- a/src/test/java/org/springframework/hateoas/mvc/TypeReferencesIntegrationTests.java +++ b/src/test/java/org/springframework/hateoas/mvc/TypeReferencesIntegrationTest.java @@ -50,7 +50,7 @@ */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class TypeReferencesIntegrationTests { +public class TypeReferencesIntegrationTest { private static final String USER = "\"firstname\" : \"Dave\", \"lastname\" : \"Matthews\""; private static final String RESOURCE = String.format("{ \"_links\" : { \"self\" : \"/resource\" }, %s }", USER); diff --git a/src/test/resources/org/springframework/hateoas/config/application-context.xml b/src/test/resources/org/springframework/hateoas/config/application-context.xml index 981c4fcc8..a1755ca08 100644 --- a/src/test/resources/org/springframework/hateoas/config/application-context.xml +++ b/src/test/resources/org/springframework/hateoas/config/application-context.xml @@ -7,6 +7,6 @@ - + From a019edae82f818c5b4764a83f0aab511e6b0e1f4 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 2 Sep 2015 08:38:49 +0200 Subject: [PATCH 25/27] #381 - Upgraded Spring 4.2 build profile to Spring 4.2.1.RELEASE. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9789d3a02..b6346ad69 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ spring42 - 4.2.0.RELEASE + 4.2.1.RELEASE 2.6.1 @@ -83,7 +83,7 @@ spring42-next - 4.2.1.BUILD-SNAPSHOT + 4.2.2.BUILD-SNAPSHOT 2.6.1 From dd4ec341b54c83da346be9691437bdd9cedb0e7d Mon Sep 17 00:00:00 2001 From: Tom Bunting Date: Mon, 31 Aug 2015 21:24:55 +0100 Subject: [PATCH 26/27] #337 - Fixed potential double-encoding in Traverson. We now avoid calling URI.toString() to early to prevent the need to re-parse it and thus accidentally cause a double-encoding. Original pull request: #382. --- .../hateoas/client/Traverson.java | 23 ++++++++++++------- .../hateoas/client/Server.java | 10 ++++++++ .../hateoas/client/TraversonTest.java | 17 ++++++++++++++ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/client/Traverson.java b/src/main/java/org/springframework/hateoas/client/Traverson.java index 54249bdfb..8f0c61194 100644 --- a/src/main/java/org/springframework/hateoas/client/Traverson.java +++ b/src/main/java/org/springframework/hateoas/client/Traverson.java @@ -59,6 +59,7 @@ * @author Oliver Gierke * @author Dietrich Schulten * @author Greg Turnquist + * @author Tom Bunting * @since 0.11 */ public class Traverson { @@ -305,7 +306,7 @@ public TraversalBuilder withHeaders(HttpHeaders headers) { public T toObject(Class type) { Assert.notNull(type, "Target type must not be null!"); - return operations.exchange(traverseToFinalUrl(true), GET, prepareRequest(headers), type).getBody(); + return operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), type).getBody(); } /** @@ -318,7 +319,7 @@ public T toObject(Class type) { public T toObject(ParameterizedTypeReference type) { Assert.notNull(type, "Target type must not be null!"); - return operations.exchange(traverseToFinalUrl(true), GET, prepareRequest(headers), type).getBody(); + return operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), type).getBody(); } /** @@ -332,7 +333,7 @@ public T toObject(String jsonPath) { Assert.hasText(jsonPath, "JSON path must not be null or empty!"); - String forObject = operations.exchange(traverseToFinalUrl(true), GET, prepareRequest(headers), String.class) + String forObject = operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), String.class) .getBody(); return JsonPath.read(forObject, jsonPath); } @@ -346,7 +347,7 @@ public T toObject(String jsonPath) { public ResponseEntity toEntity(Class type) { Assert.notNull(type, "Target type must not be null!"); - return operations.exchange(traverseToFinalUrl(true), GET, prepareRequest(headers), type); + return operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), type); } /** @@ -374,14 +375,20 @@ public Link asTemplatedLink() { private Link traverseToLink(boolean expandFinalUrl) { Assert.isTrue(rels.size() > 0, "At least one rel needs to be provided!"); - return new Link(traverseToFinalUrl(expandFinalUrl), rels.get(rels.size() - 1).getRel()); + return new Link(expandFinalUrl ? traverseToExpandedFinalUrl().toString() : traverseToFinalUrl(), + rels.get(rels.size() - 1).getRel()); } - private String traverseToFinalUrl(boolean expandFinalUrl) { + private String traverseToFinalUrl() { String uri = getAndFindLinkWithRel(baseUri.toString(), rels.iterator()); - UriTemplate uriTemplate = new UriTemplate(uri); - return expandFinalUrl ? uriTemplate.expand(templateParameters).toString() : uriTemplate.toString(); + return new UriTemplate(uri).toString(); + } + + private URI traverseToExpandedFinalUrl() { + + String uri = getAndFindLinkWithRel(baseUri.toString(), rels.iterator()); + return new UriTemplate(uri).expand(templateParameters); } private String getAndFindLinkWithRel(String uri, Iterator rels) { diff --git a/src/test/java/org/springframework/hateoas/client/Server.java b/src/test/java/org/springframework/hateoas/client/Server.java index 580b0eae6..4a17fc482 100644 --- a/src/test/java/org/springframework/hateoas/client/Server.java +++ b/src/test/java/org/springframework/hateoas/client/Server.java @@ -147,6 +147,16 @@ public Server() { respond(). // withBody(springagramItemWithoutImageHalDocument). // withContentType(MediaTypes.HAL_JSON.toString()); + + // For Traverson URI double encoding test + + onRequest(). // + havingPathEqualTo("/springagram/items"). // + havingQueryString(equalTo("projection=no%20images")). // + respond(). // + withBody(springagramItemsHalDocument). // + withContentType(MediaTypes.HAL_JSON.toString()); + } public String rootResource() { diff --git a/src/test/java/org/springframework/hateoas/client/TraversonTest.java b/src/test/java/org/springframework/hateoas/client/TraversonTest.java index 5e9b1c9b9..0f3075720 100644 --- a/src/test/java/org/springframework/hateoas/client/TraversonTest.java +++ b/src/test/java/org/springframework/hateoas/client/TraversonTest.java @@ -355,6 +355,23 @@ public void allowGlobalsToImpactSingleHops() { assertThat(item.image, equalTo(server.rootResource() + "/springagram/file/cat")); assertThat(item.description, equalTo("cat")); } + + /** + * @see #337 + */ + @Test + public void doesNotDoubleEncodeURI() { + + this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); + + Resource itemResource = traverson.// + follow(rel("items").withParameters(Collections.singletonMap("projection", "no images"))).// + toObject(Resource.class); + + assertThat(itemResource.hasLink("self"), is(true)); + assertThat(itemResource.getLink("self").expand().getHref(), + equalTo(server.rootResource() + "/springagram/items")); + } private void setUpActors() { From 4e1e5ed934953aabcf5490d96d7ac43c88bc1d60 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 3 Sep 2015 14:19:03 +0200 Subject: [PATCH 27/27] #383 - Reinstantiated Jackson 2.4 compatibility. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-introduced the simple isEmpty(…) methods in custom Jackson serializers as the more complex ones were introduced in Jackson 2.5 only. Removed the @Override annotations so that we could even compile against 2.4 if needed. --- .../hateoas/hal/Jackson2HalModule.java | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 1cffe9291..bb0f4a822 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -265,11 +265,18 @@ public JsonSerializer getContentSerializer() { return null; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#isEmpty(java.lang.Object) + */ + public boolean isEmpty(List value) { + return isEmpty(null, value); + } + /* * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) */ - @Override public boolean isEmpty(SerializerProvider provider, List value) { return value.isEmpty(); } @@ -356,7 +363,10 @@ public JsonSerializer getContentSerializer() { return null; } - @Override + public boolean isEmpty(Collection value) { + return isEmpty(null, value); + } + public boolean isEmpty(SerializerProvider provider, Collection value) { return value.isEmpty(); } @@ -379,8 +389,8 @@ protected ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { * @author Alexander Baetz * @author Oliver Gierke */ - public static class OptionalListJackson2Serializer extends ContainerSerializer implements - ContextualSerializer { + public static class OptionalListJackson2Serializer extends ContainerSerializer + implements ContextualSerializer { private final BeanProperty property; private final Map, JsonSerializer> serializers; @@ -415,8 +425,8 @@ public ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { * @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider) */ @Override - public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, - JsonGenerationException { + public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonGenerationException { List list = (List) value; @@ -488,11 +498,18 @@ public boolean hasSingleElement(Object arg0) { return false; } + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#isEmpty(java.lang.Object) + */ + public boolean isEmpty(Object value) { + return isEmpty(null, value); + } + /* * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) */ - @Override public boolean isEmpty(SerializerProvider provider, Object value) { return false; } @@ -542,8 +559,8 @@ public JsonDeserializer getContentDeserializer() { * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) */ @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, - JsonProcessingException { + public List deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { List result = new ArrayList(); String relation; @@ -573,8 +590,8 @@ public List deserialize(JsonParser jp, DeserializationContext ctxt) throws } } - public static class HalResourcesDeserializer extends ContainerDeserializerBase> implements - ContextualDeserializer { + public static class HalResourcesDeserializer extends ContainerDeserializerBase> + implements ContextualDeserializer { private static final long serialVersionUID = 4755806754621032622L; @@ -747,7 +764,6 @@ public TrueOnlyBooleanSerializer() { * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(java.lang.Object) */ - @Override public boolean isEmpty(Boolean value) { return isEmpty(null, value); } @@ -756,7 +772,6 @@ public boolean isEmpty(Boolean value) { * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isEmpty(com.fasterxml.jackson.databind.SerializerProvider, java.lang.Object) */ - @Override public boolean isEmpty(SerializerProvider provider, Boolean value) { return value == null || Boolean.FALSE.equals(value); }