-
Notifications
You must be signed in to change notification settings - Fork 470
Description
Caveat: The Spring HATEOAS project is rather new to me, so I may have missed something obvious / this may be a documentation issue.
I have been fiddling with adding dynamic HalFormsOptions to my API. The route I ended up taking was the following:
- Seeing that there is a hook for static
HalFormsOptionsin theHalFormsConfigurationI decided to lean on that and did:
new HalFormsConfiguration(halConfiguration)
.withOptions(RespondRequest.class, "responseKey",
metadata -> {
if(metadata instanceof HalFormsPropertyMetadata halFormsPropertyMetadata) { // HalFormsPropertyMetadata is my own delegating impl
return halFormsPropertyMetadata.getOptions();
} else {
return null; // could also throw here as the metadata does not live up to the expectations
}});
- As the
Affordancetype fromWebMvcLinkBuilder.afford()is locked down, I create the affordance manually, like the docs indicate and try to setup the properAffordanceModel.InputPayloadMetadata:
AffordanceModel.InputPayloadMetadata input =
PropertyUtils.getExposedProperties(ResolvableType.forClass(RespondRequest.class));
- Next problem was to customize my property inside the input metadata? (The
.customize()method does not mutate anything). I created my own InputPayloadMetadata to mutate the properties:
final class HalFormsPayloadMetadata implements AffordanceModel.InputPayloadMetadata {
private final Class<?> type;
private final SortedMap<String, AffordanceModel.PropertyMetadata> properties;
private final List<MediaType> mediaTypes;
private final List<String> i18nCodes;
public HalFormsPayloadMetadata(AffordanceModel.InputPayloadMetadata payload) {
this(payload.getType(),
new TreeMap<>(payload.stream().collect(Collectors.toMap(AffordanceModel.Named::getName, Function.identity()))),
payload.getMediaTypes(),
payload.getI18nCodes());
}
private HalFormsPayloadMetadata(Class<?> type, SortedMap<String, AffordanceModel.PropertyMetadata> properties,
List<MediaType> mediaTypes, List<String> i18nCodes) {
this.type = type;
this.properties = properties;
this.mediaTypes = List.copyOf(mediaTypes);
this.i18nCodes = List.copyOf(i18nCodes);
}
/**
* Manipulate the existing properties, the mapper should just return the properties that it is not interested in
* @param mapper
* @return a new payload metadata
*/
public HalFormsPayloadMetadata mapProperty(Function<AffordanceModel.PropertyMetadata, AffordanceModel.PropertyMetadata> mapper) {
return new HalFormsPayloadMetadata(this.type,
new TreeMap<>(this.properties.values().stream().map(mapper)
.collect(Collectors.toMap(AffordanceModel.Named::getName, Function.identity()))),
this.mediaTypes, this.i18nCodes);
}
// the other methods from the interface
..
}
- In my assembler i can now create my custom
HalFormsPropertyMetadatawith the dynamic options, create a newHalFormsPayloadMetadatawith the normal property descriptor replaced with my custom one and let the framework do its thing.
I find the dance around manipulating the state of the InputPayloadMetadata a bit unnecessary (having to implement my own version just to manipulate a property. Some variant of the InputPayloadMetadata.customize() that just returns a new InputPayloadMetadata instance would have gone a long way.
I can live with having to implement a custom property implementation, but it also seems like having a Map<String,Object> getCustomData() on AffordanceModel.PropertyMetadata could offer a lot of exiting options.
Ideally it would be possible to obtain a builder from an Affordance and just manipulate everything through that. "custom data" does not have to be tied to a media type, so it could go into the general representation. That would allow me to use the provided stuff for most of the work and take over, when something else, like dynamic options or other manipulation is needed.
I kind of hope that I missed some mechanism for setting up Input payload metadata without having to create my own, so thoughts are very welcome!