#833 - Add API to register custom media types.

This commit is contained in:
Greg Turnquist
2019-02-05 11:11:06 -06:00
committed by Oliver Drotbohm
parent 8af414d3cf
commit 8c366e1687
19 changed files with 708 additions and 420 deletions

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2019 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
*
* http://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.collectionjson;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.http.MediaType;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Greg Turnquist
*/
@Configuration
public class CollectionJsonConfigurer {
@Bean
Hypermedia collectionJsonBean() {
return new Hypermedia() {
@Override
public List<MediaType> getMediaTypes() {
return HypermediaType.COLLECTION_JSON.getMediaTypes();
}
@Override
public ObjectMapper createObjectMapper(ObjectMapper mapper) {
ObjectMapper mapper1 = mapper.copy();
mapper1.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper1.registerModule(new Jackson2CollectionJsonModule());
return mapper1;
}
};
}
@Bean
LinkDiscoverer linkDiscoverer() {
return new CollectionJsonLinkDiscoverer();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* Copyright 2013-2019 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.
@@ -20,24 +20,23 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.collectionjson.CollectionJsonConfigurer;
import org.springframework.hateoas.hal.HalConfigurer;
import org.springframework.hateoas.hal.forms.HalFormsConfigurer;
import org.springframework.hateoas.uber.UberConfigurer;
import org.springframework.http.MediaType;
/**
* Activates hypermedia support in the {@link ApplicationContext}. Will register infrastructure beans available for
* injection to ease building hypermedia related code. Which components get registered depends on the hypermedia type
* being activated through the {@link #type()} attribute. Hypermedia-type-specific implementations of the following
* components will be registered:
* <ul>
* <li>{@link LinkDiscoverer}</li>
* <li>a Jackson 2 module to correctly marshal the resource model classes into the appropriate representation.
* </ul>
* Activates hypermedia support in the {@link ApplicationContext}. Will register infrastructure beans to support all
* appropriate web stacks based on selected {@link Hypermedia}-type as well as the classpath.
*
* @see LinkDiscoverer
* @see EntityLinks
* @author Oliver Gierke
* @author Greg Turnquist
*/
@@ -61,7 +60,7 @@ public @interface EnableHypermediaSupport {
* @author Oliver Gierke
* @author Greg Turnquist
*/
enum HypermediaType {
enum HypermediaType implements Hypermedia {
/**
* HAL - Hypermedia Application Language.
@@ -69,27 +68,47 @@ public @interface EnableHypermediaSupport {
* @see http://stateless.co/hal_specification.html
* @see http://tools.ietf.org/html/draft-kelly-json-hal-05
*/
HAL,
HAL(HalConfigurer.class, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8),
/**
* HAL-FORMS - Independent, backward-compatible extension of the HAL designed to add runtime FORM support
*
* @see https://rwcbook.github.io/hal-forms/
*/
HAL_FORMS,
HAL_FORMS(HalFormsConfigurer.class, MediaTypes.HAL_FORMS_JSON),
/**
* Collection+JSON
*
* @see http://amundsen.com/media-types/collection/format/
*/
COLLECTION_JSON,
COLLECTION_JSON(CollectionJsonConfigurer.class, MediaTypes.COLLECTION_JSON),
/**
* UBER Hypermedia
*
* @see http://uberhypermedia.org/
*/
UBER;
UBER(UberConfigurer.class, MediaTypes.UBER_JSON);
private final Class<?> configurer;
private final List<MediaType> mediaTypes;
HypermediaType(Class<?> configurer, MediaType... mediaTypes) {
this.configurer = configurer;
this.mediaTypes = Arrays.asList(mediaTypes);
}
@Override
public List<MediaType> getMediaTypes() {
return this.mediaTypes;
}
@Override
public Optional<Class<?>> configurer() {
return Optional.ofNullable(this.configurer);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2019 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
*
* http://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.config;
import java.util.List;
import java.util.Optional;
import org.springframework.http.MediaType;
import org.springframework.util.MimeType;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Interface for registering custom hypermedia handlers.
*
* @author Greg Turnquist
*/
public interface Hypermedia {
/**
* {@link MediaType}s this hypermedia can handle.
*/
List<MediaType> getMediaTypes();
/**
* Convert list of {@link MediaType}s into an array of {@link MimeType}s to support various Spring constructors.
*/
default MimeType[] getMimeTypes() {
return getMediaTypes().toArray(new MimeType[0]);
}
/**
* Create an {@link ObjectMapper} and register custom serializers and deserializers for the supported media types.
*/
default ObjectMapper createObjectMapper(ObjectMapper mapper) {
return mapper.copy();
}
default Optional<Class<?>> configurer() {
return Optional.empty();
}
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright 2019 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
*
* http://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.config;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator;
import org.springframework.hateoas.hal.forms.HalFormsConfiguration;
import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule;
import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule.HalFormsHandlerInstantiator;
import org.springframework.hateoas.uber.Jackson2UberModule;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Utility class for create {@link ObjectMapper} instances of all hypermedia types.
*
* @author Greg Turnquist
* @since 1.0
*/
public final class HypermediaObjectMapperCreator {
/**
* Create a {@link org.springframework.hateoas.MediaTypes#HAL_JSON}-based {@link ObjectMapper}.
*
* @param objectMapper
* @param curieProvider
* @param relProvider
* @param linkRelationMessageSource
* @param halConfiguration
* @return
*/
public static ObjectMapper createHalObjectMapper(ObjectMapper objectMapper, CurieProvider curieProvider,
RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource, HalConfiguration halConfiguration) {
ObjectMapper mapper = objectMapper.copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2HalModule());
mapper.setHandlerInstantiator(
new HalHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource, halConfiguration));
return mapper;
}
/**
* Create a {@link org.springframework.hateoas.MediaTypes#HAL_FORMS_JSON}-based {@link ObjectMapper}.
*
* @param objectMapper
* @param curieProvider
* @param relProvider
* @param linkRelationMessageSource
* @param halFormsConfiguration
* @return properly configured objectMapper
*/
public static ObjectMapper createHalFormsObjectMapper(ObjectMapper objectMapper, CurieProvider curieProvider,
RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource,
HalFormsConfiguration halFormsConfiguration) {
ObjectMapper mapper = objectMapper.copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2HalFormsModule());
mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource,
true, halFormsConfiguration));
return mapper;
}
/**
* Create a {@link org.springframework.hateoas.MediaTypes#COLLECTION_JSON}-based {@link ObjectMapper}.
*
* @param objectMapper
* @param linkRelationMessageSource
* @return properly configured objectMapper
*/
public static ObjectMapper createCollectionJsonObjectMapper(ObjectMapper objectMapper) {
ObjectMapper mapper = objectMapper.copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2CollectionJsonModule());
return mapper;
}
/**
* Create a {@link org.springframework.hateoas.MediaTypes#UBER_JSON}-based {@link ObjectMapper}.
*
* @param objectMapper
* @return properly configured objectMapper
*/
public static ObjectMapper createUberObjectMapper(ObjectMapper objectMapper) {
ObjectMapper mapper = objectMapper.copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2UberModule());
return mapper;
}
}

View File

@@ -22,22 +22,17 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.collectionjson.CollectionJsonLinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.hal.HalLinkDiscoverer;
import org.springframework.hateoas.hal.forms.HalFormsLinkDiscoverer;
import org.springframework.hateoas.uber.UberLinkDiscoverer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
@@ -68,55 +63,18 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
*/
for (HypermediaType type : types) {
BeanDefinitionBuilder hypermediaTypeBeanDefinition = genericBeanDefinition(HypermediaType.class, () -> type);
registerSourcedBeanDefinition(hypermediaTypeBeanDefinition, metadata, registry);
type.configurer().ifPresent(clazz -> {
Assert.isTrue(clazz.isAnnotationPresent(Configuration.class),
clazz + " must have Spring's @Configuration annotation!");
BeanDefinitionBuilder hypermediaTypeBeanDefinition = genericBeanDefinition(clazz);
registerSourcedBeanDefinition(hypermediaTypeBeanDefinition, metadata, registry);
});
}
/*
* Only register JSONPath-based {@link LinkDiscoverer}s based on the registered {@link HypermediaType}s.
*/
if (JSONPATH_PRESENT) {
for (HypermediaType type : types) {
AbstractBeanDefinition linkDiscovererBeanDefinition = getLinkDiscovererBeanDefinition(type);
registerBeanDefinition(new BeanDefinitionHolder(linkDiscovererBeanDefinition,
BeanDefinitionReaderUtils.generateBeanName(linkDiscovererBeanDefinition, registry)), registry);
}
}
}
/**
* Returns a {@link LinkDiscoverer} {@link BeanDefinition} suitable for the given {@link HypermediaType}.
*
* @param type
* @return
*/
private AbstractBeanDefinition getLinkDiscovererBeanDefinition(HypermediaType type) {
AbstractBeanDefinition definition;
switch (type) {
case HAL:
definition = new RootBeanDefinition(HalLinkDiscoverer.class);
break;
case HAL_FORMS:
definition = new RootBeanDefinition(HalFormsLinkDiscoverer.class);
break;
case COLLECTION_JSON:
definition = new RootBeanDefinition(CollectionJsonLinkDiscoverer.class);
break;
case UBER:
definition = new RootBeanDefinition(UberLinkDiscoverer.class);
break;
default:
throw new IllegalStateException(String.format("Unsupported hypermedia type %s!", type));
}
definition.setSource(this);
return definition;
}
private static String registerSourcedBeanDefinition(BeanDefinitionBuilder builder, AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {

View File

@@ -15,32 +15,16 @@
*/
package org.springframework.hateoas.config.mvc;
import static org.springframework.hateoas.MediaTypes.*;
import static org.springframework.hateoas.config.HypermediaObjectMapperCreator.*;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.hateoas.hal.forms.HalFormsConfiguration;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -51,27 +35,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
@Configuration
@RequiredArgsConstructor
public class HypermediaWebMvcConfigurer implements WebMvcConfigurer, BeanFactoryAware {
private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource";
public class HypermediaWebMvcConfigurer implements WebMvcConfigurer {
private final ObjectMapper mapper;
private final DelegatingRelProvider relProvider;
private final CurieProvider curieProvider;
private final HalConfiguration halConfiguration;
private final HalFormsConfiguration halFormsConfiguration;
private final Collection<HypermediaType> hypermediaTypes;
private BeanFactory beanFactory;
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
private final Collection<Hypermedia> hypermediaTypes;
/*
* (non-Javadoc)
@@ -80,44 +47,10 @@ public class HypermediaWebMvcConfigurer implements WebMvcConfigurer, BeanFactory
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
if (converters.stream().filter(MappingJackson2HttpMessageConverter.class::isInstance)
.filter(AbstractJackson2HttpMessageConverter.class::isInstance)
.map(AbstractJackson2HttpMessageConverter.class::cast)
.map(AbstractJackson2HttpMessageConverter::getObjectMapper)
.anyMatch(Jackson2HalModule::isAlreadyRegisteredIn)) {
return;
}
MessageSourceAccessor linkRelationMessageSource = this.beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
MessageSourceAccessor.class);
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
converters.add(0,
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class,
Arrays.asList(HAL_JSON, HAL_JSON_UTF8), createHalObjectMapper(this.mapper, this.curieProvider,
this.relProvider, linkRelationMessageSource, this.halConfiguration)));
}
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class, Collections.singletonList(HAL_FORMS_JSON),
createHalFormsObjectMapper(this.mapper, this.curieProvider, this.relProvider, linkRelationMessageSource,
this.halFormsConfiguration)));
}
if (this.hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) {
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class,
Arrays.asList(COLLECTION_JSON), createCollectionJsonObjectMapper(this.mapper)));
}
if (this.hypermediaTypes.contains(HypermediaType.UBER)) {
this.hypermediaTypes.forEach(hypermedia -> {
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class, Collections.singletonList(UBER_JSON), createUberObjectMapper(this.mapper)));
}
ResourceSupport.class, hypermedia.getMediaTypes(), hypermedia.createObjectMapper(this.mapper)));
});
}
}

View File

@@ -20,7 +20,7 @@ import java.util.Collection;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
@@ -38,13 +38,11 @@ public class WebMvcHateoasConfiguration {
@Bean
HypermediaWebMvcConfigurer hypermediaWebMvcConfigurer(ObjectProvider<ObjectMapper> mapper,
DelegatingRelProvider relProvider, ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<HalConfiguration> halConfiguration, ObjectProvider<HalFormsConfiguration> halFormsConfiguration,
Collection<HypermediaType> hypermediaTypes) {
// DelegatingRelProvider relProvider, ObjectProvider<CurieProvider> curieProvider,
// ObjectProvider<HalConfiguration> halConfiguration, ObjectProvider<HalFormsConfiguration> halFormsConfiguration,
Collection<Hypermedia> hypermediaTypes) {
return new HypermediaWebMvcConfigurer(mapper.getIfAvailable(ObjectMapper::new), relProvider,
curieProvider.getIfAvailable(), halConfiguration.getIfAvailable(HalConfiguration::new),
halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), hypermediaTypes);
return new HypermediaWebMvcConfigurer(mapper.getIfAvailable(ObjectMapper::new), hypermediaTypes);
}
@Bean

View File

@@ -15,25 +15,14 @@
*/
package org.springframework.hateoas.config.reactive;
import static org.springframework.hateoas.config.HypermediaObjectMapperCreator.*;
import lombok.RequiredArgsConstructor;
import java.util.Collection;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
import org.springframework.hateoas.hal.forms.HalFormsConfiguration;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
@@ -51,23 +40,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
@Configuration
@RequiredArgsConstructor
public class HypermediaWebFluxConfigurer implements WebFluxConfigurer, BeanFactoryAware {
private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource";
public class HypermediaWebFluxConfigurer implements WebFluxConfigurer {
private final ObjectMapper mapper;
private final DelegatingRelProvider relProvider;
private final CurieProvider curieProvider;
private final HalConfiguration halConfiguration;
private final HalFormsConfiguration halFormsConfiguration;
private final Collection<HypermediaType> hypermediaTypes;
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
private final Collection<Hypermedia> hypermediaTypes;
/**
* Configure custom HTTP message readers and writers or override built-in ones.
@@ -79,58 +55,14 @@ public class HypermediaWebFluxConfigurer implements WebFluxConfigurer, BeanFacto
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
if (configurer.getReaders().stream()
.flatMap(httpMessageReader -> httpMessageReader.getReadableMediaTypes().stream())
.anyMatch(mediaType -> mediaType == MediaTypes.HAL_JSON || mediaType == MediaTypes.HAL_JSON_UTF8
|| mediaType == MediaTypes.HAL_FORMS_JSON || mediaType == MediaTypes.COLLECTION_JSON
|| mediaType == MediaTypes.UBER_JSON)) {
// Already configured for hypermedia!
return;
}
MessageSourceAccessor linkRelationMessageSource = this.beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
MessageSourceAccessor.class);
CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
this.hypermediaTypes.forEach(hypermedia -> {
ObjectMapper halObjectMapper = createHalObjectMapper(this.mapper, this.curieProvider, this.relProvider,
linkRelationMessageSource, this.halConfiguration);
customCodecs.encoder(
new Jackson2JsonEncoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
customCodecs.decoder(
new Jackson2JsonDecoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
}
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
ObjectMapper halFormsObjectMapper = createHalFormsObjectMapper(this.mapper, this.curieProvider,
this.relProvider, linkRelationMessageSource, this.halFormsConfiguration);
customCodecs.encoder(
new Jackson2JsonEncoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
customCodecs.decoder(
new Jackson2JsonDecoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
}
if (this.hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) {
ObjectMapper collectionJsonObjectMapper = createCollectionJsonObjectMapper(this.mapper);
customCodecs.encoder(new Jackson2JsonEncoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
customCodecs.decoder(new Jackson2JsonDecoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
}
if (this.hypermediaTypes.contains(HypermediaType.UBER)) {
ObjectMapper uberObjectMapper = createUberObjectMapper(this.mapper);
customCodecs.encoder(new Jackson2JsonEncoder(uberObjectMapper, MediaTypes.UBER_JSON));
customCodecs.decoder(new Jackson2JsonDecoder(uberObjectMapper, MediaTypes.UBER_JSON));
}
ObjectMapper objectMapper = hypermedia.createObjectMapper(this.mapper);
customCodecs.encoder(new Jackson2JsonEncoder(objectMapper, hypermedia.getMimeTypes()));
customCodecs.decoder(new Jackson2JsonDecoder(objectMapper, hypermedia.getMimeTypes()));
});
customCodecs.encoder(CharSequenceEncoder.allMimeTypes());
customCodecs.decoder(StringDecoder.allMimeTypes());

View File

@@ -15,31 +15,22 @@
*/
package org.springframework.hateoas.config.reactive;
import static org.springframework.hateoas.config.HypermediaObjectMapperCreator.*;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
import org.springframework.hateoas.hal.forms.HalFormsConfiguration;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.util.MimeType;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
@@ -53,23 +44,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
@Configuration
@RequiredArgsConstructor
public class WebClientConfigurer implements BeanFactoryAware {
private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource";
public class WebClientConfigurer {
private final ObjectMapper mapper;
private final DelegatingRelProvider relProvider;
private final CurieProvider curieProvider;
private final HalConfiguration halConfiguration;
private final HalFormsConfiguration halFormsConfiguration;
private final Collection<HypermediaType> hypermediaTypes;
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
private final Collection<Hypermedia> hypermediaTypes;
/**
* Return a set of {@link ExchangeStrategies} driven by registered {@link HypermediaType}s.
@@ -78,45 +56,16 @@ public class WebClientConfigurer implements BeanFactoryAware {
*/
public ExchangeStrategies hypermediaExchangeStrategies() {
MessageSourceAccessor linkRelationMessageSource = this.beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
MessageSourceAccessor.class);
List<Encoder<?>> encoders = new ArrayList<>();
List<Decoder<?>> decoders = new ArrayList<>();
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
this.hypermediaTypes.forEach(hypermedia -> {
ObjectMapper halObjectMapper = createHalObjectMapper(this.mapper, this.curieProvider, this.relProvider,
linkRelationMessageSource, this.halConfiguration);
ObjectMapper objectMapper = hypermedia.createObjectMapper(this.mapper);
encoders.add(new Jackson2JsonEncoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
decoders.add(new Jackson2JsonDecoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
}
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
ObjectMapper halFormsObjectMapper = createHalFormsObjectMapper(this.mapper, this.curieProvider,
this.relProvider, linkRelationMessageSource, this.halFormsConfiguration);
encoders.add(new Jackson2JsonEncoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
decoders.add(new Jackson2JsonDecoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
}
if (this.hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) {
ObjectMapper collectionJsonObjectMapper = createCollectionJsonObjectMapper(this.mapper);
encoders.add(new Jackson2JsonEncoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
decoders.add(new Jackson2JsonDecoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
}
if (this.hypermediaTypes.contains(HypermediaType.UBER)) {
ObjectMapper uberObjectMapper = createUberObjectMapper(this.mapper);
encoders.add(new Jackson2JsonEncoder(uberObjectMapper, MediaTypes.UBER_JSON));
decoders.add(new Jackson2JsonDecoder(uberObjectMapper, MediaTypes.UBER_JSON));
}
encoders.add(new Jackson2JsonEncoder(objectMapper, hypermedia.getMediaTypes().toArray(new MimeType[]{})));
decoders.add(new Jackson2JsonDecoder(objectMapper, hypermedia.getMediaTypes().toArray(new MimeType[]{})));
});
encoders.add(CharSequenceEncoder.allMimeTypes());
decoders.add(StringDecoder.allMimeTypes());

View File

@@ -20,7 +20,7 @@ import java.util.Collection;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
@@ -39,13 +39,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
public class WebFluxHateoasConfiguration {
@Bean
WebClientConfigurer webClientConfigurer(ObjectProvider<ObjectMapper> mapper, DelegatingRelProvider relProvider,
ObjectProvider<CurieProvider> curieProvider, ObjectProvider<HalConfiguration> halConfiguration,
ObjectProvider<HalFormsConfiguration> halFormsConfiguration, Collection<HypermediaType> hypermediaTypes) {
WebClientConfigurer webClientConfigurer(ObjectProvider<ObjectMapper> mapper,
Collection<Hypermedia> hypermediaTypes) {
return new WebClientConfigurer(mapper.getIfAvailable(ObjectMapper::new), relProvider,
curieProvider.getIfAvailable(), halConfiguration.getIfAvailable(HalConfiguration::new),
halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), hypermediaTypes);
return new WebClientConfigurer(mapper.getIfAvailable(ObjectMapper::new), hypermediaTypes);
}
@Bean
@@ -55,13 +52,9 @@ public class WebFluxHateoasConfiguration {
@Bean
HypermediaWebFluxConfigurer hypermediaWebFluxConfigurer(ObjectProvider<ObjectMapper> mapper,
DelegatingRelProvider relProvider, ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<HalConfiguration> halConfiguration, ObjectProvider<HalFormsConfiguration> halFormsConfiguration,
Collection<HypermediaType> hypermediaTypes) {
Collection<Hypermedia> hypermediaTypes) {
return new HypermediaWebFluxConfigurer(mapper.getIfAvailable(ObjectMapper::new), relProvider,
curieProvider.getIfAvailable(), halConfiguration.getIfAvailable(HalConfiguration::new),
halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), hypermediaTypes);
return new HypermediaWebFluxConfigurer(mapper.getIfAvailable(ObjectMapper::new), hypermediaTypes);
}
/**

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2019 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
*
* http://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.hal;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.http.MediaType;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Greg Turnquist
*/
@Configuration
public class HalConfigurer {
@Bean
Hypermedia halBean(DelegatingRelProvider relProvider,
ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<HalConfiguration> halConfiguration,
MessageSourceAccessor messageSourceAccessor) {
return new Hypermedia() {
@Override
public List<MediaType> getMediaTypes() {
return HypermediaType.HAL.getMediaTypes();
}
@Override
public ObjectMapper createObjectMapper(ObjectMapper mapper) {
ObjectMapper mapper1 = mapper.copy();
mapper1.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper1.registerModule(new Jackson2HalModule());
mapper1.setHandlerInstantiator(
new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider.getIfAvailable(), messageSourceAccessor, halConfiguration.getIfAvailable(HalConfiguration::new)));
return mapper1;
}
};
}
@Bean
LinkDiscoverer linkDiscoverer() {
return new HalLinkDiscoverer();
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2019 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
*
* http://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.hal.forms;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.http.MediaType;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Greg Turnquist
*/
@Configuration
public class HalFormsConfigurer {
@Bean
Hypermedia halFormsBean(DelegatingRelProvider relProvider,
ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<HalFormsConfiguration> halFormsConfiguration,
MessageSourceAccessor messageSourceAccessor) {
return new Hypermedia() {
@Override
public List<MediaType> getMediaTypes() {
return HypermediaType.HAL_FORMS.getMediaTypes();
}
@Override
public ObjectMapper createObjectMapper(ObjectMapper mapper) {
ObjectMapper mapper1 = mapper.copy();
mapper1.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper1.registerModule(new Jackson2HalFormsModule());
mapper1.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider, curieProvider.getIfAvailable(), messageSourceAccessor,
true, halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new)));
return mapper1;
}
};
}
@Bean LinkDiscoverer linkDiscoverer() {
return new HalFormsLinkDiscoverer();
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2019 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
*
* http://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.uber;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.config.Hypermedia;
import org.springframework.http.MediaType;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Greg Turnquist
*/
@Configuration
public class UberConfigurer {
@Bean
Hypermedia uberBean() {
return new Hypermedia() {
@Override
public List<MediaType> getMediaTypes() {
return HypermediaType.UBER.getMediaTypes();
}
@Override
public ObjectMapper createObjectMapper(ObjectMapper mapper) {
ObjectMapper mapper1 = mapper.copy();
mapper1.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper1.registerModule(new Jackson2UberModule());
return mapper1;
}
};
}
@Bean
LinkDiscoverer linkDiscoverer() {
return new UberLinkDiscoverer();
}
}