#135 - HAL embedded resources are now rendered as collection by default.

To get consistent representations rendered for single-item collections and multi-item collections, we now use an enforceEmbeddedCollections flag in the Jackson 2 HAL module. Adapted HalEmbeddedBuilder to respect that setting and exposed it via the HandlerInstantiator for user level customization.

Changed RelProvider.getSingleResourceRel(…) to getItemResourceRel(…).
This commit is contained in:
Oliver Gierke
2014-01-24 14:50:41 +01:00
parent 9caa352470
commit bb0a578701
13 changed files with 77 additions and 37 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, List<Object>> embeddeds = new HashMap<String, List<Object>>();
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<Object> 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;
}

View File

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

View File

@@ -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<Object> 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<Class<?>, Object> instanceMap = new HashMap<Class<?>, 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));
}

View File

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