#307 - Add back support for JsonValueAdapter and JsonReadOptions into 4.x

This commit is contained in:
rbygrave
2015-06-21 01:46:23 +12:00
parent f0b8270e5e
commit 721a50219d
15 changed files with 407 additions and 47 deletions
@@ -21,6 +21,13 @@ public interface JsonContext {
*/
<T> T toBean(Class<T> rootType, String json) throws JsonIOException;
/**
* Convert json string input into a Bean of a specific type additionally using JsonReadOptions.
*
* @throws JsonIOException When IOException occurs
*/
<T> T toBean(Class<T> rootType, String json, JsonReadOptions options) throws JsonIOException;
/**
* Convert json reader input into a Bean of a specific type.
*
@@ -28,6 +35,13 @@ public interface JsonContext {
*/
<T> T toBean(Class<T> rootType, Reader json) throws JsonIOException;
/**
* Convert json reader input into a Bean of a specific type additionally using JsonReadOptions.
*
* @throws JsonIOException When IOException occurs
*/
<T> T toBean(Class<T> rootType, Reader json, JsonReadOptions options) throws JsonIOException;
/**
* Convert json parser input into a Bean of a specific type.
*
@@ -35,6 +49,13 @@ public interface JsonContext {
*/
<T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException;
/**
* Convert json parser input into a Bean of a specific type additionally using JsonReadOptions..
*
* @throws JsonIOException When IOException occurs
*/
<T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
/**
* Convert json string input into a list of beans of a specific type.
*
@@ -42,6 +63,13 @@ public interface JsonContext {
*/
<T> List<T> toList(Class<T> rootType, String json) throws JsonIOException;
/**
* Convert json string input into a list of beans of a specific type additionally using JsonReadOptions.
*
* @throws JsonIOException When IOException occurs
*/
<T> List<T> toList(Class<T> rootType, String json, JsonReadOptions options) throws JsonIOException;
/**
* Convert json reader input into a list of beans of a specific type.
*
@@ -49,12 +77,26 @@ public interface JsonContext {
*/
<T> List<T> toList(Class<T> rootType, Reader json) throws JsonIOException;
/**
* Convert json reader input into a list of beans of a specific type additionally using JsonReadOptions.
*
* @throws JsonIOException When IOException occurs
*/
<T> List<T> toList(Class<T> rootType, Reader json, JsonReadOptions options) throws JsonIOException;
/**
* Convert json parser input into a list of beans of a specific type.
*
* @throws JsonIOException When IOException occurs
*/
<T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException;
<T> List<T> toList(Class<T> cls, JsonParser json) throws JsonIOException;
/**
* Convert json parser input into a list of beans of a specific type additionally using JsonReadOptions.
*
* @throws JsonIOException When IOException occurs
*/
<T> List<T> toList(Class<T> cls, JsonParser json, JsonReadOptions options) throws JsonIOException;
/**
* Use the genericType to determine if this should be converted into a List or
@@ -0,0 +1,30 @@
package com.avaje.ebean.text.json;
import java.util.Map;
/**
* Provides for custom handling of json content as it is read.
* <p>
* This visit method is called after all the known properties of the bean have
* been processed. Any JSON elements that could not be mapped to known bean
* properties are available in the unmapped Map.
* </p>
*
* @param <T> The type of entity bean
*/
public interface JsonReadBeanVisitor<T> {
/**
* Visit the bean that has just been processed.
* <p>
* This provides a method of customising the bean and processing any custom
* JSON content.
* </p>
*
* @param bean the bean being processed
* @param unmapped Map of any JSON elements that didn't map to known bean properties
*/
void visit(T bean, Map<String, Object> unmapped);
}
@@ -0,0 +1,47 @@
package com.avaje.ebean.text.json;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Provides the ability to customise the reading of JSON content.
* <p>
* You can register JsonReadBeanVisitors to customise the processing of the
* beans as they are processed and handle any custom JSON elements that
* could not be mapped to bean properties.
* </p>
*/
public class JsonReadOptions {
protected Map<String, JsonReadBeanVisitor<?>> visitorMap;
/**
* Default constructor.
*/
public JsonReadOptions() {
this.visitorMap = new LinkedHashMap<String, JsonReadBeanVisitor<?>>();
}
/**
* Return the map of JsonReadBeanVisitor's.
*/
public Map<String, JsonReadBeanVisitor<?>> getVisitorMap() {
return visitorMap;
}
/**
* Register a JsonReadBeanVisitor for the root level.
*/
public JsonReadOptions addRootVisitor(JsonReadBeanVisitor<?> visitor) {
return addVisitor(null, visitor);
}
/**
* Register a JsonReadBeanVisitor for a given path.
*/
public JsonReadOptions addVisitor(String path, JsonReadBeanVisitor<?> visitor) {
visitorMap.put(path, visitor);
return this;
}
}
@@ -19,7 +19,6 @@ import com.avaje.ebean.meta.MetaQueryPlanStatistic;
import com.avaje.ebeaninternal.api.*;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
import com.avaje.ebeaninternal.server.core.CacheOptions;
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
import com.avaje.ebeaninternal.server.core.InternString;
@@ -33,13 +32,13 @@ import com.avaje.ebeaninternal.server.query.CQueryPlan;
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.TypeManager;
import com.avaje.ebeaninternal.util.SortByClause;
import com.avaje.ebeaninternal.util.SortByClause.Property;
import com.avaje.ebeaninternal.util.SortByClauseParser;
import com.fasterxml.jackson.core.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -1894,11 +1893,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
jsonHelp.jsonWriteProperties(writeJson, bean);
}
public T jsonRead(JsonParser parser, String path) throws IOException {
return jsonHelp.jsonRead(parser, path);
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
return jsonHelp.jsonRead(jsonRead, path);
}
protected T jsonReadObject(JsonParser parser, String path) throws IOException {
return jsonHelp.jsonReadObject(parser, path);
protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException {
return jsonHelp.jsonReadObject(jsonRead, path);
}
}
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.json.EJson;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson.WriteBean;
import com.fasterxml.jackson.core.JsonParseException;
@@ -9,6 +10,8 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class BeanDescriptorJsonHelp<T> {
@@ -50,8 +53,9 @@ public class BeanDescriptorJsonHelp<T> {
@SuppressWarnings("unchecked")
public T jsonRead(JsonParser parser, String path) throws IOException {
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
JsonParser parser = jsonRead.getParser();
if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
// start object token read by Jackson already
} else {
@@ -66,7 +70,7 @@ public class BeanDescriptorJsonHelp<T> {
}
if (desc.inheritInfo == null) {
return jsonReadObject(parser, path);
return jsonReadObject(jsonRead, path);
}
// check for the discriminator value to determine the correct sub type
@@ -83,8 +87,8 @@ public class BeanDescriptorJsonHelp<T> {
BeanProperty property = desc.getBeanProperty(propName);
if (property != null) {
EntityBean bean = desc.createEntityBean();
property.jsonRead(parser, bean);
return jsonReadProperties(parser, bean);
property.jsonRead(jsonRead, bean);
return jsonReadProperties(jsonRead, bean, path);
}
String msg = "Error reading inheritance discriminator, expected property ["+discColumn+"] but got [" + propName + "] ?";
throw new JsonParseException(msg, parser.getCurrentLocation());
@@ -95,29 +99,39 @@ public class BeanDescriptorJsonHelp<T> {
// determine the sub type for this particular json object
InheritInfo localInheritInfo = inheritInfo.readType(discValue);
BeanDescriptor<?> localDescriptor = localInheritInfo.getBeanDescriptor();
return (T) localDescriptor.jsonReadObject(parser, path);
return (T) localDescriptor.jsonReadObject(jsonRead, path);
}
protected T jsonReadObject(JsonParser parser, String path) throws IOException {
protected T jsonReadObject(ReadJson readJson, String path) throws IOException {
EntityBean bean = desc.createEntityBean();
return jsonReadProperties(parser, bean);
return jsonReadProperties(readJson, bean, path);
}
@SuppressWarnings("unchecked")
protected T jsonReadProperties(JsonParser parser, EntityBean bean) throws IOException {
protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException {
if (path != null) {
readJson.pushPath(path);
}
// unmapped properties, send to JsonReadBeanVisitor later
Map<String,Object> unmappedProperties = null;
do {
JsonParser parser = readJson.getParser();
JsonToken event = parser.nextToken();
if (JsonToken.FIELD_NAME == event) {
String key = parser.getCurrentName();
BeanProperty p = desc.getBeanProperty(key);
if (p != null) {
p.jsonRead(parser, bean);
p.jsonRead(readJson, bean);
} else {
// unknown property ... read and ignore
EJson.parse(parser);
// read an unmapped property
if (unmappedProperties == null) {
unmappedProperties = new LinkedHashMap<String, Object>();
}
unmappedProperties.put(key, EJson.parse(parser));
}
} else if (JsonToken.END_OBJECT == event) {
@@ -128,6 +142,13 @@ public class BeanDescriptorJsonHelp<T> {
}
} while (true);
// visit JsonReadBeanVisitor (if registered for this path)
readJson.beanVisitor(bean, unmappedProperties);
if (path != null) {
readJson.popPath();
}
return (T)bean;
}
@@ -17,10 +17,10 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter;
import com.avaje.ebeaninternal.server.properties.BeanPropertySetter;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import javax.persistence.PersistenceException;
@@ -1076,7 +1076,7 @@ public class BeanProperty implements ElPropertyValue {
}
}
public void jsonRead(JsonParser ctx, EntityBean bean) throws IOException {
public void jsonRead(ReadJson ctx, EntityBean bean) throws IOException {
if (!jsonDeserialize) {
return;
}
@@ -1086,7 +1086,7 @@ public class BeanProperty implements ElPropertyValue {
setValue(bean, null);
} else {
// expect to read non-null json value
Object objValue = scalarType.jsonRead(ctx, event);
Object objValue = scalarType.jsonRead(ctx.getParser(), event);
setValue(bean, objValue);
}
}
@@ -13,8 +13,8 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.fasterxml.jackson.core.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -878,7 +878,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
}
public void jsonRead(JsonParser parser, EntityBean parentBean) throws IOException {
jsonHelp.jsonRead(parser, parentBean);
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
jsonHelp.jsonRead(readJson, parentBean);
}
}
@@ -5,6 +5,7 @@ import java.io.IOException;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -17,12 +18,13 @@ public class BeanPropertyAssocManyJsonHelp {
this.many = many;
}
public void jsonRead(JsonParser parser, EntityBean parentBean) throws IOException {
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
if (!this.many.jsonDeserialize) {
return;
}
JsonParser parser = readJson.getParser();
JsonToken event = parser.nextToken();
if (JsonToken.VALUE_NULL == event) {
return;
@@ -34,7 +36,7 @@ public class BeanPropertyAssocManyJsonHelp {
BeanCollection<?> collection = many.createEmpty(parentBean);
BeanCollectionAdd add = many.getBeanCollectionAdd(collection, null);
do {
EntityBean detailBean = (EntityBean) many.targetDescriptor.jsonRead(parser, many.name);
EntityBean detailBean = (EntityBean) many.targetDescriptor.jsonRead(readJson, many.name);
if (detailBean == null) {
// read the entire array
break;
@@ -25,8 +25,8 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.fasterxml.jackson.core.JsonParser;
/**
* Property mapped to a joined bean.
@@ -843,9 +843,9 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
public void jsonRead(JsonParser parser, EntityBean bean) throws IOException {
public void jsonRead(ReadJson readJson, EntityBean bean) throws IOException {
if (jsonDeserialize && targetDescriptor != null) {
T assocBean = targetDescriptor.jsonRead(parser, name);
T assocBean = targetDescriptor.jsonRead(readJson, name);
setValue(bean, assocBean);
}
}
@@ -13,11 +13,11 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound;
import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.avaje.ebeaninternal.server.type.CtCompoundProperty;
import com.avaje.ebeaninternal.server.type.CtCompoundPropertyElAdapter;
import com.avaje.ebeaninternal.server.type.CtCompoundType;
import com.fasterxml.jackson.core.JsonParser;
/**
* Property mapped to an Immutable Compound Value Object.
@@ -177,18 +177,17 @@ public class BeanPropertyCompound extends BeanProperty {
}
}
public void jsonRead(JsonParser ctx, EntityBean bean) throws IOException {
@Override
public void jsonRead(ReadJson readJson, EntityBean bean) throws IOException {
if (!jsonDeserialize) {
return;
}
Object value = EJson.parse(ctx);
if (value == null) {
Map<String, Object> map = EJson.parseObject(readJson.getParser());
if (map == null) {
setValue(bean, null);
} else {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
Object objValue = compoundType.jsonConvert(map);
setValue(bean, objValue);
}
@@ -1,11 +1,8 @@
package com.avaje.ebeaninternal.server.text.json;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.json.EJson;
import com.avaje.ebean.text.json.*;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebean.text.json.JsonIOException;
import com.avaje.ebean.text.json.JsonWriteOptions;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.ParamTypeHelper;
@@ -56,15 +53,29 @@ public class DJsonContext implements JsonContext {
return toBean(cls, new StringReader(json));
}
@Override
public <T> T toBean(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException {
return toBean(cls, new StringReader(json), options);
}
public <T> T toBean(Class<T> cls, Reader jsonReader) throws JsonIOException {
return toBean(cls, createParser(jsonReader));
}
public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException {
public <T> T toBean(Class<T> cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException {
return toBean(cls, createParser(jsonReader), options);
}
public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException {
return toBean(cls, parser, null);
}
public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
ReadJson readJson = new ReadJson(parser, options);
try {
BeanDescriptor<T> d = getDescriptor(cls);
return d.jsonRead(parser, null);
return d.jsonRead(readJson, null);
} catch (IOException e) {
throw new JsonIOException(e);
}
@@ -74,13 +85,26 @@ public class DJsonContext implements JsonContext {
return toList(cls, new StringReader(json));
}
@Override
public <T> List<T> toList(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException {
return toList(cls, new StringReader(json), options);
}
public <T> List<T> toList(Class<T> cls, Reader jsonReader) throws JsonIOException {
return toList(cls, createParser(jsonReader));
}
public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException {
public <T> List<T> toList(Class<T> cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException {
return toList(cls, createParser(jsonReader), options);
}
public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException {
return toList(cls, src, null);
}
public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
ReadJson readJson = new ReadJson(src, options);
try {
BeanDescriptor<T> d = getDescriptor(cls);
@@ -92,7 +116,7 @@ public class DJsonContext implements JsonContext {
}
do {
T bean = d.jsonRead(src, null);
T bean = d.jsonRead(readJson, null);
if (bean == null) {
break;
} else {
@@ -0,0 +1,87 @@
package com.avaje.ebeaninternal.server.text.json;
import com.avaje.ebean.text.json.JsonReadBeanVisitor;
import com.avaje.ebean.text.json.JsonReadOptions;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.util.Map;
/**
* Context for JSON read processing.
*/
public class ReadJson {
/**
* Jackson parser.
*/
final JsonParser parser;
/**
* Stack of the path - used to find the appropriate JsonReadBeanVisitor.
*/
final PathStack pathStack;
/**
* Map of the JsonReadBeanVisitor keyed by path.
*/
final Map<String, JsonReadBeanVisitor<?>> visitorMap;
/**
* Construct with parser and readOptions.
*/
public ReadJson(JsonParser parser, JsonReadOptions readOptions) {
this.parser = parser;
// only create visitorMap, pathStack if needed ...
this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap();
this.pathStack = (visitorMap == null) ? null : new PathStack();
}
/**
* Return the JsonParser.
*/
public JsonParser getParser() {
return parser;
}
/**
* Return the next JsonToken from the underlying parser.
*/
public JsonToken nextToken() throws IOException {
return parser.nextToken();
}
/**
* Push the path onto the stack (traversing a 1-M or M-1 etc)
*/
public void pushPath(String path) {
if (pathStack != null) {
pathStack.pushPathKey(path);
}
}
/**
* Pop the path stack.
*/
public void popPath() {
if (pathStack != null) {
pathStack.pop();
}
}
/**
* If there is a JsonReadBeanVisitor registered to the current path then
* call it's visit method with the bean and unmappedProperties.
*/
@SuppressWarnings(value = "unchecked")
public void beanVisitor(Object bean, Map<String, Object> unmappedProperties) {
if (visitorMap != null) {
JsonReadBeanVisitor visitor = visitorMap.get(pathStack.peekWithNull());
if (visitor != null) {
visitor.visit(bean, unmappedProperties);
}
}
}
}