diff --git a/src/main/java/org/springframework/hateoas/MediaTypes.java b/src/main/java/org/springframework/hateoas/MediaTypes.java index 6e020bda..f8d09a14 100644 --- a/src/main/java/org/springframework/hateoas/MediaTypes.java +++ b/src/main/java/org/springframework/hateoas/MediaTypes.java @@ -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); } diff --git a/src/main/java/org/springframework/hateoas/client/Traverson.java b/src/main/java/org/springframework/hateoas/client/Traverson.java index 9f9d2257..1db7c704 100644 --- a/src/main/java/org/springframework/hateoas/client/Traverson.java +++ b/src/main/java/org/springframework/hateoas/client/Traverson.java @@ -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> converters = new ArrayList>(); converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); - if (mediaTypes.contains(MediaTypes.HAL_JSON)) { - converters.add(getHalConverter()); + List 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 getHalJsonFlavors(Collection mediaTypes) { + + List result = new ArrayList(); + + for (MediaType mediaType : mediaTypes) { + if (MediaTypes.HAL_JSON.isCompatibleWith(mediaType)) { + result.add(mediaType); + } + } + + return result; + } + private static final RestOperations createDefaultTemplate(List mediaTypes) { RestTemplate template = new RestTemplate(); @@ -150,7 +172,7 @@ public class Traverson { * * @return */ - private static final HttpMessageConverter getHalConverter() { + private static final HttpMessageConverter getHalConverter(List 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; } diff --git a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java index 6268de0f..50e22134 100644 --- a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java +++ b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java @@ -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> result = new ArrayList>(converters.size()); diff --git a/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java index 1c340dd8..9b95062a 100644 --- a/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java @@ -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 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 mediaTypes = new ArrayList(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; } } diff --git a/src/test/java/org/springframework/hateoas/client/TraversonTest.java b/src/test/java/org/springframework/hateoas/client/TraversonTest.java index 0f307572..8534646e 100644 --- a/src/test/java/org/springframework/hateoas/client/TraversonTest.java +++ b/src/test/java/org/springframework/hateoas/client/TraversonTest.java @@ -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() { diff --git a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java index 4ac60d3a..3d08d80b 100644 --- a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java @@ -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 discoverers = context.getBeansOfType(EntityLinks.class); - assertThat(discoverers.values(), Matchers.hasItem(instanceOf(DelegatingEntityLinks.class))); + assertThat(discoverers.values(), Matchers. hasItem(instanceOf(DelegatingEntityLinks.class))); } private static void assertRelProvidersSetUp(ApplicationContext context) { Map discoverers = context.getBeansOfType(RelProvider.class); - assertThat(discoverers.values(), Matchers.hasItem(instanceOf(DelegatingRelProvider.class))); + assertThat(discoverers.values(), Matchers. hasItem(instanceOf(DelegatingRelProvider.class))); } @SuppressWarnings({ "unchecked" }) @@ -168,7 +171,7 @@ public class EnableHypermediaSupportIntegrationTest { RequestMappingHandlerAdapter rmha = context.getBean(RequestMappingHandlerAdapter.class); assertThat(rmha.getMessageConverters(), - Matchers.>hasItems(instanceOf(MappingJackson2HttpMessageConverter.class))); + Matchers.> hasItems(instanceOf(MappingJackson2HttpMessageConverter.class))); } /** diff --git a/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java index e597cdd9..2a32c00f 100644 --- a/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java @@ -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); } } diff --git a/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java index c42f945c..e1bc3fb7 100644 --- a/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java @@ -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;