Added @Relation annotation to be able to define the relations that shall be exposed. The annotation can be used on domain classes or ResourceSupport subtypes to define the relations that shall be used to build links pointing to them. Introduced RelProvider implementations that use the plain domain class name plus an appended List by default (DefaultRelProvider, fallback) as well as an AnnotationRelProvider that will inspect @Relation on the domain type. Added a DelegatingRelProvider that is based on a Spring Plugin PluginRegistry to allow adding RelProvider implementations transparently. Polished AnnotationRelProvider (added annotation caching) and corrected ControllerRelProvider to lookup relation types from Controller classes. Added BeanDefinition setup to HypermediaSupportBeanDefinitionRegistrar to setup a PluginRegistry of RelProviders and the wrapping DelegatingRelProvider to become the primary auto wiring candidate. Moved RelProvider registration into Jackson HandlerInstantiator implementations. Made the BeanPostProcessors for Jackson ObjectMapper customization aware of the BeanFactory to lookup the DelegatingRelProvider instance to hand them into the Jackson HandlerInstantiators. TODOs: - More tests - Correctly register ControllerRelProviders
This commit is contained in:
@@ -35,24 +35,32 @@ import org.codehaus.jackson.map.ContextualDeserializer;
|
||||
import org.codehaus.jackson.map.ContextualSerializer;
|
||||
import org.codehaus.jackson.map.DeserializationConfig;
|
||||
import org.codehaus.jackson.map.DeserializationContext;
|
||||
import org.codehaus.jackson.map.HandlerInstantiator;
|
||||
import org.codehaus.jackson.map.JsonDeserializer;
|
||||
import org.codehaus.jackson.map.JsonMappingException;
|
||||
import org.codehaus.jackson.map.JsonSerializer;
|
||||
import org.codehaus.jackson.map.KeyDeserializer;
|
||||
import org.codehaus.jackson.map.MapperConfig;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.codehaus.jackson.map.SerializerProvider;
|
||||
import org.codehaus.jackson.map.TypeSerializer;
|
||||
import org.codehaus.jackson.map.deser.std.ContainerDeserializerBase;
|
||||
import org.codehaus.jackson.map.introspect.Annotated;
|
||||
import org.codehaus.jackson.map.jsontype.TypeIdResolver;
|
||||
import org.codehaus.jackson.map.jsontype.TypeResolverBuilder;
|
||||
import org.codehaus.jackson.map.module.SimpleModule;
|
||||
import org.codehaus.jackson.map.module.SimpleSerializers;
|
||||
import org.codehaus.jackson.map.ser.std.ContainerSerializerBase;
|
||||
import org.codehaus.jackson.map.ser.std.MapSerializer;
|
||||
import org.codehaus.jackson.map.type.TypeFactory;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.core.ObjectUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -66,18 +74,13 @@ public class Jackson1HalModule extends SimpleModule {
|
||||
/**
|
||||
* Creates a new {@link Jackson1HalModule}.
|
||||
*/
|
||||
public Jackson1HalModule(RelProvider relProvider) {
|
||||
public Jackson1HalModule() {
|
||||
|
||||
super("json-hal-module", new Version(1, 0, 0, null));
|
||||
|
||||
setMixInAnnotation(Link.class, LinkMixin.class);
|
||||
setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class);
|
||||
setMixInAnnotation(Resources.class, ResourcesMixin.class);
|
||||
|
||||
SimpleSerializers serializers = new SimpleSerializers();
|
||||
serializers.addSerializer(new HalResourcesSerializer(relProvider));
|
||||
|
||||
setSerializers(serializers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +201,12 @@ public class Jackson1HalModule extends SimpleModule {
|
||||
|
||||
for (Object resource : value) {
|
||||
|
||||
String relation = relProvider == null ? "content" : relProvider.getRelForSingleResource(value.getClass());
|
||||
Class<?> type = ObjectUtils.getResourceType(resource);
|
||||
String relation = relProvider == null ? "content" : relProvider.getSingleResourceRelFor(type);
|
||||
|
||||
if (relation == null) {
|
||||
relation = "content";
|
||||
}
|
||||
|
||||
if (sortedLinks.get(relation) == null) {
|
||||
sortedLinks.put(relation, new ArrayList<Object>());
|
||||
@@ -454,4 +462,59 @@ public class Jackson1HalModule extends SimpleModule {
|
||||
return des;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HalHandlerInstantiator extends HandlerInstantiator {
|
||||
|
||||
private final Map<Class<?>, Object> instanceMap = new HashMap<Class<?>, Object>();
|
||||
|
||||
public HalHandlerInstantiator(RelProvider relProvider) {
|
||||
|
||||
Assert.notNull(relProvider, "RelProvider must not be null!");
|
||||
this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(null, relProvider));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<? extends JsonDeserializer<?>> deserClass) {
|
||||
return (JsonDeserializer<?>) findInstance(deserClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<? extends KeyDeserializer> keyDeserClass) {
|
||||
return (KeyDeserializer) findInstance(keyDeserClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated,
|
||||
Class<? extends JsonSerializer<?>> serClass) {
|
||||
// there is a know bug in jackson that will not create a serializer instance if the handler instantiator returns
|
||||
// null!
|
||||
return (JsonSerializer<?>) findInstance(serClass, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<? extends TypeResolverBuilder<?>> builderClass) {
|
||||
return (TypeResolverBuilder<?>) findInstance(builderClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<? extends TypeIdResolver> resolverClass) {
|
||||
return (TypeIdResolver) findInstance(resolverClass, false);
|
||||
}
|
||||
|
||||
private Object findInstance(Class<?> type, boolean createInstance) {
|
||||
|
||||
Object result = instanceMap.get(type);
|
||||
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return createInstance ? BeanUtils.instantiateClass(type) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,14 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.core.ObjectUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerationException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
@@ -37,17 +40,24 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.KeyDeserializer;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.module.SimpleSerializers;
|
||||
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
|
||||
@@ -63,17 +73,13 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = 7806951456457932384L;
|
||||
|
||||
public Jackson2HalModule(RelProvider relProvider) {
|
||||
public Jackson2HalModule() {
|
||||
|
||||
super("json-hal-module", new Version(1, 0, 0, null, "org.springframework.hateoas", "spring-hateoas"));
|
||||
|
||||
setMixInAnnotation(Link.class, LinkMixin.class);
|
||||
setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class);
|
||||
setMixInAnnotation(Resources.class, ResourcesMixin.class);
|
||||
|
||||
SimpleSerializers serializers = new SimpleSerializers();
|
||||
serializers.addSerializer(new HalResourcesSerializer(relProvider));
|
||||
setSerializers(serializers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +204,8 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
*/
|
||||
public static class HalResourcesSerializer extends ContainerSerializer<Collection<?>> implements ContextualSerializer {
|
||||
|
||||
private static final String DEFAULT_REL = "content";
|
||||
|
||||
private final BeanProperty property;
|
||||
private final RelProvider relProvider;
|
||||
|
||||
@@ -234,12 +242,16 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
|
||||
for (Object resource : value) {
|
||||
|
||||
// TODO: do something fancy to get the relation name
|
||||
String relation = relProvider == null ? "content" : relProvider.getRelForSingleResource(value.getClass());
|
||||
Class<?> type = ObjectUtils.getResourceType(resource);
|
||||
String relation = relProvider == null ? DEFAULT_REL : relProvider.getSingleResourceRelFor(type);
|
||||
|
||||
if (relation == null) {
|
||||
relation = DEFAULT_REL;
|
||||
}
|
||||
|
||||
if (sortedLinks.get(relation) == null) {
|
||||
sortedLinks.put(relation, new ArrayList<Object>());
|
||||
}
|
||||
|
||||
sortedLinks.get(relation).add(resource);
|
||||
}
|
||||
|
||||
@@ -257,7 +269,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new HalResourcesSerializer(property, null);
|
||||
return new HalResourcesSerializer(property, relProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -564,4 +576,69 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
return des;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HalHandlerInstantiator extends HandlerInstantiator {
|
||||
|
||||
private final Map<Class<?>, Object> instanceMap = new HashMap<Class<?>, Object>();
|
||||
|
||||
public HalHandlerInstantiator(RelProvider resolver) {
|
||||
|
||||
Assert.notNull(resolver, "RelProvider must not be null!");
|
||||
this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(null, resolver));
|
||||
}
|
||||
|
||||
private Object findInstance(Class<?> type) {
|
||||
|
||||
Object result = instanceMap.get(type);
|
||||
return result != null ? result : BeanUtils.instantiateClass(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#deserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> deserClass) {
|
||||
return (JsonDeserializer<?>) findInstance(deserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#keyDeserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> keyDeserClass) {
|
||||
return (KeyDeserializer) findInstance(keyDeserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#serializerInstance(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) {
|
||||
return (JsonSerializer<?>) findInstance(serClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeResolverBuilderInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<?> builderClass) {
|
||||
return (TypeResolverBuilder<?>) findInstance(builderClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeIdResolverInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) {
|
||||
return (TypeIdResolver) findInstance(resolverClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user