diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java index dce2d065..505b716a 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsSerializers.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.context.MessageSourceResolvable; import org.springframework.context.support.MessageSourceAccessor; @@ -285,23 +286,77 @@ class HalFormsSerializers { Class type = it.getInputType().resolve(Object.class); List propertiesWithPrompt = it.getInputProperties().stream() // - .map(property -> property.withPrompt(accessor.getMessage(PromptLookup.of(type, property)))) + .map(property -> property.withPrompt(accessor.getMessage(PropertyPrompt.of(type, property)))) .collect(Collectors.toList()); HalFormsTemplate template = HalFormsTemplate.forMethod(it.getHttpMethod()) // .withProperties(propertiesWithPrompt); + String defaultedName = templates.isEmpty() ? "default" : it.getName(); + String title = accessor.getMessage(TemplateTitle.of(it, templates.isEmpty())); + + if (StringUtils.hasText(title)) { + template = template.withTitle(title); + } + /* * First template in HAL-FORMS is "default". */ - templates.put(templates.isEmpty() ? "default" : it.getName(), template); + templates.put(defaultedName, template); }); return templates; } @RequiredArgsConstructor(staticName = "of") - static class PromptLookup implements MessageSourceResolvable { + static class TemplateTitle implements MessageSourceResolvable { + + private static final String TEMPLATE_TEMPLATE = "_templates.%s.title"; + + private final HalFormsAffordanceModel affordance; + private final boolean soleTemplate; + + /* + * (non-Javadoc) + * @see org.springframework.context.MessageSourceResolvable#getCodes() + */ + @NonNull + @Override + public String[] getCodes() { + + Stream seed = Stream.concat(// + Stream.of(affordance.getName()), // + soleTemplate ? Stream.of("default") : Stream.empty()); + + Class type = affordance.getInputType().resolve(Object.class); + + return seed.flatMap(it -> getCodesFor(it, type)) // + .toArray(String[]::new); + } + + private static Stream getCodesFor(String name, Class type) { + + String global = String.format(TEMPLATE_TEMPLATE, name); + + return Stream.of(// + String.format("%s.%s", type.getName(), global), // + String.format("%s.%s", type.getSimpleName(), global), // + global); + } + + /* + * (non-Javadoc) + * @see org.springframework.context.MessageSourceResolvable#getDefaultMessage() + */ + @Nullable + @Override + public String getDefaultMessage() { + return ""; + } + } + + @RequiredArgsConstructor(staticName = "of") + static class PropertyPrompt implements MessageSourceResolvable { private static final String PROMPT_TEMPLATE = "%s._prompt"; diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java index fd5e0c48..5248c8fb 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java @@ -404,7 +404,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra @ValueSource(strings = { "firstname._prompt", // "HalFormsPayload.firstname._prompt", // "org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsIntegrationTest$HalFormsPayload.firstname._prompt" }) - public void usesResourceBundleToCreatePropertyPrompts(String key) { + void usesResourceBundleToCreatePropertyPrompts(String key) { StaticMessageSource source = new StaticMessageSource(); source.addMessage(key, Locale.US, "Vorname"); @@ -425,6 +425,35 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra }).doesNotThrowAnyException(); } + @ParameterizedTest // #849 + @ValueSource(strings = { // + "_templates.postHalFormsPayload.title", // + "HalFormsPayload._templates.postHalFormsPayload.title", // + "org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsIntegrationTest$HalFormsPayload._templates.postHalFormsPayload.title", // + "_templates.default.title", // + "HalFormsPayload._templates.default.title", // + "org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsIntegrationTest$HalFormsPayload._templates.default.title" }) + void usesResourceBundleToCreateTemplateTitle(String key) { + + StaticMessageSource source = new StaticMessageSource(); + source.addMessage(key, Locale.US, "Template title"); + + Link link = new Link("some:link") // + .andAffordance(HttpMethod.POST, HalFormsPayload.class, Collections.emptyList(), Object.class); + + EntityModel model = new EntityModel<>(new HalFormsPayload(), link); + ObjectMapper mapper = getCuriedObjectMapper(CurieProvider.NONE, source); + + assertThatCode(() -> { + + String promptString = JsonPath.compile("$._templates.default.title") // + .read(mapper.writeValueAsString(model)); + + assertThat(promptString).isEqualTo("Template title"); + + }).doesNotThrowAnyException(); + } + private void verifyResolvedTitle(String resourceBundleKey) throws Exception { LocaleContextHolder.setLocale(Locale.US);