#297 - ENH: Add support for mapping Map<String,Object> as JSON content to DB including Postgres JSON and JSON types.

This commit is contained in:
rbygrave
2015-05-28 00:02:57 +12:00
parent 4e1cda691f
commit f5a2cf8ff9
37 changed files with 1898 additions and 193 deletions
@@ -135,6 +135,18 @@ public class AnnotationFields extends AnnotationParser {
util.setLobType(prop);
}
DbJson dbJson = get(prop, DbJson.class);
if (dbJson != null) {
util.setDbJsonType(prop, dbJson);
} else {
if (get(prop, DbJsonB.class) != null) {
util.setDbJsonBType(prop);
}
}
if (get(prop, ColumnHstore.class) != null) {
util.setDbHstore(prop);
}
Formula formula = get(prop, Formula.class);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
@@ -1,33 +1,22 @@
package com.avaje.ebeaninternal.server.deploy.parse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import com.avaje.ebean.annotation.ColumnHstore;
import com.avaje.ebean.annotation.DbJson;
import com.avaje.ebean.annotation.DbJsonB;
import com.avaje.ebeaninternal.server.deploy.DetermineManyType;
import com.avaje.ebeaninternal.server.deploy.ManyType;
import com.avaje.ebeaninternal.server.deploy.meta.*;
import com.avaje.ebeaninternal.server.type.CtCompoundType;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.TypeManager;
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.ManyToOne;
import javax.persistence.PersistenceException;
import javax.persistence.Transient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.annotation.ColumnHstore;
import com.avaje.ebeaninternal.server.core.Message;
import com.avaje.ebeaninternal.server.deploy.DetermineManyType;
import com.avaje.ebeaninternal.server.deploy.ManyType;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
import com.avaje.ebeaninternal.server.type.CtCompoundType;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypePostgresHstore;
import com.avaje.ebeaninternal.server.type.TypeManager;
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
import java.lang.reflect.*;
/**
* Create the properties for a bean.
@@ -95,15 +84,13 @@ public class DeployCreateProperties {
Field field = fields[i];
if (Modifier.isStatic(field.getModifiers())) {
// not interested in static fields
logger.trace("Skipping static field {} in {}", field.getName(), beanType.getName());
} else if (Modifier.isTransient(field.getModifiers())) {
// not interested in transient fields
logger.trace("Skipping transient field " + field.getName() + " in " + beanType.getName());
logger.trace("Skipping transient field {} in {}", field.getName(), beanType.getName());
} else if (ignoreFieldByName(field.getName())) {
// not interested this field (ebean or aspectJ field)
} else {
} else if (!ignoreFieldByName(field.getName())) {
String fieldName = getFieldName(field, beanType);
String initFieldName = initCap(fieldName);
@@ -118,14 +105,10 @@ public class DeployCreateProperties {
prop.setSortOrder((level * 10000 + 100 - i + sortOverride));
DeployBeanProperty replaced = desc.addBeanProperty(prop);
if (replaced != null) {
if (replaced.isTransient()) {
// expected for inheritance...
} else {
String msg = "Huh??? property " + prop.getFullBeanName() + " being defined twice";
msg += " but replaced property was not transient? This is not expected?";
logger.warn(msg);
}
if (replaced != null && !replaced.isTransient()) {
String msg = "Huh??? property " + prop.getFullBeanName() + " being defined twice";
msg += " but replaced property was not transient? This is not expected?";
logger.warn(msg);
}
}
}
@@ -210,33 +193,6 @@ public class DeployCreateProperties {
return null;
}
/**
* Find a public non-static setter method that matches this field (according to bean-spec rules).
*/
private Method findSetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject) {
String methSetName = "set" + initFieldName;
String scalaSetName = field.getName() + "_$eq";
for (int i = 0; i < declaredMethods.length; i++) {
Method m = declaredMethods[i];
if ((scalaObject && m.getName().equals(scalaSetName)) || m.getName().equals(methSetName)) {
Class<?>[] params = m.getParameterTypes();
if (params.length == 1 && field.getType().equals(params[0])) {
if (void.class.equals(m.getReturnType())) {
int modifiers = m.getModifiers();
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
return m;
}
}
}
}
}
return null;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private DeployBeanProperty createManyType(DeployBeanDescriptor<?> desc, Class<?> targetType, ManyType manyType) {
@@ -266,16 +222,8 @@ public class DeployCreateProperties {
propertyType = tt;
}
}
Class<?> innerType = propertyType;
String specialTypeKey = getSpecialScalarType(field);
if (specialTypeKey != null) {
ScalarType<?> scalarType = typeManager.getScalarTypeFromKey(specialTypeKey);
if (scalarType == null) {
logger.error("Could not find ScalarType to match key ["+specialTypeKey+"]");
} else {
return new DeployBeanProperty(desc, propertyType, scalarType, null);
}
if (isMappedType(field)) {
return new DeployBeanProperty(desc, propertyType, null, null);
}
// check for Collection type (list, set or map)
@@ -295,16 +243,16 @@ public class DeployCreateProperties {
return createManyType(desc, targetType, manyType);
}
if (innerType.isEnum() || innerType.isPrimitive()) {
if (propertyType.isEnum() || propertyType.isPrimitive()) {
return new DeployBeanProperty(desc, propertyType, null, null);
}
ScalarType<?> scalarType = typeManager.getScalarType(innerType);
ScalarType<?> scalarType = typeManager.getScalarType(propertyType);
if (scalarType != null) {
return new DeployBeanProperty(desc, propertyType, scalarType, null);
}
CtCompoundType<?> compoundType = typeManager.getCompoundType(innerType);
CtCompoundType<?> compoundType = typeManager.getCompoundType(propertyType);
if (compoundType != null) {
return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null);
}
@@ -313,19 +261,19 @@ public class DeployCreateProperties {
return null;
}
try {
CheckImmutableResponse checkImmutable = typeManager.checkImmutable(innerType);
CheckImmutableResponse checkImmutable = typeManager.checkImmutable(propertyType);
if (checkImmutable.isImmutable()) {
if (checkImmutable.isCompoundType()) {
// use reflection to support compound immutable value objects
typeManager.recursiveCreateScalarDataReader(innerType);
compoundType = typeManager.getCompoundType(innerType);
typeManager.recursiveCreateScalarDataReader(propertyType);
compoundType = typeManager.getCompoundType(propertyType);
if (compoundType != null) {
return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null);
}
} else {
// use reflection to support simple immutable value objects
scalarType = typeManager.recursiveCreateScalarTypes(innerType);
scalarType = typeManager.recursiveCreateScalarTypes(propertyType);
return new DeployBeanProperty(desc, propertyType, scalarType, null);
}
}
@@ -338,13 +286,13 @@ public class DeployCreateProperties {
}
}
private String getSpecialScalarType(Field field) {
if (field.getAnnotation(ColumnHstore.class) != null) {
return ScalarTypePostgresHstore.KEY;
}
return null;
/**
* Return true if the field has one of the special mappings.
*/
private boolean isMappedType(Field field) {
return (field.getAnnotation(DbJson.class) != null)
|| (field.getAnnotation(DbJsonB.class) != null)
|| (field.getAnnotation(ColumnHstore.class) != null);
}
private boolean isTransientField(Field field) {
@@ -5,6 +5,8 @@ import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.PersistenceException;
import com.avaje.ebean.annotation.DbJson;
import com.avaje.ebean.annotation.DbJsonType;
import com.avaje.ebean.config.EncryptDeploy;
import com.avaje.ebean.config.EncryptDeployManager;
import com.avaje.ebean.config.EncryptKeyManager;
@@ -13,6 +15,7 @@ import com.avaje.ebean.config.NamingConvention;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.TableName;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound;
import com.avaje.ebeaninternal.server.type.DataEncryptSupport;
@@ -41,6 +44,8 @@ public class DeployUtil {
*/
private static final int dbBLOBType = Types.BLOB;
private static final int DEFAULT_JSON_VARCHAR_LENGTH = 3000;
private final NamingConvention namingConvention;
private final TypeManager typeManager;
@@ -184,6 +189,72 @@ public class DeployUtil {
}
}
/**
* Map to Postgres HSTORE type.
*/
public void setDbHstore(DeployBeanProperty prop) {
ScalarType<?> scalarType = typeManager.getScalarType(DbType.HSTORE);
if (scalarType == null) {
// this should never occur actually
throw new RuntimeException("No ScalarType found for HSTORE on [" + prop.getFullBeanName() + "]");
}
prop.setDbType(DbType.HSTORE);
prop.setScalarType(scalarType);
}
/**
* This property is marked as a Lob object.
*/
public void setDbJsonType(DeployBeanProperty prop, DbJson dbJsonType) {
int dbType = getDbJsonStorage(dbJsonType.storage());
setDbJsonType(prop, dbType, dbJsonType.length());
}
public void setDbJsonBType(DeployBeanProperty prop) {
setDbJsonType(prop, DbType.JSONB, 0);
}
private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) {
Class<?> type = prop.getPropertyType();
ScalarType<?> scalarType = typeManager.getJsonScalarType(type, dbType);
if (scalarType == null) {
// this should never occur actually
throw new RuntimeException("No ScalarType for JSON type [" + type + "] [" + dbType + "]");
}
prop.setDbType(dbType);
prop.setScalarType(scalarType);
if (dbType == Types.VARCHAR) {
// determine the db column size
int columnLength = (dbLength > 0) ? dbLength : DEFAULT_JSON_VARCHAR_LENGTH;
prop.setDbLength(columnLength);
}
}
/**
* Return the JDBC type for the JSON storage type.
*/
private int getDbJsonStorage(DbJsonType dbJsonType) {
switch (dbJsonType) {
case JSON:
return DbType.JSON;
case JSONB:
return DbType.JSONB;
case VARCHAR:
return Types.VARCHAR;
case CLOB:
return Types.CLOB;
case BLOB:
return Types.BLOB;
default:
return DbType.JSON;
}
}
/**
* This property is marked as a Lob object.
*/