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
HalFormsOptions in the HalFormsConfiguration I 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
Affordance type from WebMvcLinkBuilder.afford() is locked down, I create the affordance manually, like the docs indicate and try to setup the proper AffordanceModel.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
HalFormsPropertyMetadata with the dynamic options, create a new HalFormsPayloadMetadata with 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!
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
HalFormsOptionsto my API. The route I ended up taking was the following:HalFormsOptionsin theHalFormsConfigurationI decided to lean on that and did:Affordancetype fromWebMvcLinkBuilder.afford()is locked down, I create the affordance manually, like the docs indicate and try to setup the properAffordanceModel.InputPayloadMetadata:.customize()method does not mutate anything). I created my own InputPayloadMetadata to mutate the properties: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
InputPayloadMetadataa bit unnecessary (having to implement my own version just to manipulate a property. Some variant of theInputPayloadMetadata.customize()that just returns a newInputPayloadMetadatainstance 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()onAffordanceModel.PropertyMetadatacould offer a lot of exiting options.Ideally it would be possible to obtain a builder from an
Affordanceand 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!