From 368d22baa66c18d7825dfaa82305660a5ebd804e Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Tue, 3 Oct 2017 01:31:37 +1300 Subject: [PATCH] #1145 - Refactor @ChangeLog based on JSON documents for 'new values' and 'old values' --- .../java/io/ebean/bean/BeanDiffVisitor.java | 22 ++++ .../io/ebean/bean/EntityBeanIntercept.java | 22 ++++ .../io/ebean/event/changelog/BeanChange.java | 98 +++++---------- .../io/ebeaninternal/api/SpiEbeanServer.java | 5 + .../io/ebeaninternal/api/SpiJsonContext.java | 24 ++++ .../server/changelog/ChangeJsonBuilder.java | 75 ++++------- .../changelog/DefaultChangeLogListener.java | 28 ++--- .../server/core/DefaultServer.java | 17 ++- .../ebeaninternal/server/core/DiffHelp.java | 52 -------- .../server/core/InternalConfiguration.java | 8 +- .../server/core/OrmQueryRequest.java | 9 +- .../server/core/PersistRequestBean.java | 15 ++- .../server/core/SpiOrmQueryRequest.java | 4 +- .../server/deploy/BeanChangeJson.java | 108 ++++++++++++++++ .../server/deploy/BeanCollectionHelp.java | 4 +- .../server/deploy/BeanDescriptor.java | 117 +++++++++--------- .../server/deploy/BeanDescriptorJsonHelp.java | 14 +-- .../server/deploy/BeanListHelp.java | 4 +- .../server/deploy/BeanMapHelp.java | 4 +- .../server/deploy/BeanProperty.java | 65 ++++++---- .../server/deploy/BeanPropertyAssocMany.java | 11 +- .../deploy/BeanPropertyAssocManyJsonHelp.java | 2 +- .../server/deploy/BeanPropertyAssocOne.java | 70 ++++++++--- .../server/deploy/BeanSetHelp.java | 4 +- .../server/text/json/DJsonContext.java | 50 +++++--- .../server/text/json/SpiJsonWriter.java | 68 ++++++++++ .../server/text/json/WriteJson.java | 20 +-- .../ebeaninternal/api/TDSpiEbeanServer.java | 5 + .../changelog/ChangeJsonBuilderTest.java | 2 +- .../server/changelog/Helper.java | 40 +++--- .../core/TestDiffHelpInsertWithEmbedded.java | 105 ---------------- .../TestDiffHelpInsertSimple.java | 42 ++++--- .../TestDiffHelpInsertWithEmbedded.java | 100 +++++++++++++++ .../org/tests/changelog/TestChangeLogInt.java | 22 ++++ src/test/resources/logback-test.xml | 10 +- 35 files changed, 743 insertions(+), 503 deletions(-) create mode 100644 src/main/java/io/ebean/bean/BeanDiffVisitor.java create mode 100644 src/main/java/io/ebeaninternal/api/SpiJsonContext.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/BeanChangeJson.java create mode 100644 src/main/java/io/ebeaninternal/server/text/json/SpiJsonWriter.java delete mode 100644 src/test/java/io/ebeaninternal/server/core/TestDiffHelpInsertWithEmbedded.java rename src/test/java/io/ebeaninternal/server/{core => deploy}/TestDiffHelpInsertSimple.java (53%) create mode 100644 src/test/java/io/ebeaninternal/server/deploy/TestDiffHelpInsertWithEmbedded.java create mode 100644 src/test/java/org/tests/changelog/TestChangeLogInt.java diff --git a/src/main/java/io/ebean/bean/BeanDiffVisitor.java b/src/main/java/io/ebean/bean/BeanDiffVisitor.java new file mode 100644 index 000000000..1fef6e2a4 --- /dev/null +++ b/src/main/java/io/ebean/bean/BeanDiffVisitor.java @@ -0,0 +1,22 @@ +package io.ebean.bean; + +/** + * Visitor for collecting new/old values for a bean update. + */ +public interface BeanDiffVisitor { + + /** + * Collect a new/old value pair. + */ + void visit(int position, Object newVal, Object oldVal); + + /** + * Start processing an associated bean. + */ + void visitPush(int position); + + /** + * Stop processing an associated bean. + */ + void visitPop(); +} diff --git a/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/src/main/java/io/ebean/bean/EntityBeanIntercept.java index 16e694910..d1dc7a77b 100644 --- a/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -694,6 +694,28 @@ public final class EntityBeanIntercept implements Serializable { } } + /** + * Recursively add dirty properties. + */ + public void addDirtyPropertyValues(BeanDiffVisitor visitor) { + int len = getPropertyLength(); + for (int i = 0; i < len; i++) { + if (changedProps != null && changedProps[i]) { + // the property has been changed on this bean + Object newVal = owner._ebean_getField(i); + Object oldVal = getOrigValue(i); + visitor.visit(i, newVal, oldVal); + + } else if (embeddedDirty != null && embeddedDirty[i]) { + // an embedded property has been changed - recurse + EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i); + visitor.visitPush(i); + embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor); + visitor.visitPop(); + } + } + } + /** * Return a dirty property hash taking into account embedded beans. */ diff --git a/src/main/java/io/ebean/event/changelog/BeanChange.java b/src/main/java/io/ebean/event/changelog/BeanChange.java index 12029b75c..2b4971799 100644 --- a/src/main/java/io/ebean/event/changelog/BeanChange.java +++ b/src/main/java/io/ebean/event/changelog/BeanChange.java @@ -1,9 +1,5 @@ package io.ebean.event.changelog; -import io.ebean.ValuePair; - -import java.util.Map; - /** * A bean insert, update or delete change sent as part of a ChangeSet. */ @@ -12,68 +8,68 @@ public class BeanChange { /** * The underling base table name. */ - String table; + private final String type; /** * The tenantId value. */ - Object tenantId; - + private final Object tenantId; + /** * The id value. */ - Object id; + private final Object id; /** * The INSERT, UPDATE or DELETE change type. */ - ChangeType type; + private final ChangeType event; /** * The time the bean change was created. */ - long eventTime; + private final long eventTime; /** - * The values for insert or update. Note that null values are not included for insert. + * The change in JSON form. */ - Map values; + private final String data; /** - * Construct with all the details. + * The change in JSON form. */ - public BeanChange(String table, Object tenantId, Object id, ChangeType type, Map values) { - this.table = table; + private final String oldData; + + /** + * Construct with change as JSON. + */ + public BeanChange(String type, Object tenantId, Object id, ChangeType event, String data, String oldData) { + this.type = type; this.tenantId = tenantId; this.id = id; - this.type = type; + this.event = event; this.eventTime = System.currentTimeMillis(); - this.values = values; + this.data = data; + this.oldData = oldData; } /** - * Default constructor for JSON tools. + * Construct with change as JSON. */ - public BeanChange() { + public BeanChange(String table, Object tenantId, Object id, ChangeType event, String data) { + this(table, tenantId , id , event , data , null); } @Override public String toString() { - return "table:" + table + " tenantId: " + tenantId + " id:" + id + " values:" + values; + return "type:" + type + " tenantId: " + tenantId + " id:" + id + " data:" + data; } /** * Return the object type (typically table name). */ - public String getTable() { - return table; - } - - /** - * Set the object type (for JSON tools). - */ - public void setTable(String table) { - this.table = table; + public String getType() { + return type; } /** @@ -83,13 +79,6 @@ public class BeanChange { return tenantId; } - /** - * Set the bean id (for JSON tools). - */ - public void setTenantId(Object tenantId) { - this.tenantId = tenantId; - } - /** * Return the object id. */ @@ -97,25 +86,11 @@ public class BeanChange { return id; } - /** - * Set the bean id (for JSON tools). - */ - public void setId(Object id) { - this.id = id; - } - /** * Return the change type (INSERT, UPDATE or DELETE). */ - public ChangeType getType() { - return type; - } - - /** - * Set the type (for JSON tools). - */ - public void setType(ChangeType type) { - this.type = type; + public ChangeType getEvent() { + return event; } /** @@ -126,23 +101,16 @@ public class BeanChange { } /** - * Set the event time in epoch millis. + * Return the change data in JSON form. */ - public void setEventTime(long eventTime) { - this.eventTime = eventTime; + public String getData() { + return data; } /** - * Return the value pairs. For inserts the ValuePair oldValue is always null. + * Return the old data in JSON form. */ - public Map getValues() { - return values; - } - - /** - * Set the value pairs (for JSON tools). - */ - public void setValues(Map values) { - this.values = values; + public String getOldData() { + return oldData; } } diff --git a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index af7bef5c8..7bd6d64ee 100644 --- a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -25,6 +25,11 @@ import java.util.List; */ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader { + /** + * Return the server extended Json context. + */ + SpiJsonContext jsonExtended(); + /** * For internal use, shutdown of the server invoked by JVM Shutdown. */ diff --git a/src/main/java/io/ebeaninternal/api/SpiJsonContext.java b/src/main/java/io/ebeaninternal/api/SpiJsonContext.java new file mode 100644 index 000000000..4be1a3034 --- /dev/null +++ b/src/main/java/io/ebeaninternal/api/SpiJsonContext.java @@ -0,0 +1,24 @@ +package io.ebeaninternal.api; + +import com.fasterxml.jackson.core.JsonGenerator; +import io.ebean.text.json.JsonContext; +import io.ebean.text.json.JsonWriteOptions; +import io.ebeaninternal.server.text.json.SpiJsonWriter; + +import java.io.Writer; + +/** + * Extended Json Context for internal server use. + */ +public interface SpiJsonContext extends JsonContext { + + /** + * Create a Json Writer for writing beans as JSON. + */ + SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options); + + /** + * Create a Json Writer for writing beans as JSON supplying a writer. + */ + SpiJsonWriter createJsonWriter(Writer writer); +} diff --git a/src/main/java/io/ebeaninternal/server/changelog/ChangeJsonBuilder.java b/src/main/java/io/ebeaninternal/server/changelog/ChangeJsonBuilder.java index c8dc08334..372de9589 100644 --- a/src/main/java/io/ebeaninternal/server/changelog/ChangeJsonBuilder.java +++ b/src/main/java/io/ebeaninternal/server/changelog/ChangeJsonBuilder.java @@ -1,12 +1,11 @@ package io.ebeaninternal.server.changelog; -import io.ebean.ValuePair; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; import io.ebean.event.changelog.ChangeType; import io.ebean.text.json.JsonContext; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; import java.io.Writer; @@ -15,25 +14,23 @@ import java.util.Map; /** * Builds JSON document for a bean change. */ -public class ChangeJsonBuilder { +class ChangeJsonBuilder { protected final JsonFactory jsonFactory = new JsonFactory(); protected final JsonContext json; - protected ChangeJsonBuilder(JsonContext json) { + ChangeJsonBuilder(JsonContext json) { this.json = json; } /** * Write the bean change as JSON. */ - public void writeBeanJson(Writer writer, BeanChange bean, ChangeSet changeSet, int position) throws IOException { + void writeBeanJson(Writer writer, BeanChange bean, ChangeSet changeSet) throws IOException { try (JsonGenerator generator = jsonFactory.createGenerator(writer)) { - - writeBeanChange(generator, bean, changeSet, position); - + writeBeanChange(generator, bean, changeSet); generator.flush(); } } @@ -41,34 +38,29 @@ public class ChangeJsonBuilder { /** * Write the bean change as JSON document containing the transaction header details. */ - protected void writeBeanChange(JsonGenerator gen, BeanChange bean, ChangeSet changeSet, int position) throws IOException { + private void writeBeanChange(JsonGenerator gen, BeanChange bean, ChangeSet changeSet) throws IOException { gen.writeStartObject(); - writeBeanTransactionDetails(gen, changeSet, position); - - gen.writeStringField("object", bean.getTable()); + gen.writeNumberField("ts", bean.getEventTime()); + gen.writeStringField("change", bean.getEvent().getCode()); + gen.writeStringField("type", bean.getType()); + gen.writeStringField("id", bean.getId().toString()); if (bean.getTenantId() != null) { gen.writeStringField("tenantId", bean.getTenantId().toString()); } - gen.writeStringField("objectId", bean.getId().toString()); - gen.writeStringField("change", bean.getType().getCode()); - gen.writeNumberField("eventTime", bean.getEventTime()); + + writeBeanTransactionDetails(gen, changeSet); writeBeanValues(gen, bean); - gen.writeEndObject(); } /** * Denormalise by writing the transaction header details. */ - protected void writeBeanTransactionDetails(JsonGenerator gen, ChangeSet changeSet, int position) throws IOException { + private void writeBeanTransactionDetails(JsonGenerator gen, ChangeSet changeSet) throws IOException { - gen.writeStringField("txnId", changeSet.getTxnId()); - gen.writeStringField("txnState", changeSet.getTxnState().getCode()); - gen.writeNumberField("txnBatch", changeSet.getTxnBatch()); - gen.writeNumberField("txnPosition", position); String source = changeSet.getSource(); if (source != null) { gen.writeStringField("source", source); @@ -94,39 +86,18 @@ public class ChangeJsonBuilder { /** * For insert and update write the new/old values. */ - protected void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException { + private void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException { - if (bean.getType() != ChangeType.DELETE) { - gen.writeFieldName("values"); - gen.writeStartObject(); - writeValuePairs(bean, gen); - gen.writeEndObject(); - } - } + if (bean.getEvent() != ChangeType.DELETE) { + gen.writeFieldName("data"); + gen.writeRaw(":"); + gen.writeRaw(bean.getData()); - /** - * Write all the value pairs suppressing null values. - *

- * We are intentionally keeping the same new/old structure for both inserts and updates. - *

- */ - protected void writeValuePairs(BeanChange bean, JsonGenerator gen) throws IOException { - - for (Map.Entry entry : bean.getValues().entrySet()) { - gen.writeFieldName(entry.getKey()); - gen.writeStartObject(); - ValuePair value = entry.getValue(); - Object newValue = value.getNewValue(); - if (newValue != null) { - gen.writeFieldName("new"); - json.writeScalar(gen, newValue); + String oldData = bean.getOldData(); + if (oldData != null) { + gen.writeRaw(",\"oldData\":"); + gen.writeRaw(oldData); } - Object oldValue = value.getOldValue(); - if (oldValue != null) { - gen.writeFieldName("old"); - json.writeScalar(gen, oldValue); - } - gen.writeEndObject(); } } diff --git a/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogListener.java b/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogListener.java index b903c89bf..0168fa222 100644 --- a/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogListener.java +++ b/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogListener.java @@ -10,15 +10,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.StringWriter; -import java.util.List; import java.util.Properties; /** - * Logs the change sets in JSON to logger named io.ebean.ChangeLog. - *

- * The logged entries duplicate/denormalise the transaction details so that each bean change - * is fully contained with the transaction information. - *

+ * Simply logs the change sets in JSON form to logger named io.ebean.ChangeLog. */ public class DefaultChangeLogListener implements ChangeLogListener, Plugin { @@ -30,22 +25,17 @@ public class DefaultChangeLogListener implements ChangeLogListener, Plugin { /** * The named logger we send the change set payload to. Can be externally configured as desired. */ - protected static final Logger changeLog = LoggerFactory.getLogger("io.ebean.ChangeLog"); + private static final Logger changeLog = LoggerFactory.getLogger("io.ebean.ChangeLog"); /** * Used to build the JSON. */ - protected ChangeJsonBuilder jsonBuilder; + private ChangeJsonBuilder jsonBuilder; /** * A bigger default buffer for bean inserts and updates (that have value pairs). */ - protected int defaultBufferSize = 400; - - /** - * Expected to be a reasonable buffer size for deletes (which do not have value pairs). - */ - protected int defaultDeleteBufferSize = 250; + private int defaultBufferSize = 400; public DefaultChangeLogListener() { } @@ -79,13 +69,11 @@ public class DefaultChangeLogListener implements ChangeLogListener, Plugin { @Override public void log(ChangeSet changeSet) { - List changes = changeSet.getChanges(); - for (int i = 0; i < changes.size(); i++) { + for (BeanChange beanChange : changeSet.getChanges()) { // log each bean change as a separate log entry - BeanChange beanChange = changes.get(i); try { StringWriter writer = new StringWriter(getBufferSize(beanChange)); - jsonBuilder.writeBeanJson(writer, beanChange, changeSet, i); + jsonBuilder.writeBeanJson(writer, beanChange, changeSet); changeLog.info(writer.toString()); } catch (Exception e) { logger.error("Exception logging beanChange " + beanChange.toString(), e); @@ -96,9 +84,9 @@ public class DefaultChangeLogListener implements ChangeLogListener, Plugin { /** * Return a decent buffer size based on the bean change. */ - protected int getBufferSize(BeanChange beanChange) { + private int getBufferSize(BeanChange beanChange) { - return ChangeType.DELETE == beanChange.getType() ? defaultDeleteBufferSize : defaultBufferSize; + return ChangeType.DELETE == beanChange.getEvent() ? 250 : defaultBufferSize; } } diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 9e2c0c454..3292e5022 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -51,16 +51,8 @@ import io.ebean.plugin.Plugin; import io.ebean.plugin.SpiServer; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; -import io.ebeaninternal.api.LoadBeanRequest; -import io.ebeaninternal.api.LoadManyRequest; -import io.ebeaninternal.api.ScopeTrans; -import io.ebeaninternal.api.ScopedTransaction; -import io.ebeaninternal.api.SpiBackgroundExecutor; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.api.*; import io.ebeaninternal.api.SpiQuery.Type; -import io.ebeaninternal.api.SpiTransaction; -import io.ebeaninternal.api.TransactionEventTable; import io.ebeaninternal.server.autotune.AutoTuneService; import io.ebeaninternal.server.core.timezone.DataTimeZone; import io.ebeaninternal.server.deploy.BeanDescriptor; @@ -179,7 +171,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final EncryptKeyManager encryptKeyManager; - private final JsonContext jsonContext; + private final SpiJsonContext jsonContext; private final DocumentStore documentStore; @@ -2209,6 +2201,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return jsonContext; } + @Override + public SpiJsonContext jsonExtended() { + return jsonContext; + } + @Override public void collectQueryStats(ObjectGraphNode node, long loadedBeanCount, long timeMicros) { diff --git a/src/main/java/io/ebeaninternal/server/core/DiffHelp.java b/src/main/java/io/ebeaninternal/server/core/DiffHelp.java index 1634fe84a..b879727db 100644 --- a/src/main/java/io/ebeaninternal/server/core/DiffHelp.java +++ b/src/main/java/io/ebeaninternal/server/core/DiffHelp.java @@ -3,12 +3,7 @@ package io.ebeaninternal.server.core; import io.ebean.ValuePair; import io.ebean.bean.EntityBean; import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.Map; /** @@ -54,51 +49,4 @@ public class DiffHelp { return desc.diff((EntityBean) newBean, (EntityBean) oldBean); } - - /** - * Flattens an existing diff map converting assoc one beans into the associated id changes. - */ - public static Map flatten(Map values, BeanDescriptor desc) { - - Map flattened = null; - - Iterator> iterator = values.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - BeanProperty beanProperty = desc.getBeanProperty(entry.getKey()); - if (beanProperty instanceof BeanPropertyAssocMany) { - // filter out assoc many bean properties - iterator.remove(); - - } else if (beanProperty instanceof BeanPropertyAssocOne) { - BeanPropertyAssocOne assoc = (BeanPropertyAssocOne) beanProperty; - if (!assoc.isEmbedded()) { - // flatten for assoc one beans - if (flattened == null) { - flattened = new LinkedHashMap<>(); - } - flattenToId(flattened, entry, beanProperty, assoc); - iterator.remove(); - } - } - } - - if (flattened != null) { - values.putAll(flattened); - } - - return values; - } - - private static void flattenToId(Map flattened, Map.Entry entry, BeanProperty beanProperty, BeanPropertyAssocOne assoc) { - - BeanDescriptor oneDesc = assoc.getTargetDescriptor(); - - ValuePair value = entry.getValue(); - Object newId = value.getNewValue() == null ? null : oneDesc.getId((EntityBean) value.getNewValue()); - Object oldId = value.getOldValue() == null ? null : oneDesc.getId((EntityBean) value.getOldValue()); - - String propName = beanProperty.getName() + "." + oneDesc.getIdProperty().getName(); - flattened.put(propName, new ValuePair(newId, oldId)); - } } diff --git a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java index c426a9819..bc8c19964 100644 --- a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java @@ -1,9 +1,10 @@ package io.ebeaninternal.server.core; +import com.fasterxml.jackson.core.JsonFactory; import io.ebean.ExpressionFactory; +import io.ebean.annotation.Platform; import io.ebean.cache.ServerCacheManager; import io.ebean.config.ExternalTransactionManager; -import io.ebean.annotation.Platform; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbHistorySupport; @@ -14,9 +15,9 @@ import io.ebean.event.readaudit.ReadAuditLogger; import io.ebean.event.readaudit.ReadAuditPrepare; import io.ebean.plugin.Plugin; import io.ebean.plugin.SpiServer; -import io.ebean.text.json.JsonContext; import io.ebeaninternal.api.SpiBackgroundExecutor; import io.ebeaninternal.api.SpiEbeanServer; +import io.ebeaninternal.api.SpiJsonContext; import io.ebeaninternal.server.autotune.AutoTuneService; import io.ebeaninternal.server.autotune.service.AutoTuneServiceFactory; import io.ebeaninternal.server.cache.DefaultCacheAdapter; @@ -60,7 +61,6 @@ import io.ebeanservice.docstore.api.DocStoreFactory; import io.ebeanservice.docstore.api.DocStoreIntegration; import io.ebeanservice.docstore.api.DocStoreUpdateProcessor; import io.ebeanservice.docstore.none.NoneDocStoreFactory; -import com.fasterxml.jackson.core.JsonFactory; import org.avaje.datasource.DataSourcePool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -261,7 +261,7 @@ public class InternalConfiguration { } } - public JsonContext createJsonContext(SpiEbeanServer server) { + public SpiJsonContext createJsonContext(SpiEbeanServer server) { return new DJsonContext(server, jsonFactory, typeManager); } diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index cd45991b9..778f2a480 100644 --- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -385,7 +385,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe */ @Override @SuppressWarnings("unchecked") - public Set findSet() { + public Set findSet() { return (Set) queryEngine.findMany(this); } @@ -393,7 +393,8 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe * Execute the query as findMap. */ @Override - public Map findMap() { + @SuppressWarnings("unchecked") + public Map findMap() { String mapKey = query.getMapKey(); if (mapKey == null) { BeanProperty idProp = beanDescriptor.getIdProperty(); @@ -403,7 +404,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe throw new PersistenceException("No mapKey specified for query"); } } - return (Map) queryEngine.findMany(this); + return (Map) queryEngine.findMany(this); } /** @@ -480,7 +481,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe } else { cacheKey = query.queryHash(); } - + if (!query.getUseQueryCache().isGet()) { return null; } diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index ca35cfb54..e7270f01e 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -828,17 +828,20 @@ public final class PersistRequestBean extends PersistRequest implements BeanP @Override public void postExecute() { - changeLog(); - if (controller != null) { controllerPost(); } setNotifyCache(); - if (type == Type.UPDATE && (notifyCache || docStoreMode == DocStoreMode.UPDATE)) { + boolean isChangeLog = beanDescriptor.isChangeLog(); + if (type == Type.UPDATE && (isChangeLog || notifyCache || docStoreMode == DocStoreMode.UPDATE)) { // get the dirty properties for update notification to the doc store dirtyProperties = intercept.getDirtyProperties(); } + if (isChangeLog) { + changeLog(); + } + // if bean persisted again then should result in an update intercept.setLoaded(); if (isInsert()) { @@ -1174,4 +1177,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return now; } + /** + * Return true if this is a stateless update request (in which case it doesn't really have 'old values'). + */ + public boolean isStatelessUpdate() { + return statelessUpdate; + } } diff --git a/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java index 88d51432d..9d37205ba 100644 --- a/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -101,12 +101,12 @@ public interface SpiOrmQueryRequest extends DocQueryRequest { /** * Execute the query as findSet. */ - Set findSet(); + Set findSet(); /** * Execute the query as findMap. */ - Map findMap(); + Map findMap(); /** * Execute the findSingleAttributeList query. diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanChangeJson.java b/src/main/java/io/ebeaninternal/server/deploy/BeanChangeJson.java new file mode 100644 index 000000000..ae77429a0 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanChangeJson.java @@ -0,0 +1,108 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.PersistenceIOException; +import io.ebean.bean.BeanDiffVisitor; +import io.ebeaninternal.server.text.json.SpiJsonWriter; +import io.ebeaninternal.server.util.ArrayStack; + +import java.io.IOException; +import java.io.StringWriter; + +/** + * Builds the 'new values' and 'old values' in JSON form for ChangeLog. + */ +class BeanChangeJson implements BeanDiffVisitor { + + private final StringWriter newData; + private final StringWriter oldData; + + private final SpiJsonWriter newJson; + private final SpiJsonWriter oldJson; + + private final ArrayStack> stack = new ArrayStack<>(); + + private BeanDescriptor descriptor; + + BeanChangeJson(BeanDescriptor descriptor, boolean statelessUpdate) { + this.descriptor = descriptor; + this.newData = new StringWriter(200); + this.newJson = descriptor.createJsonWriter(newData); + newJson.writeStartObject(); + + if (statelessUpdate) { + this.oldJson = null; + this.oldData = null; + } else { + this.oldData = new StringWriter(200); + this.oldJson = descriptor.createJsonWriter(oldData); + oldJson.writeStartObject(); + } + } + + @Override + public void visit(int position, Object newVal, Object oldVal) { + + try { + BeanProperty prop = descriptor.propertiesIndex[position]; + if (prop.isDbUpdatable()) { + prop.jsonWriteValue(newJson, newVal); + if (oldJson != null) { + prop.jsonWriteValue(oldJson, oldVal); + } + } + } catch (IOException e) { + throw new PersistenceIOException(e); + } + } + + @Override + public void visitPush(int position) { + stack.push(descriptor); + + BeanPropertyAssocOne embedded = (BeanPropertyAssocOne)descriptor.propertiesIndex[position]; + descriptor = embedded.getTargetDescriptor(); + newJson.writeStartObject(embedded.getName()); + if (oldJson != null) { + oldJson.writeStartObject(embedded.getName()); + } + } + + @Override + public void visitPop() { + newJson.writeEndObject(); + if (oldJson != null) { + oldJson.writeEndObject(); + } + descriptor = stack.pop(); + } + + /** + * Flush the buffers. + */ + void flush() { + try { + newJson.writeEndObject(); + newJson.gen().flush(); + if (oldJson != null) { + oldJson.writeEndObject(); + oldJson.gen().flush(); + } + } catch (IOException e) { + throw new PersistenceIOException(e); + } + } + + /** + * Return the new values JSON. + */ + String newJson() { + return newData.toString(); + } + + /** + * Return the old values JSON. + */ + String oldJson() { + return oldData == null ? null : oldData.toString(); + } +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java index 4d05dd0c9..1de0e6d24 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java @@ -7,7 +7,7 @@ import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollectionAdd; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import java.io.IOException; @@ -62,6 +62,6 @@ public interface BeanCollectionHelp { /** * Write the collection out as json. */ - void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException; + void jsonWrite(SpiJsonWriter ctx, String name, Object collection, boolean explicitInclude) throws IOException; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index e23d17f45..4972cff0f 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -47,7 +47,6 @@ import io.ebeaninternal.server.cache.CachedBeanData; import io.ebeaninternal.server.cache.CachedManyIds; import io.ebeaninternal.server.core.CacheOptions; import io.ebeaninternal.server.core.DefaultSqlUpdate; -import io.ebeaninternal.server.core.DiffHelp; import io.ebeaninternal.server.core.InternString; import io.ebeaninternal.server.core.PersistRequest; import io.ebeaninternal.server.core.PersistRequestBean; @@ -67,7 +66,7 @@ import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import io.ebeaninternal.server.query.SplitName; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.text.json.ReadJson; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import io.ebeaninternal.server.type.DataBind; import io.ebeaninternal.util.SortByClause; import io.ebeaninternal.util.SortByClauseParser; @@ -89,7 +88,6 @@ import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -810,11 +808,6 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { * Return true if this request should be included in the change log. */ public BeanChange getChangeLogBean(PersistRequestBean request) { - - if (changeLogFilter == null) { - return null; - } - PersistRequest.Type type = request.getType(); switch (request.getType()) { case INSERT: return changeLogFilter.includeInsert(request) ? insertBeanChange(request) : null; @@ -828,30 +821,75 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } } + private BeanChange beanChange(ChangeType type, Object id, String data, String oldData) { + Object tenantId = ebeanServer.currentTenantId(); + return new BeanChange(name, tenantId, id, type, data, oldData); + } + /** * Return the bean change for a delete. */ private BeanChange deleteBeanChange(PersistRequestBean request) { - return newBeanChange(request.getBeanId(), ChangeType.DELETE, Collections.emptyMap()); + return beanChange(ChangeType.DELETE, request.getBeanId(), null, null); } /** - * Return the bean change for an update. + * Return the bean change for an update generating 'new values' and 'old values' in JSON form. */ private BeanChange updateBeanChange(PersistRequestBean request) { - return newBeanChange(request.getBeanId(), ChangeType.UPDATE, diffFlatten(request.getEntityBeanIntercept().getDirtyValues())); + + try { + BeanChangeJson changeJson = new BeanChangeJson(this, request.isStatelessUpdate()); + request.getEntityBeanIntercept().addDirtyPropertyValues(changeJson); + changeJson.flush(); + + return beanChange(ChangeType.UPDATE, request.getBeanId(), changeJson.newJson(), changeJson.oldJson()); + + } catch (RuntimeException e) { + logger.error("Failed to write ChangeLog entry for update", e); + return null; + } } /** * Return the bean change for an insert. */ private BeanChange insertBeanChange(PersistRequestBean request) { - return newBeanChange(request.getBeanId(), ChangeType.INSERT, diffForInsert(request.getEntityBean())); + + try { + StringWriter writer = new StringWriter(200); + SpiJsonWriter jsonWriter = createJsonWriter(writer); + + jsonWriteForInsert(jsonWriter, request.getEntityBean()); + jsonWriter.gen().flush(); + + return beanChange(ChangeType.INSERT, request.getBeanId(), writer.toString(), null); + + } catch (IOException e) { + logger.error("Failed to write ChangeLog entry for insert", e); + return null; + } } - private BeanChange newBeanChange(Object id, ChangeType changeType, Map values) { - Object tenantId = ebeanServer.currentTenantId(); - return new BeanChange(getBaseTable(), tenantId, id, changeType, values); + SpiJsonWriter createJsonWriter(StringWriter writer) { + return ebeanServer.jsonExtended().createJsonWriter(writer); + } + + /** + * Populate the diff for inserts with flattened non-null property values. + */ + protected void jsonWriteForInsert(SpiJsonWriter jsonWriter, EntityBean newBean) throws IOException { + jsonWriter.writeStartObject(); + for (BeanProperty prop : propertiesBaseScalar) { + prop.jsonWriteForInsert(jsonWriter, newBean); + } + for (BeanPropertyAssocOne prop : propertiesOne) { + prop.jsonWriteForInsert(jsonWriter, newBean); + } + for (BeanPropertyAssocOne prop : propertiesEmbedded) { + prop.jsonWriteForInsert(jsonWriter, newBean); + } + jsonWriter.writeEndObject(); } public SqlUpdate deleteById(Object id, List idList, boolean softDelete) { @@ -2856,45 +2894,6 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } } - /** - * Flatten the diff that comes from the entity bean intercept. - */ - Map diffFlatten(Map diff) { - return DiffHelp.flatten(diff, this); - } - - /** - * Return a map of the differences between a and b. - *

- * A and B must be of the same type. B can be null, in which case the 'dirty - * diff' of a is returned. - *

- *

- * This intentionally does not include as OneToMany or ManyToMany properties. - *

- */ - public Map diffForInsert(EntityBean newBean) { - - Map map = new LinkedHashMap<>(); - diffForInsert(null, map, newBean); - return map; - } - - /** - * Populate the diff for inserts with flattened non-null property values. - */ - public void diffForInsert(String prefix, Map map, EntityBean newBean) { - for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) { - aPropertiesBaseScalar.diffForInsert(prefix, map, newBean); - } - for (BeanPropertyAssocOne aPropertiesOne : propertiesOne) { - aPropertiesOne.diffForInsert(prefix, map, newBean); - } - for (BeanPropertyAssocOne aPropertiesEmbedded : propertiesEmbedded) { - aPropertiesEmbedded.diffForInsert(prefix, map, newBean); - } - } - /** * Return the diff comparing the bean values. */ @@ -3071,23 +3070,23 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return propertiesGenUpdate; } - public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + public void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { jsonHelp.jsonWriteDirty(writeJson, bean, dirtyProps); } - protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + protected void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps); } - public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException { + public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean) throws IOException { jsonHelp.jsonWrite(writeJson, bean, null); } - public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException { + public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException { jsonHelp.jsonWrite(writeJson, bean, key); } - protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException { + protected void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) throws IOException { jsonHelp.jsonWriteProperties(writeJson, bean); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java index 0e02e805f..ad9d640a3 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java @@ -6,8 +6,7 @@ import com.fasterxml.jackson.core.JsonToken; import io.ebean.bean.EntityBean; import io.ebean.text.json.EJson; import io.ebeaninternal.server.text.json.ReadJson; -import io.ebeaninternal.server.text.json.WriteJson; -import io.ebeaninternal.server.text.json.WriteJson.WriteBean; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import java.io.IOException; import java.util.LinkedHashMap; @@ -24,7 +23,7 @@ public class BeanDescriptorJsonHelp { this.inheritInfo = desc.inheritInfo; } - public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException { + public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException { writeJson.writeStartObject(key); @@ -43,13 +42,12 @@ public class BeanDescriptorJsonHelp { writeJson.writeEndObject(); } - protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException { + protected void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) throws IOException { - WriteBean writeBean = writeJson.createWriteBean(desc, bean); - writeBean.write(writeJson); + writeJson.writeBean(desc, bean); } - public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + public void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { if (inheritInfo == null) { jsonWriteDirtyProperties(writeJson, bean, dirtyProps); @@ -58,7 +56,7 @@ public class BeanDescriptorJsonHelp { } } - protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + protected void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { writeJson.writeStartObject(null); // render the dirty properties diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java index 0cf435615..1f5bde3c8 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java @@ -8,7 +8,7 @@ import io.ebean.bean.BeanCollectionAdd; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; import io.ebean.common.BeanList; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import java.io.IOException; import java.util.ArrayList; @@ -125,7 +125,7 @@ public final class BeanListHelp implements BeanCollectionHelp { } @Override - public void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException { + public void jsonWrite(SpiJsonWriter ctx, String name, Object collection, boolean explicitInclude) throws IOException { List list; if (collection instanceof BeanCollection) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java index 202f0a464..c91b4d22e 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java @@ -8,7 +8,7 @@ import io.ebean.bean.BeanCollectionAdd; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; import io.ebean.common.BeanMap; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import java.io.IOException; import java.util.LinkedHashMap; @@ -160,7 +160,7 @@ public final class BeanMapHelp implements BeanCollectionHelp { } @Override - public void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException { + public void jsonWrite(SpiJsonWriter ctx, String name, Object collection, boolean explicitInclude) throws IOException { Map map; if (collection instanceof BeanCollection) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index f0255bb53..a85b40da1 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -24,7 +24,7 @@ import io.ebeaninternal.server.query.SplitName; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.query.SqlJoinType; import io.ebeaninternal.server.text.json.ReadJson; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import io.ebeaninternal.server.type.DataBind; import io.ebeaninternal.server.type.ScalarType; import io.ebeaninternal.server.type.ScalarTypeBoolean; @@ -1360,21 +1360,55 @@ public class BeanProperty implements ElPropertyValue, Property { return jsonSerialize; } - @SuppressWarnings(value = "unchecked") - public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException { + /** + * JSON write the property for 'insert only depth'. + */ + @SuppressWarnings("unchecked") + public void jsonWriteForInsert(SpiJsonWriter writeJson, EntityBean bean) throws IOException { if (!jsonSerialize) { return; } - Object value = getValueIntercept(bean); + Object value = getValue(bean); + if (value != null) { + jsonWriteScalar(writeJson, value); + } + } + + /** + * JSON write the property value. + */ + public void jsonWriteValue(SpiJsonWriter writeJson, Object value) throws IOException { + if (!jsonSerialize) { + return; + } + jsonWriteVal(writeJson, value); + } + + /** + * JSON write the bean property. + */ + public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean) throws IOException { + if (!jsonSerialize) { + return; + } + jsonWriteVal(writeJson, getValueIntercept(bean)); + } + + @SuppressWarnings("unchecked") + private void jsonWriteVal(SpiJsonWriter writeJson, Object value) throws IOException { if (value == null) { writeJson.writeNullField(name); } else { - if (scalarType != null) { - writeJson.writeFieldName(name); - scalarType.jsonWrite(writeJson.gen(), value); - } else { - writeJson.writeValueUsingObjectMapper(name, value); - } + jsonWriteScalar(writeJson, value); + } + } + + private void jsonWriteScalar(SpiJsonWriter writeJson, Object value) throws IOException { + if (scalarType != null) { + writeJson.writeFieldName(name); + scalarType.jsonWrite(writeJson.gen(), value); + } else { + writeJson.writeValueUsingObjectMapper(name, value); } } @@ -1407,17 +1441,6 @@ public class BeanProperty implements ElPropertyValue, Property { } } - /** - * Populate diff map for insert if the property is not null. - */ - public void diffForInsert(String prefix, Map map, EntityBean newBean) { - Object newVal = (newBean == null) ? null : getValue(newBean); - if (newVal != null) { - String propName = (prefix == null) ? name : prefix + "." + name; - map.put(propName, new ValuePair(newVal, null)); - } - } - /** * Populate diff map comparing the property values between the beans. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 4d5193487..7b3b54718 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -20,7 +20,7 @@ import io.ebeaninternal.server.el.ElPropertyChainBuilder; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.text.json.ReadJson; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -962,8 +962,15 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { return null != targetDescriptor.getId(otherBean); } + /** + * Skip JSON write value for ToMany property. + */ + public void jsonWriteValue(SpiJsonWriter writeJson, Object value) throws IOException { + // do nothing, exclude ToMany properties + } + @Override - public void jsonWrite(WriteJson ctx, EntityBean bean) throws IOException { + public void jsonWrite(SpiJsonWriter ctx, EntityBean bean) throws IOException { if (!this.jsonSerialize) { return; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java index aa115c54b..258f74ae5 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java @@ -49,7 +49,7 @@ public class BeanPropertyAssocManyJsonHelp { return; } if (JsonToken.START_ARRAY != event) { - throw new JsonParseException("Unexpected token " + event + " - expecting start_array ", parser.getCurrentLocation()); + throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array "); } if (many.isTransient()) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index c1154baa0..21451dfb4 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -18,7 +18,7 @@ import io.ebeaninternal.server.query.SplitName; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.query.SqlJoinType; import io.ebeaninternal.server.text.json.ReadJson; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import javax.persistence.PersistenceException; import java.io.IOException; @@ -351,22 +351,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { return importedPrimaryKey; } - @Override - public void diffForInsert(String prefix, Map map, EntityBean newBean) { - Object newEmb = (newBean == null) ? null : getValue(newBean); - if (newEmb != null) { - prefix = (prefix == null) ? name : prefix + "." + name; - if (embedded) { - getTargetDescriptor().diffForInsert(prefix, map, (EntityBean) newEmb); - } else { - // we are only interested in the Id value - BeanDescriptor targetDescriptor = getTargetDescriptor(); - BeanProperty idProperty = targetDescriptor.getIdProperty(); - idProperty.diffForInsert(prefix, map, (EntityBean) newEmb); - } - } - } - @Override public void diff(String prefix, Map map, EntityBean newBean, EntityBean oldBean) { @@ -676,8 +660,57 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } } + /** + * JSON write property (non-recursive to other beans). + */ @Override - public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException { + public void jsonWriteForInsert(SpiJsonWriter writeJson, EntityBean bean) throws IOException { + + if (!jsonSerialize) { + return; + } + jsonWriteBean(writeJson, getValue(bean)); + } + + /** + * JSON write property value (non-recursive to other beans). + */ + @Override + public void jsonWriteValue(SpiJsonWriter writeJson, Object value) throws IOException { + if (!jsonSerialize) { + return; + } + jsonWriteBean(writeJson, value); + } + + private void jsonWriteBean(SpiJsonWriter writeJson, Object value) throws IOException { + + if (value instanceof EntityBean) { + if (embedded) { + writeJson.writeFieldName(name); + BeanDescriptor refDesc = descriptor.getBeanDescriptor(value.getClass()); + refDesc.jsonWriteForInsert(writeJson, (EntityBean)value); + + } else { + jsonWriteTargetId(writeJson, (EntityBean)value); + } + } + } + + /** + * Just write the Id property of the ToOne property. + */ + private void jsonWriteTargetId(SpiJsonWriter writeJson, EntityBean childBean) throws IOException { + BeanProperty idProperty = targetDescriptor.getIdProperty(); + if (idProperty != null) { + writeJson.writeStartObject(name); + idProperty.jsonWriteForInsert(writeJson, childBean); + writeJson.writeEndObject(); + } + } + + @Override + public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean) throws IOException { if (!jsonSerialize) { return; @@ -688,7 +721,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { writeJson.writeNullField(name); } else { - //noinspection StatementWithEmptyBody if (writeJson.isParentBean(value)) { // bi-directional and already rendered parent diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java index 8f93d4984..f191b919f 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java @@ -8,7 +8,7 @@ import io.ebean.bean.BeanCollectionAdd; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; import io.ebean.common.BeanSet; -import io.ebeaninternal.server.text.json.WriteJson; +import io.ebeaninternal.server.text.json.SpiJsonWriter; import java.io.IOException; import java.util.LinkedHashSet; @@ -124,7 +124,7 @@ public final class BeanSetHelp implements BeanCollectionHelp { } @Override - public void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException { + public void jsonWrite(SpiJsonWriter ctx, String name, Object collection, boolean explicitInclude) throws IOException { Set set; if (collection instanceof BeanCollection) { diff --git a/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java b/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java index ec8b84c43..cc4302d20 100644 --- a/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java +++ b/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java @@ -1,26 +1,26 @@ package io.ebeaninternal.server.text.json; -import io.ebean.FetchPath; -import io.ebean.bean.EntityBean; -import io.ebean.config.JsonConfig; -import io.ebean.plugin.BeanType; -import io.ebean.text.json.EJson; -import io.ebean.text.json.JsonContext; -import io.ebean.text.json.JsonIOException; -import io.ebean.text.json.JsonReadOptions; -import io.ebean.text.json.JsonWriteBeanVisitor; -import io.ebean.text.json.JsonWriteOptions; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.type.TypeManager; -import io.ebeaninternal.util.ParamTypeHelper; -import io.ebeaninternal.util.ParamTypeHelper.ManyType; -import io.ebeaninternal.util.ParamTypeHelper.TypeInfo; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import io.ebean.FetchPath; +import io.ebean.bean.EntityBean; +import io.ebean.config.JsonConfig; +import io.ebean.plugin.BeanType; +import io.ebean.text.json.EJson; +import io.ebean.text.json.JsonIOException; +import io.ebean.text.json.JsonReadOptions; +import io.ebean.text.json.JsonWriteBeanVisitor; +import io.ebean.text.json.JsonWriteOptions; +import io.ebeaninternal.api.SpiEbeanServer; +import io.ebeaninternal.api.SpiJsonContext; +import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.type.TypeManager; +import io.ebeaninternal.util.ParamTypeHelper; +import io.ebeaninternal.util.ParamTypeHelper.ManyType; +import io.ebeaninternal.util.ParamTypeHelper.TypeInfo; import java.io.IOException; import java.io.Reader; @@ -39,7 +39,7 @@ import java.util.Set; /** * Default implementation of JsonContext. */ -public class DJsonContext implements JsonContext { +public class DJsonContext implements SpiJsonContext { private final SpiEbeanServer server; @@ -179,7 +179,7 @@ public class DJsonContext implements JsonContext { if (currentToken != JsonToken.START_ARRAY) { JsonToken event = src.nextToken(); if (event != JsonToken.START_ARRAY) { - throw new JsonParseException("Expecting start_array event but got " + event, src.getCurrentLocation()); + throw new JsonParseException(src, "Expecting start_array event but got " + event); } } @@ -336,9 +336,23 @@ public class DJsonContext implements JsonContext { BeanDescriptor d = getDescriptor(value.getClass()); WriteJson writeJson = createWriteJson(gen, options); d.jsonWrite(writeJson, (EntityBean) value, null); + + } else { + jsonScalar.write(gen, value); } } + @Override + public SpiJsonWriter createJsonWriter(Writer writer) { + JsonGenerator generator = createGenerator(writer); + return createJsonWriter(generator, null); + } + + @Override + public SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options) { + return createWriteJson(gen, options); + } + private WriteJson createWriteJson(JsonGenerator gen, JsonWriteOptions options) { FetchPath pathProps = (options == null) ? null : options.getPathProperties(); Map> visitors = (options == null) ? null : options.getVisitorMap(); diff --git a/src/main/java/io/ebeaninternal/server/text/json/SpiJsonWriter.java b/src/main/java/io/ebeaninternal/server/text/json/SpiJsonWriter.java new file mode 100644 index 000000000..ebcb88379 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/text/json/SpiJsonWriter.java @@ -0,0 +1,68 @@ +package io.ebeaninternal.server.text.json; + +import io.ebean.bean.EntityBean; +import io.ebean.text.json.JsonWriter; +import io.ebeaninternal.server.deploy.BeanDescriptor; + +import java.util.Collection; + +/** + * Internal API extensions for JSON writing of Bean properties. + */ +public interface SpiJsonWriter extends JsonWriter { + + /** + * Return true if the value is a parent bean. + */ + boolean isParentBean(Object value); + + /** + * Start an assoc one path. + */ + void beginAssocOne(String name, EntityBean bean); + + /** + * End an assoc one path. + */ + void endAssocOne(); + + /** + * Return true if the many property should be included. + */ + Boolean includeMany(String name); + + /** + * Push the parent bean of a ToMany. + */ + void pushParentBeanMany(EntityBean bean); + + /** + * Pop the parent of a ToMany. + */ + void popParentBeanMany(); + + /** + * Write the collection. + */ + void toJson(String name, Collection collection); + + /** + * Start a Many. + */ + void beginAssocMany(String name); + + /** + * End a Many. + */ + void endAssocMany(); + + /** + * Write value using underlying Jaskson object mapper if available. + */ + void writeValueUsingObjectMapper(String name, Object value); + + /** + * Write the bean properties. + */ + void writeBean(BeanDescriptor desc, EntityBean bean); +} diff --git a/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java b/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java index 7c5e02eb3..7e65c7829 100644 --- a/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java +++ b/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java @@ -1,18 +1,17 @@ package io.ebeaninternal.server.text.json; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; import io.ebean.FetchPath; import io.ebean.bean.EntityBean; import io.ebean.config.JsonConfig; import io.ebean.text.json.EJson; import io.ebean.text.json.JsonIOException; import io.ebean.text.json.JsonWriteBeanVisitor; -import io.ebean.text.json.JsonWriter; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.util.ArrayStack; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; @@ -21,7 +20,7 @@ import java.util.Collection; import java.util.Map; import java.util.Set; -public class WriteJson implements JsonWriter { +public class WriteJson implements SpiJsonWriter { private final SpiEbeanServer server; @@ -347,7 +346,7 @@ public class WriteJson implements JsonWriter { return !parentBeans.isEmpty() && parentBeans.contains(bean); } - public void pushParentBeanMany(Object parentBean) { + public void pushParentBeanMany(EntityBean parentBean) { parentBeans.push(parentBean); } @@ -355,7 +354,7 @@ public class WriteJson implements JsonWriter { parentBeans.pop(); } - public void beginAssocOne(String key, Object bean) { + public void beginAssocOne(String key, EntityBean bean) { parentBeans.push(bean); pathStack.pushPathKey(key); } @@ -384,7 +383,12 @@ public class WriteJson implements JsonWriter { } } - public WriteBean createWriteBean(BeanDescriptor desc, EntityBean bean) { + @Override + public void writeBean(BeanDescriptor desc, EntityBean bean) { + createWriteBean(desc, bean).write(this); + } + + private WriteBean createWriteBean(BeanDescriptor desc, EntityBean bean) { String path = pathStack.peekWithNull(); JsonWriteBeanVisitor visitor = (visitors == null) ? null : visitors.get(path); @@ -436,7 +440,7 @@ public class WriteJson implements JsonWriter { final Set currentIncludeProps; final BeanDescriptor desc; final EntityBean currentBean; - + @SuppressWarnings("rawtypes") final JsonWriteBeanVisitor visitor; diff --git a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index 380eef448..9f9180a22 100644 --- a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -735,6 +735,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public SpiJsonContext jsonExtended() { + return null; + } + @Override public int saveAll(Collection beans) throws OptimisticLockException { return 0; diff --git a/src/test/java/io/ebeaninternal/server/changelog/ChangeJsonBuilderTest.java b/src/test/java/io/ebeaninternal/server/changelog/ChangeJsonBuilderTest.java index d4d714482..66b8ed660 100644 --- a/src/test/java/io/ebeaninternal/server/changelog/ChangeJsonBuilderTest.java +++ b/src/test/java/io/ebeaninternal/server/changelog/ChangeJsonBuilderTest.java @@ -29,7 +29,7 @@ public class ChangeJsonBuilderTest extends BaseTestCase { List changes = changeSet.getChanges(); for (int i = 0; i < changes.size(); i++) { StringWriter buffer = new StringWriter(); - builder.writeBeanJson(buffer, changes.get(i), changeSet, i); + builder.writeBeanJson(buffer, changes.get(i), changeSet); assertThat(buffer.toString()).contains("\"source\""); assertThat(buffer.toString()).contains("\"userId\""); diff --git a/src/test/java/io/ebeaninternal/server/changelog/Helper.java b/src/test/java/io/ebeaninternal/server/changelog/Helper.java index 86228cdf4..356e4774f 100644 --- a/src/test/java/io/ebeaninternal/server/changelog/Helper.java +++ b/src/test/java/io/ebeaninternal/server/changelog/Helper.java @@ -1,6 +1,5 @@ package io.ebeaninternal.server.changelog; -import io.ebean.ValuePair; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; import io.ebean.event.changelog.ChangeType; @@ -8,7 +7,6 @@ import io.ebean.event.changelog.TxnState; import org.jetbrains.annotations.NotNull; import java.sql.Timestamp; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -37,32 +35,40 @@ public class Helper { @NotNull private BeanChange createInsert(long startId) { - Map values = new LinkedHashMap<>(); - values.put("name", new ValuePair("rob", null)); - values.put("modified", new ValuePair(new Timestamp(System.currentTimeMillis()), null)); - BeanChange bean = new BeanChange("mytable", null, startId + 1, ChangeType.INSERT, null); - bean.setValues(values); - return bean; + Map values = new LinkedHashMap<>(); + values.put("name", "rob"); + values.put("modified", new Timestamp(System.currentTimeMillis())); + + Map oldValues = new LinkedHashMap<>(); + oldValues.put("name", "rob"); + oldValues.put("modified", new Timestamp(System.currentTimeMillis() - 100000)); + + return new BeanChange("mytable", null, startId + 1, ChangeType.INSERT, "");//values, oldValues); } @NotNull private BeanChange createUpdate(long startId) { - Map values = new LinkedHashMap<>(); - values.put("name", new ValuePair("jim", "steve")); - values.put("nowHasVal", new ValuePair("wasNull", null)); - values.put("nowNull", new ValuePair(null, "hadVal")); + Map values = new LinkedHashMap<>(); + values.put("name", "jim"); + values.put("nowHasVal", "wasNull"); + values.put("nowNull", null); + values.put("modified", new Timestamp(System.currentTimeMillis())); - values.put("modified", new ValuePair(new Timestamp(System.currentTimeMillis()), null)); - BeanChange bean = new BeanChange("mytable", null, startId + 2, ChangeType.UPDATE, null); - bean.setValues(values); - return bean; + Map oldValues = new LinkedHashMap<>(); + oldValues.put("name", "steve"); + oldValues.put("nowHasVal", null); + oldValues.put("nowNull", "hadVal"); + oldValues.put("modified", new Timestamp(System.currentTimeMillis() - 100000)); + + + return new BeanChange("mytable", null, startId + 2, ChangeType.UPDATE, "");//values, oldValues); } @NotNull private BeanChange createDelete(long startId) { - return new BeanChange("mytable", null, startId + 3, ChangeType.DELETE, new HashMap<>()); + return new BeanChange("mytable", null, startId + 3, ChangeType.DELETE, null); } } diff --git a/src/test/java/io/ebeaninternal/server/core/TestDiffHelpInsertWithEmbedded.java b/src/test/java/io/ebeaninternal/server/core/TestDiffHelpInsertWithEmbedded.java deleted file mode 100644 index 5f277dfec..000000000 --- a/src/test/java/io/ebeaninternal/server/core/TestDiffHelpInsertWithEmbedded.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.ebeaninternal.server.core; - -import io.ebean.BaseTestCase; -import io.ebean.Ebean; -import io.ebean.EbeanServer; -import io.ebean.ValuePair; -import io.ebean.bean.EntityBean; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import org.tests.model.embedded.EMain; -import org.tests.model.embedded.Eembeddable; -import org.junit.Test; - -import java.util.Map; - -import static org.junit.Assert.*; - -public class TestDiffHelpInsertWithEmbedded extends BaseTestCase { - - EbeanServer server; - - BeanDescriptor emainDesc; - - public TestDiffHelpInsertWithEmbedded() { - server = Ebean.getServer(null); - SpiEbeanServer spiServer = (SpiEbeanServer) server; - emainDesc = spiServer.getBeanDescriptor(EMain.class); - } - - @Test - public void simple() { - - EMain emain1 = createEMain(); - - Map diff = emainDesc.diffForInsert((EntityBean) emain1); - assertEquals(3, diff.size()); - assertEquals("foo", diff.get("name").getNewValue()); - assertEquals(13L, diff.get("version").getNewValue()); - - ValuePair valuePair = diff.get("embeddable.description"); - assertNotNull(valuePair); - assertEquals("bar", valuePair.getNewValue()); - assertNull(valuePair.getOldValue()); - } - - @Test - public void scalarPropertyAsNull() { - - EMain emain1 = createEMain(); - emain1.setName(null); - - Map diff = emainDesc.diffForInsert((EntityBean) emain1); - assertEquals(2, diff.size()); - assertNull(diff.get("name")); - assertEquals(13L, diff.get("version").getNewValue()); - - ValuePair valuePair = diff.get("embeddable.description"); - assertNotNull(valuePair); - assertEquals("bar", valuePair.getNewValue()); - assertNull(valuePair.getOldValue()); - } - - @Test - public void embeddedAsNull() { - - EMain emain1 = createEMain(); - emain1.setEmbeddable(null); - - Map diff = emainDesc.diffForInsert((EntityBean) emain1); - assertEquals(2, diff.size()); - assertEquals("foo", diff.get("name").getNewValue()); - assertEquals(13L, diff.get("version").getNewValue()); - - assertNull(diff.get("embeddable.description")); - } - - - @Test - public void embeddedPropertiesAsNull() { - - EMain emain1 = createEMain(); - emain1.getEmbeddable().setDescription(null); - - Map diff = emainDesc.diffForInsert((EntityBean) emain1); - assertEquals(2, diff.size()); - assertEquals("foo", diff.get("name").getNewValue()); - assertEquals(13L, diff.get("version").getNewValue()); - - assertNull(diff.get("embeddable.description")); - } - - private EMain createEMain() { - - EMain emain = new EMain(); - emain.setName("foo"); - emain.setVersion(13l); - - Eembeddable embeddable = new Eembeddable(); - embeddable.setDescription("bar"); - emain.setEmbeddable(embeddable); - - return emain; - } - -} diff --git a/src/test/java/io/ebeaninternal/server/core/TestDiffHelpInsertSimple.java b/src/test/java/io/ebeaninternal/server/deploy/TestDiffHelpInsertSimple.java similarity index 53% rename from src/test/java/io/ebeaninternal/server/core/TestDiffHelpInsertSimple.java rename to src/test/java/io/ebeaninternal/server/deploy/TestDiffHelpInsertSimple.java index 791425dee..0a76923c2 100644 --- a/src/test/java/io/ebeaninternal/server/core/TestDiffHelpInsertSimple.java +++ b/src/test/java/io/ebeaninternal/server/deploy/TestDiffHelpInsertSimple.java @@ -1,31 +1,31 @@ -package io.ebeaninternal.server.core; +package io.ebeaninternal.server.deploy; import io.ebean.BaseTestCase; import io.ebean.Ebean; import io.ebean.EbeanServer; -import io.ebean.ValuePair; import io.ebean.bean.EntityBean; import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.text.json.SpiJsonWriter; +import org.junit.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; import org.tests.model.basic.Order.Status; -import org.junit.Test; +import java.io.IOException; +import java.io.StringWriter; import java.sql.Date; import java.sql.Timestamp; -import java.util.Map; -import java.util.Set; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; + public class TestDiffHelpInsertSimple extends BaseTestCase { - long firstTime = System.currentTimeMillis() - 10000; + private long firstTime = System.currentTimeMillis() - 10000; - EbeanServer server; + private EbeanServer server; - BeanDescriptor orderDesc; + private BeanDescriptor orderDesc; public TestDiffHelpInsertSimple() { server = Ebean.getServer(null); @@ -45,20 +45,24 @@ public class TestDiffHelpInsertSimple extends BaseTestCase { } @Test - public void basic() { + public void basic() throws IOException { + + Date date = Date.valueOf("2000-01-01"); Order order1 = createBaseOrder(server); + order1.setOrderDate(date); - Map diff = orderDesc.diffForInsert((EntityBean) order1); + StringWriter buffer = new StringWriter(); - assertEquals(4, diff.size()); + SpiJsonWriter jsonWriter = spiEbeanServer().jsonExtended().createJsonWriter(buffer); - Set keySet = diff.keySet(); - assertTrue(keySet.contains("cretime")); - assertTrue(keySet.contains("status")); - assertTrue(keySet.contains("orderDate")); - assertTrue(keySet.contains("customer.id")); - assertFalse(keySet.contains("shipDate")); + orderDesc.jsonWriteForInsert(jsonWriter, (EntityBean) order1); + jsonWriter.gen().flush(); + + String asJson = buffer.toString(); + + assertThat(asJson).startsWith("{\"status\":\"NEW\",\"orderDate\":946638000000,\"cretime\":"); + assertThat(asJson).endsWith(",\"customer\":{\"id\":1234}}"); } } diff --git a/src/test/java/io/ebeaninternal/server/deploy/TestDiffHelpInsertWithEmbedded.java b/src/test/java/io/ebeaninternal/server/deploy/TestDiffHelpInsertWithEmbedded.java new file mode 100644 index 000000000..2cfed3cde --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/deploy/TestDiffHelpInsertWithEmbedded.java @@ -0,0 +1,100 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.EbeanServer; +import io.ebean.bean.EntityBean; +import io.ebeaninternal.api.SpiEbeanServer; +import io.ebeaninternal.server.text.json.SpiJsonWriter; +import org.junit.Test; +import org.tests.model.embedded.EMain; +import org.tests.model.embedded.Eembeddable; + +import java.io.IOException; +import java.io.StringWriter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestDiffHelpInsertWithEmbedded extends BaseTestCase { + + private EbeanServer server; + + private BeanDescriptor emainDesc; + + public TestDiffHelpInsertWithEmbedded() { + server = Ebean.getServer(null); + SpiEbeanServer spiServer = (SpiEbeanServer) server; + emainDesc = spiServer.getBeanDescriptor(EMain.class); + } + + @Test + @SuppressWarnings("unchecked") + public void simple() throws IOException { + + EMain emain1 = createEMain(); + + String asJson = asInsertJson((EntityBean) emain1); + + assertThat(asJson).contains("{\"name\":\"foo\",\"version\":13,\"embeddable\":{\"description\":\"bar\"}}"); + + } + + private String asInsertJson(EntityBean emain1) throws IOException { + StringWriter buffer = new StringWriter(); + + SpiJsonWriter jsonWriter = spiEbeanServer().jsonExtended().createJsonWriter(buffer); + + emainDesc.jsonWriteForInsert(jsonWriter, emain1); + jsonWriter.gen().flush(); + + return buffer.toString(); + } + + @Test + public void scalarPropertyAsNull() throws IOException { + + EMain emain1 = createEMain(); + emain1.setName(null); + + String asJson = asInsertJson((EntityBean) emain1); + + assertThat(asJson).contains("{\"version\":13,\"embeddable\":{\"description\":\"bar\"}}"); + } + + @Test + public void embeddedAsNull() throws IOException { + + EMain emain1 = createEMain(); + emain1.setEmbeddable(null); + + String asJson = asInsertJson((EntityBean) emain1); + + assertThat(asJson).contains("{\"name\":\"foo\",\"version\":13}"); + } + + + @Test + public void embeddedPropertiesAsNull() throws IOException { + + EMain emain1 = createEMain(); + emain1.getEmbeddable().setDescription(null); + + String asJson = asInsertJson((EntityBean) emain1); + + assertThat(asJson).contains("{\"name\":\"foo\",\"version\":13,\"embeddable\":{}}"); + } + + private EMain createEMain() { + + EMain emain = new EMain(); + emain.setName("foo"); + emain.setVersion(13l); + + Eembeddable embeddable = new Eembeddable(); + embeddable.setDescription("bar"); + emain.setEmbeddable(embeddable); + + return emain; + } + +} diff --git a/src/test/java/org/tests/changelog/TestChangeLogInt.java b/src/test/java/org/tests/changelog/TestChangeLogInt.java new file mode 100644 index 000000000..3cd3dfa39 --- /dev/null +++ b/src/test/java/org/tests/changelog/TestChangeLogInt.java @@ -0,0 +1,22 @@ +package org.tests.changelog; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.junit.Test; +import org.tests.model.basic.EBasicChangeLog; + +public class TestChangeLogInt extends BaseTestCase { + + @Test + public void test() { + + EBasicChangeLog bean3 = new EBasicChangeLog(); + bean3.setName("bean3"); + bean3.setShortDescription("bean3 hi"); + Ebean.save(bean3); + + bean3.setName("mod bean3"); + bean3.setShortDescription("update bean3"); + Ebean.save(bean3); + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index a82fd3e8d..4085d320c 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -45,7 +45,7 @@ - log/changeLog.log + log/change-log.log log/changeLog.log.%d{yyyy-MM-dd} 90 @@ -55,15 +55,15 @@ - + - + - + @@ -92,6 +92,6 @@ - +