Skip to content

Commit 8a8e4b0

Browse files
committed
GH-2276 - Add support for official IANA HAL media type identifier.
The media type identifier for HAL registered with the IANA is application/vnd.hal+json, notably different from application/hal+json used in the RFC document. This commit adds a constant for that media type identifier and updates the configuration spots necessary to additionally consider it. [0] https://www.iana.org/assignments/media-types/application/vnd.hal+json [1] https://datatracker.ietf.org/doc/html/draft-kelly-json-hal
1 parent b19f7de commit 8a8e4b0

11 files changed

+59
-26
lines changed

Diff for: src/main/java/org/springframework/hateoas/MediaTypes.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
/**
2121
* Constants for well-known hypermedia types.
22-
*
22+
*
2323
* @author Oliver Gierke
2424
* @author Przemek Nowak
2525
* @author Drummond Dawson
@@ -37,6 +37,20 @@ public class MediaTypes {
3737
*/
3838
public static final MediaType HAL_JSON = MediaType.valueOf(HAL_JSON_VALUE);
3939

40+
/**
41+
* A String equivalent of {@link MediaTypes#VND_HAL_JSON}.
42+
*
43+
* @since 2.5
44+
*/
45+
public static final String VND_HAL_JSON_VALUE = "application/vnd.hal+json";
46+
47+
/**
48+
* Public constant media type for {@code application/vnd.hal+json}.
49+
*
50+
* @since 2.5
51+
*/
52+
public static final MediaType VND_HAL_JSON = MediaType.valueOf(VND_HAL_JSON_VALUE);
53+
4054
/**
4155
* A String equivalent of {@link MediaTypes#ALPS_JSON}.
4256
*/
@@ -76,7 +90,7 @@ public class MediaTypes {
7690
* Public constant media type for {@code application/vnd.amundsen-uber+json}.
7791
*/
7892
public static final MediaType UBER_JSON = MediaType.parseMediaType(UBER_JSON_VALUE);
79-
93+
8094
/**
8195
* A String equivalent of {@link MediaTypes#VND_ERROR_JSON}.
8296
*/

Diff for: src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java

+23-9
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@
2020
import java.lang.annotation.Retention;
2121
import java.lang.annotation.RetentionPolicy;
2222
import java.lang.annotation.Target;
23+
import java.util.List;
2324

24-
import org.springframework.context.ApplicationContext;
2525
import org.springframework.context.annotation.Import;
2626
import org.springframework.hateoas.MediaTypes;
2727
import org.springframework.hateoas.support.WebStack;
2828
import org.springframework.http.MediaType;
29-
import org.springframework.web.reactive.function.client.WebClient;
3029

3130
/**
3231
* Activates hypermedia support in the {@link ApplicationContext}. Will register infrastructure beans to support all
@@ -50,8 +49,8 @@
5049

5150
/**
5251
* Configures which {@link WebStack}s we're supposed to enable support for. By default we're activating it for all
53-
* available ones if they happen to be in use. Configure this explicitly in case you're using WebFlux components
54-
* like {@link WebClient} but don't want to use hypermedia operations with it.
52+
* available ones if they happen to be in use. Configure this explicitly in case you're using WebFlux components like
53+
* {@link WebClient} but don't want to use hypermedia operations with it.
5554
*
5655
* @return
5756
*/
@@ -69,9 +68,9 @@ enum HypermediaType {
6968
* HAL - Hypermedia Application Language.
7069
*
7170
* @see http://stateless.co/hal_specification.html
72-
* @see https://tools.ietf.org/html/draft-kelly-json-hal-05
71+
* @see https://tools.ietf.org/html/draft-kelly-json-hal
7372
*/
74-
HAL(MediaTypes.HAL_JSON, "hal"),
73+
HAL(List.of(MediaTypes.HAL_JSON, MediaTypes.VND_HAL_JSON), "hal"),
7574

7675
/**
7776
* HAL-FORMS - Independent, backward-compatible extension of the HAL designed to add runtime FORM support
@@ -96,18 +95,33 @@ enum HypermediaType {
9695
*/
9796
UBER(MediaTypes.UBER_JSON, "uber");
9897

99-
private final MediaType mediaTypes;
98+
private final List<MediaType> mediaTypes;
10099
private final String localPackageName;
101100

102101
HypermediaType(MediaType mediaType, String localPackageName) {
103-
this.mediaTypes = mediaType;
102+
this.mediaTypes = List.of(mediaType);
104103
this.localPackageName = localPackageName;
105104
}
106105

107-
public MediaType getMediaType() {
106+
HypermediaType(List<MediaType> mediaTypes, String localPackageName) {
107+
this.mediaTypes = mediaTypes;
108+
this.localPackageName = localPackageName;
109+
}
110+
111+
public List<MediaType> getMediaTypes() {
108112
return this.mediaTypes;
109113
}
110114

115+
/**
116+
* @deprecated since 2.5, in favor of {@link #getMediaTypes()} as a logical media type might have been associated
117+
* with multiple media type identifiers (see {@link #HAL}).
118+
* @return will never be {@literal null}.
119+
*/
120+
@Deprecated(since = "2.5", forRemoval = true)
121+
public MediaType getMediaType() {
122+
return this.mediaTypes.get(0);
123+
}
124+
111125
public String getLocalPackageName() {
112126
return localPackageName;
113127
}

Diff for: src/main/java/org/springframework/hateoas/config/HypermediaConfigurationImportSelector.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public String[] selectImports(AnnotationMetadata metadata) {
7979
List<MediaType> types = attributes == null //
8080
? Collections.emptyList() //
8181
: Arrays.stream((HypermediaType[]) attributes.get("type")) //
82-
.map(it -> it.getMediaType()) //
82+
.flatMap(it -> it.getMediaTypes().stream()) //
8383
.collect(Collectors.toList());
8484

8585
if (!beanFactory.containsBean("hateoasMediaTypeConfigurer")) {

Diff for: src/main/java/org/springframework/hateoas/mediatype/collectionjson/CollectionJsonMediaTypeConfiguration.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.hateoas.mediatype.collectionjson;
1717

18-
import java.util.Collections;
1918
import java.util.List;
2019

2120
import org.springframework.context.annotation.Bean;
@@ -48,7 +47,7 @@ LinkDiscoverer collectionJsonLinkDiscoverer() {
4847
*/
4948
@Override
5049
public List<MediaType> getMediaTypes() {
51-
return Collections.singletonList(HypermediaType.COLLECTION_JSON.getMediaType());
50+
return HypermediaType.COLLECTION_JSON.getMediaTypes();
5251
}
5352

5453
/*

Diff for: src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616
package org.springframework.hateoas.mediatype.hal;
1717

1818
import java.util.ArrayList;
19-
import java.util.Collections;
2019
import java.util.LinkedHashMap;
2120
import java.util.List;
2221
import java.util.Map;
2322
import java.util.Map.Entry;
2423
import java.util.function.Consumer;
2524

26-
import org.springframework.hateoas.Link;
2725
import org.springframework.hateoas.LinkRelation;
2826
import org.springframework.hateoas.MediaTypes;
2927
import org.springframework.http.MediaType;
@@ -70,7 +68,7 @@ public class HalConfiguration {
7068
public HalConfiguration() {
7169

7270
this(RenderSingleLinks.AS_SINGLE, new LinkedHashMap<>(), true, true, __ -> {},
73-
Collections.singletonList(MediaTypes.HAL_JSON));
71+
List.of(MediaTypes.HAL_JSON, MediaTypes.VND_HAL_JSON));
7472
}
7573

7674
private HalConfiguration(RenderSingleLinks renderSingleLinks, Map<String, RenderSingleLinks> singleLinksPerPattern,

Diff for: src/main/java/org/springframework/hateoas/mediatype/hal/HalLinkDiscoverer.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.springframework.hateoas.LinkRelation;
2222
import org.springframework.hateoas.MediaTypes;
2323
import org.springframework.hateoas.client.JsonPathLinkDiscoverer;
24-
import org.springframework.hateoas.client.LinkDiscoverer;
2524
import org.springframework.http.MediaType;
2625

2726
/**
@@ -36,7 +35,7 @@ public class HalLinkDiscoverer extends JsonPathLinkDiscoverer {
3635
* Constructor for {@link MediaTypes#HAL_JSON}.
3736
*/
3837
public HalLinkDiscoverer() {
39-
this(MediaTypes.HAL_JSON);
38+
this(MediaTypes.HAL_JSON, MediaTypes.VND_HAL_JSON);
4039
}
4140

4241
protected HalLinkDiscoverer(MediaType... mediaTypes) {

Diff for: src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfigurationProvider.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public Class<? extends HypermediaMappingInformation> getConfiguration() {
4444
*/
4545
@Override
4646
public boolean supportsAny(Collection<MediaType> mediaTypes) {
47-
return mediaTypes.contains(MediaTypes.HAL_JSON);
47+
48+
return mediaTypes.contains(MediaTypes.HAL_JSON)
49+
|| mediaTypes.contains(MediaTypes.VND_HAL_JSON);
4850
}
4951
}

Diff for: src/main/java/org/springframework/hateoas/mediatype/hal/HalTraversonDefaults.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
*/
4141
class HalTraversonDefaults implements TraversonDefaults {
4242

43-
private static final List<MediaType> HAL_FLAVORS = Collections.singletonList(MediaTypes.HAL_JSON);
43+
private static final List<MediaType> HAL_FLAVORS = List.of(MediaTypes.HAL_JSON, MediaTypes.VND_HAL_JSON,
44+
MediaTypes.HAL_FORMS_JSON);
4445

4546
/*
4647
* (non-Javadoc)
@@ -70,8 +71,8 @@ public List<HttpMessageConverter<?>> getHttpMessageConverters(Collection<MediaTy
7071
@Override
7172
public List<LinkDiscoverer> getLinkDiscoverers(Collection<MediaType> mediaTypes) {
7273

73-
return mediaTypes.stream().anyMatch(it -> it.isCompatibleWith(MediaTypes.HAL_JSON)) //
74-
? Collections.singletonList(new HalLinkDiscoverer()) //
74+
return mediaTypes.stream().anyMatch(it -> HAL_FLAVORS.stream().anyMatch(it::isCompatibleWith)) //
75+
? List.of(new HalLinkDiscoverer()) //
7576
: Collections.emptyList();
7677
}
7778

Diff for: src/main/java/org/springframework/hateoas/mediatype/uber/UberMediaTypeConfiguration.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.hateoas.mediatype.uber;
1717

18-
import java.util.Collections;
1918
import java.util.List;
2019

2120
import org.springframework.context.annotation.Bean;
@@ -48,7 +47,7 @@ LinkDiscoverer uberLinkDiscoverer() {
4847
*/
4948
@Override
5049
public List<MediaType> getMediaTypes() {
51-
return Collections.singletonList(HypermediaType.UBER.getMediaType());
50+
return HypermediaType.UBER.getMediaTypes();
5251
}
5352

5453
/*

Diff for: src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java

+4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ void registersHalLinkDiscoverers() {
110110
assertThat(discoverers).isNotNull();
111111
assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON))
112112
.hasValueSatisfying(HalLinkDiscoverer.class::isInstance);
113+
assertThat(discoverers.getLinkDiscovererFor(MediaTypes.VND_HAL_JSON))
114+
.hasValueSatisfying(HalLinkDiscoverer.class::isInstance);
113115
assertRelProvidersSetUp(context);
114116
});
115117
}
@@ -138,6 +140,8 @@ void registersHalAndHalFormsLinkDiscoverers() {
138140
assertThat(discoverers).isNotNull();
139141
assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON))
140142
.hasValueSatisfying(HalLinkDiscoverer.class::isInstance);
143+
assertThat(discoverers.getLinkDiscovererFor(MediaTypes.VND_HAL_JSON))
144+
.hasValueSatisfying(HalLinkDiscoverer.class::isInstance);
141145

142146
assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_FORMS_JSON))
143147
.hasValueSatisfying(HalFormsLinkDiscoverer.class::isInstance);

Diff for: src/test/java/org/springframework/hateoas/config/HypermediaRestTemplateBeanPostProcessorTest.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.springframework.context.annotation.Configuration;
2929
import org.springframework.hateoas.MediaTypes;
3030
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
31-
import org.springframework.hateoas.config.RestTemplateHateoasConfiguration.HypermediaRestTemplateBeanPostProcessor;
3231
import org.springframework.hateoas.support.CustomHypermediaType;
3332
import org.springframework.http.MediaType;
3433
import org.springframework.http.converter.HttpMessageConverter;
@@ -55,6 +54,7 @@ void shouldRegisterJustHal() {
5554
assertThat(getSupportedHypermediaTypes(context, REST_TEMPLATE_EXTRACTOR)) //
5655
.containsExactlyInAnyOrder( //
5756
MediaTypes.HAL_JSON, //
57+
MediaTypes.VND_HAL_JSON, //
5858
MediaType.APPLICATION_JSON, //
5959
MediaType.parseMediaType("application/*+json"));
6060
});
@@ -71,6 +71,7 @@ void shouldRegisterHalAndCollectionJsonMessageConverters() {
7171
assertThat(getSupportedHypermediaTypes(context, REST_TEMPLATE_EXTRACTOR)) //
7272
.containsExactlyInAnyOrder( //
7373
MediaTypes.HAL_JSON, //
74+
MediaTypes.VND_HAL_JSON, //
7475
MediaTypes.COLLECTION_JSON, //
7576
MediaType.APPLICATION_JSON, //
7677
MediaType.parseMediaType("application/*+json"));
@@ -88,6 +89,7 @@ void shouldRegisterHypermediaMessageConverters() {
8889
assertThat(getSupportedHypermediaTypes(context, REST_TEMPLATE_EXTRACTOR)) //
8990
.containsExactlyInAnyOrder( //
9091
MediaTypes.HAL_JSON, //
92+
MediaTypes.VND_HAL_JSON, //
9193
MediaTypes.HAL_FORMS_JSON, //
9294
MediaTypes.COLLECTION_JSON, //
9395
MediaTypes.UBER_JSON, //
@@ -104,6 +106,7 @@ void shouldRegisterCustomHypermediaMessageConverters() {
104106
assertThat(getSupportedHypermediaTypes(context, REST_TEMPLATE_EXTRACTOR)) //
105107
.containsExactlyInAnyOrder( //
106108
MediaTypes.HAL_JSON, //
109+
MediaTypes.VND_HAL_JSON, //
107110
MediaType.parseMediaType("application/frodo+json"), //
108111
MediaType.APPLICATION_JSON, //
109112
MediaType.parseMediaType("application/*+json") //

0 commit comments

Comments
 (0)