diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
index 76a54220a..9f26a4f34 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
@@ -223,6 +223,8 @@ public class DeployBeanDescriptor {
private short profileId;
+ private Object jacksonAnnotatedClass;
+
/**
* Construct the BeanDescriptor.
*/
@@ -1238,6 +1240,7 @@ public class DeployBeanDescriptor {
this.descriptor = descriptor;
}
+ @Override
public String getDeployWord(String expression) {
return descriptor.getDeployWord(expression);
}
@@ -1252,4 +1255,16 @@ public class DeployBeanDescriptor {
return (property == null) ? null : "${ta}." + property.getDbColumn();
}
+ /**
+ * Returns the jackson annotated class, if jackson is present.
+ */
+ public Object /*AnnotatedClass*/ getJacksonAnnotatedClass() {
+ if (jacksonAnnotatedClass == null) {
+ com.fasterxml.jackson.databind.ObjectMapper objectMapper = (com.fasterxml.jackson.databind.ObjectMapper) serverConfig.getObjectMapper();
+ com.fasterxml.jackson.databind.JavaType javaType = objectMapper.getTypeFactory().constructType(beanType);
+ jacksonAnnotatedClass = com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver
+ .resolve(objectMapper.getDeserializationConfig(), javaType, objectMapper.getDeserializationConfig());
+ }
+ return jacksonAnnotatedClass;
+ }
}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
index 0bfa5b328..4b38812d5 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
@@ -1124,4 +1124,18 @@ public class DeployBeanProperty {
public void setElementProperty() {
this.elementProperty = true;
}
+
+ /**
+ * Returns the jackson annotated field, if jackson is present.
+ */
+ public Object /*AnnotatedField*/ getJacksonField() {
+ com.fasterxml.jackson.databind.introspect.AnnotatedClass jac =
+ (com.fasterxml.jackson.databind.introspect.AnnotatedClass) getDesc().getJacksonAnnotatedClass();
+ for (com.fasterxml.jackson.databind.introspect.AnnotatedField candidate : jac.fields()) {
+ if (candidate.getName().equals(getName())) {
+ return candidate;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java
index 83ed5eb21..303135e91 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java
@@ -239,10 +239,9 @@ public class DeployUtil {
private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) {
- Class> type = prop.getPropertyType();
- ScalarType> scalarType = typeManager.getJsonScalarType(type, dbType, dbLength, prop.getGenericType());
+ ScalarType> scalarType = typeManager.getJsonScalarType(prop, dbType, dbLength);
if (scalarType == null) {
- throw new RuntimeException("No ScalarType for JSON type [" + type + "] [" + dbType + "]");
+ throw new RuntimeException("No ScalarType for JSON property [" + prop + "] [" + dbType + "]");
}
prop.setDbType(dbType);
prop.setScalarType(scalarType);
diff --git a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
index d6fb20e33..896391ff5 100644
--- a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
@@ -2,6 +2,8 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.introspect.AnnotatedField;
+
import io.ebean.annotation.DbArray;
import io.ebean.annotation.DbEnumType;
import io.ebean.annotation.DbEnumValue;
@@ -17,6 +19,7 @@ import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.ExtraTypeFactory;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.core.bootup.BootupClasses;
+import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -379,31 +382,36 @@ public final class DefaultTypeManager implements TypeManager {
}
@Override
- public ScalarType> getJsonScalarType(Class> type, int dbType, int dbLength, Type genericType) {
+ public ScalarType> getJsonScalarType(DeployBeanProperty prop, int dbType, int dbLength) {
+
+ Class> type = prop.getPropertyType();
+ Type genericType = prop.getGenericType();
+
+ boolean hasJacksonAnnotations = objectMapperPresent && checkJacksonAnnotations(prop);
if (type.equals(List.class)) {
DocPropertyType docType = getDocType(genericType);
- if (isValueTypeSimple(genericType)) {
+ if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) {
return ScalarTypeJsonList.typeFor(postgres, dbType, docType);
} else {
- return createJsonObjectMapperType(type, genericType, dbType, docType);
+ return createJsonObjectMapperType(prop, dbType, docType);
}
}
if (type.equals(Set.class)) {
DocPropertyType docType = getDocType(genericType);
- if (isValueTypeSimple(genericType)) {
+ if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) {
return ScalarTypeJsonSet.typeFor(postgres, dbType, docType);
} else {
- return createJsonObjectMapperType(type, genericType, dbType, docType);
+ return createJsonObjectMapperType(prop, dbType, docType);
}
}
if (type.equals(Map.class)) {
- if (isMapValueTypeObject(genericType)) {
+ if (!hasJacksonAnnotations && isMapValueTypeObject(genericType)) {
return ScalarTypeJsonMap.typeFor(postgres, dbType);
} else {
- return createJsonObjectMapperType(type, genericType, dbType, DocPropertyType.OBJECT);
+ return createJsonObjectMapperType(prop, dbType, DocPropertyType.OBJECT);
}
}
@@ -426,7 +434,15 @@ public final class DefaultTypeManager implements TypeManager {
}
}
- return createJsonObjectMapperType(type, type, dbType, DocPropertyType.OBJECT);
+ return createJsonObjectMapperType(prop, dbType, DocPropertyType.OBJECT);
+ }
+
+ /**
+ * Returns TRUE, if there is any jackson annotation on that property. All jackson annotations
+ * are annotated with the @JacksonAnnotation meta annotation. So detection is easy.
+ */
+ private boolean checkJacksonAnnotations(DeployBeanProperty prop) {
+ return AnnotationUtil.findAnnotation(prop.getField(), com.fasterxml.jackson.annotation.JacksonAnnotation.class) != null;
}
private DocPropertyType getDocType(Type genericType) {
@@ -459,11 +475,12 @@ public final class DefaultTypeManager implements TypeManager {
return Object.class.equals(typeArgs[1]) || "?".equals(typeArgs[1].toString());
}
- private ScalarType> createJsonObjectMapperType(Class> type, Type genericType, int dbType, DocPropertyType docType) {
+ private ScalarType> createJsonObjectMapperType(DeployBeanProperty prop, int dbType, DocPropertyType docType) {
+ Class> type = prop.getPropertyType();
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
- return ScalarTypeJsonObjectMapper.createTypeFor(postgres, type, (ObjectMapper) objectMapper, genericType, dbType, docType);
+ return ScalarTypeJsonObjectMapper.createTypeFor(postgres, (AnnotatedField) prop.getJacksonField(), (ObjectMapper) objectMapper, dbType, docType);
}
/**
diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
index eda46122a..91c1e1c9b 100644
--- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
+++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
@@ -9,14 +9,19 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import javax.persistence.PersistenceException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
-import java.lang.reflect.Type;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
@@ -31,20 +36,21 @@ public class ScalarTypeJsonObjectMapper {
/**
* Create and return the appropriate ScalarType.
*/
- public static ScalarType> createTypeFor(boolean postgres, Class> type, ObjectMapper objectMapper,
- Type genericType, int dbType, DocPropertyType docType) {
+ public static ScalarType> createTypeFor(boolean postgres, AnnotatedField field, ObjectMapper objectMapper,
+ int dbType, DocPropertyType docType) {
+ Class> type = field.getRawType();
String pgType = getPostgresType(postgres, dbType);
if (Set.class.equals(type)) {
- return new OmSet(objectMapper, genericType, dbType, pgType, docType);
+ return new OmSet(objectMapper, field, dbType, pgType, docType);
}
if (List.class.equals(type)) {
- return new OmList(objectMapper, genericType, dbType, pgType, docType);
+ return new OmList(objectMapper, field, dbType, pgType, docType);
}
if (Map.class.equals(type)) {
- return new OmMap(objectMapper, genericType, dbType, pgType);
+ return new OmMap(objectMapper, field, dbType, pgType);
}
- return new GenericObject(objectMapper, genericType, dbType, pgType);
+ return new GenericObject(objectMapper, field, dbType, pgType);
}
private static String getPostgresType(boolean postgres, int dbType) {
@@ -64,8 +70,8 @@ public class ScalarTypeJsonObjectMapper {
*/
private static class GenericObject extends Base
*/
- ScalarType> getJsonScalarType(Class> type, int dbType, int dbLength, Type genericType);
+ ScalarType> getJsonScalarType(DeployBeanProperty prop, int dbType, int dbLength);
/**
* Return the ScalarType used to handle DB ARRAY.
diff --git a/src/test/java/org/tests/basic/TestTransient.java b/src/test/java/org/tests/basic/TestTransient.java
index 70e560a1c..0652fd5e2 100644
--- a/src/test/java/org/tests/basic/TestTransient.java
+++ b/src/test/java/org/tests/basic/TestTransient.java
@@ -54,7 +54,7 @@ public class TestTransient extends BaseTestCase {
selected = c.getSelected();
Assert.assertNotNull(selected);
- String updateStmt = "update customer set name = 'Rob' where id = :id";
+ String updateStmt = "update customer set name = 'testTrans2' where id = :id";
int rows = Ebean.createUpdate(Customer.class, updateStmt).set("id", custId).execute();
Assert.assertTrue("changed name back", 1 == rows);
diff --git a/src/test/java/org/tests/json/TestDbJson_Jackson.java b/src/test/java/org/tests/json/TestDbJson_Jackson.java
new file mode 100644
index 000000000..c281444a7
--- /dev/null
+++ b/src/test/java/org/tests/json/TestDbJson_Jackson.java
@@ -0,0 +1,113 @@
+package org.tests.json;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.StrictAssertions.assertThat;
+
+import java.io.IOException;
+
+import org.junit.Test;
+import org.tests.model.json.EBasicJsonJackson;
+import org.tests.model.json.EBasicJsonJackson2;
+import org.tests.model.json.LongJacksonType;
+import org.tests.model.json.StringJacksonType;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.ebean.BaseTestCase;
+import io.ebean.Ebean;
+
+public class TestDbJson_Jackson extends BaseTestCase {
+
+
+ /**
+ * This testcase verifies if the @JsonDeserialize Annotation will work properly in ebean.
+ */
+ @Test
+ public void testJsonDeserializeAnnotation() throws IOException {
+
+ EBasicJsonJackson bean = new EBasicJsonJackson();
+
+ bean.setName("stuff");
+
+ bean.setPlainValue("plain");
+
+ bean.getValueSet().add("A");
+ bean.getValueSet().add("B");
+
+ bean.getValueList().add("A");
+ bean.getValueList().add("B");
+
+ bean.getValueMap().put(1, "one");
+ bean.getValueMap().put(2, "two");
+
+ ObjectMapper mapper = (ObjectMapper) Ebean.getDefaultServer().getPluginApi().getServerConfig().getObjectMapper();
+
+ String json = mapper.writeValueAsString(bean);
+ EBasicJsonJackson found = mapper.readValue(json, EBasicJsonJackson.class);
+
+ // here we assert, that we can serialize/deserialize that bean with jackson
+ assertThat(found.getPlainValue()).isEqualTo("plain");
+ assertThat(found.getValueList()).containsExactly("A", "B");
+ assertThat(found.getValueSet()).containsExactly("A", "B");
+ assertThat(found.getValueMap()).containsEntry(1L, "one").containsEntry(2L, "two");
+
+ Ebean.save(bean);
+
+ // here we do the same and expect, that we can deserialize the bean also
+ found = Ebean.find(EBasicJsonJackson.class, bean.getId());
+
+ assertThat(found.getPlainValue()).isEqualTo("plain");
+ assertThat(found.getValueList()).containsExactly("A", "B");
+ assertThat(found.getValueSet()).containsExactly("A", "B");
+ assertThat(found.getValueMap()).containsEntry(1L, "one").containsEntry(2L, "two");
+ }
+
+ /**
+ * This testcase verifies if polymorph objects will work in ebean.
+ *
+ * for BasicJacksonType there exists two types and has a @JsonTypeInfo
+ * annotation. It is expected that this information is also honored by ebean.
+ */
+ @Test
+ public void testPolymorph() throws IOException {
+
+ EBasicJsonJackson2 bean = new EBasicJsonJackson2();
+
+ bean.setName("stuff");
+
+ bean.setPlainValue(new LongJacksonType(42L));
+
+ bean.getValueSet().add(new StringJacksonType("A"));
+ bean.getValueSet().add(new LongJacksonType(7l));
+
+ bean.getValueList().add(new StringJacksonType("A"));
+ bean.getValueList().add(new LongJacksonType(7l));
+
+ bean.getValueMap().put(1, new StringJacksonType("A"));
+ bean.getValueMap().put(2, new LongJacksonType(7l));
+
+ ObjectMapper mapper = (ObjectMapper) Ebean.getDefaultServer().getPluginApi().getServerConfig().getObjectMapper();
+
+ String json = mapper.writeValueAsString(bean);
+ assertThat(json)
+ .contains("\"type\":\"string\"")
+ .contains("\"type\":\"long\"");
+ EBasicJsonJackson2 found = mapper.readValue(json, EBasicJsonJackson2.class);
+
+ assertThat(found.getPlainValue()).isInstanceOf(LongJacksonType.class);
+ assertThat(found.getValueList()).hasSize(2);
+ assertThat(found.getValueSet()).hasSize(2);
+ assertThat(found.getValueMap()).hasSize(2);;
+
+ Ebean.save(bean);
+
+ found = Ebean.find(EBasicJsonJackson2.class, bean.getId());
+
+ assertThat(found.getPlainValue()).isInstanceOf(LongJacksonType.class);
+ assertThat(found.getValueList()).hasSize(2);
+ assertThat(found.getValueSet()).hasSize(2);
+ assertThat(found.getValueMap()).hasSize(2);;
+
+ }
+
+}
diff --git a/src/test/java/org/tests/json/TestDbJson_Jackson2.java b/src/test/java/org/tests/json/TestDbJson_Jackson2.java
new file mode 100644
index 000000000..866161cf0
--- /dev/null
+++ b/src/test/java/org/tests/json/TestDbJson_Jackson2.java
@@ -0,0 +1,62 @@
+package org.tests.json;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.StrictAssertions.assertThat;
+
+import java.io.IOException;
+
+import org.junit.Test;
+import org.tests.model.json.EBasicJsonJackson2;
+import org.tests.model.json.LongJacksonType;
+import org.tests.model.json.StringJacksonType;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.ebean.BaseTestCase;
+import io.ebean.Ebean;
+
+public class TestDbJson_Jackson2 extends BaseTestCase {
+
+ private EBasicJsonJackson2 bean = new EBasicJsonJackson2();
+
+ private EBasicJsonJackson2 found;
+
+ @Test
+ public void insert() throws IOException {
+
+ bean.setName("stuff");
+
+ bean.setPlainValue(new LongJacksonType(42L));
+
+ bean.getValueSet().add(new StringJacksonType("A"));
+ bean.getValueSet().add(new LongJacksonType(7l));
+
+ bean.getValueList().add(new StringJacksonType("A"));
+ bean.getValueList().add(new LongJacksonType(7l));
+
+ bean.getValueMap().put(1, new StringJacksonType("A"));
+ bean.getValueMap().put(2, new LongJacksonType(7l));
+
+ ObjectMapper mapper = (ObjectMapper) Ebean.getDefaultServer().getPluginApi().getServerConfig().getObjectMapper();
+
+ String json = mapper.writeValueAsString(bean);
+ found = mapper.readValue(json, EBasicJsonJackson2.class);
+
+ assertThat(found.getPlainValue()).isInstanceOf(LongJacksonType.class);
+ assertThat(found.getValueList()).hasSize(2);
+ assertThat(found.getValueSet()).hasSize(2);
+ assertThat(found.getValueMap()).hasSize(2);;
+
+ Ebean.save(bean);
+
+ found = Ebean.find(EBasicJsonJackson2.class, bean.getId());
+
+ assertThat(found.getPlainValue()).isInstanceOf(LongJacksonType.class);
+ assertThat(found.getValueList()).hasSize(2);
+ assertThat(found.getValueSet()).hasSize(2);
+ assertThat(found.getValueMap()).hasSize(2);;
+
+ }
+
+
+}
diff --git a/src/test/java/org/tests/model/json/BasicJacksonType.java b/src/test/java/org/tests/model/json/BasicJacksonType.java
new file mode 100644
index 000000000..fee853259
--- /dev/null
+++ b/src/test/java/org/tests/model/json/BasicJacksonType.java
@@ -0,0 +1,15 @@
+package org.tests.model.json;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = LongJacksonType.class, name = "long"),
+ @JsonSubTypes.Type(value = StringJacksonType.class, name = "string")
+})
+public interface BasicJacksonType {
+ T getValue();
+}
diff --git a/src/test/java/org/tests/model/json/EBasicJsonJackson.java b/src/test/java/org/tests/model/json/EBasicJsonJackson.java
new file mode 100644
index 000000000..9faa35eee
--- /dev/null
+++ b/src/test/java/org/tests/model/json/EBasicJsonJackson.java
@@ -0,0 +1,101 @@
+package org.tests.model.json;
+
+import io.ebean.annotation.DbJson;
+import io.ebean.annotation.DbJsonB;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Entity
+public class EBasicJsonJackson {
+
+ @Id
+ Long id;
+
+ String name;
+
+ @DbJson(length = 700)
+ @JsonDeserialize(contentAs = String.class)
+ Set