diff --git a/src/main/java/org/springframework/hateoas/RelAware.java b/src/main/java/org/springframework/hateoas/RelAware.java new file mode 100644 index 00000000..90b9ea3d --- /dev/null +++ b/src/main/java/org/springframework/hateoas/RelAware.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 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; + +/** + * Interface to mark objects that are aware of the rel they'd like to be exposed under. + * + * @author Oliver Gierke + */ +public interface RelAware { + + /** + * Returns the rel to be used with the given object. + * + * @return + */ + String getRel(); +} diff --git a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java index bf9b4b89..83b07c4b 100644 --- a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java +++ b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java @@ -22,9 +22,11 @@ import java.util.List; import java.util.Map; import org.springframework.aop.support.AopUtils; +import org.springframework.hateoas.RelAware; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.Resource; import org.springframework.hateoas.core.ObjectUtils; +import org.springframework.util.Assert; /** * Builder class that allows collecting objects under the relation types defined for the objects but moving from the @@ -39,16 +41,22 @@ class HalEmbeddedBuilder { private final Map> embeddeds = new HashMap>(); private final RelProvider provider; - private final boolean enforceCollections; + private final boolean preferCollectionRels; + + private boolean relAwareFound; /** - * Creates a new {@link HalEmbeddedBuilder} using the given {@link RelProvider}. + * Creates a new {@link HalEmbeddedBuilder} using the given {@link RelProvider} and prefer collection rels flag. * * @param provider can be {@literal null}. + * @param preferCollectionRels whether to prefer to ask the provider for collection rels. */ - public HalEmbeddedBuilder(RelProvider provider, boolean enforceCollections) { + public HalEmbeddedBuilder(RelProvider provider, boolean preferCollectionRels) { + + Assert.notNull(provider, "Relprovider must not be null!"); + this.provider = provider; - this.enforceCollections = enforceCollections; + this.preferCollectionRels = preferCollectionRels; } /** @@ -59,16 +67,14 @@ class HalEmbeddedBuilder { */ public void add(Object value) { - Object unwrapped = ObjectUtils.getResourceType(value); - - if (unwrapped == null) { + if (ObjectUtils.getResourceType(value) == null) { return; } - String rel = getDefaultedRelFor(unwrapped, true); + String rel = getDefaultedRelFor(value, true); if (!embeddeds.containsKey(rel)) { - rel = getDefaultedRelFor(unwrapped, enforceCollections); + rel = getDefaultedRelFor(value, preferCollectionRels); } List currentValue = embeddeds.get(rel); @@ -80,7 +86,7 @@ class HalEmbeddedBuilder { } else if (currentValue.size() == 1) { currentValue.add(value); embeddeds.remove(rel); - embeddeds.put(getDefaultedRelFor(unwrapped, true), currentValue); + embeddeds.put(getDefaultedRelFor(value, true), currentValue); } else { currentValue.add(value); } @@ -88,16 +94,32 @@ class HalEmbeddedBuilder { private String getDefaultedRelFor(Object value, boolean forCollection) { + Object unwrapped = ObjectUtils.getResourceType(value); + + if (value instanceof RelAware) { + this.relAwareFound = true; + return ((RelAware) value).getRel(); + } + if (provider == null) { return DEFAULT_REL; } - Class type = AopUtils.getTargetClass(value); + Class type = AopUtils.getTargetClass(unwrapped); String rel = forCollection ? provider.getCollectionResourceRelFor(type) : provider.getItemResourceRelFor(type); return rel == null ? DEFAULT_REL : rel; } + /** + * Returns whether the builder only created collection rels. + * + * @return + */ + public boolean hasOnlyCollections() { + return preferCollectionRels && !relAwareFound; + } + /** * Returns the added objects keyed up by their relation types. * diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 08b00259..210e510f 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -274,7 +274,7 @@ public class Jackson2HalModule extends SimpleModule { JavaType valueType = typeFactory.constructCollectionType(ArrayList.class, Resource.class); JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType); - JsonSerializer valueSerializer = enforceEmbeddedCollections ? provider.findValueSerializer(valueType, + JsonSerializer valueSerializer = builder.hasOnlyCollections() ? provider.findValueSerializer(valueType, property) : new OptionalListJackson2Serializer(property); MapSerializer serializer = MapSerializer.construct(new String[] {}, mapType, true, null, diff --git a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java index 23f5b71b..18c54d06 100644 --- a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java @@ -25,6 +25,7 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.BeanUtils; +import org.springframework.hateoas.RelAware; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.Resource; import org.springframework.hateoas.core.EvoInflectorRelProvider; @@ -103,6 +104,27 @@ public class HalEmbeddedBuilderUnitTest { assertThat(builder.asMap().get("strings"), hasItem("Sample")); } + /** + * @see #195 + */ + @Test + public void doesNotPreferCollectionsIfRelAwareWasAdded() { + + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, true); + builder.add(new Sample()); + + assertThat(builder.hasOnlyCollections(), is(false)); + assertThat(builder.asMap().get("foo"), is(notNullValue())); + } + + /** + * @see #195 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullRelProvider() { + new HalEmbeddedBuilder(null, false); + } + private Map> setUpBuilder(Object... values) { HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, false); @@ -113,4 +135,16 @@ public class HalEmbeddedBuilderUnitTest { return builder.asMap(); } + + static class Sample implements RelAware { + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.RelAware#getRel() + */ + @Override + public String getRel() { + return "foo"; + } + } }