From 752b000e6c7cc3dd3d5e03ed012dee87857117f4 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 20 Jan 2021 19:12:43 +0100 Subject: [PATCH] #1427 - Support for the target attribute of HAL FORMS templates. We now explicitly set the target attribute of HAL FORMS templates if the target of the affordance does not equal the target of the self link. --- .../mediatype/hal/forms/HalFormsTemplate.java | 29 ++++++++++++++----- .../hal/forms/HalFormsTemplateBuilder.java | 22 +++++++------- .../HalFormsTemplateBuilderUnitTest.java | 16 ++++++++++ 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java index 5953d5a6..3388432a 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplate.java @@ -54,19 +54,21 @@ final class HalFormsTemplate { private HttpMethod httpMethod; private List properties; private List contentTypes; + private String target; @SuppressWarnings("null") private HalFormsTemplate() { - this(null, null, Collections.emptyList(), Collections.emptyList()); + this(null, null, Collections.emptyList(), Collections.emptyList(), null); } private HalFormsTemplate(String title, HttpMethod httpMethod, List properties, - List contentTypes) { + List contentTypes, String target) { this.title = title; this.httpMethod = httpMethod; this.properties = properties; this.contentTypes = contentTypes; + this.target = target; } static HalFormsTemplate forMethod(HttpMethod httpMethod) { @@ -76,13 +78,13 @@ final class HalFormsTemplate { HalFormsTemplate withTitle(String title) { return this.title == title ? this - : new HalFormsTemplate(title, this.httpMethod, this.properties, this.contentTypes); + : new HalFormsTemplate(title, this.httpMethod, this.properties, this.contentTypes, this.target); } private HalFormsTemplate withHttpMethod(HttpMethod httpMethod) { return this.httpMethod == httpMethod ? this - : new HalFormsTemplate(this.title, httpMethod, this.properties, this.contentTypes); + : new HalFormsTemplate(this.title, httpMethod, this.properties, this.contentTypes, this.target); } /** @@ -98,13 +100,13 @@ final class HalFormsTemplate { List properties = new ArrayList<>(this.properties); properties.add(property); - return new HalFormsTemplate(title, httpMethod, properties, contentTypes); + return new HalFormsTemplate(title, httpMethod, properties, contentTypes, target); } HalFormsTemplate withProperties(List properties) { return this.properties == properties ? this - : new HalFormsTemplate(this.title, this.httpMethod, properties, this.contentTypes); + : new HalFormsTemplate(title, httpMethod, properties, contentTypes, target); } /** @@ -120,13 +122,19 @@ final class HalFormsTemplate { List contentTypes = new ArrayList<>(this.contentTypes); contentTypes.add(mediaType); - return new HalFormsTemplate(title, httpMethod, properties, contentTypes); + return new HalFormsTemplate(title, httpMethod, properties, contentTypes, target); } HalFormsTemplate withContentTypes(List contentTypes) { return this.contentTypes == contentTypes ? this - : new HalFormsTemplate(this.title, this.httpMethod, this.properties, contentTypes); + : new HalFormsTemplate(title, httpMethod, properties, contentTypes, target); + } + + HalFormsTemplate withTarget(String target) { + + return this.target == target ? this + : new HalFormsTemplate(title, httpMethod, properties, contentTypes, target); } // Jackson helper methods to create the right representation format @@ -174,6 +182,11 @@ final class HalFormsTemplate { return this.title; } + @JsonInclude(Include.NON_EMPTY) + String getTarget() { + return target; + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java index 56b708ff..a58932a4 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java @@ -15,9 +15,7 @@ */ package org.springframework.hateoas.mediatype.hal.forms; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -34,6 +32,7 @@ import org.springframework.http.HttpMethod; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; class HalFormsTemplateBuilder { @@ -54,16 +53,11 @@ class HalFormsTemplateBuilder { */ public Map findTemplates(RepresentationModel resource) { - if (!resource.hasLink(IanaLinkRelations.SELF)) { - return Collections.emptyMap(); - } - Map templates = new HashMap<>(); - List affordances = resource.getLink(IanaLinkRelations.SELF) // - .map(Link::getAffordances) // - .orElse(Collections.emptyList()); + Link selfLink = resource.getLink(IanaLinkRelations.SELF).orElse(null); - affordances.stream() // + resource.getLinks().stream() // + .flatMap(it -> it.getAffordances().stream()) // .map(it -> it.getAffordanceModel(MediaTypes.HAL_FORMS_JSON)) // .peek(it -> { Assert.notNull(it, "No HAL Forms affordance model found but expected!"); @@ -75,16 +69,24 @@ class HalFormsTemplateBuilder { HalFormsTemplate template = HalFormsTemplate.forMethod(it.getHttpMethod()) // .withProperties(factory.createProperties(it)); + String target = it.getLink().getHref(); + + if (selfLink == null || !target.equals(selfLink.getHref())) { + template = template.withTarget(target); + } + template = applyTo(template, TemplateTitle.of(it, templates.isEmpty())); templates.put(templates.isEmpty() ? "default" : it.getName(), template); }); return templates; + } private HalFormsTemplate applyTo(HalFormsTemplate template, HalFormsTemplateBuilder.TemplateTitle templateTitle) { return Optional.ofNullable(resolver.resolve(templateTitle)) // + .filter(StringUtils::hasText) // .map(template::withTitle) // .orElse(template); } diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java index da32aea7..9f9f9161 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.mediatype.Affordances; import org.springframework.hateoas.mediatype.MessageResolver; @@ -115,6 +116,21 @@ class HalFormsTemplateBuilderUnitTest { assertThat(text).map(HalFormsProperty::getMaxLength).hasValue(5L); } + @Test // #1427 + void addsTargetAttributeForLinksNotPointingToSelf() { + + Link link = Affordances.of(Link.of("/example", LinkRelation.of("create"))) // + .afford(HttpMethod.POST) // + .withInput(Payload.class) // + .withName("create") + .toLink(); + + Map templates = new HalFormsTemplateBuilder(new HalFormsConfiguration(), + MessageResolver.DEFAULTS_ONLY).findTemplates(new RepresentationModel<>().add(link)); + + assertThat(templates.get("default").getTarget()).isEqualTo("/example"); + } + @Getter static class PatternExample extends RepresentationModel {