From 609dc233a88ba951c2fad15984ed071fdd1e4776 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 25 Nov 2016 23:58:55 +1300 Subject: [PATCH] #899 - ENH: Add @UnmappedJson support ... (mostly for document store only use / ElasticSearch) --- pom.xml | 2 +- .../server/deploy/BeanDescriptor.java | 17 +++++- .../server/deploy/BeanDescriptorJsonHelp.java | 3 + .../server/deploy/BeanProperty.java | 15 ++++- .../deploy/meta/DeployBeanProperty.java | 11 ++++ .../deploy/meta/DeployBeanPropertyLists.java | 12 +++- .../server/deploy/parse/AnnotationFields.java | 7 +-- .../server/text/json/WriteJson.java | 17 +++++- .../tests/json/TestUnmappedProperties.java | 54 ++++++++++++++++++ .../tests/model/json/EBasicJsonUnmapped.java | 55 +++++++++++++++++++ 10 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/avaje/tests/json/TestUnmappedProperties.java create mode 100644 src/test/java/com/avaje/tests/model/json/EBasicJsonUnmapped.java diff --git a/pom.xml b/pom.xml index a4a654d29..7b36cd736 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ org.avaje.ebean ebean-annotation - 1.2 + 1.3 diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 0036f68af..d11fe0494 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -213,6 +213,8 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final boolean draftableElement; + private final BeanProperty unmappedJson; + private final BeanProperty draft; private final BeanProperty draftDirty; @@ -462,6 +464,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.softDelete = (softDeleteProperty != null); this.idProperty = listHelper.getId(); this.versionProperty = listHelper.getVersionProperty(); + this.unmappedJson = listHelper.getUnmappedJson(); this.draft = listHelper.getDraft(); this.draftDirty = listHelper.getDraftDirty(); this.propMap = listHelper.getPropertyMap(); @@ -474,7 +477,6 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.propertiesMutable = listHelper.getMutable(); this.unidirectional = listHelper.getUnidirectional(); this.propertiesOne = listHelper.getOnes(); - //this.propertiesOneExported = listHelper.getOneExported(); this.propertiesOneExportedSave = listHelper.getOneExportedSave(); this.propertiesOneExportedDelete = listHelper.getOneExportedDelete(); this.propertiesOneImported = listHelper.getOneImported(); @@ -2468,6 +2470,12 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return draftableElement; } + public void setUnmappedJson(EntityBean bean, Map unmappedProperties) { + if( unmappedJson != null) { + unmappedJson.setValueIntercept(bean, unmappedProperties); + } + } + /** * Set the draft to true for this entity bean instance. * This bean is being loaded via asDraft() query. @@ -2597,6 +2605,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return propMap.values(); } + /** + * Return the property that holds unmapped JSON content. + */ + public BeanProperty propertyUnmappedJson() { + return unmappedJson; + } + /** * Return the non transient non id properties. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java index 6dbf2e357..bdc79731d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java @@ -159,6 +159,9 @@ public class BeanDescriptorJsonHelp { } while (true); + if (unmappedProperties != null) { + desc.setUnmappedJson(bean, unmappedProperties); + } Object contextBean = null; Object id = desc.beanId(bean); if (id != null) { 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 780c574e9..fa8b70bc5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -242,8 +242,8 @@ public class BeanProperty implements ElPropertyValue, Property { int deployOrder; final boolean jsonSerialize; - final boolean jsonDeserialize; + final boolean unmappedJson; final boolean draft; @@ -279,6 +279,7 @@ public class BeanProperty implements ElPropertyValue, Property { this.dbInsertable = deploy.isDbInsertable(); this.dbUpdatable = deploy.isDbUpdateable(); this.excludedFromHistory = deploy.isExcludedFromHistory(); + this.unmappedJson = deploy.isUnmappedJson(); this.draft = deploy.isDraft(); this.draftDirty = deploy.isDraftDirty(); this.draftOnly = deploy.isDraftOnly(); @@ -426,6 +427,7 @@ public class BeanProperty implements ElPropertyValue, Property { this.propertyType = source.getPropertyType(); this.field = source.getField(); this.docOptions = source.docOptions; + this.unmappedJson = source.unmappedJson; this.elPrefix = override.replace(source.elPrefix, source.dbColumn); this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn); @@ -1201,6 +1203,13 @@ public class BeanProperty implements ElPropertyValue, Property { return !excludedFromHistory && descriptor.isHistorySupport(); } + /** + * Return true if this property hold unmapped JSON. + */ + public boolean isUnmappedJson() { + return unmappedJson; + } + /** * Return true if this property only exists on the draft table. */ @@ -1302,6 +1311,10 @@ public class BeanProperty implements ElPropertyValue, Property { } } + public boolean isJsonSerialize() { + return jsonSerialize; + } + @SuppressWarnings(value = "unchecked") public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException { if (!jsonSerialize) { 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 a0aa3f45e..9cb0532c0 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 @@ -203,6 +203,7 @@ public class DeployBeanProperty { private boolean draftReset; private boolean softDelete; + private boolean unmappedJson; private String dbComment; @@ -939,6 +940,15 @@ public class DeployBeanProperty { return softDelete; } + public void setUnmappedJson() { + this.unmappedJson = true; + this.isTransient = true; + } + + public boolean isUnmappedJson() { + return unmappedJson; + } + public void setDbComment(String dbComment) { this.dbComment = dbComment; } @@ -966,4 +976,5 @@ public class DeployBeanProperty { public String getDbColumnDefault() { return dbColumnDefault; } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java index 3c055d507..cdee51e94 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java @@ -27,6 +27,8 @@ public class DeployBeanPropertyLists { private BeanProperty versionProperty; + private BeanProperty unmappedJson; + private BeanProperty draft; private BeanProperty draftDirty; @@ -167,6 +169,9 @@ public class DeployBeanPropertyLists { if (prop.isDraft()) { draft = prop; } + if (prop.isUnmappedJson()) { + unmappedJson = prop; + } return; } if (prop.isId()) { @@ -205,8 +210,7 @@ public class DeployBeanPropertyLists { if (versionProperty == null) { versionProperty = prop; } else { - logger.warn("Multiple @Version properties - property " + prop.getFullBeanName() - + " not treated as a version property"); + logger.warn("Multiple @Version properties - property " + prop.getFullBeanName() + " not treated as a version property"); } } else if (prop.isDraftDirty()) { draftDirty = prop; @@ -326,6 +330,10 @@ public class DeployBeanPropertyLists { return draftDirty; } + public BeanProperty getUnmappedJson() { + return unmappedJson; + } + public BeanProperty getDraft() { return draft; } 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 edfdac0a4..92334ace4 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 @@ -13,9 +13,6 @@ import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; -import com.avaje.ebeaninternal.server.lib.util.StringHelper; -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; @@ -26,7 +23,6 @@ import javax.persistence.*; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.sql.Types; -import java.util.Map; import java.util.Set; import java.util.UUID; @@ -364,6 +360,9 @@ public class AnnotationFields extends AnnotationParser { prop.setJsonSerialize(jsonIgnore.serialize()); prop.setJsonDeserialize(jsonIgnore.deserialize()); } + if (get(prop, UnmappedJson.class) != null) { + prop.setUnmappedJson(); + } } private boolean hasRelationshipItem(DeployBeanProperty prop) { 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 f7713cd4c..8f69d4b60 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 @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.text.json; import com.avaje.ebean.FetchPath; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.config.JsonConfig; +import com.avaje.ebean.text.json.EJson; import com.avaje.ebean.text.json.JsonIOException; import com.avaje.ebean.text.json.JsonWriteBeanVisitor; import com.avaje.ebean.text.json.JsonWriter; @@ -465,7 +466,9 @@ public class WriteJson implements JsonWriter { } private boolean isIncludeTransientProperty(BeanProperty prop) { - if (!explicitAllProps && currentIncludeProps != null) { + if (prop.isUnmappedJson()) { + return false; + } else if (!explicitAllProps && currentIncludeProps != null) { // explicitly controlled by pathProperties return currentIncludeProps.contains(prop.getName()); } else { @@ -501,6 +504,18 @@ public class WriteJson implements JsonWriter { } } + BeanProperty unmappedJson = desc.propertyUnmappedJson(); + if (unmappedJson != null && unmappedJson.isJsonSerialize()) { + Map map = (Map)unmappedJson.getValue(currentBean); + if (map != null) { + // write to JSON at the current level + for (Map.Entry entry : map.entrySet()) { + writeJson.writeFieldName(entry.getKey()); + EJson.write(entry.getValue(), writeJson.generator); + } + } + } + if (visitor != null) { visitor.visit(currentBean, writeJson); } diff --git a/src/test/java/com/avaje/tests/json/TestUnmappedProperties.java b/src/test/java/com/avaje/tests/json/TestUnmappedProperties.java new file mode 100644 index 000000000..86a9f53ce --- /dev/null +++ b/src/test/java/com/avaje/tests/json/TestUnmappedProperties.java @@ -0,0 +1,54 @@ +package com.avaje.tests.json; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.tests.model.json.EBasicJsonUnmapped; +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestUnmappedProperties extends BaseTestCase { + + @Test + public void toJson() { + + Map nested = new LinkedHashMap<>(); + nested.put("alpha", "aa"); + nested.put("beta", "bb"); + + + Map unmapped = new LinkedHashMap<>(); + unmapped.put("one", 42); + unmapped.put("nested", nested); + + EBasicJsonUnmapped bean = new EBasicJsonUnmapped(); + bean.setName("someName"); + bean.setUnmapped(unmapped); + + + String asJson = Ebean.json().toJson(bean); + + assertThat(asJson).contains("{\"name\":\"someName\",\"one\":42,\"nested\":{\"alpha\":\"aa\",\"beta\":\"bb\"}}"); + + } + + @Test + public void fromJson() { + + String json = "{\"name\":\"someName\",\"one\":42,\"nested\":{\"alpha\":\"aa\",\"beta\":\"bb\"}}"; + + EBasicJsonUnmapped bean = Ebean.json().toBean(EBasicJsonUnmapped.class, json); + + assertThat(bean.getName()).isEqualTo("someName"); + assertThat(bean.getUnmapped().get("one")).isEqualTo(42L); + + @SuppressWarnings("unchecked") + Map fromNested = (Map)bean.getUnmapped().get("nested"); + assertThat(fromNested).containsKeys("alpha", "beta"); + assertThat(fromNested).containsValues("aa", "bb"); + } + +} diff --git a/src/test/java/com/avaje/tests/model/json/EBasicJsonUnmapped.java b/src/test/java/com/avaje/tests/model/json/EBasicJsonUnmapped.java new file mode 100644 index 000000000..7e2f0e411 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/json/EBasicJsonUnmapped.java @@ -0,0 +1,55 @@ +package com.avaje.tests.model.json; + +import com.avaje.ebean.annotation.UnmappedJson; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; +import java.util.Map; + +@Entity +public class EBasicJsonUnmapped { + + @Id + Long id; + + @Version + Long version; + + String name; + + @UnmappedJson + Map unmapped; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getUnmapped() { + return unmapped; + } + + public void setUnmapped(Map unmapped) { + this.unmapped = unmapped; + } +}