#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.
This commit is contained in:
Oliver Drotbohm
2021-01-20 19:12:43 +01:00
parent 675f9e3895
commit 752b000e6c
3 changed files with 49 additions and 18 deletions

View File

@@ -54,19 +54,21 @@ final class HalFormsTemplate {
private HttpMethod httpMethod;
private List<HalFormsProperty> properties;
private List<MediaType> 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<HalFormsProperty> properties,
List<MediaType> contentTypes) {
List<MediaType> 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<HalFormsProperty> 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<HalFormsProperty> 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<MediaType> 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<MediaType> 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)

View File

@@ -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<String, HalFormsTemplate> findTemplates(RepresentationModel<?> resource) {
if (!resource.hasLink(IanaLinkRelations.SELF)) {
return Collections.emptyMap();
}
Map<String, HalFormsTemplate> templates = new HashMap<>();
List<Affordance> 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);
}

View File

@@ -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<String, HalFormsTemplate> 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<PatternExample> {