diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java b/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java index 4594cbe25..8082e049a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java @@ -33,4 +33,6 @@ public interface SpiJsonReader { void beanVisitor(Object bean, Map unmappedProperties); Object readValueUsingObjectMapper(Class propertyType) throws IOException; + + boolean isIntercept(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index 7ab4afe3e..243db22ac 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -1414,7 +1414,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { JsonToken event = ctx.nextToken(); if (JsonToken.VALUE_NULL == event) { if (jsonDeserialize) { - setValue(bean, null); + if (ctx.isIntercept()) { + setValueIntercept(bean, null); + } else { + setValue(bean, null); + } } } else { // expect to read non-null json value @@ -1433,7 +1437,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { } } if (jsonDeserialize) { - setValue(bean, objValue); + if (ctx.isIntercept()) { + setValueIntercept(bean, objValue); + } else { + setValue(bean, objValue); + } } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java index f7ff9fe73..ba06352b7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java @@ -50,7 +50,11 @@ class BeanPropertyAssocManyJsonHelp { if (JsonToken.START_ARRAY != event && JsonToken.START_OBJECT != event) { throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start array or object"); } - many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean)); + if (readJson.isIntercept()) { + many.setValueIntercept(parentBean, many.jsonReadCollection(readJson, parentBean)); + } else { + many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean)); + } } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index e534075f1..a024c1ddb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -785,7 +785,11 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr if (jsonDeserialize && targetDescriptor != null) { T target = (T) value(bean); T assocBean = targetDescriptor.jsonRead(readJson, name, target); - setValue(bean, assocBean); + if (readJson.isIntercept()) { + setValueIntercept(bean, assocBean); + } else { + setValue(bean, assocBean); + } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java index 83b04cb6d..c48ead0ca 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonContext.java @@ -125,7 +125,7 @@ public final class DJsonContext implements SpiJsonContext { BeanDescriptor desc = getDescriptor(cls); try { - return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options)), null, null); + return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), false), null, null); } catch (IOException e) { throw new JsonIOException(e); } @@ -158,10 +158,10 @@ public final class DJsonContext implements SpiJsonContext { @Override public void toBean(T target, JsonParser parser, JsonReadOptions options) throws JsonIOException { - + BeanDescriptor desc = (BeanDescriptor) getDescriptor(target.getClass()); try { - desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options)), null, target); + desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), target != null), null, target); } catch (IOException e) { throw new JsonIOException(e); } @@ -171,14 +171,14 @@ public final class DJsonContext implements SpiJsonContext { public DJsonBeanReader createBeanReader(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException { BeanDescriptor desc = getDescriptor(cls); - return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options))); + return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options), false)); } @Override public DJsonBeanReader createBeanReader(BeanType beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException { BeanDescriptor desc = (BeanDescriptor) beanType; - SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options)); + SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options), false); return new DJsonBeanReader<>(desc, readJson); } @@ -211,7 +211,7 @@ public final class DJsonContext implements SpiJsonContext { public List toList(Class cls, JsonParser src, JsonReadOptions options) throws JsonIOException { BeanDescriptor desc = getDescriptor(cls); - SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options)); + SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options), false); try { JsonToken currentToken = src.getCurrentToken(); @@ -225,7 +225,7 @@ public final class DJsonContext implements SpiJsonContext { List list = new ArrayList<>(); do { // CHECKME: Should we update the list - T bean = desc.jsonRead(readJson, null, null); + T bean = desc.jsonRead(readJson, null, null); if (bean == null) { break; } else { @@ -395,7 +395,7 @@ public final class DJsonContext implements SpiJsonContext { BeanDescriptor desc = (BeanDescriptor) beanType; JsonParser parser = createParser(new StringReader(json)); - return new ReadJson(desc, parser, null, defaultObjectMapper); + return new ReadJson(desc, parser, null, defaultObjectMapper, false); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java index 7acf4e531..e95066fad 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java @@ -32,11 +32,12 @@ public final class ReadJson implements SpiJsonReader { private final Object objectMapper; private final PersistenceContext persistenceContext; private final LoadContext loadContext; + private final boolean intercept; /** * Construct with parser and readOptions. */ - public ReadJson(BeanDescriptor desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper) { + public ReadJson(BeanDescriptor desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper, boolean intercept) { this.rootDesc = desc; this.parser = parser; this.objectMapper = objectMapper; @@ -45,6 +46,7 @@ public final class ReadJson implements SpiJsonReader { // only create visitorMap, pathStack if needed ... this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap(); this.pathStack = (visitorMap == null && loadContext == null) ? null : new PathStack(); + this.intercept = intercept; } /** @@ -58,6 +60,7 @@ public final class ReadJson implements SpiJsonReader { this.objectMapper = source.objectMapper; this.persistenceContext = source.persistenceContext; this.loadContext = source.loadContext; + this.intercept = source.intercept; } private LoadContext initLoadContext(BeanDescriptor desc, JsonReadOptions readOptions) { @@ -203,4 +206,11 @@ public final class ReadJson implements SpiJsonReader { return getObjectMapper().readValue(parser, propertyType); } + /** + * Do we have to set values via intercept or not. + */ + @Override + public boolean isIntercept() { + return intercept; + } } diff --git a/ebean-test/src/test/java/org/tests/text/json/TestJsonBeanDescriptorParse.java b/ebean-test/src/test/java/org/tests/text/json/TestJsonBeanDescriptorParse.java index 177611724..bbc918b6c 100644 --- a/ebean-test/src/test/java/org/tests/text/json/TestJsonBeanDescriptorParse.java +++ b/ebean-test/src/test/java/org/tests/text/json/TestJsonBeanDescriptorParse.java @@ -1,58 +1,142 @@ package org.tests.text.json; import com.fasterxml.jackson.core.JsonParser; -import io.ebean.xtest.BaseTestCase; import io.ebean.BeanState; import io.ebean.DB; +import io.ebean.xtest.BaseTestCase; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.json.SpiJsonReader; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.text.json.ReadJson; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.tests.model.basic.Address; import org.tests.model.basic.Customer; import java.io.IOException; import java.io.StringReader; import java.util.Set; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; public class TestJsonBeanDescriptorParse extends BaseTestCase { - @Test - public void test() throws IOException { - SpiEbeanServer server = (SpiEbeanServer) DB.getDefault(); + @BeforeEach + void setup() { + Customer customer = new Customer(); + customer.setName("Hello Roland"); + customer.setId(234); + Address address = new Address(); + address.setLine1("foo"); + DB.save(address); + customer.setBillingAddress(address); + DB.save(customer); + } + @AfterEach + void teardown() { + DB.delete(Customer.class, 234); + } + + @Test + public void testJsonRead() throws IOException { + SpiEbeanServer server = (SpiEbeanServer) DB.getDefault(); BeanDescriptor descriptor = server.descriptor(Customer.class); SpiJsonReader readJson = createRead(server, descriptor); - Customer customer = descriptor.jsonRead(readJson, null, null); assertEquals(Integer.valueOf(123), customer.getId()); - assertEquals("Hello rob", customer.getName()); + assertEquals("Hello Rob", customer.getName()); BeanState beanState = DB.beanState(customer); Set loadedProps = beanState.loadedProps(); - assertEquals(2, loadedProps.size()); assertTrue(loadedProps.contains("id")); assertTrue(loadedProps.contains("name")); - + assertThat(beanState.dirtyValues()).isEmpty(); + } + + @Test + public void testJsonUpdate() throws IOException { + Customer customer = DB.find(Customer.class, 234); + DB.json().toBean(customer, "{}"); + assertFalse(DB.beanState(customer).isDirty()); + + assertEquals("Hello Roland", customer.getName()); + DB.json().toBean(customer, "{\"name\":\"Hello Roland\"}"); + assertFalse(DB.beanState(customer).isDirty()); + + DB.json().toBean(customer, "{\"name\":\"Hello Rob\"}"); + assertEquals("Hello Rob", customer.getName()); + assertThat(DB.beanState(customer).changedProps()).containsExactly("name"); + assertTrue(DB.beanState(customer).isDirty()); + DB.json().toBean(customer, "{\"name\":null}"); + assertEquals(null, customer.getName()); + + assertEquals("foo", customer.getBillingAddress().getLine1()); + DB.json().toBean(customer, "{\"billingAddress\":{\"line1\":\"foo\"}}"); + assertFalse(DB.beanState(customer.getBillingAddress()).isDirty()); + DB.json().toBean(customer, "{\"billingAddress\":{\"line1\":\"bar\"}}"); + assertEquals("bar", customer.getBillingAddress().getLine1()); + assertTrue(DB.beanState(customer.getBillingAddress()).isDirty()); + + DB.json().toBean(customer, "{\"contacts\":[{\"firstName\":\"Noemi\"},{\"firstName\":\"Roland\"}]}"); + assertThat(customer.getContacts()).hasSize(2); + // must not change contacts + DB.json().toBean(customer, "{}"); + assertThat(customer.getContacts()).hasSize(2); + DB.json().toBean(customer, "{\"contacts\":[]}"); + assertThat(customer.getContacts()).hasSize(0); + + +/* customer.setName("Hello Roland"); customer.setId(234); + Address address = new Address(); + address.setLine1("foo"); + DB.save(address); + customer.setBillingAddress(address); + DB.save(customer); + + readJson = createRead(server, descriptor); descriptor.jsonRead(readJson, null, customer); assertEquals(Integer.valueOf(123), customer.getId()); - assertEquals("Hello rob", customer.getName()); + assertEquals("Hello Rob", customer.getName()); + assertEquals(123, customer.getId()); + + + // now check, if toJson is routed over intercept for existing models + customer = DB.find(Customer.class, 234); + DB.json().toBean(customer, "{}"); + assertFalse(DB.beanState(customer).isDirty()); + + assertEquals("Hello Roland", customer.getName()); + DB.json().toBean(customer, "{\"name\":\"Hello Roland\"}"); + assertFalse(DB.beanState(customer).isDirty()); + + + DB.json().toBean(customer, "{\"name\":\"Hello Rob\"}"); + assertEquals("Hello Roland", customer.getName()); + assertThat(DB.beanState(customer).changedProps()).containsExactly("name"); + assertTrue(DB.beanState(customer).isDirty()); + DB.json().toBean(customer, "{\"name\":null}"); + assertEquals(null, customer.getName()); + + assertEquals("foo", customer.getBillingAddress().getLine2()); + DB.json().toBean(customer, "{\"billingAdress\":{\"line2\":\"foo\"}}"); + assertEquals("foo", customer.getBillingAddress().getLine2()); + DB.delete(customer); // cleanup*/ } private SpiJsonReader createRead(SpiEbeanServer server, BeanDescriptor descriptor) { - StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello rob\"}"); + StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello Rob\"}"); JsonParser parser = server.json().createParser(reader); - SpiJsonReader readJson = new ReadJson(descriptor, parser, null, null); + SpiJsonReader readJson = new ReadJson(descriptor, parser, null, null, false); return readJson; }