Implemented a new style of JSON serialization and deserialization by registering Repository-aware components with Jackson's underlying ObjectMapper. This has the benefit of allowing us to transliterate domain objects into link representations and back again, no matter where those entities appear in complex nested object graphs.

This commit is contained in:
Jon Brisbin
2012-08-15 14:31:57 -05:00
committed by Jon Brisbin
parent d2c6ecacc5
commit d7e2e53ce2
19 changed files with 723 additions and 372 deletions

View File

@@ -9,7 +9,6 @@ import org.codehaus.jackson.annotate.JsonTypeInfo;
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, defaultImpl = ResourceLink.class)
public interface Link {
/**

View File

@@ -2,8 +2,8 @@ package org.springframework.data.rest.core;
import java.net.URI;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.springframework.util.Assert;
/**
* Implementation of {@link Link}.
@@ -33,6 +33,25 @@ public class ResourceLink implements Link, Comparable<Link> {
return href;
}
public String getRel() {
return rel();
}
public ResourceLink setRel(String rel) {
this.rel = rel;
return this;
}
public URI getHref() {
return href();
}
public ResourceLink setHref(URI href) {
Assert.notNull(href, "href URI cannot be null.");
this.href = href;
return this;
}
@Override public int compareTo(Link link) {
if(null == rel || null == link.rel()) {
return -1;

View File

@@ -1,89 +0,0 @@
package org.springframework.data.rest.core.util;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.deser.std.StdDeserializer;
import org.springframework.core.convert.ConversionService;
import org.springframework.util.ClassUtils;
/**
* A "fluent" bean is one that does not use the JavaBean conventions of "setProperty" and "getProperty" but instead
* uses just "property" with 0 or 1 arguments to distinguish between getter (0 arg) and setter (1 arg).
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public class FluentBeanDeserializer extends StdDeserializer {
private ConversionService conversionService;
private FluentBeanUtils.Metadata beanMeta;
@SuppressWarnings({"unchecked"})
public FluentBeanDeserializer(final Class<?> valueClass, ConversionService conversionService) {
super(valueClass);
this.conversionService = conversionService;
this.beanMeta = FluentBeanUtils.metadata(valueClass);
if(!FluentBeanUtils.isFluentBean(valueClass)) {
throw new IllegalArgumentException("Class of type " + valueClass + " is not a FluentBean");
}
}
@Override
public Object deserialize(JsonParser jp,
DeserializationContext ctxt) throws IOException {
if(jp.getCurrentToken() != JsonToken.START_OBJECT) {
throw ctxt.mappingException(_valueClass);
}
Object bean;
try {
bean = _valueClass.newInstance();
} catch(InstantiationException e) {
throw new IllegalStateException(e);
} catch(IllegalAccessException e) {
throw new IllegalStateException(e);
}
while(jp.nextToken() != JsonToken.END_OBJECT) {
String name = jp.getCurrentName();
Method setter = beanMeta.setters().get(name);
Object obj;
if(null != setter) {
Class<?> targetType = setter.getParameterTypes()[0];
if(ClassUtils.isAssignable(targetType, Long.class)) {
obj = jp.nextLongValue(-1);
} else if(ClassUtils.isAssignable(targetType, Integer.class)) {
obj = jp.nextIntValue(-1);
} else if(ClassUtils.isAssignable(targetType, Boolean.class)) {
obj = jp.nextBooleanValue();
} else {
obj = jp.nextTextValue();
}
if(null != obj) {
if(!ClassUtils.isAssignable(obj.getClass(), targetType)) {
obj = conversionService.convert(obj, targetType);
}
try {
setter.invoke(bean, obj);
} catch(IllegalAccessException e) {
throw new IllegalStateException(e);
} catch(InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
}
return bean;
}
}

View File

@@ -1,78 +0,0 @@
package org.springframework.data.rest.core.util;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.ser.std.SerializerBase;
import org.springframework.util.ClassUtils;
/**
* A "fluent" bean is one that does not use the JavaBean conventions of "setProperty" and "getProperty" but instead
* uses just "property" with 0 or 1 arguments to distinguish between getter (0 arg) and setter (1 arg).
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public class FluentBeanSerializer
extends SerializerBase<Object> {
@SuppressWarnings({"unchecked", "rawtypes"})
public FluentBeanSerializer(final Class t) {
super(t);
if(!FluentBeanUtils.isFluentBean(t)) {
throw new IllegalArgumentException("Class of type " + t + " is not a FluentBean");
}
}
@SuppressWarnings({"unchecked"})
@Override
public void serialize(final Object value,
final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException,
JsonGenerationException {
if(null == value) {
provider.defaultSerializeNull(jgen);
} else {
Class<?> type = value.getClass();
if(ClassUtils.isAssignable(type, Collection.class)) {
jgen.writeStartArray();
for(Object o : (Collection)value) {
write(o, jgen, provider);
}
jgen.writeEndArray();
} else if(ClassUtils.isAssignable(type, Map.class)) {
jgen.writeStartObject();
for(Map.Entry<String, Object> entry : ((Map<String, Object>)value).entrySet()) {
jgen.writeFieldName(entry.getKey());
write(entry.getValue(), jgen, provider);
}
jgen.writeEndObject();
} else {
write(value, jgen, provider);
}
}
}
private void write(final Object value,
final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException {
Class<?> type = value.getClass();
if(ClassUtils.isAssignable(type, _handledType)) {
jgen.writeStartObject();
for(String fname : FluentBeanUtils.metadata(type).fieldNames()) {
jgen.writeFieldName(fname);
write(FluentBeanUtils.get(fname, value), jgen, provider);
}
jgen.writeEndObject();
} else {
jgen.writeObject(value);
}
}
}