#470 - Added HAL JSON UTF-8 variant to MediaTypes.
Added application/hal+json;charset=UTF-8 to the media types enum. Traverson now configures all HAL flavors to be supported by the registered HAL-specific HttpMessageConverter. Removed nullability of media type in JsonPathLinkDiscoverer. This requires subclasses that want to support all media types to explicitly configure MediaType.ALL. Heavily inspired by the PR submitted by @drummond but very significantly rewritten.
This commit is contained in:
@@ -22,6 +22,7 @@ import org.springframework.http.MediaType;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Przemek Nowak
|
||||
* @author Drummond Dawson
|
||||
*/
|
||||
public class MediaTypes {
|
||||
|
||||
@@ -34,4 +35,14 @@ public class MediaTypes {
|
||||
* Public constant media type for {@code application/hal+json}.
|
||||
*/
|
||||
public static final MediaType HAL_JSON = MediaType.valueOf(HAL_JSON_VALUE);
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaTypes#HAL_JSON_UTF8}.
|
||||
*/
|
||||
public static final String HAL_JSON_UTF8_VALUE = HAL_JSON_VALUE + ";charset=UTF-8";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/hal+json;charset=UTF-8}.
|
||||
*/
|
||||
public static final MediaType HAL_JSON_UTF8 = MediaType.valueOf(HAL_JSON_UTF8_VALUE);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -130,13 +131,34 @@ public class Traverson {
|
||||
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
|
||||
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
|
||||
|
||||
if (mediaTypes.contains(MediaTypes.HAL_JSON)) {
|
||||
converters.add(getHalConverter());
|
||||
List<MediaType> halFlavors = getHalJsonFlavors(mediaTypes);
|
||||
|
||||
if (!halFlavors.isEmpty()) {
|
||||
converters.add(getHalConverter(halFlavors));
|
||||
}
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all HAL JSON compatible media types from the given list.
|
||||
*
|
||||
* @param mediaTypes must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static List<MediaType> getHalJsonFlavors(Collection<MediaType> mediaTypes) {
|
||||
|
||||
List<MediaType> result = new ArrayList<MediaType>();
|
||||
|
||||
for (MediaType mediaType : mediaTypes) {
|
||||
if (MediaTypes.HAL_JSON.isCompatibleWith(mediaType)) {
|
||||
result.add(mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final RestOperations createDefaultTemplate(List<MediaType> mediaTypes) {
|
||||
|
||||
RestTemplate template = new RestTemplate();
|
||||
@@ -150,7 +172,7 @@ public class Traverson {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static final HttpMessageConverter<?> getHalConverter() {
|
||||
private static final HttpMessageConverter<?> getHalConverter(List<MediaType> halFlavours) {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new Jackson2HalModule());
|
||||
@@ -159,7 +181,7 @@ public class Traverson {
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
|
||||
converter.setObjectMapper(mapper);
|
||||
converter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));
|
||||
converter.setSupportedMediaTypes(halFlavours);
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
|
||||
|
||||
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
|
||||
ResourceSupport.class);
|
||||
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
|
||||
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON, HAL_JSON_UTF8));
|
||||
halConverter.setObjectMapper(halObjectMapper);
|
||||
|
||||
List<HttpMessageConverter<?>> result = new ArrayList<HttpMessageConverter<?>>(converters.size());
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.hateoas.core;
|
||||
|
||||
import net.minidev.json.JSONArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Array;
|
||||
@@ -24,10 +26,9 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.minidev.json.JSONArray;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkDiscoverer;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -67,23 +68,31 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer {
|
||||
}
|
||||
|
||||
private final String pathTemplate;
|
||||
private final MediaType mediaType;
|
||||
private final List<MediaType> mediaTypes;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JsonPathLinkDiscoverer} using the given path template supporting the given {@link MediaType}.
|
||||
* The template has to contain a single {@code %s} placeholder which will be replaced by the relation type.
|
||||
*
|
||||
* @param pathTemplate must not be {@literal null} or empty and contain a single placeholder.
|
||||
* @param mediaType the {@link MediaType} to support.
|
||||
* @param mediaType the primary {@link MediaType}s to support.
|
||||
* @param additional {@link MediaTypes} to support.
|
||||
*/
|
||||
public JsonPathLinkDiscoverer(String pathTemplate, MediaType mediaType) {
|
||||
public JsonPathLinkDiscoverer(String pathTemplate, MediaType mediaType, MediaType... others) {
|
||||
|
||||
Assert.hasText(pathTemplate, "Path template must not be null!");
|
||||
Assert.isTrue(StringUtils.countOccurrencesOf(pathTemplate, "%s") == 1,
|
||||
"Path template must contain a single placeholder!");
|
||||
Assert.notNull(mediaType, "Primary MediaType must not be null!");
|
||||
Assert.notNull(others, "Other MediaTypes must not be null!");
|
||||
|
||||
this.pathTemplate = pathTemplate;
|
||||
this.mediaType = mediaType;
|
||||
|
||||
List<MediaType> mediaTypes = new ArrayList<MediaType>(others.length + 1);
|
||||
mediaTypes.add(mediaType);
|
||||
mediaTypes.addAll(Arrays.asList(others));
|
||||
|
||||
this.mediaTypes = mediaTypes;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -178,6 +187,13 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer {
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(MediaType delimiter) {
|
||||
return this.mediaType == null ? true : this.mediaType.isCompatibleWith(delimiter);
|
||||
|
||||
for (MediaType mediaType : this.mediaTypes) {
|
||||
if (mediaType.isCompatibleWith(delimiter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -66,7 +66,7 @@ public class TraversonTest {
|
||||
|
||||
this.server = new Server();
|
||||
this.baseUri = URI.create(server.rootResource());
|
||||
this.traverson = new Traverson(baseUri, MediaTypes.HAL_JSON);
|
||||
this.traverson = new Traverson(baseUri, MediaTypes.HAL_JSON_UTF8, MediaTypes.HAL_JSON);
|
||||
|
||||
setUpActors();
|
||||
}
|
||||
@@ -102,9 +102,10 @@ public class TraversonTest {
|
||||
|
||||
traverson.follow().toObject(String.class);
|
||||
|
||||
verifyThatRequest(). //
|
||||
havingPathEqualTo("/"). //
|
||||
havingHeader("Accept", hasItem("application/hal+json"));
|
||||
verifyThatRequest() //
|
||||
.havingPathEqualTo("/") //
|
||||
.havingHeader("Accept", contains(MediaTypes.HAL_JSON_UTF8_VALUE + ", " + MediaTypes.HAL_JSON_VALUE)) //
|
||||
.receivedOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,6 +250,12 @@ public class TraversonTest {
|
||||
assertThat(converters, hasSize(2));
|
||||
assertThat(converters.get(0), is(instanceOf(StringHttpMessageConverter.class)));
|
||||
assertThat(converters.get(1), is(instanceOf(MappingJackson2HttpMessageConverter.class)));
|
||||
|
||||
converters = Traverson.getDefaultMessageConverters(MediaTypes.HAL_JSON_UTF8);
|
||||
|
||||
assertThat(converters, hasSize(2));
|
||||
assertThat(converters.get(0), is(instanceOf(StringHttpMessageConverter.class)));
|
||||
assertThat(converters.get(1), is(instanceOf(MappingJackson2HttpMessageConverter.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,13 +362,13 @@ public class TraversonTest {
|
||||
assertThat(item.image, equalTo(server.rootResource() + "/springagram/file/cat"));
|
||||
assertThat(item.description, equalTo("cat"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see #337
|
||||
*/
|
||||
@Test
|
||||
public void doesNotDoubleEncodeURI() {
|
||||
|
||||
|
||||
this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON);
|
||||
|
||||
Resource<?> itemResource = traverson.//
|
||||
@@ -369,8 +376,7 @@ public class TraversonTest {
|
||||
toObject(Resource.class);
|
||||
|
||||
assertThat(itemResource.hasLink("self"), is(true));
|
||||
assertThat(itemResource.getLink("self").expand().getHref(),
|
||||
equalTo(server.rootResource() + "/springagram/items"));
|
||||
assertThat(itemResource.getLink("self").expand().getHref(), equalTo(server.rootResource() + "/springagram/items"));
|
||||
}
|
||||
|
||||
private void setUpActors() {
|
||||
|
||||
@@ -76,6 +76,7 @@ public class EnableHypermediaSupportIntegrationTest {
|
||||
|
||||
assertThat(discoverers, is(notNullValue()));
|
||||
assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON), is(instanceOf(HalLinkDiscoverer.class)));
|
||||
assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON_UTF8), is(instanceOf(HalLinkDiscoverer.class)));
|
||||
assertRelProvidersSetUp(context);
|
||||
}
|
||||
|
||||
@@ -98,7 +99,8 @@ public class EnableHypermediaSupportIntegrationTest {
|
||||
|
||||
RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
|
||||
|
||||
assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON));
|
||||
assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes(),
|
||||
hasItems(MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
|
||||
boolean found = false;
|
||||
|
||||
@@ -113,7 +115,7 @@ public class EnableHypermediaSupportIntegrationTest {
|
||||
.getField(processor, "messageConverters");
|
||||
|
||||
assertThat(converters.get(0), is(instanceOf(TypeConstrainedMappingJackson2HttpMessageConverter.class)));
|
||||
assertThat(converters.get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON));
|
||||
assertThat(converters.get(0).getSupportedMediaTypes(), hasItems(MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +131,8 @@ public class EnableHypermediaSupportIntegrationTest {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(HalConfig.class);
|
||||
RestTemplate template = context.getBean(RestTemplate.class);
|
||||
|
||||
assertThat(template.getMessageConverters().get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON));
|
||||
assertThat(template.getMessageConverters().get(0).getSupportedMediaTypes(),
|
||||
hasItems(MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -149,13 +152,13 @@ public class EnableHypermediaSupportIntegrationTest {
|
||||
private static void assertEntityLinksSetUp(ApplicationContext context) {
|
||||
|
||||
Map<String, EntityLinks> discoverers = context.getBeansOfType(EntityLinks.class);
|
||||
assertThat(discoverers.values(), Matchers.<EntityLinks>hasItem(instanceOf(DelegatingEntityLinks.class)));
|
||||
assertThat(discoverers.values(), Matchers.<EntityLinks> hasItem(instanceOf(DelegatingEntityLinks.class)));
|
||||
}
|
||||
|
||||
private static void assertRelProvidersSetUp(ApplicationContext context) {
|
||||
|
||||
Map<String, RelProvider> discoverers = context.getBeansOfType(RelProvider.class);
|
||||
assertThat(discoverers.values(), Matchers.<RelProvider>hasItem(instanceOf(DelegatingRelProvider.class)));
|
||||
assertThat(discoverers.values(), Matchers.<RelProvider> hasItem(instanceOf(DelegatingRelProvider.class)));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@@ -168,7 +171,7 @@ public class EnableHypermediaSupportIntegrationTest {
|
||||
|
||||
RequestMappingHandlerAdapter rmha = context.getBean(RequestMappingHandlerAdapter.class);
|
||||
assertThat(rmha.getMessageConverters(),
|
||||
Matchers.<HttpMessageConverter<?>>hasItems(instanceOf(MappingJackson2HttpMessageConverter.class)));
|
||||
Matchers.<HttpMessageConverter<?>> hasItems(instanceOf(MappingJackson2HttpMessageConverter.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
* Copyright 2012-2017 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.hateoas.core;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link JsonPathLinkDiscoverer}.
|
||||
@@ -26,16 +27,16 @@ public class JsonPathLinkDiscovererUnitTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullPattern() {
|
||||
new JsonPathLinkDiscoverer(null, null);
|
||||
new JsonPathLinkDiscoverer(null, MediaType.ALL);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPatternWithWithoutPlaceholder() {
|
||||
new JsonPathLinkDiscoverer("$links", null);
|
||||
new JsonPathLinkDiscoverer("$links", MediaType.ALL);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPatternWithMultiplePlaceholders() {
|
||||
new JsonPathLinkDiscoverer("$links%s%s", null);
|
||||
new JsonPathLinkDiscoverer("$links%s%s", MediaType.ALL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 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,6 +20,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.hateoas.LinkDiscoverer;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.core.AbstractLinkDiscovererUnitTest;
|
||||
|
||||
/**
|
||||
@@ -42,6 +43,14 @@ public class HalLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest {
|
||||
assertThat(getDiscoverer().findLinkWithRel("http://foo.com/bar", SAMPLE), is(notNullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #470
|
||||
*/
|
||||
@Test
|
||||
public void supportsHalUtf8() {
|
||||
assertThat(getDiscoverer().supports(MediaTypes.HAL_JSON_UTF8), is(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinkDiscoverer getDiscoverer() {
|
||||
return discoverer;
|
||||
|
||||
Reference in New Issue
Block a user