diff --git a/src/main/java/org/springframework/hateoas/hal/CurieProvider.java b/src/main/java/org/springframework/hateoas/hal/CurieProvider.java index 36905b9d..f552567d 100644 --- a/src/main/java/org/springframework/hateoas/hal/CurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/CurieProvider.java @@ -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. diff --git a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java index 39393752..ee6be505 100644 --- a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java @@ -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; diff --git a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java index f269ac9e..8cd0dc73 100644 --- a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java +++ b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java @@ -42,6 +42,7 @@ class HalEmbeddedBuilder { private final Map embeddeds = new HashMap(); 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; } diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 468ea8f5..03e53595 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -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)); } diff --git a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java index 8dbfc3d0..d22ec15e 100644 --- a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java @@ -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")); + } } diff --git a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTests.java b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTests.java deleted file mode 100644 index 4f99af64..00000000 --- a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTests.java +++ /dev/null @@ -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")); - } -} diff --git a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java index 2d7d3187..2693af5e 100644 --- a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java @@ -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 map = setUpBuilder("foo", 1L); + Map 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 map = setUpBuilder("foo", "bar", 1L); + Map 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 map = setUpBuilder("foo", "bar", "foobar", 1L); + Map 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 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 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 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 source, String rel, Object... values) { Object value = source.get(rel); @@ -118,9 +173,9 @@ public class HalEmbeddedBuilderUnitTest { assertThat((List) value, Matchers.> allOf(hasSize(values.length), hasItems(values))); } - private Map setUpBuilder(Object... values) { + private Map 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);