#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:
Jeff Stano
2014-08-22 08:46:47 -06:00
committed by Oliver Gierke
parent 994347c005
commit 3ca6fa5504
7 changed files with 127 additions and 93 deletions

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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"));
}
}

View File

@@ -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"));
}
}

View File

@@ -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);