diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsConfiguration.java index 15f081037..3ba45194e 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsConfiguration.java @@ -46,6 +46,7 @@ public class HalFormsConfiguration { private final Map, String> patterns; private final Consumer objectMapperCustomizer; private final HalFormsOptionsFactory options; + private final HalFormsValueFactory values; private final List mediaTypes; /** @@ -61,24 +62,26 @@ public HalFormsConfiguration() { * @param halConfiguration must not be {@literal null}. */ public HalFormsConfiguration(HalConfiguration halConfiguration) { - this(halConfiguration, new HashMap<>(), new HalFormsOptionsFactory(), __ -> {}, + this(halConfiguration, new HashMap<>(), new HalFormsOptionsFactory(), new HalFormsValueFactory(), __ -> {}, Collections.singletonList(MediaTypes.HAL_FORMS_JSON)); } private HalFormsConfiguration(HalConfiguration halConfiguration, Map, String> patterns, - HalFormsOptionsFactory options, @Nullable Consumer objectMapperCustomizer, + HalFormsOptionsFactory options, HalFormsValueFactory values, @Nullable Consumer objectMapperCustomizer, List mediaTypes) { Assert.notNull(halConfiguration, "HalConfiguration must not be null!"); Assert.notNull(patterns, "Patterns must not be null!"); Assert.notNull(objectMapperCustomizer, "ObjectMapper customizer must not be null!"); Assert.notNull(options, "HalFormsSuggests must not be null!"); + Assert.notNull(values, "HalFormsValueFactory must not be null!"); Assert.notNull(mediaTypes, "Media types must not be null!"); this.halConfiguration = halConfiguration; this.patterns = patterns; this.objectMapperCustomizer = objectMapperCustomizer; this.options = options; + this.values = values; this.mediaTypes = new ArrayList<>(mediaTypes); } @@ -97,7 +100,7 @@ public HalFormsConfiguration withPattern(Class type, String pattern) { Map, String> newPatterns = new HashMap<>(patterns); newPatterns.put(type, pattern); - return new HalFormsConfiguration(halConfiguration, newPatterns, options, objectMapperCustomizer, mediaTypes); + return new HalFormsConfiguration(halConfiguration, newPatterns, options, values, objectMapperCustomizer, mediaTypes); } /** @@ -113,7 +116,7 @@ public HalFormsConfiguration withObjectMapperCustomizer(Consumer o return this.objectMapperCustomizer == objectMapperCustomizer // ? this // - : new HalFormsConfiguration(halConfiguration, patterns, options, objectMapperCustomizer, mediaTypes); + : new HalFormsConfiguration(halConfiguration, patterns, options, values, objectMapperCustomizer, mediaTypes); } /** @@ -136,7 +139,7 @@ public HalFormsConfiguration withMediaType(MediaType mediaType) { List newMediaTypes = new ArrayList<>(mediaTypes); newMediaTypes.add(mediaTypes.size() - 1, mediaType); - return new HalFormsConfiguration(halConfiguration, patterns, options, objectMapperCustomizer, newMediaTypes); + return new HalFormsConfiguration(halConfiguration, patterns, options, values, objectMapperCustomizer, newMediaTypes); } /** @@ -167,7 +170,14 @@ public HalFormsConfiguration customize(ObjectMapper mapper) { public HalFormsConfiguration withOptions(Class type, String property, Function creator) { - return new HalFormsConfiguration(halConfiguration, patterns, options.withOptions(type, property, creator), + return new HalFormsConfiguration(halConfiguration, patterns, options.withOptions(type, property, creator), values, + objectMapperCustomizer, mediaTypes); + } + + public HalFormsConfiguration withValues(Class type, String property, + Function creator) { + + return new HalFormsConfiguration(halConfiguration, patterns, options, values.withValues(type, property, creator), objectMapperCustomizer, mediaTypes); } @@ -189,6 +199,15 @@ HalFormsOptionsFactory getOptionsFactory() { return options; } + /** + * Returns the {@link HalFormsValueFactory} to look up value from payload and property metadata. + * + * @return will never be {@literal null}. + */ + HalFormsValueFactory getValuesFactory() { + return values; + } + /** * Returns the regular expression pattern that is registered for the given type. * diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsPropertyFactory.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsPropertyFactory.java index d169437a9..7f209b496 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsPropertyFactory.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsPropertyFactory.java @@ -79,6 +79,7 @@ public List createProperties(HalFormsAffordanceModel model) { } HalFormsOptionsFactory optionsFactory = configuration.getOptionsFactory(); + HalFormsValueFactory valuesFactory = configuration.getValuesFactory(); return model.createProperties((payload, metadata) -> { @@ -95,7 +96,7 @@ public List createProperties(HalFormsAffordanceModel model) { .withMaxLength(metadata.getMaxLength()) .withRegex(lookupRegex(metadata)) // .withType(inputType) // - .withValue(options != null ? options.getSelectedValue() : null) // + .withValue(options != null ? options.getSelectedValue() : valuesFactory.getValue(payload, metadata)) // .withOptions(options); Function factory = I18nedPropertyMetadata.factory(payload, property); diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsValueFactory.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsValueFactory.java new file mode 100644 index 000000000..872ff69a0 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsValueFactory.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 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 + * + * https://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.mediatype.hal.forms; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.hateoas.AffordanceModel; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * + * Factory implementation to register creator functions to eventually create value from + * {@link AffordanceModel.PropertyMetadata} to decouple the registration (via {@link HalFormsConfiguration}) from the consumption during + * rendering. + * + * @author Réda Housni Alaoui + */ +class HalFormsValueFactory { + + private final Map, Map>> values; + + /** + * Creates a new, empty {@link HalFormsValueFactory}. + */ + public HalFormsValueFactory() { + this.values = new HashMap<>(); + } + + /** + * Copy-constructor to keep {@link HalFormsValueFactory} immutable during registrations. + * + * @param values must not be {@literal null}. + */ + private HalFormsValueFactory(Map, Map>> values) { + this.values = values; + } + + /** + * Registers a {@link Function} to create a {@link String} instance from the given {@link AffordanceModel.PropertyMetadata} + * to supply value for the given property of the given type. + * + * @param type must not be {@literal null}. + */ + HalFormsValueFactory withValues(Class type, String property, + Function creator) { + + Assert.notNull(type, "Type must not be null!"); + Assert.hasText(property, "Property must not be null or empty!"); + Assert.notNull(creator, "Creator function must not be null!"); + + Map, Map>> values = new HashMap<>(this.values); + + values.compute(type, (it, map) -> { + + if (map == null) { + map = new HashMap<>(); + } + + map.put(property, creator); + + return map; + }); + + return new HalFormsValueFactory(values); + } + + /** + * Returns the value to be used for the property with the given {@link AffordanceModel.PayloadMetadata} and + * {@link AffordanceModel.PropertyMetadata}. + * + * @param payload must not be {@literal null}. + * @param property must not be {@literal null}. + */ + @Nullable + String getValue(AffordanceModel.PayloadMetadata payload, AffordanceModel.PropertyMetadata property) { + + Assert.notNull(payload, "Payload metadata must not be null!"); + Assert.notNull(property, "Property metadata must not be null!"); + + Class type = payload.getType(); + String name = property.getName(); + + Map> map = values.get(type); + + if (map == null) { + return null; + } + + Function function = map.get(name); + + return function == null ? null : function.apply(property); + } + +} 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 93001e8bf..fb046d946 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 @@ -201,6 +201,28 @@ void rendersInlineOptions() { }); } + @Test + void rendersValues() { + String value = "1234123412341234"; + + HalFormsConfiguration configuration = new HalFormsConfiguration() // + .withValues(PatternExample.class, "number", metadata -> value); + + RepresentationModel models = new RepresentationModel<>( + Affordances.of(Link.of("/example", LinkRelation.of("create"))) // + .afford(HttpMethod.POST) // + .withInput(PatternExample.class) // + .toLink()); + + Map templates = new HalFormsTemplateBuilder(configuration, MessageResolver.DEFAULTS_ONLY) + .findTemplates(models); + + assertThat(templates.get("default").getPropertyByName("number")) // + .hasValueSatisfying(it -> { + assertThat(it.getValue()).isEqualTo(value); + }); + } + @Test // #1510 void propagatesSelectedValueToProperty() {