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 3a533599..786fb307 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 @@ -15,7 +15,10 @@ */ package org.springframework.hateoas.mediatype.hal.forms; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; @@ -23,7 +26,9 @@ import java.util.function.Function; import org.springframework.core.ResolvableType; import org.springframework.hateoas.AffordanceModel.PropertyMetadata; +import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.mediatype.hal.HalConfiguration; +import org.springframework.http.MediaType; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -41,6 +46,7 @@ public class HalFormsConfiguration { private final Map, String> patterns; private final Consumer objectMapperCustomizer; private final HalFormsOptionsFactory options; + private final List mediaTypes; /** * Creates a new {@link HalFormsConfiguration} backed by a default {@link HalConfiguration}. @@ -55,21 +61,25 @@ public class HalFormsConfiguration { * @param halConfiguration must not be {@literal null}. */ public HalFormsConfiguration(HalConfiguration halConfiguration) { - this(halConfiguration, new HashMap<>(), new HalFormsOptionsFactory(), __ -> {}); + this(halConfiguration, new HashMap<>(), new HalFormsOptionsFactory(), __ -> {}, + Collections.singletonList(MediaTypes.HAL_FORMS_JSON)); } private HalFormsConfiguration(HalConfiguration halConfiguration, Map, String> patterns, - HalFormsOptionsFactory options, @Nullable Consumer objectMapperCustomizer) { + HalFormsOptionsFactory options, @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(mediaTypes, "Media types must not be null!"); this.halConfiguration = halConfiguration; this.patterns = patterns; this.objectMapperCustomizer = objectMapperCustomizer; this.options = options; + this.mediaTypes = new ArrayList<>(mediaTypes); } /** @@ -106,7 +116,7 @@ public class HalFormsConfiguration { Map, String> newPatterns = new HashMap<>(patterns); newPatterns.put(type, pattern); - return new HalFormsConfiguration(halConfiguration, newPatterns, options, objectMapperCustomizer); + return new HalFormsConfiguration(halConfiguration, newPatterns, options, objectMapperCustomizer, mediaTypes); } /** @@ -122,7 +132,30 @@ public class HalFormsConfiguration { return this.objectMapperCustomizer == objectMapperCustomizer // ? this // - : new HalFormsConfiguration(halConfiguration, patterns, options, objectMapperCustomizer); + : new HalFormsConfiguration(halConfiguration, patterns, options, objectMapperCustomizer, mediaTypes); + } + + /** + * Registers additional media types that are supposed to be aliases to {@link MediaTypes#HAL_FORMS_JSON}. Registered + * {@link MediaType}s will be preferred over the default one, i.e. they'll be listed first in client's accept headers + * etc. + * + * @param mediaType must not be {@literal null}. + * @return will never be {@literal null}. + * @since 1.4 + */ + public HalFormsConfiguration withMediaType(MediaType mediaType) { + + Assert.notNull(mediaType, "MediaType must not be null!"); + + if (mediaTypes.contains(mediaType)) { + return this; + } + + List newMediaTypes = new ArrayList<>(mediaTypes); + newMediaTypes.add(mediaTypes.size() - 1, mediaType); + + return new HalFormsConfiguration(halConfiguration, patterns, options, objectMapperCustomizer, newMediaTypes); } /** @@ -154,7 +187,7 @@ public class HalFormsConfiguration { Function creator) { return new HalFormsConfiguration(halConfiguration, patterns, options.withOptions(type, property, creator), - objectMapperCustomizer); + objectMapperCustomizer, mediaTypes); } /** @@ -179,9 +212,18 @@ public class HalFormsConfiguration { * Returns the regular expression pattern that is registered for the given type. * * @param type must not be {@literal null}. - * @return + * @return will never be {@literal null}. */ Optional getTypePatternFor(ResolvableType type) { return Optional.ofNullable(patterns.get(type.resolve(Object.class))); } + + /** + * The {@link MediaType}s that we want to register this configuration for. + * + * @return will never be {@literal null}. + */ + List getMediaTypes() { + return Collections.unmodifiableList(mediaTypes); + } } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java index 4dd73f13..8cd9518c 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java @@ -23,7 +23,6 @@ import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFact import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.client.LinkDiscoverer; -import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; import org.springframework.hateoas.config.HypermediaMappingInformation; import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.CurieProvider; @@ -106,7 +105,7 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation { */ @Override public List getMediaTypes() { - return HypermediaType.HAL_FORMS.getMediaTypes(); + return getResolvedConfiguration().getMediaTypes(); } HalFormsConfiguration getResolvedConfiguration() { diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfigurationIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfigurationIntegrationTest.java new file mode 100644 index 00000000..520ac6b4 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfigurationIntegrationTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 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 static org.assertj.core.api.Assertions.*; +import static org.springframework.hateoas.support.ContextTester.*; + +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.hateoas.config.EnableHypermediaSupport; +import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; +import org.springframework.hateoas.mediatype.MediaTypeTestUtils; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * Integration tests for HAL media type configuration. + * + * @author Oliver Drotbohm + */ +class HalFormsMediaTypeConfigurationIntegrationTest { + + static final MediaType CUSTOM_MEDIA_TYPE = MediaType.parseMediaType("application/vnd.my-custom-mediatype"); + + @Test // #1591 + void includesCustomMediaTypeFromConfiguration() { + + withServletContext(ConfigurationWithCustomMediaType.class, it -> { + assertThat(MediaTypeTestUtils.getSupportedHypermediaTypes(it)).contains(CUSTOM_MEDIA_TYPE); + }); + } + + @Configuration + @EnableWebMvc + @EnableHypermediaSupport(type = HypermediaType.HAL_FORMS) + static class ConfigurationWithCustomMediaType { + + @Bean + HalFormsConfiguration halConfiguration() { + return new HalFormsConfiguration().withMediaType(CUSTOM_MEDIA_TYPE); + } + } +}