From 08bc96493e074f345566522216594df48db380a9 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 30 Jun 2021 09:58:32 +0200 Subject: [PATCH] #1540 - Avoid double curie-ing of HalModel embeds. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Embedded elements assembled through a HalModelBuilder expose those through a CollectionModel to make sure that the individual elements get grouped by their link relation and those potentially curied. To indicate a curie link needing to be added because of curied embeds, the serialization adds a magic link to the list of links of that CollectionModel. As the latter is unwrapped, an additiona _links block rendering a curie had been added to the representation rendering the JSON representation invalid, as it now contained two _links fields (the latter stemming from the actual links added to the HAL representation model). We now tweak the CollectionModel returned to override ….add(Link) and rather route the magic link to the outer representation model, causing the inner one to never accumulate any links in the first place and thus not render an invalid _links. --- .../mediatype/hal/HalModelBuilder.java | 23 +++++++++++++++---- .../hal/HalModelBuilderUnitTest.java | 18 +++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalModelBuilder.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalModelBuilder.java index 2793780f..8d43e3e0 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalModelBuilder.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalModelBuilder.java @@ -359,7 +359,7 @@ public class HalModelBuilder { */ @SuppressWarnings("unchecked") public > RepresentationModel build() { - return (T) new HalRepresentationModel<>(model, CollectionModel.of(embeddeds), links); + return (T) new HalRepresentationModel<>(model, embeddeds, links); } /** @@ -381,16 +381,16 @@ public class HalModelBuilder { private static class HalRepresentationModel extends EntityModel { private final @Nullable T entity; - private final CollectionModel embeddeds; + private final List embeddeds; - public HalRepresentationModel(@Nullable T entity, CollectionModel embeddeds, Links links) { + public HalRepresentationModel(@Nullable T entity, List embeddeds, Links links) { this(entity, embeddeds); add(links); } - private HalRepresentationModel(@Nullable T entity, CollectionModel embeddeds) { + private HalRepresentationModel(@Nullable T entity, List embeddeds) { Assert.notNull(embeddeds, "Embedds must not be null!"); @@ -409,8 +409,21 @@ public class HalModelBuilder { } @JsonUnwrapped + @SuppressWarnings("deprecation") public CollectionModel getEmbeddeds() { - return embeddeds; + + return new CollectionModel(embeddeds) { + + /** + * Overriding this to make sure that the marker link added to signal the need for curie-ing is added to the + * outer representation model. + */ + @Override + public CollectionModel add(Link link) { + HalRepresentationModel.this.add(link); + return this; + } + }; } } diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java index 4bb4bc1a..4df71282 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java @@ -42,8 +42,10 @@ import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.MappingTestUtils.ContextualMapper; import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.server.core.EvoInflectorLinkRelationProvider; +import org.springframework.util.StringUtils; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -360,6 +362,22 @@ public class HalModelBuilderUnitTest { assertEmptyEmbed(halModel().embed(Stream.empty(), LinkRelation.of("products")).build(), "products"); } + @Test // #1540 + void doesNotRenderAdditionalLinksBlockForCuriedEmbeds() throws Exception { + + this.mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator( // + new EvoInflectorLinkRelationProvider(), // + new DefaultCurieProvider("foo", UriTemplate.of("http://localhost/foo/{rel}")), // + MessageResolver.DEFAULTS_ONLY)); + + RepresentationModel model = halModel() // + .embed(new Staff("Frodo Baggins", "ring bearer")) // + .build(); + + assertThat(StringUtils.countOccurrencesOf(mapper.writeValueAsString(model), "_links")) // + .isEqualTo(1); + } + private void assertEmptyEmbed(RepresentationModel model, String name) throws Exception { DocumentContext context = JsonPath.parse(mapper.writeValueAsString(model));