mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#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:
@@ -0,0 +1,44 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify a property holding JSON content.
|
||||
* <p>
|
||||
* By default the content will be stored in a DB Clob except on Postgres where DB JSON type is used.
|
||||
* </p>
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Store as JSON on Postgres or Clob on other databases
|
||||
* @DbJson
|
||||
* Map<String,Object> content;
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Example with JSONB storage</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Store as JSONB on Postgres or Clob on other databases
|
||||
* @DbJson(storage = DbJsonType.JSONB)
|
||||
* Map<String,Object> content;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DbJson {
|
||||
|
||||
/**
|
||||
* Specify the database type used to store the JSON content.
|
||||
*/
|
||||
DbJsonType storage() default DbJsonType.JSON;
|
||||
|
||||
/**
|
||||
* For VARCHAR storage specify the column length (defaults to 3000).
|
||||
*/
|
||||
int length() default 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify a property holding JSON content.
|
||||
* <p>
|
||||
* The content will be stored on Postgres using it's JSONB type and as Clob for other databases.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is equivalent to using <code>@DbJson(storage = DbJsonType.JSONB)</code>
|
||||
* </p>
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Store as JSONB on Postgres or Clob on other databases
|
||||
* @DbJsonB
|
||||
* Map<String,Object> content;
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Equivalent to:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Store as JSONB on Postgres or Clob on other databases
|
||||
* @DbJson(storage = DbJsonType.JSONB)
|
||||
* Map<String,Object> content;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DbJsonB {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
/**
|
||||
* Specify the DB storage type used to store JSON content.
|
||||
*/
|
||||
public enum DbJsonType {
|
||||
|
||||
/**
|
||||
* Store as JSON on Postgres and for other databases store as CLOB.
|
||||
*/
|
||||
JSON,
|
||||
|
||||
/**
|
||||
* Store as JSONB on Postgres and for other databases store as CLOB.
|
||||
*/
|
||||
JSONB,
|
||||
|
||||
/**
|
||||
* Store as database VARCHAR.
|
||||
*/
|
||||
VARCHAR,
|
||||
|
||||
/**
|
||||
* Store as database CLOB.
|
||||
*/
|
||||
CLOB,
|
||||
|
||||
/**
|
||||
* Store as database BLOB.
|
||||
*/
|
||||
BLOB
|
||||
}
|
||||
@@ -291,6 +291,7 @@ public class ServerConfig {
|
||||
private int queryCacheMaxSize = 1000;
|
||||
private int queryCacheMaxIdleTime = 600;
|
||||
private int queryCacheMaxTimeToLive = 60*60*6;
|
||||
private Object objectMapper;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
@@ -1864,4 +1865,12 @@ public class ServerConfig {
|
||||
|
||||
return databasePlatform.isDisallowBatchOnCascade() ? PersistBatch.NONE : persistBatchOnCascade;
|
||||
}
|
||||
|
||||
public Object getObjectMapper() {
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
public void setObjectMapper(Object objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,36 @@ package com.avaje.ebean.config.dbplatform;
|
||||
*/
|
||||
public class DbType {
|
||||
|
||||
/**
|
||||
* Type to map Map content to Postgres HSTORE.
|
||||
*/
|
||||
public static final int HSTORE = 5000;
|
||||
|
||||
/**
|
||||
* Type to map JSON content to Clob or Postgres JSON type.
|
||||
*/
|
||||
public static final int JSON = 5001;
|
||||
|
||||
/**
|
||||
* Type to map JSON content to Clob or Postgres JSONB type.
|
||||
*/
|
||||
public static final int JSONB = 5002;
|
||||
|
||||
/**
|
||||
* Type to map JSON content to VARCHAR.
|
||||
*/
|
||||
public static final int JSONVarchar = 5003;
|
||||
|
||||
/**
|
||||
* Type to map JSON content to Clob.
|
||||
*/
|
||||
public static final int JSONClob = 5004;
|
||||
|
||||
/**
|
||||
* Type to map JSON content to Blob.
|
||||
*/
|
||||
public static final int JSONBlob = 5005;
|
||||
|
||||
/**
|
||||
* The data type name (VARCHAR, INTEGER ...)
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,13 @@ public class DbTypeMap {
|
||||
|
||||
put(Types.BLOB, new DbType("blob"));
|
||||
put(Types.CLOB, new DbType("clob"));
|
||||
|
||||
put(DbType.JSON, new DbType("clob")); // Postgres maps this to JSON
|
||||
put(DbType.JSONB, new DbType("clob")); // Postgres maps this to JSONB
|
||||
put(DbType.JSONClob, new DbType("clob"));
|
||||
put(DbType.JSONBlob, new DbType("blob"));
|
||||
put(DbType.JSONVarchar, new DbType("varchar", 1000));
|
||||
|
||||
put(Types.LONGVARBINARY, new DbType("longvarbinary"));
|
||||
put(Types.LONGVARCHAR, new DbType("lonvarchar"));
|
||||
put(Types.VARBINARY, new DbType("varbinary", 255));
|
||||
|
||||
@@ -13,11 +13,6 @@ import java.sql.Types;
|
||||
*/
|
||||
public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Unique jdbc type id defined for hstore type.
|
||||
*/
|
||||
public static final int TYPE_HSTORE = 4001;
|
||||
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
@@ -43,8 +38,10 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
|
||||
|
||||
dbTypeMap.put(DbType.HSTORE, new DbType("hstore"));
|
||||
dbTypeMap.put(DbType.JSON, new DbType("json"));
|
||||
dbTypeMap.put(DbType.JSONB, new DbType("jsonb"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
|
||||
@@ -35,14 +35,29 @@ public class EJson {
|
||||
public static void write(Object object, JsonGenerator jsonGenerator) throws IOException {
|
||||
EJsonWriter.write(object, jsonGenerator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map additionally specifying if the returned map should
|
||||
* be modify aware meaning that it can detect when it has been modified.
|
||||
*/
|
||||
public static Map<String,Object> parseObject(String json, boolean modifyAware) throws IOException {
|
||||
return EJsonReader.parseObject(json, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map.
|
||||
*/
|
||||
public static Map<String,Object> parseObject(String json) throws IOException {
|
||||
return EJsonReader.parseObject(json);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
public static Map<String,Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
|
||||
return EJsonReader.parseObject(reader, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import com.avaje.ebeaninternal.server.type.ModifyAwareFlag;
|
||||
import com.avaje.ebeaninternal.server.type.ModifyAwareList;
|
||||
import com.avaje.ebeaninternal.server.type.ModifyAwareMap;
|
||||
import com.avaje.ebeaninternal.server.type.ModifyAwareOwner;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
@@ -16,7 +20,12 @@ import com.fasterxml.jackson.core.JsonToken;
|
||||
class EJsonReader {
|
||||
|
||||
static JsonFactory json = new JsonFactory();
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
|
||||
return (Map<String, Object>) parse(json, modifyAware);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(String json) throws IOException {
|
||||
return (Map<String, Object>) parse(json);
|
||||
@@ -26,7 +35,12 @@ class EJsonReader {
|
||||
static Map<String, Object> parseObject(Reader reader) throws IOException {
|
||||
return (Map<String, Object>) parse(reader);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
|
||||
return (Map<String, Object>) parse(reader, modifyAware);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(JsonParser parser) throws IOException {
|
||||
return (Map<String, Object>) parse(parser);
|
||||
@@ -34,7 +48,7 @@ class EJsonReader {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
|
||||
return (Map<String, Object>)parse(parser, token);
|
||||
return (Map<String, Object>)parse(parser, token, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -56,40 +70,58 @@ class EJsonReader {
|
||||
return parse(new StringReader(json));
|
||||
}
|
||||
|
||||
static Object parse(String json, boolean modifyAware) throws IOException {
|
||||
return parse(new StringReader(json), modifyAware);
|
||||
}
|
||||
|
||||
static Object parse(Reader reader) throws IOException {
|
||||
return parse(json.createParser(reader));
|
||||
}
|
||||
|
||||
static Object parse(JsonParser parser) throws IOException {
|
||||
return parse(parser, null);
|
||||
static Object parse(Reader reader, boolean modifyAware) throws IOException {
|
||||
return parse(json.createParser(reader), modifyAware);
|
||||
}
|
||||
|
||||
static Object parse(JsonParser parser, JsonToken token) throws IOException {
|
||||
return new EJsonReader(parser).parseJson(token);
|
||||
static Object parse(JsonParser parser) throws IOException {
|
||||
return parse(parser, null, false);
|
||||
}
|
||||
|
||||
static Object parse(JsonParser parser, boolean modifyAware) throws IOException {
|
||||
return parse(parser, null, modifyAware);
|
||||
}
|
||||
|
||||
static Object parse(JsonParser parser, JsonToken token, boolean modifyAware) throws IOException {
|
||||
return new EJsonReader(parser, modifyAware).parseJson(token);
|
||||
}
|
||||
|
||||
private final JsonParser parser;
|
||||
|
||||
private final boolean modifyAware;
|
||||
|
||||
private final ModifyAwareFlag modifyAwareOwner;
|
||||
|
||||
private int depth;
|
||||
|
||||
private Stack stack;
|
||||
|
||||
private Context currentContext;
|
||||
|
||||
EJsonReader(JsonParser parser) {
|
||||
EJsonReader(JsonParser parser, boolean modifyAware) {
|
||||
this.parser = parser;
|
||||
this.modifyAware = modifyAware;
|
||||
this.modifyAwareOwner = (modifyAware) ? new ModifyAwareFlag() : null;
|
||||
}
|
||||
|
||||
private void startArray() {
|
||||
depth++;
|
||||
stack.push(currentContext);
|
||||
currentContext = new ArrayContext();
|
||||
currentContext = modifyAware ? new ArrayContext(modifyAwareOwner) : new ArrayContext();
|
||||
}
|
||||
|
||||
private void startObject() {
|
||||
depth++;
|
||||
stack.push(currentContext);
|
||||
currentContext = new ObjectContext();
|
||||
currentContext = modifyAware ? new ObjectContext(modifyAwareOwner) : new ObjectContext();
|
||||
}
|
||||
|
||||
private void endArray() {
|
||||
@@ -105,6 +137,9 @@ class EJsonReader {
|
||||
if (!stack.isEmpty()) {
|
||||
currentContext = stack.pop(currentContext);
|
||||
}
|
||||
if (modifyAwareOwner != null) {
|
||||
modifyAwareOwner.resetMarkedDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private void setValue(Object value) {
|
||||
@@ -235,10 +270,18 @@ class EJsonReader {
|
||||
|
||||
private static class ObjectContext extends Context {
|
||||
|
||||
private final Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
private final Map<String, Object> map;
|
||||
|
||||
private String key;
|
||||
|
||||
ObjectContext() {
|
||||
map = new LinkedHashMap<String, Object>();
|
||||
}
|
||||
|
||||
ObjectContext(ModifyAwareOwner owner) {
|
||||
map = new ModifyAwareMap<String, Object>(owner, new LinkedHashMap<String, Object>());
|
||||
}
|
||||
|
||||
public void popContext(Context temp) {
|
||||
setValue(temp.getValue());
|
||||
}
|
||||
@@ -262,7 +305,15 @@ class EJsonReader {
|
||||
|
||||
private static class ArrayContext extends Context {
|
||||
|
||||
private final List<Object> values = new ArrayList<Object>();
|
||||
private final List<Object> values;
|
||||
|
||||
ArrayContext() {
|
||||
values = new ArrayList<Object>();
|
||||
}
|
||||
|
||||
ArrayContext(ModifyAwareOwner owner) {
|
||||
values = new ModifyAwareList<Object>(owner, new ArrayList<Object>());
|
||||
}
|
||||
|
||||
public void popContext(Context temp) {
|
||||
values.add(temp.getValue());
|
||||
|
||||
+2
-2
@@ -97,7 +97,7 @@ public class DeployBeanPropertyLists {
|
||||
discDeployProp.setDbColumn(discriminatorColumn);
|
||||
|
||||
// create the discriminator BeanProperty and only register it in the propertyMap
|
||||
BeanProperty dprop = new BeanProperty(owner, desc, discDeployProp);
|
||||
BeanProperty dprop = new BeanProperty(desc, discDeployProp);
|
||||
propertyMap.put(dprop.getName(), dprop);
|
||||
}
|
||||
|
||||
@@ -382,6 +382,6 @@ public class DeployBeanPropertyLists {
|
||||
return new BeanPropertyCompound(owner, desc, (DeployBeanPropertyCompound) deployProp);
|
||||
}
|
||||
|
||||
return new BeanProperty(owner, desc, deployProp);
|
||||
return new BeanProperty(desc, deployProp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
+36
-88
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Array;
|
||||
import java.sql.SQLException;
|
||||
@@ -19,10 +20,14 @@ public interface DataReader {
|
||||
|
||||
byte[] getBlobBytes() throws SQLException;
|
||||
|
||||
InputStream getBlobInputStream() throws SQLException;
|
||||
|
||||
String getStringFromStream() throws SQLException;
|
||||
|
||||
String getStringClob() throws SQLException;
|
||||
|
||||
Reader getClobReader() throws SQLException;
|
||||
|
||||
String getString() throws SQLException;
|
||||
|
||||
Boolean getBoolean() throws SQLException;
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.annotation.EnumMapping;
|
||||
import com.avaje.ebean.annotation.EnumValue;
|
||||
import com.avaje.ebean.config.*;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.joda.time.*;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.joda.time.LocalTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -13,42 +30,12 @@ import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Currency;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.Period;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.avaje.ebean.config.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.joda.time.DateMidnight;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.joda.time.LocalTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.annotation.EnumMapping;
|
||||
import com.avaje.ebean.annotation.EnumValue;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutable;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ImmutableMeta;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ImmutableMetaFactory;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.KnownImmutable;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundType;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundTypeProperty;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedTypeBuilder;
|
||||
|
||||
/**
|
||||
* Default implementation of TypeManager.
|
||||
* <p>
|
||||
@@ -65,10 +52,16 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
|
||||
private final ConcurrentHashMap<Integer, ScalarType<?>> nativeMap;
|
||||
|
||||
private final ConcurrentHashMap<String, ScalarType<?>> customTypeMap;
|
||||
|
||||
private final DefaultTypeFactory extraTypeFactory;
|
||||
|
||||
private final ScalarTypeJsonMap JSON_MAP_CLOB = new ScalarTypeJsonMap.Clob();
|
||||
|
||||
private final ScalarTypeJsonMap jsonMapClob = JSON_MAP_CLOB;
|
||||
private final ScalarTypeJsonMap jsonMapBlob = new ScalarTypeJsonMap.Blob();
|
||||
private final ScalarTypeJsonMap jsonMapVarchar = new ScalarTypeJsonMap.Varchar();
|
||||
private final ScalarTypeJsonMap jsonMapJson;
|
||||
private final ScalarTypeJsonMap jsonMapJsonb;
|
||||
|
||||
private final ScalarTypeFile fileType = new ScalarTypeFile();
|
||||
|
||||
private final ScalarType<?> charType = new ScalarTypeChar();
|
||||
@@ -134,9 +127,6 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
*/
|
||||
public DefaultTypeManager(ServerConfig config, BootupClasses bootupClasses) {
|
||||
|
||||
int clobType = config.getDatabasePlatform().getClobDbType();
|
||||
int blobType = config.getDatabasePlatform().getBlobDbType();
|
||||
|
||||
this.jsonDateTime = config.getJsonDateTime();
|
||||
this.checkImmutable = new CheckImmutable(this);
|
||||
this.reflectScalarBuilder = new ReflectionBasedTypeBuilder(this);
|
||||
@@ -144,18 +134,24 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
this.compoundTypeMap = new ConcurrentHashMap<Class<?>, CtCompoundType<?>>();
|
||||
this.typeMap = new ConcurrentHashMap<Class<?>, ScalarType<?>>();
|
||||
this.nativeMap = new ConcurrentHashMap<Integer, ScalarType<?>>();
|
||||
this.customTypeMap = new ConcurrentHashMap<String, ScalarType<?>>();
|
||||
|
||||
this.customTypeMap.put(ScalarTypePostgresHstore.KEY, new ScalarTypePostgresHstore());
|
||||
|
||||
this.objectMapperPresent = ClassUtil.isPresent("com.fasterxml.jackson.databind.ObjectMapper", this.getClass());
|
||||
|
||||
this.extraTypeFactory = new DefaultTypeFactory(config);
|
||||
|
||||
initialiseStandard(jsonDateTime, clobType, blobType, config.isUuidStoreAsBinary());
|
||||
initialiseStandard(jsonDateTime, config);
|
||||
initialiseJavaTimeTypes(jsonDateTime, config);
|
||||
initialiseJodaTypes(jsonDateTime);
|
||||
|
||||
if (isPostgres(config.getDatabasePlatform())) {
|
||||
// Postgres has special DB types for JSON/JSONB
|
||||
this.jsonMapJson = new ScalarTypeJsonMapPostgres.JSON();
|
||||
this.jsonMapJsonb = new ScalarTypeJsonMapPostgres.JSONB();
|
||||
} else {
|
||||
this.jsonMapJson = JSON_MAP_CLOB;
|
||||
this.jsonMapJsonb = JSON_MAP_CLOB;
|
||||
}
|
||||
|
||||
if (bootupClasses != null) {
|
||||
initialiseCustomScalarTypes(jsonDateTime, bootupClasses, config);
|
||||
initialiseScalarConverters(bootupClasses);
|
||||
@@ -163,12 +159,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a special or custom scalar type by key.
|
||||
*/
|
||||
@Override
|
||||
public ScalarType<?> getScalarTypeFromKey(String specialTypeKey) {
|
||||
return customTypeMap.get(specialTypeKey);
|
||||
private boolean isPostgres(DatabasePlatform databasePlatform) {
|
||||
return databasePlatform.getName().toLowerCase().startsWith("postgre");
|
||||
}
|
||||
|
||||
public boolean isKnownImmutable(Class<?> cls) {
|
||||
@@ -295,6 +287,24 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getJsonScalarType(Class<?> type, int dbType) {
|
||||
|
||||
if (type.equals(Map.class)) {
|
||||
switch (dbType) {
|
||||
case Types.VARCHAR : return jsonMapVarchar;
|
||||
case Types.BLOB: return jsonMapBlob;
|
||||
case Types.CLOB : return jsonMapClob;
|
||||
case DbType.JSONB: return jsonMapJsonb;
|
||||
case DbType.JSON: return jsonMapJson;
|
||||
default:
|
||||
return jsonMapJson;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ScalarType for a given class.
|
||||
* <p>
|
||||
@@ -370,8 +380,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
return value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private boolean isIntegerType(String s) {
|
||||
|
||||
try {
|
||||
Integer.parseInt(s);
|
||||
return true;
|
||||
@@ -685,7 +695,14 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
* Register all the standard types supported. This is the standard JDBC types
|
||||
* plus some other common types such as java.util.Date and java.util.Calendar.
|
||||
*/
|
||||
protected void initialiseStandard(JsonConfig.DateTime mode, int platformClobType, int platformBlobType, boolean binaryUUID) {
|
||||
protected void initialiseStandard(JsonConfig.DateTime mode, ServerConfig config) {
|
||||
|
||||
boolean binaryUUID = config.isUuidStoreAsBinary();
|
||||
DatabasePlatform databasePlatform = config.getDatabasePlatform();
|
||||
int platformClobType = databasePlatform.getClobDbType();
|
||||
int platformBlobType = databasePlatform.getBlobDbType();
|
||||
|
||||
nativeMap.put(DbType.HSTORE, new ScalarTypePostgresHstore());
|
||||
|
||||
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(mode);
|
||||
typeMap.put(java.util.Date.class, utilDateType);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
/**
|
||||
* Detects when content has been modified and as such needs to be persisted (included in an update).
|
||||
*/
|
||||
public class ModifyAwareFlag implements ModifyAwareOwner {
|
||||
|
||||
boolean dirty;
|
||||
|
||||
@Override
|
||||
public boolean isMarkedDirty() {
|
||||
if (!dirty) return false;
|
||||
dirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsModified() {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetMarkedDirty() {
|
||||
dirty = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Modify aware wrapper of a list.
|
||||
*/
|
||||
public class ModifyAwareList<E> implements List<E>, ModifyAwareOwner {
|
||||
|
||||
final List<E> list;
|
||||
|
||||
final ModifyAwareOwner owner;
|
||||
|
||||
public ModifyAwareList(List<E> list) {
|
||||
this.list = list;
|
||||
this.owner = new ModifyAwareFlag();
|
||||
}
|
||||
|
||||
public ModifyAwareList(ModifyAwareOwner owner, List<E> list) {
|
||||
this.list = list;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMarkedDirty() {
|
||||
return owner.isMarkedDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsModified() {
|
||||
owner.markAsModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetMarkedDirty() {
|
||||
owner.resetMarkedDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return list.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return new ModifyAwareIterator<E>(owner, list.iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
owner.markAsModified();
|
||||
return list.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
owner.markAsModified();
|
||||
return list.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return list.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
owner.markAsModified();
|
||||
return list.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends E> c) {
|
||||
owner.markAsModified();
|
||||
return list.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
owner.markAsModified();
|
||||
return list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
owner.markAsModified();
|
||||
return list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
owner.markAsModified();
|
||||
list.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(int index, E element) {
|
||||
owner.markAsModified();
|
||||
return list.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
owner.markAsModified();
|
||||
list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove(int index) {
|
||||
owner.markAsModified();
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<E> listIterator() {
|
||||
return new ModifyAwareListIterator<E>(owner, list.listIterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<E> listIterator(int index) {
|
||||
return new ModifyAwareListIterator<E>(owner, list.listIterator(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> subList(int fromIndex, int toIndex) {
|
||||
return new ModifyAwareList<E>(owner,list.subList(fromIndex, toIndex));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Modify aware wrapper of a ListIterator.
|
||||
*/
|
||||
public class ModifyAwareListIterator<E> implements ListIterator<E> {
|
||||
|
||||
final ModifyAwareOwner owner;
|
||||
|
||||
final ListIterator<E> iterator;
|
||||
|
||||
public ModifyAwareListIterator(ModifyAwareOwner owner, ListIterator<E> iterator) {
|
||||
this.owner = owner;
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
return iterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return iterator.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E previous() {
|
||||
return iterator.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return iterator.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return iterator.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
owner.markAsModified();
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(E e) {
|
||||
owner.markAsModified();
|
||||
iterator.set(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(E e) {
|
||||
owner.markAsModified();
|
||||
iterator.add(e);
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,21 @@ import java.util.Set;
|
||||
*/
|
||||
public class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareOwner {
|
||||
|
||||
/**
|
||||
* Dirty flag set when the map has been modified.
|
||||
*/
|
||||
private boolean dirty;
|
||||
final ModifyAwareOwner owner;
|
||||
|
||||
/**
|
||||
* The underlying map.
|
||||
*/
|
||||
private Map<K, V> map;
|
||||
final Map<K, V> map;
|
||||
|
||||
public ModifyAwareMap(Map<K, V> underyling) {
|
||||
this.map = underyling;
|
||||
this.owner = new ModifyAwareFlag();
|
||||
}
|
||||
|
||||
public ModifyAwareMap(ModifyAwareOwner owner, Map<K, V> underyling) {
|
||||
this.owner = owner;
|
||||
this.map = underyling;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -29,12 +32,17 @@ public class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareOwner {
|
||||
|
||||
@Override
|
||||
public boolean isMarkedDirty() {
|
||||
return dirty;
|
||||
return owner.isMarkedDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsModified() {
|
||||
dirty = true;
|
||||
owner.markAsModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetMarkedDirty() {
|
||||
owner.resetMarkedDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,7 @@ public interface ModifyAwareOwner {
|
||||
|
||||
/**
|
||||
* Return true if the value is considered dirty.
|
||||
* Note that this resets the dirty status back to clean.
|
||||
*/
|
||||
boolean isMarkedDirty();
|
||||
|
||||
@@ -14,4 +15,10 @@ public interface ModifyAwareOwner {
|
||||
* Marks the object as modified.
|
||||
*/
|
||||
void markAsModified();
|
||||
|
||||
/**
|
||||
* Reset the dirty state to clean.
|
||||
*/
|
||||
void resetMarkedDirty();
|
||||
|
||||
}
|
||||
|
||||
@@ -168,6 +168,15 @@ public class RsetDataReader implements DataReader {
|
||||
return readStringLob(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getClobReader() throws SQLException {
|
||||
Clob clob = rset.getClob(pos());
|
||||
if (clob == null) {
|
||||
return null;
|
||||
}
|
||||
return clob.getCharacterStream();
|
||||
}
|
||||
|
||||
public String getStringClob() throws SQLException {
|
||||
|
||||
Clob clob = rset.getClob(pos());
|
||||
@@ -212,6 +221,14 @@ public class RsetDataReader implements DataReader {
|
||||
return getBinaryLob(in);
|
||||
}
|
||||
|
||||
public InputStream getBlobInputStream() throws SQLException {
|
||||
Blob blob = rset.getBlob(pos());
|
||||
if (blob == null) {
|
||||
return null;
|
||||
}
|
||||
return blob.getBinaryStream();
|
||||
}
|
||||
|
||||
protected byte[] getBinaryLob(InputStream in) throws SQLException {
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.text.TextException;
|
||||
import com.avaje.ebean.text.json.EJson;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Type which maps Map<String,Object> to various DB types (Clob, Varchar, Blob) in JSON format.
|
||||
*/
|
||||
public abstract class ScalarTypeJsonMap extends ScalarTypeBase<Map> {
|
||||
|
||||
public static class Clob extends ScalarTypeJsonMap {
|
||||
|
||||
public Clob() {
|
||||
super(Types.CLOB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map read(DataReader dataReader) throws SQLException {
|
||||
|
||||
Reader reader = dataReader.getClobReader();
|
||||
if (reader == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Map map = parse(reader);
|
||||
reader.close();
|
||||
return map;
|
||||
} catch (IOException e) {
|
||||
throw new SQLException("Error reading Clob stream from DB", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Varchar extends ScalarTypeJsonMap {
|
||||
|
||||
public Varchar() {
|
||||
super(Types.VARCHAR);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Blob extends ScalarTypeJsonMap {
|
||||
public Blob() {
|
||||
super(Types.BLOB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map read(DataReader dataReader) throws SQLException {
|
||||
|
||||
InputStream is = dataReader.getBlobInputStream();
|
||||
if (is == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
InputStreamReader reader = new InputStreamReader(is);
|
||||
Map map = parse(reader);
|
||||
reader.close();
|
||||
return map;
|
||||
} catch (IOException e) {
|
||||
throw new SQLException("Error reading Blob stream from DB", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBind b, Map value) throws SQLException {
|
||||
|
||||
if (value == null) {
|
||||
b.setNull(Types.BLOB);
|
||||
} else {
|
||||
String rawJson = formatValue(value);
|
||||
InputStream stream = new ByteArrayInputStream(rawJson.getBytes(StandardCharsets.UTF_8));
|
||||
b.setBlob(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ScalarTypeJsonMap(int jdbcType) {
|
||||
super(Map.class, false, jdbcType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map is a mutable type. Use the isDirty() method to check for dirty state.
|
||||
*/
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the value should be considered dirty (and included in an update).
|
||||
*/
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
return !(value instanceof ModifyAwareOwner) || ((ModifyAwareOwner) value).isMarkedDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map read(DataReader dataReader) throws SQLException {
|
||||
|
||||
String rawJson = dataReader.getString();
|
||||
if (rawJson == null) {
|
||||
return null;
|
||||
}
|
||||
return parse(rawJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBind b, Map value) throws SQLException {
|
||||
|
||||
if (value == null) {
|
||||
b.setNull(Types.VARCHAR);
|
||||
} else {
|
||||
String rawJson = formatValue(value);
|
||||
b.setString(rawJson);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object toJdbcType(Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map toBeanType(Object value) {
|
||||
return (Map) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatValue(Map v) {
|
||||
try {
|
||||
return EJson.write(v);
|
||||
} catch (IOException e) {
|
||||
throw new TextException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map parse(String value) {
|
||||
try {
|
||||
// return a modify aware map
|
||||
return EJson.parseObject(value, true);
|
||||
} catch (IOException e) {
|
||||
throw new TextException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map parse(Reader reader) {
|
||||
try {
|
||||
// return a modify aware map
|
||||
return EJson.parseObject(reader, true);
|
||||
} catch (IOException e) {
|
||||
throw new TextException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map convertFromMillis(long dateTime) {
|
||||
throw new RuntimeException("Should never be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDateTimeCapable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map readData(DataInput dataInput) throws IOException {
|
||||
if (!dataInput.readBoolean()) {
|
||||
return null;
|
||||
} else {
|
||||
String json = dataInput.readUTF();
|
||||
return parse(json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(DataOutput dataOutput, Map v) throws IOException {
|
||||
if (v == null) {
|
||||
dataOutput.writeBoolean(false);
|
||||
} else {
|
||||
dataOutput.writeBoolean(true);
|
||||
String json = format(v);
|
||||
dataOutput.writeUTF(json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(JsonGenerator ctx, String name, Map value) throws IOException {
|
||||
// write the field name followed by the Map/JSON Object
|
||||
if (value == null) {
|
||||
ctx.writeNullField(name);
|
||||
} else {
|
||||
ctx.writeFieldName(name);
|
||||
EJson.write(value, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map jsonRead(JsonParser ctx, JsonToken event) throws IOException {
|
||||
// at this point the BeanProperty has read the START_OBJECT token
|
||||
// to check for a null value. Pass the START_OBJECT token through to
|
||||
// the EJson parsing so that it knows the first token has been read
|
||||
return EJson.parseObject(ctx, event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import org.postgresql.util.PGobject;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Support for the Postgres DB types JSON and JSONB.
|
||||
*/
|
||||
public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap {
|
||||
|
||||
private static final String POSTGRES_TYPE_JSON = "json";
|
||||
|
||||
private static final String POSTGRES_TYPE_JSONB = "jsonb";
|
||||
|
||||
final String postgresType;
|
||||
|
||||
ScalarTypeJsonMapPostgres(int jdbcType, String postgresType) {
|
||||
super(jdbcType);
|
||||
this.postgresType = postgresType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBind b, Map value) throws SQLException {
|
||||
|
||||
String rawJson = (value == null) ? null : formatValue(value);
|
||||
|
||||
PGobject pgo = new PGobject();
|
||||
pgo.setType(postgresType);
|
||||
pgo.setValue(rawJson);
|
||||
b.setObject(pgo);
|
||||
}
|
||||
|
||||
/**
|
||||
* ScalarType mapping java Map type to Postgres JSON database type.
|
||||
*/
|
||||
public static class JSON extends ScalarTypeJsonMapPostgres {
|
||||
|
||||
public JSON() {
|
||||
super(DbType.JSON, POSTGRES_TYPE_JSON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ScalarType mapping java Map type to Postgres JSONB database type.
|
||||
*/
|
||||
public static class JSONB extends ScalarTypeJsonMapPostgres {
|
||||
|
||||
public JSONB() {
|
||||
super(DbType.JSONB, POSTGRES_TYPE_JSONB);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebean.text.TextException;
|
||||
import com.avaje.ebean.text.json.EJson;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.PostgresPlatform;
|
||||
import com.avaje.ebean.text.json.EJson;
|
||||
import com.avaje.ebean.text.TextException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
/**
|
||||
* Postgres Hstore type which maps Map<String,String> to a single 'HStore column' in the DB.
|
||||
*/
|
||||
@@ -21,10 +21,8 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase<Map> {
|
||||
|
||||
public static final String KEY = "hstore";
|
||||
|
||||
public static final int HSTORE_TYPE = PostgresPlatform.TYPE_HSTORE;
|
||||
|
||||
public ScalarTypePostgresHstore() {
|
||||
super(Map.class, false, HSTORE_TYPE);
|
||||
super(Map.class, false, DbType.HSTORE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -58,7 +58,10 @@ public interface TypeManager {
|
||||
ScalarType<?> createEnumScalarType(Class<?> enumType);
|
||||
|
||||
/**
|
||||
* Find a scalarType using a custom type key. Used for Hstore and similar special types.
|
||||
* Return the ScalarType used to handle JSON content.
|
||||
* <p>
|
||||
* Note that type expected to be JsonNode or Map.
|
||||
* </p>
|
||||
*/
|
||||
ScalarType<?> getScalarTypeFromKey(String specialTypeKey);
|
||||
ScalarType<?> getJsonScalarType(Class<?> type, int dbType);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user