From 160e4d7dd16e69051de50ea1daba04279a494c56 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Sun, 19 Jul 2015 21:41:20 +1200 Subject: [PATCH] #318 - JSON - toBean error at Transient (with support for #327, #328, #329) --- .../ebean/text/json/JsonReadOptions.java | 15 + .../avaje/ebeaninternal/api/ClassUtil.java | 15 + .../server/deploy/BeanProperty.java | 40 +- .../server/deploy/BeanProperty.java.orig | 1257 ----------------- .../server/deploy/BeanPropertyAssocMany.java | 6 +- .../deploy/BeanPropertyAssocManyJsonHelp.java | 39 +- .../BeanPropertyAssocManyJsonTransient.java | 44 + .../ebeaninternal/server/deploy/ManyType.java | 106 +- .../deploy/meta/DeployBeanProperty.java | 20 +- .../server/deploy/parse/AnnotationFields.java | 45 +- .../deploy/parse/DeployCreateProperties.java | 3 +- .../server/deploy/parse/DeployUtil.java | 30 +- .../server/text/json/DJsonContext.java | 40 +- .../server/text/json/ReadJson.java | 28 +- .../server/text/json/WriteJson.java | 20 +- .../server/type/DefaultTypeManager.java | 4 +- .../server/text/json/WriteJsonTest.java | 2 +- .../EJsonJacksonIgnore.java | 44 + .../EJsonTransientList.java | 57 + .../EJsonTransientObject.java | 63 + .../TestJacksonIgnore.java | 42 + .../TestTransientListProperty.java | 25 + .../TestTransientObjectProperty.java | 27 + .../com/avaje/tests/model/basic/Customer.java | 17 +- .../json/TestJsonBeanDescriptorParse.java | 2 +- 25 files changed, 633 insertions(+), 1358 deletions(-) delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java.orig create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java create mode 100644 src/test/java/com/avaje/tests/json/transientproperties/EJsonJacksonIgnore.java create mode 100644 src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientList.java create mode 100644 src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientObject.java create mode 100644 src/test/java/com/avaje/tests/json/transientproperties/TestJacksonIgnore.java create mode 100644 src/test/java/com/avaje/tests/json/transientproperties/TestTransientListProperty.java create mode 100644 src/test/java/com/avaje/tests/json/transientproperties/TestTransientObjectProperty.java diff --git a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java index 81d4efd6f..e85e67466 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java @@ -15,6 +15,8 @@ public class JsonReadOptions { protected Map> visitorMap; + protected Object objectMapper; + /** * Default constructor. */ @@ -44,4 +46,17 @@ public class JsonReadOptions { return this; } + /** + * Return the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig). + */ + public Object getObjectMapper() { + return objectMapper; + } + + /** + * Set the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig). + */ + public void setObjectMapper(Object objectMapper) { + this.objectMapper = objectMapper; + } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java b/src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java index 4d4a3c3c7..2ea8eda71 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java +++ b/src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java @@ -49,6 +49,21 @@ public class ClassUtil { return classLoader; } + /** + * Return true if Jackson annotations like JsonIgnore are present. + */ + public static boolean isJacksonAnnotationsPresent() { + return isPresent("com.fasterxml.jackson.annotation.JsonIgnore", null); + } + + /** + * Return true if Jackson ObjectMapper is present. + */ + public static boolean isJacksonObjectMapperPresent() { + return isPresent("com.fasterxml.jackson.databind.ObjectMapper", null); + } + + /** * Return true if the given class is present. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java index eec6b8e61..69fe731f5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -22,6 +22,8 @@ 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.JsonToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.persistence.PersistenceException; import java.io.DataInput; @@ -39,6 +41,8 @@ import java.util.Map; */ public class BeanProperty implements ElPropertyValue { + private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class); + /** * Flag to mark this at part of the unique id. */ @@ -299,8 +303,8 @@ public class BeanProperty implements ElPropertyValue { this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), false, null); this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), dbEncrypted, dbColumn); - this.jsonSerialize = deploy.isExposeSerialize(); - this.jsonDeserialize = deploy.isExposeDeserialize(); + this.jsonSerialize = deploy.isJsonSerialize(); + this.jsonDeserialize = deploy.isJsonDeserialize(); } private String tableAliasIntern(BeanDescriptor descriptor, String s, boolean dbEncrypted, String dbColumn) { @@ -1072,22 +1076,40 @@ public class BeanProperty implements ElPropertyValue { if (value == null) { writeJson.writeNull(name); } else { - scalarType.jsonWrite(writeJson.gen(), name, value); + if (scalarType != null) { + scalarType.jsonWrite(writeJson.gen(), name, value); + } else { + writeJson.writeValueUsingObjectMapper(name, value); + } } } public void jsonRead(ReadJson ctx, EntityBean bean) throws IOException { - if (!jsonDeserialize) { - return; - } JsonToken event = ctx.nextToken(); if (JsonToken.VALUE_NULL == event) { - setValue(bean, null); + if (jsonDeserialize) { + setValue(bean, null); + } } else { // expect to read non-null json value - Object objValue = scalarType.jsonRead(ctx.getParser(), event); - setValue(bean, objValue); + Object objValue; + if (scalarType != null) { + objValue = scalarType.jsonRead(ctx.getParser(), event); + } else { + try { + objValue = ctx.readValueUsingObjectMapper(propertyType); + } catch (IOException e) { + // change in behavior for #318 + objValue = null; + String msg = "Error trying to use Jackson ObjectMapper to read transient property " + + getFullBeanName() +" - consider marking this property with @JsonIgnore"; + logger.error(msg, e); + } + } + if (jsonDeserialize) { + setValue(bean, objValue); + } } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java.orig b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java.orig deleted file mode 100644 index 339c6f8b6..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java.orig +++ /dev/null @@ -1,1257 +0,0 @@ -package com.avaje.ebeaninternal.server.deploy; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.sql.SQLException; -import java.sql.Types; -import java.util.List; -import java.util.Map; - -import javax.json.stream.JsonParser; -import javax.json.stream.JsonParser.Event; -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.config.EncryptKey; -import com.avaje.ebean.config.dbplatform.DbEncryptFunction; -import com.avaje.ebean.config.dbplatform.DbType; -import com.avaje.ebean.text.StringFormatter; -import com.avaje.ebean.text.StringParser; -import com.avaje.ebeaninternal.server.core.InternString; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; -import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; -import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; -import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; -import com.avaje.ebeaninternal.server.lib.util.StringHelper; -import com.avaje.ebeaninternal.server.query.SqlBeanLoad; -import com.avaje.ebeaninternal.server.query.SqlJoinType; -import com.avaje.ebeaninternal.server.reflect.BeanReflectGetter; -import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter; -import com.avaje.ebeaninternal.server.text.json.WriteJson; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.type.ScalarType; - -/** - * Description of a property of a bean. Includes its deployment information such - * as database column mapping information. - */ -public class BeanProperty implements ElPropertyValue { - - /** - * Advanced bean deployment. To exclude this property from update where - * clause. - */ - public static final String EXCLUDE_FROM_UPDATE_WHERE = "EXCLUDE_FROM_UPDATE_WHERE"; - - /** - * Advanced bean deployment. To exclude this property from delete where - * clause. - */ - public static final String EXCLUDE_FROM_DELETE_WHERE = "EXCLUDE_FROM_DELETE_WHERE"; - - /** - * Advanced bean deployment. To exclude this property from insert. - */ - public static final String EXCLUDE_FROM_INSERT = "EXCLUDE_FROM_INSERT"; - - /** - * Advanced bean deployment. To exclude this property from update set - * clause. - */ - public static final String EXCLUDE_FROM_UPDATE = "EXCLUDE_FROM_UPDATE"; - - /** - * Flag to mark this at part of the unique id. - */ - final boolean id; - - /** - * Flag to make this as a dummy property for unidirecitonal relationships. - */ - final boolean unidirectionalShadow; - - /** - * Flag set if this maps to the inheritance discriminator column - */ - final boolean discriminator; - - /** - * Flag to mark the property as embedded. This could be on - * BeanPropertyAssocOne rather than here. Put it here for checking Id type - * (embedded or not). - */ - final boolean embedded; - - /** - * Flag indicating if this the version property. - */ - final boolean version; - - final boolean naturalKey; - - /** - * Set if this property is nullable. - */ - final boolean nullable; - - final boolean unique; - - /** - * Is this property include in database resultSet. - */ - final boolean dbRead; - - /** - * Include in DB insert. - */ - final boolean dbInsertable; - - /** - * Include in DB update. - */ - final boolean dbUpdatable; - - /** - * True if the property is based on a SECONDARY table. - */ - final boolean secondaryTable; - - final TableJoin secondaryTableJoin; - final String secondaryTableJoinPrefix; - - /** - * The property is inherited from a super class. - */ - final boolean inherited; - - final Class owningType; - - final boolean local; - - /** - * True if the property is a Clob, Blob LongVarchar or LongVarbinary. - */ - final boolean lob; - - final boolean fetchEager; - - final boolean isTransient; - - /** - * The logical bean property name. - */ - final String name; - - final int propertyIndex; - - /** - * The reflected field. - */ - final Field field; - - /** - * The bean type. - */ - final Class propertyType; - - final String dbBind; - - /** - * The database column. This can include quoted identifiers. - */ - final String dbColumn; - - final String elPlaceHolder; - final String elPlaceHolderEncrypted; - - /** - * Select part of a SQL Formula used to populate this property. - */ - final String sqlFormulaSelect; - - /** - * Join part of a SQL Formula. - */ - final String sqlFormulaJoin; - - final boolean formula; - - /** - * Set to true if stored encrypted. - */ - final boolean dbEncrypted; - - final boolean localEncrypted; - - final int dbEncryptedType; - - /** - * The jdbc data type this maps to. - */ - final int dbType; - - /** - * The default value to insert if null. - */ - final Object defaultValue; - - /** - * Extra deployment parameters. - */ - final Map extraAttributeMap; - - /** - * The method used to read the property. - */ - final Method readMethod; - - /** - * The method used to write the property. - */ - final Method writeMethod; - - /** - * Generator for insert or update timestamp etc. - */ - final GeneratedProperty generatedProperty; - - final BeanReflectGetter getter; - - final BeanReflectSetter setter; - - final BeanDescriptor descriptor; - - /** - * Used for non-jdbc native types (java.util.Date Enums etc). Converts from - * logical to jdbc types. - */ - @SuppressWarnings("rawtypes") - final ScalarType scalarType; - - boolean cascadeValidate; - - /** - * The length or precision for DB column. - */ - final int dbLength; - - /** - * The scale for DB column (decimal). - */ - final int dbScale; - - /** - * Deployment defined DB column definition. - */ - final String dbColumnDefn; - - /** - * DB Constraint (typically check constraint on enum) - */ - final String dbConstraintExpression; - - final DbEncryptFunction dbEncryptFunction; - - int deployOrder; - - final boolean jsonSerialize; - - final boolean jsonDeserialize; - - final boolean indexed; - - final String indexName; - - public BeanProperty(DeployBeanProperty deploy) { - this(null, null, deploy); - } - - public BeanProperty(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanProperty deploy) { - - this.descriptor = descriptor; - this.name = InternString.intern(deploy.getName()); - this.propertyIndex = deploy.getPropertyIndex(); - - this.indexed = deploy.isIndexed(); - this.indexName = deploy.getIndexName(); - - this.unidirectionalShadow = deploy.isUndirectionalShadow(); - this.discriminator = deploy.isDiscriminator(); - this.localEncrypted = deploy.isLocalEncrypted(); - this.dbEncrypted = deploy.isDbEncrypted(); - this.dbEncryptedType = deploy.getDbEncryptedType(); - this.dbEncryptFunction = deploy.getDbEncryptFunction(); - this.dbBind = deploy.getDbBind(); - this.dbRead = deploy.isDbRead(); - this.dbInsertable = deploy.isDbInsertable(); - this.dbUpdatable = deploy.isDbUpdateable(); - - this.secondaryTable = deploy.isSecondaryTable(); - if (secondaryTable) { - this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin(), null); - this.secondaryTableJoinPrefix = deploy.getSecondaryTableJoinPrefix(); - } else { - this.secondaryTableJoin = null; - this.secondaryTableJoinPrefix = null; - } - this.fetchEager = deploy.isFetchEager(); - this.isTransient = deploy.isTransient(); - this.nullable = deploy.isNullable(); - this.unique = deploy.isUnique(); - this.naturalKey = deploy.isNaturalKey(); - this.dbLength = deploy.getDbLength(); - this.dbScale = deploy.getDbScale(); - this.dbColumnDefn = InternString.intern(deploy.getDbColumnDefn()); - this.dbConstraintExpression = InternString.intern(deploy.getDbConstraintExpression()); - - this.inherited = false;// deploy.isInherited(); - this.owningType = deploy.getOwningType(); - this.local = deploy.isLocal(); - - this.version = deploy.isVersionColumn(); - this.embedded = deploy.isEmbedded(); - this.id = deploy.isId(); - this.generatedProperty = deploy.getGeneratedProperty(); - this.readMethod = deploy.getReadMethod(); - this.writeMethod = deploy.getWriteMethod(); - this.getter = deploy.getGetter(); - this.setter = deploy.getSetter(); - - this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null); - this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin()); - this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect()); - this.formula = sqlFormulaSelect != null; - - this.extraAttributeMap = deploy.getExtraAttributeMap(); - this.defaultValue = deploy.getDefaultValue(); - this.dbType = deploy.getDbType(); - this.scalarType = deploy.getScalarType(); - this.lob = isLobType(dbType); - this.propertyType = deploy.getPropertyType(); - this.field = deploy.getField(); - - EntityType et = descriptor == null ? null : descriptor.getEntityType(); - this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), false, null); - this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), dbEncrypted, dbColumn); - - this.jsonSerialize = deploy.isExposeSerialize(); - this.jsonDeserialize = deploy.isExposeDeserialize(); - } - - private String tableAliasIntern(BeanDescriptor descriptor, String s, boolean dbEncrypted, String dbColumn) { - if (descriptor != null) { - s = StringHelper.replaceString(s, "${ta}.", "${}"); - s = StringHelper.replaceString(s, "${ta}", "${}"); - - if (dbEncrypted) { - s = dbEncryptFunction.getDecryptSql(s); - String namedParam = ":encryptkey_" + descriptor.getBaseTable() + "___" + dbColumn; - s = StringHelper.replaceString(s, "?", namedParam); - } - } - return InternString.intern(s); - } - - /** - * Create a Matching BeanProperty with some attributes overridden. - *

- * Primarily for supporting Embedded beans with overridden dbColumn - * mappings. - *

- */ - public BeanProperty(BeanProperty source, BeanPropertyOverride override) { - - this.descriptor = source.descriptor; - this.name = InternString.intern(source.getName()); - this.propertyIndex = source.propertyIndex; - - this.indexed = source.isIndexed(); - this.indexName = source.getIndexName(); - - this.dbColumn = InternString.intern(override.getDbColumn()); - this.sqlFormulaJoin = InternString.intern(override.getSqlFormulaJoin()); - this.sqlFormulaSelect = InternString.intern(override.getSqlFormulaSelect()); - this.formula = sqlFormulaSelect != null; - - this.fetchEager = source.fetchEager; - this.unidirectionalShadow = source.unidirectionalShadow; - this.discriminator = source.discriminator; - this.localEncrypted = source.isLocalEncrypted(); - this.isTransient = source.isTransient(); - this.secondaryTable = source.isSecondaryTable(); - this.secondaryTableJoin = source.secondaryTableJoin; - this.secondaryTableJoinPrefix = source.secondaryTableJoinPrefix; - - this.dbBind = source.getDbBind(); - this.dbEncrypted = source.isDbEncrypted(); - this.dbEncryptedType = source.getDbEncryptedType(); - this.dbEncryptFunction = source.dbEncryptFunction; - this.dbRead = source.isDbRead(); - this.dbInsertable = source.isDbInsertable(); - this.dbUpdatable = source.isDbUpdatable(); - this.nullable = source.isNullable(); - this.unique = source.isUnique(); - this.naturalKey = source.isNaturalKey(); - this.dbLength = source.getDbLength(); - this.dbScale = source.getDbScale(); - this.dbColumnDefn = InternString.intern(source.getDbColumnDefn()); - this.dbConstraintExpression = InternString.intern(source.getDbConstraintExpression()); - - this.inherited = source.isInherited(); - this.owningType = source.owningType; - this.local = owningType.equals(descriptor.getBeanType()); - - this.version = source.isVersion(); - this.embedded = source.isEmbedded(); - this.id = source.isId(); - this.generatedProperty = source.getGeneratedProperty(); - this.readMethod = source.getReadMethod(); - this.writeMethod = source.getWriteMethod(); - this.getter = source.getter; - this.setter = source.setter; - this.extraAttributeMap = source.extraAttributeMap; - this.defaultValue = source.getDefaultValue(); - this.dbType = source.getDbType(); - this.scalarType = source.scalarType; - this.lob = isLobType(dbType); - this.propertyType = source.getPropertyType(); - this.field = source.getField(); - - this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn); - this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn); - - this.jsonSerialize = source.jsonSerialize; - this.jsonDeserialize = source.jsonDeserialize; - } - - /** - * Initialise the property before returning to client code. Used to - * initialise variables that can't be done in construction due to recursive - * issues. - */ - public void initialise() { - // do nothing for normal BeanProperty - if (!isTransient && scalarType == null) { - String msg = "No ScalarType assigned to " + descriptor.getFullName() + "." + getName(); - throw new RuntimeException(msg); - } - } - - /** - * Return the order this property appears in the bean. - */ - public int getDeployOrder() { - return deployOrder; - } - - /** - * Set the order this property appears in the bean. - */ - public void setDeployOrder(int deployOrder) { - this.deployOrder = deployOrder; - } - - public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, - boolean propertyDeploy) { - throw new PersistenceException("Not valid on scalar bean property " + getFullBeanName()); - } - - /** - * Return the BeanDescriptor that owns this property. - */ - public BeanDescriptor getBeanDescriptor() { - return descriptor; - } - - /** - * Return true is this is a simple scalar property. - */ - public boolean isScalar() { - return true; - } - - /** - * Return true if this property is based on a formula. - */ - public boolean isFormula() { - return formula; - } - - /** - * Return true if this property maps to the inheritance discriminator column. - */ - public boolean isDiscriminator() { - return discriminator; - } - - /** - * Return true if the underlying type is mutable. - */ - public boolean isMutableScalarType() { - if (scalarType == null) { - return false; - } - return scalarType.isMutable(); - } - - public void copyProperty(EntityBean sourceBean, EntityBean destBean) { - Object value = getValue(sourceBean); - setValue(destBean, value); - } - - /** - * Return the encrypt key for the column matching this property. - */ - public EncryptKey getEncryptKey() { - return descriptor.getEncryptKey(this); - } - - public String getDecryptProperty() { - return dbEncryptFunction.getDecryptSql(this.getName()); - } - - public String getDecryptProperty(String propertyName) { - return dbEncryptFunction.getDecryptSql(propertyName); - } - - public String getDecryptSql() { - return dbEncryptFunction.getDecryptSql(this.getDbColumn()); - } - - public String getDecryptSql(String tableAlias) { - return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()); - } - - /** - * Add any extra joins required to support this property. Generally a no - * operation except for a OneToOne exported. - */ - public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { - if (formula && sqlFormulaJoin != null) { - ctx.appendFormulaJoin(sqlFormulaJoin, joinType); - - } else if (secondaryTableJoin != null) { - - String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix); - secondaryTableJoin.addJoin(joinType, relativePrefix, ctx); - } - } - - /** - * Returns null unless this property is using a secondary table. In that - * case this returns the logical property prefix. - */ - public String getSecondaryTableJoinPrefix() { - return secondaryTableJoinPrefix; - } - - public void appendSelect(DbSqlContext ctx, boolean subQuery) { - if (formula) { - ctx.appendFormulaSelect(sqlFormulaSelect); - - } else if (!isTransient) { - - if (secondaryTableJoin != null) { - String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix); - ctx.pushTableAlias(relativePrefix); - } - - if (dbEncrypted) { - String decryptSql = getDecryptSql(ctx.peekTableAlias()); - ctx.appendRawColumn(decryptSql); - ctx.addEncryptedProp(this); - - } else { - ctx.appendColumn(dbColumn); - } - - if (secondaryTableJoin != null) { - ctx.popTableAlias(); - } - } - } - - public boolean isAssignableFrom(Class type) { - return owningType.isAssignableFrom(type); - } - - public Object readSetOwning(DbReadContext ctx, EntityBean bean, Class type) throws SQLException { - - try { - Object value = scalarType.read(ctx.getDataReader()); - if (value == null || bean == null) { - // not setting the value... - } else { - if (owningType.equals(type)) { - setValue(bean, value); - } - } - return value; - } catch (Exception e) { - String msg = "Error readSet on " + descriptor + "." + name; - throw new PersistenceException(msg, e); - } - } - - public void loadIgnore(DbReadContext ctx) { - scalarType.loadIgnore(ctx.getDataReader()); - } - - public void load(SqlBeanLoad sqlBeanLoad) throws SQLException { - sqlBeanLoad.load(this); - } - - public void buildSelectExpressionChain(String prefix, List selectChain) { - if (prefix == null) { - selectChain.add(name); - } else { - selectChain.add(prefix + "." + name); - } - } - - public Object read(DbReadContext ctx) throws SQLException { - return scalarType.read(ctx.getDataReader()); - } - - public Object readSet(DbReadContext ctx, EntityBean bean, Class type) throws SQLException { - - try { - Object value = scalarType.read(ctx.getDataReader()); - if (bean == null || (type != null && !owningType.isAssignableFrom(type))) { - // not setting the value... - } else { - setValue(bean, value); - } - return value; - } catch (Exception e) { - String msg = "Error readSet on " + descriptor + "." + name; - throw new PersistenceException(msg, e); - } - } - - /** - * Convert the type to the bean type if required. - *

- * Generally only used to ensure id properties are converted for - * Query.setId() use. - *

- */ - public Object toBeanType(Object value) { - return scalarType.toBeanType(value); - } - - @SuppressWarnings("unchecked") - public void bind(DataBind b, Object value) throws SQLException { - scalarType.bind(b, value); - } - - public void writeData(DataOutput dataOutput, Object value) throws IOException { - scalarType.writeData(dataOutput, value); - } - - public Object readData(DataInput dataInput) throws IOException { - return scalarType.readData(dataInput); - } - - public boolean isCascadeValidate() { - return cascadeValidate; - } - - /** - * Checks to see if a bean is a reference (will be lazy loaded) or a - * BeanCollection that has not yet been populated. - *

- * For base types this returns true. - *

- */ - public boolean isValueLoaded(Object value) { - return true; - } - - public BeanProperty getBeanProperty() { - return this; - } - - /** - * Return the getter method. - */ - public Method getReadMethod() { - return readMethod; - } - - /** - * Return the setter method. - */ - public Method getWriteMethod() { - return writeMethod; - } - - /** - * Return true if this object is part of an inheritance hierarchy. - */ - public boolean isInherited() { - return inherited; - } - - /** - * Return true is this type is not from a super type. - */ - public boolean isLocal() { - return local; - } - - /** - * Set the value of the property without interception or - * PropertyChangeSupport. - */ - public void setValue(EntityBean bean, Object value) { - try { - setter.set(bean, value); - } catch (Exception ex) { - String beanType = bean == null ? "null" : bean.getClass().getName(); - String msg = "set " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType - + "] threw error"; - throw new RuntimeException(msg, ex); - } - } - - /** - * Set the value of the property. - */ - public void setValueIntercept(EntityBean bean, Object value) { - try { - setter.setIntercept(bean, value); - } catch (Exception ex) { - String beanType = bean == null ? "null" : bean.getClass().getName(); - String msg = "setIntercept " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType - + "] threw error"; - throw new RuntimeException(msg, ex); - } - } - - private static Object[] NO_ARGS = new Object[0]; - - public Object getCacheDataValue(EntityBean bean) { - return getValue(bean); - } - - public void setCacheDataValue(EntityBean bean, Object cacheData) { - setValue(bean, cacheData); - } - - /** - * Return the value of the property method. - */ - public Object getValue(EntityBean bean) { - try { - return getter.get(bean); - } catch (Exception ex) { - String beanType = bean == null ? "null" : bean.getClass().getName(); - String msg = "get " + name + " on [" + descriptor + "] type[" + beanType + "] threw error."; - throw new RuntimeException(msg, ex); - } - } - - /** - * Explicitly use reflection to get value. - */ - public Object getValueViaReflection(Object bean) { - try { - return readMethod.invoke(bean, NO_ARGS); - } catch (Exception ex) { - String beanType = bean == null ? "null" : bean.getClass().getName(); - String msg = "get " + name + " on [" + descriptor + "] type[" + beanType + "] threw error."; - throw new RuntimeException(msg, ex); - } - } - - public Object getValueIntercept(EntityBean bean) { - try { - return getter.getIntercept(bean); - } catch (Exception ex) { - String beanType = bean == null ? "null" : bean.getClass().getName(); - String msg = "getIntercept " + name + " on [" + descriptor + "] type[" + beanType + "] threw error."; - throw new RuntimeException(msg, ex); - } - } - - public Object elConvertType(Object value) { - if (value == null) { - return null; - } - return convertToLogicalType(value); - } - - public void elSetValue(EntityBean bean, Object value, boolean populate) { - if (bean != null) { - // Not using setValueIntercept at this stage - setValue(bean, value); - } - } - - public Object elGetValue(EntityBean bean) { - if (bean == null) { - return null; - } - return getValueIntercept(bean); - } - - public Object elGetReference(EntityBean bean) { - throw new RuntimeException("Not expected to call this"); - } - - /** - * Return the name of the property. - */ - public String getName() { - return name; - } - - /** - * Return the position of this property in the enhanced bean. - */ - public int getPropertyIndex() { - return propertyIndex; - } - - public String getElName() { - return name; - } - - /** - * This is a full ElGetValue. - */ - public boolean isDeployOnly() { - return false; - } - - - @Override - public boolean containsFormulaWithJoin() { - return formula && sqlFormulaJoin != null; - } - - public boolean containsManySince(String sinceProperty) { - return containsMany(); - } - - public boolean containsMany() { - return false; - } - - public Object[] getAssocOneIdValues(EntityBean bean) { - // Returns null as not an AssocOne. - return null; - } - - public String getAssocOneIdExpr(String prefix, String operator) { - // Returns null as not an AssocOne. - return null; - } - - public String getAssocIdInExpr(String prefix) { - // Returns null as not an AssocOne. - return null; - } - - public String getAssocIdInValueExpr(int size) { - // Returns null as not an AssocOne. - return null; - } - - public boolean isAssocId() { - // Returns false - override in BeanPropertyAssocOne. - return false; - } - - public boolean isAssocProperty() { - // Returns false - override in BeanPropertyAssocOne. - return false; - } - - public String getElPlaceholder(boolean encrypted) { - return encrypted ? elPlaceHolderEncrypted : elPlaceHolder; - } - - public String getElPrefix() { - return secondaryTableJoinPrefix; - } - - /** - * Return the full name of this property. - */ - public String getFullBeanName() { - return descriptor.getFullName() + "." + name; - } - - /** - * Return true if the mutable value is considered dirty. - * This is only used for 'mutable' scalar types like hstore etc. - */ - public boolean isDirtyValue(Object value) { - return scalarType.isDirty(value); - } - - /** - * Return the scalarType. - */ - public ScalarType getScalarType() { - return scalarType; - } - - public StringFormatter getStringFormatter() { - return scalarType; - } - - public StringParser getStringParser() { - return scalarType; - } - - public boolean isDateTimeCapable() { - return scalarType != null && scalarType.isDateTimeCapable(); - } - - public int getJdbcType() { - return scalarType == null ? 0 : scalarType.getJdbcType(); - } - - public Object parseDateTime(long systemTimeMillis) { - return scalarType.parseDateTime(systemTimeMillis); - } - - /** - * Return the DB max length (varchar) or precision (decimal). - */ - public int getDbLength() { - return dbLength; - } - - /** - * Return the DB scale for numeric columns. - */ - public int getDbScale() { - return dbScale; - } - - /** - * Return a specific column DDL definition if specified (otherwise null). - */ - public String getDbColumnDefn() { - return dbColumnDefn; - } - - /** - * Return the DB constraint expression (can be null). - *

- * For an Enum returns IN expression for the set of Enum values. - *

- */ - public String getDbConstraintExpression() { - return dbConstraintExpression; - } - - /** - * Return the DB column type definition. - */ - public String renderDbType(DbType dbType) { - if (dbColumnDefn != null) { - return dbColumnDefn; - } - return dbType.renderType(dbLength, dbScale); - } - - /** - * Return the bean Field associated with this property. - */ - public Field getField() { - return field; - } - - /** - * Return the GeneratedValue. Used to generate update timestamp etc. - */ - public GeneratedProperty getGeneratedProperty() { - return generatedProperty; - } - - /** - * Return true if this is the natural key property. - */ - public boolean isNaturalKey() { - return naturalKey; - } - - /** - * Return true if this property is mandatory. - */ - public boolean isNullable() { - return nullable; - } - - /** - * Return true if DDL Not NULL constraint should be defined for this column - * based on it being a version column or having a generated property. - */ - public boolean isDDLNotNull() { - return isVersion() || (generatedProperty != null && generatedProperty.isDDLNotNullable()); - } - - /** - * Return true if the DB column should be unique. - */ - public boolean isUnique() { - return unique; - } - - /** - * Return true if the property is transient. - */ - public boolean isTransient() { - return isTransient; - } - - /** - * Return true if this property is loadable from a resultSet. - */ - public boolean isLoadProperty() { - return !isTransient || formula; - } - - /** - * Return true if this is a version column used for concurrency checking. - */ - public boolean isVersion() { - return version; - } - - public String getDeployProperty() { - return dbColumn; - } - - /** - * The database column name this is mapped to. - */ - public String getDbColumn() { - return dbColumn; - } - - /** - * Return the database jdbc data type this is mapped to. - */ - public int getDbType() { - return dbType; - } - - /** - * Perform DB to Logical type conversion (if necessary). - */ - public Object convertToLogicalType(Object value) { - if (scalarType != null) { - return scalarType.toBeanType(value); - } - return value; - } - - /** - * Return true if by default this property is set to fetch eager. - * Lob's usually default to fetch lazy. - */ - public boolean isFetchEager() { - return fetchEager; - } - - /** - * Return true if this is mapped to a Clob Blob LongVarchar or - * LongVarbinary. - */ - public boolean isLob() { - return lob; - } - - private boolean isLobType(int type) { - switch (type) { - case Types.CLOB: - return true; - case Types.BLOB: - return true; - case Types.LONGVARBINARY: - return true; - case Types.LONGVARCHAR: - return true; - - default: - return false; - } - } - - /** - * Return the DB bind parameter. Typically is "?" but different for - * encrypted bind. - */ - public String getDbBind() { - return dbBind; - } - - /** - * Returns true if DB encrypted. - */ - public boolean isLocalEncrypted() { - return localEncrypted; - } - - /** - * Return true if this property is stored encrypted. - */ - public boolean isDbEncrypted() { - return dbEncrypted; - } - - public int getDbEncryptedType() { - return dbEncryptedType; - } - - /** - * Return true if this property should be included in an Insert. - */ - public boolean isDbInsertable() { - return dbInsertable; - } - - /** - * Return true if this property should be included in an Update. - */ - public boolean isDbUpdatable() { - return dbUpdatable; - } - - /** - * Return true if this property is included in database queries. - */ - public boolean isDbRead() { - return dbRead; - } - - /** - * Return true if this property is based on a secondary table (not the base - * table). - */ - public boolean isSecondaryTable() { - return secondaryTable; - } - - /** - * Return the property type. - */ - public Class getPropertyType() { - return propertyType; - } - - /** - * Return true if this is included in the unique id. - */ - public boolean isId() { - return id; - } - - /** - * Return true if this is an Embedded property. In this case it shares the - * table and primary key of its owner object. - */ - public boolean isEmbedded() { - return embedded; - } - - /** - * Return an extra attribute set on this property. - */ - public String getExtraAttribute(String key) { - return extraAttributeMap.get(key); - } - - /** - * Return the default value. - */ - public Object getDefaultValue() { - return defaultValue; - } - - public String toString() { - return name; - } - -<<<<<<< HEAD - @SuppressWarnings("unchecked") - public void jsonWrite(WriteJsonContext ctx, EntityBean bean) { - if (!jsonSerialize) { - return; - } - Object value = getValueIntercept(bean); - if (value == null) { - ctx.appendNull(name); - } else { - ctx.appendNameValue(name, scalarType, value); - } -======= - public void jsonWrite(WriteJson writeJson, EntityBean bean) { - if (!jsonSerialize) { - return; - } - Object value = getValueIntercept(bean); - if (value == null) { - writeJson.gen().writeNull(name); - } else { - scalarType.jsonWrite(writeJson.gen(), name, value); ->>>>>>> json-refactor - } - } - -<<<<<<< HEAD - public void jsonRead(ReadJsonContext ctx, EntityBean bean) { - if (!jsonDeserialize) { - return; - } - String jsonValue; - try { - jsonValue = ctx.readScalarValue(); - } catch (TextException e) { - throw new TextException("Error reading property " + getFullBeanName(), e); - } - Object objValue; - if (jsonValue == null) { - objValue = null; - } else { - objValue = scalarType.jsonFromString(jsonValue, ctx.getValueAdapter()); - } - setValue(bean, objValue); - } - - public boolean isIndexed() { - return indexed; - } - - public String getIndexName() { - return indexName; - } -======= - public void jsonRead(JsonParser ctx, EntityBean bean) { - if (!jsonDeserialize) { - return; - } - if (!ctx.hasNext()) { - throw new RuntimeException(ctx.getLocation().toString()); - } - Event event = ctx.next(); - if (Event.VALUE_NULL == event) { - setValue(bean, null); - } else { - Object objValue = scalarType.jsonRead(ctx, event); - setValue(bean, objValue); - } - - } ->>>>>>> json-refactor -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index b23cd4779..5c234c63b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -873,7 +873,11 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { if (help != null) { help.jsonWrite(ctx, name, value, include != null); } else { - ctx.toJson(name, (Collection) value); + if (isTransient) { + ctx.writeValueUsingObjectMapper(name, value); + } else { + ctx.toJson(name, (Collection) value); + } } ctx.popParentBeanMany(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java index 873daa9b0..fbcde3bf2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java @@ -1,23 +1,42 @@ package com.avaje.ebeaninternal.server.deploy; -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.api.ClassUtil; 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; +import java.io.IOException; + +/** + * Help BeanPropertyAssocMany with JSON processing. + */ public class BeanPropertyAssocManyJsonHelp { + /** + * The associated many property. + */ private final BeanPropertyAssocMany many; + /** + * Helper used to read json for transient 'many' properties. + */ + private final BeanPropertyAssocManyJsonTransient jsonTransient; + + /** + * Construct for the owning many property. + */ public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany many) { this.many = many; + this.jsonTransient = !ClassUtil.isJacksonObjectMapperPresent() ? null : new BeanPropertyAssocManyJsonTransient(); } + /** + * Read the JSON for this property. + */ public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException { if (!this.many.jsonDeserialize) { @@ -33,6 +52,11 @@ public class BeanPropertyAssocManyJsonHelp { throw new JsonParseException("Unexpected token " + event + " - expecting start_array ", parser.getCurrentLocation()); } + if (many.isTransient()) { + jsonReadTransientUsingObjectMapper(readJson, parentBean); + return; + } + BeanCollection collection = many.createEmpty(parentBean); BeanCollectionAdd add = many.getBeanCollectionAdd(collection, null); do { @@ -51,4 +75,15 @@ public class BeanPropertyAssocManyJsonHelp { many.setValue(parentBean, collection); } + + /** + * Read a Transient property using Jackson ObjectMapper. + */ + private void jsonReadTransientUsingObjectMapper(ReadJson readJson, EntityBean parentBean) throws IOException { + + if (jsonTransient == null) { + throw new IllegalStateException("Jackson ObjectMapper is required to read this Transient property "+many.getFullBeanName()); + } + jsonTransient.jsonReadUsingObjectMapper(many, readJson, parentBean); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java new file mode 100644 index 000000000..529ed602e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java @@ -0,0 +1,44 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.text.json.ReadJson; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; +import com.fasterxml.jackson.databind.type.TypeFactory; + +import java.io.IOException; +import java.util.LinkedHashMap; + +/** + * Helper used to read transient many properties using Jackson ObjectMapper. + */ +public class BeanPropertyAssocManyJsonTransient { + + /** + * Use Jackson ObjectMapper to read the transient 'many' property. + */ + public void jsonReadUsingObjectMapper(BeanPropertyAssocMany many, ReadJson readJson, EntityBean parentBean) throws IOException { + + ObjectMapper mapper = readJson.getObjectMapper(); + + ManyType manyType = many.getManyType(); + + Object value; + if (manyType.isMap()) { + // read map using Jackson object mapper with unknown key type + TypeFactory typeFactory = mapper.getTypeFactory(); + JavaType target = typeFactory.constructType(many.getTargetType()); + MapType jacksonType = typeFactory.constructMapType(LinkedHashMap.class, typeFactory.unknownType(), target); + value = mapper.readValue(readJson.getParser(), jacksonType); + + } else { + // read list or set using Jackson object mapper + CollectionType jacksonType = mapper.getTypeFactory().constructCollectionType(manyType.getCollectionType(), many.getTargetType()); + value = mapper.readValue(readJson.getParser(), jacksonType); + } + + many.setValue(parentBean, value); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java index 21801efd8..9637aca2c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java @@ -2,58 +2,76 @@ package com.avaje.ebeaninternal.server.deploy; import com.avaje.ebeaninternal.api.SpiQuery; +import java.util.Collection; +import java.util.List; +import java.util.Set; + /** * Represents the type of a OneToMany or ManyToMany property. */ public class ManyType { - public static final ManyType JAVA_LIST = new ManyType(Underlying.LIST); - public static final ManyType JAVA_SET = new ManyType(Underlying.SET); - public static final ManyType JAVA_MAP = new ManyType(Underlying.MAP); - - public enum Underlying { - LIST, - SET, - MAP - } - - private final SpiQuery.Type queryType; - - private final Underlying underlying; - + public static final ManyType JAVA_LIST = new ManyType(Underlying.LIST); + public static final ManyType JAVA_SET = new ManyType(Underlying.SET); + public static final ManyType JAVA_MAP = new ManyType(Underlying.MAP); - public ManyType(Underlying underlying) { - this.underlying = underlying; - switch (underlying) { - case LIST: - queryType = SpiQuery.Type.LIST; - break; - case SET: - queryType = SpiQuery.Type.SET; - break; + public enum Underlying { - default: - queryType = SpiQuery.Type.MAP; - break; - } - } + LIST(List.class), + SET(Set.class), + MAP(null); - public boolean isMap() { - return Underlying.MAP.equals(underlying); + Class type; + + Underlying(Class type) { + this.type = type; } - - /** - * Return the matching Query type. - */ - public SpiQuery.Type getQueryType() { - return queryType; + } + + private final SpiQuery.Type queryType; + + private final Underlying underlying; + + + public ManyType(Underlying underlying) { + this.underlying = underlying; + switch (underlying) { + case LIST: + queryType = SpiQuery.Type.LIST; + break; + case SET: + queryType = SpiQuery.Type.SET; + break; + + default: + queryType = SpiQuery.Type.MAP; + break; } - - /** - * Return the underlying type. - */ - public Underlying getUnderlying() { - return underlying; - } - + } + + public boolean isMap() { + return Underlying.MAP.equals(underlying); + } + + /** + * Return the matching Query type. + */ + public SpiQuery.Type getQueryType() { + return queryType; + } + + /** + * Return the underlying type. + */ + public Underlying getUnderlying() { + return underlying; + } + + /** + * Returns List.class or Set.class and null for Map. + * Not intended to be called for maps. + */ + public Class getCollectionType() { + return underlying.type; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index 631ab7c8e..428df1074 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -101,8 +101,8 @@ public class DeployBeanProperty { private boolean localEncrypted; - private boolean exposeSerialize = true; - private boolean exposeDeserialize = true; + private boolean jsonSerialize = true; + private boolean jsonDeserialize = true; private boolean dbEncrypted; private DbEncryptFunction dbEncryptFunction; @@ -292,20 +292,20 @@ public class DeployBeanProperty { return dbLength; } - public boolean isExposeSerialize() { - return exposeSerialize; + public boolean isJsonSerialize() { + return jsonSerialize; } - public void setExposeSerialize(boolean exposeSerialize) { - this.exposeSerialize = exposeSerialize; + public void setJsonSerialize(boolean jsonSerialize) { + this.jsonSerialize = jsonSerialize; } - public boolean isExposeDeserialize() { - return exposeDeserialize; + public boolean isJsonDeserialize() { + return jsonDeserialize; } - public void setExposeDeserialize(boolean exposeDeserialize) { - this.exposeDeserialize = exposeDeserialize; + public void setJsonDeserialize(boolean jsonDeserialize) { + this.jsonDeserialize = jsonDeserialize; } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index 5f9ea7ad0..77d308d24 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -1,11 +1,23 @@ package com.avaje.ebeaninternal.server.deploy.parse; -import com.avaje.ebean.annotation.*; +import com.avaje.ebean.annotation.ColumnHstore; +import com.avaje.ebean.annotation.CreatedTimestamp; +import com.avaje.ebean.annotation.DbHstore; +import com.avaje.ebean.annotation.DbJson; +import com.avaje.ebean.annotation.DbJsonB; +import com.avaje.ebean.annotation.EmbeddedColumns; +import com.avaje.ebean.annotation.Encrypted; +import com.avaje.ebean.annotation.Expose; +import com.avaje.ebean.annotation.Formula; +import com.avaje.ebean.annotation.Index; +import com.avaje.ebean.annotation.JsonIgnore; +import com.avaje.ebean.annotation.UpdatedTimestamp; import com.avaje.ebean.config.EncryptDeploy; import com.avaje.ebean.config.EncryptDeploy.Mode; import com.avaje.ebean.config.dbplatform.DbEncrypt; import com.avaje.ebean.config.dbplatform.DbEncryptFunction; import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebeaninternal.api.ClassUtil; import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; @@ -13,7 +25,12 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; import com.avaje.ebeaninternal.server.idgen.UuidIdGenerator; import com.avaje.ebeaninternal.server.lib.util.StringHelper; -import com.avaje.ebeaninternal.server.type.*; +import com.avaje.ebeaninternal.server.type.CtCompoundType; +import com.avaje.ebeaninternal.server.type.DataEncryptSupport; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.ScalarTypeBytesBase; +import com.avaje.ebeaninternal.server.type.ScalarTypeBytesEncrypted; +import com.avaje.ebeaninternal.server.type.ScalarTypeEncryptedWrapper; import javax.persistence.*; import javax.validation.constraints.NotNull; @@ -27,6 +44,11 @@ import java.util.UUID; */ public class AnnotationFields extends AnnotationParser { + /** + * If present read Jackson JsonIgnore. + */ + private boolean jacksonAnnotationsPresent = ClassUtil.isJacksonAnnotationsPresent(); + /** * By default we lazy load Lob properties. */ @@ -101,10 +123,25 @@ public class AnnotationFields extends AnnotationParser { if (column != null) { readColumn(column, prop); } + + if (jacksonAnnotationsPresent) { + com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class); + if (jsonIgnore != null) { + prop.setJsonSerialize(!jsonIgnore.value()); + prop.setJsonDeserialize(!jsonIgnore.value()); + } + } + Expose expose = get(prop, Expose.class); if (expose != null) { - prop.setExposeSerialize(expose.serialize()); - prop.setExposeDeserialize(expose.deserialize()); + prop.setJsonSerialize(expose.serialize()); + prop.setJsonDeserialize(expose.deserialize()); + } + + JsonIgnore jsonIgnore = get(prop, JsonIgnore.class); + if (jsonIgnore != null) { + prop.setJsonSerialize(jsonIgnore.serialize()); + prop.setJsonDeserialize(jsonIgnore.deserialize()); } if (prop.getDbColumn() == null) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java index cd907fe9f..815ba21f8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java @@ -259,7 +259,8 @@ public class DeployCreateProperties { } if (isTransientField(field)) { - return null; + // return with no ScalarType (still support JSON features) + return new DeployBeanProperty(desc, propertyType, null, null); } try { CheckImmutableResponse checkImmutable = typeManager.checkImmutable(propertyType); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java index d300218af..506563de1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -173,19 +173,27 @@ public class DeployUtil { // Note that Temporal types already have dbType // set via annotations Class propType = property.getPropertyType(); - ScalarType scalarType = typeManager.getScalarType(propType, property.getDbType()); - if (scalarType != null) { - return scalarType; - } + try { + ScalarType scalarType = typeManager.getScalarType(propType, property.getDbType()); + if (scalarType != null) { + return scalarType; + } - String msg = property.getFullBeanName() + " has no ScalarType - type[" + propType.getName() + "]"; - if (!property.isTransient()) { - throw new PersistenceException(msg); + String msg = property.getFullBeanName() + " has no ScalarType - type[" + propType.getName() + "]"; + if (!property.isTransient()) { + throw new PersistenceException(msg); - } else { - // this is ok... - logger.trace("... transient property " + msg); - return null; + } else { + // this is ok... + logger.trace("... transient property " + msg); + return null; + } + } catch (IllegalArgumentException e) { + if (property.isTransient()) { + // expected for transient properties with unknown/non-mapped types + return null; + } + throw e; } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java index bd00dbfde..175907584 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java @@ -24,9 +24,12 @@ public class DJsonContext implements JsonContext { private final JsonFactory jsonFactory; + private final Object defaultObjectMapper; + public DJsonContext(SpiEbeanServer server, JsonFactory jsonFactory) { this.server = server; this.jsonFactory = (jsonFactory != null) ? jsonFactory : new JsonFactory(); + this.defaultObjectMapper = this.server.getServerConfig().getObjectMapper(); } public boolean isSupportedType(Type genericType) { @@ -72,7 +75,7 @@ public class DJsonContext implements JsonContext { public T toBean(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException { - ReadJson readJson = new ReadJson(parser, options); + ReadJson readJson = new ReadJson(parser, options, determineObjectMapper(options)); try { BeanDescriptor d = getDescriptor(cls); return d.jsonRead(readJson, null); @@ -104,7 +107,7 @@ public class DJsonContext implements JsonContext { public List toList(Class cls, JsonParser src, JsonReadOptions options) throws JsonIOException { - ReadJson readJson = new ReadJson(src, options); + ReadJson readJson = new ReadJson(src, options, determineObjectMapper(options)); try { BeanDescriptor d = getDescriptor(cls); @@ -271,7 +274,7 @@ public class DJsonContext implements JsonContext { private WriteJson createWriteJson(JsonGenerator gen, JsonWriteOptions options) { PathProperties pathProps = (options == null) ? null : options.getPathProperties(); - return new WriteJson(server, gen, pathProps); + return new WriteJson(server, gen, pathProps, determineObjectMapper(options)); } private void toJsonFromCollection(Collection collection, String key, JsonGenerator gen, JsonWriteOptions options) throws IOException { @@ -320,11 +323,36 @@ public class DJsonContext implements JsonContext { gen.writeEndObject(); } - private BeanDescriptor getDescriptor(Class cls) { - BeanDescriptor d = server.getBeanDescriptor(cls); + /** + * Return the BeanDescriptor for the given bean type. + */ + private BeanDescriptor getDescriptor(Class beanType) { + BeanDescriptor d = server.getBeanDescriptor(beanType); if (d == null) { - throw new RuntimeException("No BeanDescriptor found for " + cls); + throw new RuntimeException("No BeanDescriptor found for " + beanType); } return d; } + + /** + * Determine the object mapper to use for a JSON read request. + */ + private Object determineObjectMapper(JsonReadOptions options) { + if (options == null) { + return defaultObjectMapper; + } + Object mapper = options.getObjectMapper(); + return (mapper != null) ? mapper : defaultObjectMapper; + } + + /** + * Determine the object mapper to use for a JSON write request. + */ + private Object determineObjectMapper(JsonWriteOptions options) { + if (options == null) { + return defaultObjectMapper; + } + Object mapper = options.getObjectMapper(); + return (mapper != null) ? mapper : defaultObjectMapper; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java index f21e4701b..c66f209fe 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java @@ -4,6 +4,7 @@ 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 com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.Map; @@ -28,17 +29,33 @@ public class ReadJson { */ final Map> visitorMap; + final Object objectMapper; + /** * Construct with parser and readOptions. */ - public ReadJson(JsonParser parser, JsonReadOptions readOptions) { + public ReadJson(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) { + this.parser = parser; + this.objectMapper = objectMapper; // only create visitorMap, pathStack if needed ... this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap(); this.pathStack = (visitorMap == null) ? null : new PathStack(); } + /** + * Return the objectMapper used for this request. + */ + public ObjectMapper getObjectMapper() { + if (objectMapper == null) { + throw new IllegalStateException( + "Jackson ObjectMapper required but has not set. The ObjectMapper can be set on" + +" either the ServerConfig or on JsonReadOptions."); + } + return (ObjectMapper)objectMapper; + } + /** * Return the JsonParser. */ @@ -84,4 +101,13 @@ public class ReadJson { } } } + + /** + * Read the property value using Jackson ObjectMapper. + *

+ * Typically this is used to read Transient properties where the type is unknown to Ebean. + */ + public Object readValueUsingObjectMapper(Class propertyType) throws IOException { + return getObjectMapper().readValue(parser, propertyType); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java index c4beef5ea..cb6452c3f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java @@ -7,6 +7,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.util.ArrayStack; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.Collection; @@ -24,10 +25,13 @@ public class WriteJson { private final ArrayStack parentBeans = new ArrayStack(); - public WriteJson(SpiEbeanServer server, JsonGenerator generator, PathProperties pathProperties){ + private final Object objectMapper; + + public WriteJson(SpiEbeanServer server, JsonGenerator generator, PathProperties pathProperties, Object objectMapper){ this.server = server; this.generator = generator; this.pathProperties = pathProperties; + this.objectMapper = objectMapper; } public JsonGenerator gen() { @@ -73,6 +77,20 @@ public class WriteJson { return new WriteBean(desc, explicitAllProps, currentIncludeProps, bean); } + public void writeValueUsingObjectMapper(String name, Object value) throws IOException { + generator.writeFieldName(name); + objectMapper().writeValue(generator, value); + } + + private ObjectMapper objectMapper() { + if (objectMapper == null) { + throw new IllegalStateException( + "Jackson ObjectMapper required but not set. Expected to be set on either" + +" serverConfig"); + } + return (ObjectMapper)objectMapper; + } + public static class WriteBean { final boolean explicitAllProps; diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java index 98c17d00c..c3d02d74e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -159,7 +159,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { this.typeMap = new ConcurrentHashMap, ScalarType>(); this.nativeMap = new ConcurrentHashMap>(); - this.objectMapperPresent = ClassUtil.isPresent("com.fasterxml.jackson.databind.ObjectMapper", this.getClass()); + this.objectMapperPresent = ClassUtil.isJacksonObjectMapperPresent(); this.extraTypeFactory = new DefaultTypeFactory(config); @@ -384,7 +384,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { return (ScalarType) extraTypeFactory.createCalendar(jsonDateTime, jdbcType); } - throw new RuntimeException("Unmatched ScalarType for " + type + " jdbcType:" + jdbcType); + throw new IllegalArgumentException("Unmatched ScalarType for " + type + " jdbcType:" + jdbcType); } /** diff --git a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java index 318078302..e890c2251 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java @@ -19,7 +19,7 @@ public class WriteJsonTest { JsonGenerator generator = jsonFactory.createGenerator(new StringWriter()); PathProperties pathProperties = PathProperties.parse("id,status,name,customer(id,name,address(street,city)),orders(qty,product(sku,prodName))"); - WriteJson writeJson = new WriteJson(null, generator, pathProperties); + WriteJson writeJson = new WriteJson(null, generator, pathProperties, null); WriteJson.WriteBean rootLevel = writeJson.createWriteBean(null, null); assertTrue(rootLevel.currentIncludeProps.contains("id")); diff --git a/src/test/java/com/avaje/tests/json/transientproperties/EJsonJacksonIgnore.java b/src/test/java/com/avaje/tests/json/transientproperties/EJsonJacksonIgnore.java new file mode 100644 index 000000000..888e8d491 --- /dev/null +++ b/src/test/java/com/avaje/tests/json/transientproperties/EJsonJacksonIgnore.java @@ -0,0 +1,44 @@ +package com.avaje.tests.json.transientproperties; + +import com.avaje.ebean.annotation.Sql; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Sql +@Entity +public class EJsonJacksonIgnore { + + @Id + private Long id; + + private String name; + + @JsonIgnore + private Boolean basic; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getBasic() { + return basic; + } + + public void setBasic(Boolean basic) { + this.basic = basic; + } +} diff --git a/src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientList.java b/src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientList.java new file mode 100644 index 000000000..f2d9645fe --- /dev/null +++ b/src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientList.java @@ -0,0 +1,57 @@ +package com.avaje.tests.json.transientproperties; + +import com.avaje.ebean.annotation.Sql; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Transient; +import java.util.List; + +@Sql +@Entity +public class EJsonTransientList { + + @Id + private Long id; + + private String name; + + @Transient + private Boolean basic; + + @Transient + private List fileNames; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getBasic() { + return basic; + } + + public void setBasic(Boolean basic) { + this.basic = basic; + } + + public List getFileNames() { + return fileNames; + } + + public void setFileNames(List fileNames) { + this.fileNames = fileNames; + } + +} diff --git a/src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientObject.java b/src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientObject.java new file mode 100644 index 000000000..8065a2dc8 --- /dev/null +++ b/src/test/java/com/avaje/tests/json/transientproperties/EJsonTransientObject.java @@ -0,0 +1,63 @@ +package com.avaje.tests.json.transientproperties; + +import com.avaje.ebean.annotation.Sql; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Transient; + +@Sql +@Entity +public class EJsonTransientObject { + + @Id + private Long id; + + private String name; + + @Transient + private Boolean basic; + + @Transient + private SomeBean someBean; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getBasic() { + return basic; + } + + public void setBasic(Boolean basic) { + this.basic = basic; + } + + public SomeBean getSomeBean() { + return someBean; + } + + public void setSomeBean(SomeBean someBean) { + this.someBean = someBean; + } + + public static class SomeBean { + + public String name; + + public String baz; + + } +} diff --git a/src/test/java/com/avaje/tests/json/transientproperties/TestJacksonIgnore.java b/src/test/java/com/avaje/tests/json/transientproperties/TestJacksonIgnore.java new file mode 100644 index 000000000..321213be3 --- /dev/null +++ b/src/test/java/com/avaje/tests/json/transientproperties/TestJacksonIgnore.java @@ -0,0 +1,42 @@ +package com.avaje.tests.json.transientproperties; + +import com.avaje.ebean.Ebean; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +public class TestJacksonIgnore { + + @Test + public void testJacksonJsonIgnoreAnnotation() throws Exception { + + EJsonJacksonIgnore bean = new EJsonJacksonIgnore(); + bean.setId(99L); + bean.setName("foo"); + bean.setBasic(Boolean.TRUE); + + String asJson = Ebean.json().toJson(bean); + + // ignored on write + assertFalse(asJson.contains("basic")); + + EJsonJacksonIgnore bean1 = Ebean.json().toBean(EJsonJacksonIgnore.class, asJson); + + assertNull(bean1.getBasic()); + assertEquals(bean.getId(), bean1.getId()); + assertEquals(bean.getName(), bean1.getName()); + + // ignored on read + String jsonFull = "{\"id\":99,\"name\":\"foo\",\"basic\":true}"; + + bean1 = Ebean.json().toBean(EJsonJacksonIgnore.class, jsonFull); + + assertNull(bean1.getBasic()); + assertEquals(bean.getId(), bean1.getId()); + assertEquals(bean.getName(), bean1.getName()); + + } + +} diff --git a/src/test/java/com/avaje/tests/json/transientproperties/TestTransientListProperty.java b/src/test/java/com/avaje/tests/json/transientproperties/TestTransientListProperty.java new file mode 100644 index 000000000..87240ce96 --- /dev/null +++ b/src/test/java/com/avaje/tests/json/transientproperties/TestTransientListProperty.java @@ -0,0 +1,25 @@ +package com.avaje.tests.json.transientproperties; + +import com.avaje.ebean.Ebean; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TestTransientListProperty { + + @Test + public void testWithTransientListProperty() throws Exception { + + String rawJson = "{\"basic\":true,\"fileNames\":[\"1\"]}"; + + EJsonTransientList bean = Ebean.json().toBean(EJsonTransientList.class, rawJson); + + assertEquals(true, bean.getBasic()); + assertEquals(1, bean.getFileNames().size()); + + String asJson = Ebean.json().toJson(bean); + + assertEquals(rawJson, asJson); + } + +} diff --git a/src/test/java/com/avaje/tests/json/transientproperties/TestTransientObjectProperty.java b/src/test/java/com/avaje/tests/json/transientproperties/TestTransientObjectProperty.java new file mode 100644 index 000000000..de82de942 --- /dev/null +++ b/src/test/java/com/avaje/tests/json/transientproperties/TestTransientObjectProperty.java @@ -0,0 +1,27 @@ +package com.avaje.tests.json.transientproperties; + +import com.avaje.ebean.Ebean; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TestTransientObjectProperty { + + @Test + public void testToBeanToJson() throws Exception { + + String rawJson = "{\"name\":\"entityBeanName\",\"basic\":true,\"someBean\":{\"name\":\"transientBeanName\",\"baz\":\"foo\"}}"; + + EJsonTransientObject bean = Ebean.json().toBean(EJsonTransientObject.class, rawJson); + + assertEquals("entityBeanName",bean.getName()); + assertEquals(true,bean.getBasic()); + assertEquals("transientBeanName",bean.getSomeBean().name); + assertEquals("foo",bean.getSomeBean().baz); + + String asJson = Ebean.json().toJson(bean); + + assertEquals(rawJson, asJson); + } + +} diff --git a/src/test/java/com/avaje/tests/model/basic/Customer.java b/src/test/java/com/avaje/tests/model/basic/Customer.java index 8a32b5f24..344df084d 100644 --- a/src/test/java/com/avaje/tests/model/basic/Customer.java +++ b/src/test/java/com/avaje/tests/model/basic/Customer.java @@ -1,9 +1,8 @@ package com.avaje.tests.model.basic; -import java.sql.Date; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.locks.ReentrantLock; +import com.avaje.ebean.annotation.EnumValue; +import com.avaje.ebean.annotation.JsonIgnore; +import com.avaje.ebean.annotation.Where; import javax.persistence.CascadeType; import javax.persistence.Entity; @@ -13,9 +12,10 @@ import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; - -import com.avaje.ebean.annotation.EnumValue; -import com.avaje.ebean.annotation.Where; +import java.sql.Date; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; /** * Customer entity bean. @@ -43,6 +43,9 @@ public class Customer extends BasicDomain { @Transient Boolean selected; + + @JsonIgnore + //@Expose(deserialize = false, serialize = false) @Transient ReentrantLock lock = new ReentrantLock(); diff --git a/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java b/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java index 252544727..f936d1578 100644 --- a/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java +++ b/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java @@ -29,7 +29,7 @@ public class TestJsonBeanDescriptorParse extends BaseTestCase { StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello rob\"}"); JsonParser parser = server.json().createParser(reader); - ReadJson readJson = new ReadJson(parser, null); + ReadJson readJson = new ReadJson(parser, null, null); Customer customer = descriptor.jsonRead(readJson, null);