Skip to content

Supply a custom media type with my HAL+JSON-compatible resource #1051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mkhl opened this issue Aug 11, 2019 · 2 comments
Closed

Supply a custom media type with my HAL+JSON-compatible resource #1051

mkhl opened this issue Aug 11, 2019 · 2 comments

Comments

@mkhl
Copy link

mkhl commented Aug 11, 2019

I'm using Spring 5.1.8, Spring Boot 2.1.6, and Spring HATEOAS 0.25.1.

For some resource, I essentially want to do what spring boot actuator does: serve my resources with a custom media type by default, but keep the responses HAL+JSON-compatible.

For that to work, it seems like I need to essentially duplicate ConverterRegisteringWebMvcConfigurer, or alternatively ensure that it runs before my own WebMvcConfigurer so I can find and reuse the custom ObjectMapper with the Jackson2HalModule already registered, then insert my custom HttpMessageConverter in front of the one it inserts.

@Configuration
class CustomHalConfiguration implements WebMvcConfigurer {
    private final Map<Class<?>, List<MediaType>> types = Map.of(…, List.of(…));

    private final ObjectFactory<ConverterRegisteringWebMvcConfigurer> configurer;

    HalConfiguration(ObjectFactory<ConverterRegisteringWebMvcConfigurer> configurer) {
        this.configurer = configurer;
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // ensure the spring-hateoas configurer runs first so we can find its extended ObjectMapper
        configurer.getObject().extendMessageConverters(converters);
        findMapper(converters).map(this::converters)
                .ifPresent(custom -> converters.addAll(0, custom));
    }

    private Optional<ObjectMapper> findMapper(List<HttpMessageConverter<?>> converters) {
        return converters.stream()
                .filter(MappingJackson2HttpMessageConverter.class::isInstance)
                .map(MappingJackson2HttpMessageConverter.class::cast)
                .map(MappingJackson2HttpMessageConverter::getObjectMapper)
                .filter(Jackson2HalModule::isAlreadyRegisteredIn)
                .findAny();
    }

    private List<MappingJackson2HttpMessageConverter> converters(ObjectMapper mapper) {
        return types.entrySet().stream()
                .map(entry -> converter(entry.getKey(), entry.getValue()))
                .peek(converter -> converter.setObjectMapper(mapper))
                .collect(toList());
    }

    private MappingJackson2HttpMessageConverter converter(Class<?> type, List<MediaType> mediaTypes) {
        var converter = new TypeConstrainedMappingJackson2HttpMessageConverter(type);
        converter.setSupportedMediaTypes(mediaTypes);
        return converter;
    }
}

(The snippet probably overuses the Stream API, please let me know if that distracts from the issue.)

This configuration feels more like I'm fighting the API than using it, is there an easier way ot accomplish what I want that I'm missing?

If there isn't, it would be really nice if there were a better way to extend the HAL+JSON-compatible HttpMessageConverter or provide custom ones, or maybe just if there were a way to access the HAL-enabled ObjectMapper that ConverterRegisteringWebMvcConfigurer constructs.

@gregturn
Copy link
Contributor

This is something we implemented in 1.0. Backporting to 0.25 would require sweeping changes.

@mkhl
Copy link
Author

mkhl commented Aug 12, 2019

I see, thank you for letting me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants