#229 - Added support for curies in HalEmbeddedBuilder.
HalEmbeddedBuilder now takes an optional CurieProvider to create namespaced rels for embeddededs. Original pull request: #233.
This commit is contained in:
committed by
Oliver Gierke
parent
994347c005
commit
3ca6fa5504
@@ -25,6 +25,7 @@ import org.springframework.hateoas.Links;
|
||||
*
|
||||
* @see http://tools.ietf.org/html/draft-kelly-json-hal#section-8.2
|
||||
* @author Oliver Gierke
|
||||
* @author Jeff Stano
|
||||
* @since 0.9
|
||||
*/
|
||||
public interface CurieProvider {
|
||||
@@ -38,6 +39,16 @@ public interface CurieProvider {
|
||||
*/
|
||||
String getNamespacedRelFrom(Link link);
|
||||
|
||||
/**
|
||||
* Returns the rel to be rendered for the given rel. Will potentially prefix the rel but also might decide not to,
|
||||
* depending on the actual rel.
|
||||
*
|
||||
* @param rel
|
||||
* @return
|
||||
* @since 0.17
|
||||
*/
|
||||
String getNamespacedRelFor(String rel);
|
||||
|
||||
/**
|
||||
* Returns an object to render as the base curie information. Implementations have to make sure, the retunred
|
||||
* instances renders as defined in the spec.
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.util.Assert;
|
||||
* Default implementation of {@link CurieProvider} rendering a single configurable {@link UriTemplate} based curie.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Jeff Stano
|
||||
* @since 0.9
|
||||
*/
|
||||
public class DefaultCurieProvider implements CurieProvider {
|
||||
@@ -65,8 +66,15 @@ public class DefaultCurieProvider implements CurieProvider {
|
||||
*/
|
||||
@Override
|
||||
public String getNamespacedRelFrom(Link link) {
|
||||
return getNamespacedRelFor(link.getRel());
|
||||
}
|
||||
|
||||
String rel = link.getRel();
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.CurieProvider#getNamespacedRelFrom(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public String getNamespacedRelFor(String rel) {
|
||||
|
||||
boolean prefixingNeeded = !IanaRels.isIanaRel(rel) && !rel.contains(":");
|
||||
return prefixingNeeded ? String.format("%s:%s", curie.name, rel) : rel;
|
||||
|
||||
@@ -42,6 +42,7 @@ class HalEmbeddedBuilder {
|
||||
|
||||
private final Map<String, Object> embeddeds = new HashMap<String, Object>();
|
||||
private final RelProvider provider;
|
||||
private final CurieProvider curieProvider;
|
||||
private final EmbeddedWrappers wrappers;
|
||||
|
||||
/**
|
||||
@@ -50,11 +51,12 @@ class HalEmbeddedBuilder {
|
||||
* @param provider can be {@literal null}.
|
||||
* @param preferCollectionRels whether to prefer to ask the provider for collection rels.
|
||||
*/
|
||||
public HalEmbeddedBuilder(RelProvider provider, boolean preferCollectionRels) {
|
||||
public HalEmbeddedBuilder(RelProvider provider, CurieProvider curieProvider, boolean preferCollectionRels) {
|
||||
|
||||
Assert.notNull(provider, "Relprovider must not be null!");
|
||||
|
||||
this.provider = provider;
|
||||
this.curieProvider = curieProvider;
|
||||
this.wrappers = new EmbeddedWrappers(preferCollectionRels);
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ class HalEmbeddedBuilder {
|
||||
* Adds the given value to the embeddeds. Will skip doing so if the value is {@literal null} or the content of a
|
||||
* {@link Resource} is {@literal null}.
|
||||
*
|
||||
* @param value can be {@literal null}.
|
||||
* @param source can be {@literal null}.
|
||||
*/
|
||||
public void add(Object source) {
|
||||
|
||||
@@ -116,6 +118,11 @@ class HalEmbeddedBuilder {
|
||||
Class<?> type = wrapper.getRelTargetType();
|
||||
|
||||
String rel = forCollection ? provider.getCollectionResourceRelFor(type) : provider.getItemResourceRelFor(type);
|
||||
|
||||
if (curieProvider != null) {
|
||||
rel = curieProvider.getNamespacedRelFor(rel);
|
||||
}
|
||||
|
||||
return rel == null ? DEFAULT_REL : rel;
|
||||
}
|
||||
|
||||
|
||||
@@ -238,18 +238,22 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
|
||||
private final BeanProperty property;
|
||||
private final RelProvider relProvider;
|
||||
private final CurieProvider curieProvider;
|
||||
private final boolean enforceEmbeddedCollections;
|
||||
|
||||
public HalResourcesSerializer(RelProvider relPorvider, boolean enforceEmbeddedCollections) {
|
||||
this(null, relPorvider, enforceEmbeddedCollections);
|
||||
public HalResourcesSerializer(RelProvider relPorvider, CurieProvider curieProvider,
|
||||
boolean enforceEmbeddedCollections) {
|
||||
this(null, relPorvider, curieProvider, enforceEmbeddedCollections);
|
||||
}
|
||||
|
||||
public HalResourcesSerializer(BeanProperty property, RelProvider relProvider, boolean enforceEmbeddedCollections) {
|
||||
public HalResourcesSerializer(BeanProperty property, RelProvider relProvider, CurieProvider curieProvider,
|
||||
boolean enforceEmbeddedCollections) {
|
||||
|
||||
super(Collection.class, false);
|
||||
|
||||
this.property = property;
|
||||
this.relProvider = relProvider;
|
||||
this.curieProvider = curieProvider;
|
||||
this.enforceEmbeddedCollections = enforceEmbeddedCollections;
|
||||
}
|
||||
|
||||
@@ -263,7 +267,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
public void serialize(Collection<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
|
||||
JsonGenerationException {
|
||||
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider, enforceEmbeddedCollections);
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider, curieProvider, enforceEmbeddedCollections);
|
||||
|
||||
for (Object resource : value) {
|
||||
builder.add(resource);
|
||||
@@ -275,7 +279,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new HalResourcesSerializer(property, relProvider, enforceEmbeddedCollections);
|
||||
return new HalResourcesSerializer(property, relProvider, curieProvider, enforceEmbeddedCollections);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -600,7 +604,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, boolean enforceEmbeddedCollections) {
|
||||
|
||||
Assert.notNull(resolver, "RelProvider must not be null!");
|
||||
this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(resolver,
|
||||
this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(resolver, curieProvider,
|
||||
enforceEmbeddedCollections));
|
||||
this.instanceMap.put(HalLinkListSerializer.class, new HalLinkListSerializer(curieProvider));
|
||||
}
|
||||
|
||||
@@ -72,4 +72,28 @@ public class DefaultCurieProviderUnitTest {
|
||||
public void doesNotPrefixQualifiedRels() {
|
||||
assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "custom:rel")), is("custom:rel"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #229
|
||||
*/
|
||||
@Test
|
||||
public void doesNotPrefixIanaRelsForRelAsString() {
|
||||
assertThat(provider.getNamespacedRelFor("self"), is("self"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #229
|
||||
*/
|
||||
@Test
|
||||
public void prefixesNormalRelsForRelAsString() {
|
||||
assertThat(provider.getNamespacedRelFor("book"), is("acme:book"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #229
|
||||
*/
|
||||
@Test
|
||||
public void doesNotPrefixQualifiedRelsForRelAsString() {
|
||||
assertThat(provider.getNamespacedRelFor("custom:rel"), is("custom:rel"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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
|
||||
*
|
||||
* http://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.hal;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultCurieProvider}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultCurieProviderUnitTests {
|
||||
|
||||
private static final UriTemplate URI_TEMPLATE = new UriTemplate("http://localhost:8080/rels/{rel}");
|
||||
|
||||
CurieProvider provider = new DefaultCurieProvider("acme", URI_TEMPLATE);
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void preventsNullCurieName() {
|
||||
new DefaultCurieProvider(null, URI_TEMPLATE);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void preventsEmptyCurieName() {
|
||||
new DefaultCurieProvider("", URI_TEMPLATE);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void preventsNullUriTemplateName() {
|
||||
new DefaultCurieProvider("acme", null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void preventsUriTemplateWithoutVariable() {
|
||||
new DefaultCurieProvider("acme", new UriTemplate("http://localhost:8080/rels"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void preventsUriTemplateWithMoreThanOneVariable() {
|
||||
new DefaultCurieProvider("acme", new UriTemplate("http://localhost:8080/rels/{rel}/{another}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPrefixIanaRels() {
|
||||
assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com")), is("self"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefixesNormalRels() {
|
||||
assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "book")), is("acme:book"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPrefixQualifiedRels() {
|
||||
assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "custom:rel")), is("custom:rel"));
|
||||
}
|
||||
}
|
||||
@@ -25,28 +25,31 @@ import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.hateoas.core.EmbeddedWrappers;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link HalEmbeddedBuilder}.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Dietrich Schulten
|
||||
*/
|
||||
public class HalEmbeddedBuilderUnitTest {
|
||||
|
||||
RelProvider provider;
|
||||
CurieProvider curieProvider;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
provider = new EvoInflectorRelProvider();
|
||||
curieProvider = new DefaultCurieProvider("curie", new UriTemplate("http://localhost/{rel}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersSingleElementsWithSingleEntityRel() {
|
||||
|
||||
Map<String, Object> map = setUpBuilder("foo", 1L);
|
||||
Map<String, Object> map = setUpBuilder(null, "foo", 1L);
|
||||
|
||||
assertThat(map.get("string"), is((Object) "foo"));
|
||||
assertThat(map.get("long"), is((Object) 1L));
|
||||
@@ -55,7 +58,7 @@ public class HalEmbeddedBuilderUnitTest {
|
||||
@Test
|
||||
public void rendersMultipleElementsWithCollectionResourceRel() {
|
||||
|
||||
Map<String, Object> map = setUpBuilder("foo", "bar", 1L);
|
||||
Map<String, Object> map = setUpBuilder(null, "foo", "bar", 1L);
|
||||
|
||||
assertThat(map.containsKey("string"), is(false));
|
||||
assertThat(map.get("long"), is((Object) 1L));
|
||||
@@ -68,7 +71,7 @@ public class HalEmbeddedBuilderUnitTest {
|
||||
@Test
|
||||
public void correctlyPilesUpResourcesInCollectionRel() {
|
||||
|
||||
Map<String, Object> map = setUpBuilder("foo", "bar", "foobar", 1L);
|
||||
Map<String, Object> map = setUpBuilder(null, "foo", "bar", "foobar", 1L);
|
||||
|
||||
assertThat(map.containsKey("string"), is(false));
|
||||
assertHasValues(map, "strings", "foo", "bar", "foobar");
|
||||
@@ -81,7 +84,7 @@ public class HalEmbeddedBuilderUnitTest {
|
||||
@Test
|
||||
public void forcesCollectionRelToBeUsedIfConfigured() {
|
||||
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, true);
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, null, true);
|
||||
builder.add("Sample");
|
||||
|
||||
assertThat(builder.asMap().get("string"), is(nullValue()));
|
||||
@@ -96,7 +99,7 @@ public class HalEmbeddedBuilderUnitTest {
|
||||
|
||||
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
|
||||
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, true);
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, null, true);
|
||||
builder.add(wrappers.wrap("MyValue", "foo"));
|
||||
|
||||
assertThat(builder.asMap().get("foo"), is(instanceOf(String.class)));
|
||||
@@ -107,9 +110,61 @@ public class HalEmbeddedBuilderUnitTest {
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullRelProvider() {
|
||||
new HalEmbeddedBuilder(null, false);
|
||||
new HalEmbeddedBuilder(null, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #229
|
||||
*/
|
||||
@Test
|
||||
public void rendersSingleElementsWithSingleEntityRelWithCurieProvider() {
|
||||
|
||||
Map<String, Object> map = setUpBuilder(curieProvider, "foo", 1L);
|
||||
|
||||
assertThat(map.get("curie:string"), is((Object) "foo"));
|
||||
assertThat(map.get("curie:long"), is((Object) 1L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #229
|
||||
*/
|
||||
@Test
|
||||
public void rendersMultipleElementsWithCollectionResourceRelWithCurieProvider() {
|
||||
|
||||
Map<String, Object> map = setUpBuilder(curieProvider, "foo", "bar", 1L);
|
||||
|
||||
assertThat(map.containsKey("curie:string"), is(false));
|
||||
assertThat(map.get("curie:long"), is((Object) 1L));
|
||||
assertHasValues(map, "curie:strings", "foo", "bar");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #229
|
||||
*/
|
||||
@Test
|
||||
public void correctlyPilesUpResourcesInCollectionRelWithCurieprovider() {
|
||||
|
||||
Map<String, Object> map = setUpBuilder(curieProvider, "foo", "bar", "foobar", 1L);
|
||||
|
||||
assertThat(map.containsKey("curie:string"), is(false));
|
||||
assertHasValues(map, "curie:strings", "foo", "bar", "foobar");
|
||||
assertThat(map.get("curie:long"), is((Object) 1L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #229
|
||||
*/
|
||||
@Test
|
||||
public void forcesCollectionRelToBeUsedIfConfiguredWithCurieProvider() {
|
||||
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, curieProvider, true);
|
||||
builder.add("Sample");
|
||||
|
||||
assertThat(builder.asMap().get("curie:string"), is(nullValue()));
|
||||
assertHasValues(builder.asMap(), "curie:strings", "Sample");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void assertHasValues(Map<String, Object> source, String rel, Object... values) {
|
||||
|
||||
Object value = source.get(rel);
|
||||
@@ -118,9 +173,9 @@ public class HalEmbeddedBuilderUnitTest {
|
||||
assertThat((List<Object>) value, Matchers.<List<Object>> allOf(hasSize(values.length), hasItems(values)));
|
||||
}
|
||||
|
||||
private Map<String, Object> setUpBuilder(Object... values) {
|
||||
private Map<String, Object> setUpBuilder(CurieProvider curieProvider, Object... values) {
|
||||
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, false);
|
||||
HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, curieProvider, false);
|
||||
|
||||
for (Object value : values) {
|
||||
builder.add(value);
|
||||
|
||||
Reference in New Issue
Block a user