diff --git a/src/main/java/org/springframework/hateoas/RelProvider.java b/src/main/java/org/springframework/hateoas/RelProvider.java index 342b6962..56734a7a 100644 --- a/src/main/java/org/springframework/hateoas/RelProvider.java +++ b/src/main/java/org/springframework/hateoas/RelProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -18,11 +18,25 @@ package org.springframework.hateoas; import org.springframework.plugin.core.Plugin; /** + * API to provide relation types for collections and items of the given type. + * * @author Oliver Gierke */ public interface RelProvider extends Plugin> { - String getSingleResourceRelFor(Class type); + /** + * Returns the relation type to be used to point to an item resource of the given type. + * + * @param type must not be {@literal null}. + * @return + */ + String getItemResourceRelFor(Class type); + /** + * Returns the relation type to be used to point to a collection resource of the given type. + * + * @param type must not be {@literal null}. + * @return + */ String getCollectionResourceRelFor(Class type); } diff --git a/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java b/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java index 5df3cdb7..e3f29846 100644 --- a/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/AnnotationRelProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -52,7 +52,7 @@ public class AnnotationRelProvider implements RelProvider { * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Object) */ @Override - public String getSingleResourceRelFor(Class type) { + public String getItemResourceRelFor(Class type) { Relation annotation = lookupAnnotation(type); diff --git a/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java b/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java index 14fa691a..7f4f44e1 100644 --- a/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/DefaultRelProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -52,7 +52,7 @@ public class DefaultRelProvider implements RelProvider { * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class) */ @Override - public String getSingleResourceRelFor(Class type) { + public String getItemResourceRelFor(Class type) { return StringUtils.uncapitalize(type.getSimpleName()); } } diff --git a/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java b/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java index a27dca5d..93ff1e72 100644 --- a/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/DelegatingRelProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -37,8 +37,8 @@ public class DelegatingRelProvider implements RelProvider { * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class) */ @Override - public String getSingleResourceRelFor(Class type) { - return providers.getPluginFor(type).getSingleResourceRelFor(type); + public String getItemResourceRelFor(Class type) { + return providers.getPluginFor(type).getItemResourceRelFor(type); } /* diff --git a/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java b/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java index d46e7b0f..f9b380db 100644 --- a/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java +++ b/src/main/java/org/springframework/hateoas/core/EvoInflectorRelProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -33,6 +33,6 @@ public class EvoInflectorRelProvider extends DefaultRelProvider { */ @Override public String getCollectionResourceRelFor(Class type) { - return English.plural(getSingleResourceRelFor(type)); + return English.plural(getItemResourceRelFor(type)); } } diff --git a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java index 9025d88f..ff52d4c7 100644 --- a/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java +++ b/src/main/java/org/springframework/hateoas/hal/HalEmbeddedBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -38,14 +38,16 @@ class HalEmbeddedBuilder { private final Map> embeddeds = new HashMap>(); private final RelProvider provider; + private final boolean enforceCollections; /** * Creates a new {@link HalEmbeddedBuilder} using the given {@link RelProvider}. * * @param provider can be {@literal null}. */ - public HalEmbeddedBuilder(RelProvider provider) { + public HalEmbeddedBuilder(RelProvider provider, boolean enforceCollections) { this.provider = provider; + this.enforceCollections = enforceCollections; } /** @@ -65,7 +67,7 @@ class HalEmbeddedBuilder { String rel = getDefaultedRelFor(type, true); if (!embeddeds.containsKey(rel)) { - rel = getDefaultedRelFor(type, false); + rel = getDefaultedRelFor(type, enforceCollections); } List currentValue = embeddeds.get(rel); @@ -89,7 +91,7 @@ class HalEmbeddedBuilder { return DEFAULT_REL; } - String rel = forCollection ? provider.getCollectionResourceRelFor(type) : provider.getSingleResourceRelFor(type); + String rel = forCollection ? provider.getCollectionResourceRelFor(type) : provider.getItemResourceRelFor(type); return rel == null ? DEFAULT_REL : rel; } diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java index 77769fe8..87c0a81e 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java @@ -194,7 +194,7 @@ public class Jackson1HalModule extends SimpleModule { public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { - HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider); + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider, false); for (Object resource : value) { builder.add(resource); diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index d0391ad3..08b00259 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -238,17 +238,19 @@ public class Jackson2HalModule extends SimpleModule { private final BeanProperty property; private final RelProvider relProvider; + private final boolean enforceEmbeddedCollections; - public HalResourcesSerializer(RelProvider relPorvider) { - this(null, relPorvider); + public HalResourcesSerializer(RelProvider relPorvider, boolean enforceEmbeddedCollections) { + this(null, relPorvider, enforceEmbeddedCollections); } - public HalResourcesSerializer(BeanProperty property, RelProvider relProvider) { + public HalResourcesSerializer(BeanProperty property, RelProvider relProvider, boolean enforceEmbeddedCollections) { super(Collection.class, false); this.property = property; this.relProvider = relProvider; + this.enforceEmbeddedCollections = enforceEmbeddedCollections; } /* @@ -261,7 +263,7 @@ public class Jackson2HalModule extends SimpleModule { public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { - HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider); + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(relProvider, enforceEmbeddedCollections); for (Object resource : value) { builder.add(resource); @@ -272,8 +274,11 @@ 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, + property) : new OptionalListJackson2Serializer(property); + MapSerializer serializer = MapSerializer.construct(new String[] {}, mapType, true, null, - provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property), null); + provider.findKeySerializer(keyType, null), valueSerializer, null); serializer.serialize(builder.asMap(), jgen, provider); } @@ -281,12 +286,11 @@ public class Jackson2HalModule extends SimpleModule { @Override public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { - return new HalResourcesSerializer(property, relProvider); + return new HalResourcesSerializer(property, relProvider, enforceEmbeddedCollections); } @Override public JavaType getContentType() { - // TODO Auto-generated method stub return null; } @@ -601,9 +605,14 @@ public class Jackson2HalModule extends SimpleModule { private final Map, Object> instanceMap = new HashMap, Object>(); public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider) { + this(resolver, curieProvider, true); + } + + 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, + enforceEmbeddedCollections)); this.instanceMap.put(HalLinkListSerializer.class, new HalLinkListSerializer(curieProvider)); } diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java b/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java index 9e29a16f..69aa9c03 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerRelProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -56,8 +56,8 @@ public class ControllerRelProvider implements RelProvider { * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class) */ @Override - public String getSingleResourceRelFor(Class resource) { - return providers.getPluginFor(entityType).getSingleResourceRelFor(resource); + public String getItemResourceRelFor(Class resource) { + return providers.getPluginFor(entityType).getItemResourceRelFor(resource); } /* diff --git a/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java b/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java index 3e361459..1240579e 100644 --- a/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/DelegatingRelProviderUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -27,6 +27,8 @@ import org.springframework.plugin.core.OrderAwarePluginRegistry; import org.springframework.plugin.core.PluginRegistry; /** + * Unit tests for {@link DelegatingRelProvider}. + * * @author Oliver Gierke */ public class DelegatingRelProviderUnitTest { @@ -40,11 +42,11 @@ public class DelegatingRelProviderUnitTest { RelProvider delegatingProvider = new DelegatingRelProvider(registry); assertThat(delegatingProvider.supports(Sample.class), is(true)); - assertThat(delegatingProvider.getSingleResourceRelFor(Sample.class), is("foo")); + assertThat(delegatingProvider.getItemResourceRelFor(Sample.class), is("foo")); assertThat(delegatingProvider.getCollectionResourceRelFor(Sample.class), is("bar")); assertThat(delegatingProvider.supports(String.class), is(true)); - assertThat(delegatingProvider.getSingleResourceRelFor(String.class), is("string")); + assertThat(delegatingProvider.getItemResourceRelFor(String.class), is("string")); assertThat(delegatingProvider.getCollectionResourceRelFor(String.class), is("stringList")); } diff --git a/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java b/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java index fb7dc831..7bfd93e6 100644 --- a/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/EvoInflectorRelProviderUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -37,7 +37,7 @@ public class EvoInflectorRelProviderUnitTest { } private void assertRels(Class type, String singleRel, String collectionRel) { - assertThat(provider.getSingleResourceRelFor(type), is(singleRel)); + assertThat(provider.getItemResourceRelFor(type), is(singleRel)); assertThat(provider.getCollectionResourceRelFor(type), is(collectionRel)); } diff --git a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java index 581de6ae..23f5b71b 100644 --- a/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/HalEmbeddedBuilderUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -84,15 +84,28 @@ public class HalEmbeddedBuilderUnitTest { public void addsNoEmbeddedsForResourceWithoutContent() { Resource resource = BeanUtils.instantiateClass(Resource.class); - HalEmbeddedBuilder halEmbeddedBuilder = new HalEmbeddedBuilder(provider); + HalEmbeddedBuilder halEmbeddedBuilder = new HalEmbeddedBuilder(provider, true); halEmbeddedBuilder.add(resource); assertThat(halEmbeddedBuilder.asMap().isEmpty(), is(true)); } + /** + * @see #135 + */ + @Test + public void forcesCollectionRelToBeUsedIfConfigured() { + + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, true); + builder.add("Sample"); + + assertThat(builder.asMap().get("string"), is(nullValue())); + assertThat(builder.asMap().get("strings"), hasItem("Sample")); + } + private Map> setUpBuilder(Object... values) { - HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider); + HalEmbeddedBuilder builder = new HalEmbeddedBuilder(provider, false); for (Object value : values) { builder.add(value); diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java index 874f52e1..af459a2e 100644 --- a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java @@ -52,10 +52,10 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final String LIST_LINK_REFERENCE = "{\"_links\":{\"self\":[{\"href\":\"localhost\"},{\"href\":\"localhost2\"}]}}"; static final String SIMPLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"content\":[\"first\",\"second\"]}}"; - static final String SINGLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"content\":{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}}}"; + static final String SINGLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; static final String LIST_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; - static final String ANNOTATED_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"pojo\":{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}}}"; + static final String ANNOTATED_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; static final String ANNOTATED_EMBEDDED_RESOURCES_REFERENCE = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}"; static final String ANNOTATED_PAGED_RESOURCES = "{\"_links\":{\"next\":{\"href\":\"foo\"},\"prev\":{\"href\":\"bar\"}},\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"page\":{\"size\":2,\"totalElements\":4,\"totalPages\":2,\"number\":0}}";