diff --git a/pom.xml b/pom.xml
index 04c04c5f..3bff5ad8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -976,6 +976,13 @@
test
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
net.jadler
jadler-all
@@ -1007,15 +1014,6 @@
test
-
-
-
- javax.servlet
- javax.servlet-api
- 3.1.0
- provided
-
-
com.tngtech.archunit
archunit
diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java
index e62d3774..4e14ec7b 100644
--- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java
+++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java
@@ -15,13 +15,18 @@
*/
package org.springframework.hateoas.mediatype.hal;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.MediaTypes;
+import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
@@ -45,6 +50,7 @@ public class HalConfiguration {
private final RenderSingleLinks renderSingleLinks;
private final Map singleLinksPerPattern;
private final Consumer objectMapperCustomizer;
+ private final List mediaTypes;
/**
* Configures whether the Jackson property naming strategy is applied to link relations and within {@code _embedded}
@@ -62,22 +68,26 @@ public class HalConfiguration {
* Creates a new default {@link HalConfiguration} rendering single links as immediate sub-document.
*/
public HalConfiguration() {
- this(RenderSingleLinks.AS_SINGLE, new LinkedHashMap<>(), true, true, __ -> {});
+
+ this(RenderSingleLinks.AS_SINGLE, new LinkedHashMap<>(), true, true, __ -> {},
+ Collections.singletonList(MediaTypes.HAL_JSON));
}
private HalConfiguration(RenderSingleLinks renderSingleLinks, Map singleLinksPerPattern,
boolean applyPropertyNamingStrategy, boolean enforceEmbeddedCollections,
- Consumer objectMapperCustomizer) {
+ Consumer objectMapperCustomizer, List mediaTypes) {
Assert.notNull(renderSingleLinks, "RenderSingleLinks must not be null!");
Assert.notNull(singleLinksPerPattern, "Single links per pattern map must not be null!");
Assert.notNull(objectMapperCustomizer, "ObjectMapper customizer must not be null!");
+ Assert.notNull(mediaTypes, "MediaTypes must not be null!");
this.renderSingleLinks = renderSingleLinks;
this.singleLinksPerPattern = singleLinksPerPattern;
this.applyPropertyNamingStrategy = applyPropertyNamingStrategy;
this.enforceEmbeddedCollections = enforceEmbeddedCollections;
this.objectMapperCustomizer = objectMapperCustomizer;
+ this.mediaTypes = mediaTypes;
}
/**
@@ -144,7 +154,7 @@ public class HalConfiguration {
return this.renderSingleLinks == renderSingleLinks //
? this //
: new HalConfiguration(renderSingleLinks, singleLinksPerPattern, applyPropertyNamingStrategy,
- enforceEmbeddedCollections, objectMapperCustomizer);
+ enforceEmbeddedCollections, objectMapperCustomizer, mediaTypes);
}
/**
@@ -160,7 +170,7 @@ public class HalConfiguration {
return this.singleLinksPerPattern == singleLinksPerPattern //
? this //
: new HalConfiguration(renderSingleLinks, singleLinksPerPattern, applyPropertyNamingStrategy,
- enforceEmbeddedCollections, objectMapperCustomizer);
+ enforceEmbeddedCollections, objectMapperCustomizer, mediaTypes);
}
/**
@@ -175,7 +185,7 @@ public class HalConfiguration {
return this.applyPropertyNamingStrategy == applyPropertyNamingStrategy //
? this //
: new HalConfiguration(renderSingleLinks, singleLinksPerPattern, applyPropertyNamingStrategy,
- enforceEmbeddedCollections, objectMapperCustomizer);
+ enforceEmbeddedCollections, objectMapperCustomizer, mediaTypes);
}
/**
@@ -190,15 +200,46 @@ public class HalConfiguration {
return this.enforceEmbeddedCollections == enforceEmbeddedCollections //
? this //
: new HalConfiguration(renderSingleLinks, singleLinksPerPattern, applyPropertyNamingStrategy,
- enforceEmbeddedCollections, objectMapperCustomizer);
+ enforceEmbeddedCollections, objectMapperCustomizer, mediaTypes);
}
+ /**
+ * Configures an {@link ObjectMapper} customizer to tweak the instance after it has been pre-configured with all HAL
+ * specific setup.
+ *
+ * @param objectMapperCustomizer must not be {@literal null}.
+ * @return will never be {@literal null}.
+ */
public HalConfiguration withObjectMapperCustomizer(Consumer objectMapperCustomizer) {
return this.objectMapperCustomizer == objectMapperCustomizer //
? this //
: new HalConfiguration(renderSingleLinks, singleLinksPerPattern, applyPropertyNamingStrategy,
- enforceEmbeddedCollections, objectMapperCustomizer);
+ enforceEmbeddedCollections, objectMapperCustomizer, mediaTypes);
+ }
+
+ /**
+ * Registers additional media types that are supposed to be aliases to {@link MediaTypes#HAL_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.3
+ */
+ public HalConfiguration 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 HalConfiguration(renderSingleLinks, singleLinksPerPattern, applyPropertyNamingStrategy,
+ enforceEmbeddedCollections, objectMapperCustomizer, newMediaTypes);
}
public RenderSingleLinks getRenderSingleLinks() {
@@ -220,6 +261,10 @@ public class HalConfiguration {
return this;
}
+ List getMediaTypes() {
+ return mediaTypes;
+ }
+
/**
* Configuration option how to render single links of a given {@link LinkRelation}.
*
diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java
index 02e83f32..ed03c631 100644
--- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java
+++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java
@@ -23,7 +23,6 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
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.server.LinkRelationProvider;
@@ -69,7 +68,7 @@ public class HalMediaTypeConfiguration implements HypermediaMappingInformation {
*/
@Override
public List getMediaTypes() {
- return HypermediaType.HAL.getMediaTypes();
+ return this.halConfiguration.getIfAvailable(HalConfiguration::new).getMediaTypes();
}
/*
diff --git a/src/test/java/org/springframework/hateoas/mediatype/MediaTypeTestUtils.java b/src/test/java/org/springframework/hateoas/mediatype/MediaTypeTestUtils.java
new file mode 100644
index 00000000..8b667bf4
--- /dev/null
+++ b/src/test/java/org/springframework/hateoas/mediatype/MediaTypeTestUtils.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.hateoas.RepresentationModel;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+
+/**
+ * Test utilities to verify configuration of media type support.
+ *
+ * @author Oliver Drotbohm
+ */
+public class MediaTypeTestUtils {
+
+ /**
+ * Looks up the the media types supported for {@link RepresentationModel} in the {@link RequestMappingHandlerAdapter}
+ * within the given {@link ApplicationContext}.
+ *
+ * @param context must not be {@literal null}.
+ * @return will never be {@literal null}.
+ */
+ public static List getSupportedHypermediaTypes(ApplicationContext context) {
+ return getSupportedHypermediaTypes(context, RepresentationModel.class);
+ }
+
+ /**
+ * Looks up the the media types supported for the given type in the {@link RequestMappingHandlerAdapter} within the
+ * given {@link ApplicationContext}.
+ *
+ * @param context must not be {@literal null}.
+ * @param type must not be {@literal null}.
+ * @return will never be {@literal null}.
+ */
+ public static List getSupportedHypermediaTypes(ApplicationContext context, Class> type) {
+
+ RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
+
+ return adapter.getMessageConverters().stream() //
+ .filter(MappingJackson2HttpMessageConverter.class::isInstance) //
+ .map(MappingJackson2HttpMessageConverter.class::cast) //
+ .findFirst() //
+ .map(it -> it.getSupportedMediaTypes(type)) //
+ .orElseGet(() -> Collections.emptyList()); //
+ }
+}
diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfigurationIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfigurationIntegrationTest.java
new file mode 100644
index 00000000..60ba114e
--- /dev/null
+++ b/src/test/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfigurationIntegrationTest.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;
+
+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 HalMediaTypeConfigurationIntegrationTest {
+
+ static final MediaType CUSTOM_MEDIA_TYPE = MediaType.parseMediaType("application/vnd.my-custom-mediatype");
+
+ @Test // #1253
+ void includesCustomMediaTypeFromConfiguration() {
+
+ withServletContext(ConfigurationWithCustomMediaType.class, it -> {
+ assertThat(MediaTypeTestUtils.getSupportedHypermediaTypes(it)).contains(CUSTOM_MEDIA_TYPE);
+ });
+ }
+
+ @Configuration
+ @EnableWebMvc
+ @EnableHypermediaSupport(type = HypermediaType.HAL)
+ static class ConfigurationWithCustomMediaType {
+
+ @Bean
+ HalConfiguration halConfiguration() {
+ return new HalConfiguration().withMediaType(CUSTOM_MEDIA_TYPE);
+ }
+ }
+}