From c981944c8e0af196db7eef32dede93d70c3f102d Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Wed, 4 Jul 2018 21:26:17 +1200 Subject: [PATCH] #1444 - JSON parsing with inheritance - java.lang.UnsupportedOperationException: cannot create entity bean for abstract entity ... --- .../server/deploy/BeanDescriptor.java | 18 ++++-- .../server/deploy/BeanDescriptorJsonHelp.java | 56 +++++++++---------- .../server/deploy/InheritInfo.java | 1 - .../deploy/meta/DeployBeanPropertyLists.java | 18 ++++++ .../text/json/TestTextJsonInheritance.java | 15 ++++- 5 files changed, 71 insertions(+), 37 deletions(-) diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 3edb14519..1df2c0d0e 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -291,6 +291,8 @@ public class BeanDescriptor implements BeanType, STreeType { */ protected final InheritInfo inheritInfo; + private final boolean abstractType; + /** * Derived list of properties that make up the unique id. */ @@ -546,7 +548,8 @@ public class BeanDescriptor implements BeanType, STreeType { this.whenCreatedProperty = findWhenCreatedProperty(); // derive the index position of the Id and Version properties - if (Modifier.isAbstract(beanType.getModifiers())) { + this.abstractType = Modifier.isAbstract(beanType.getModifiers()); + if (abstractType) { this.idPropertyIndex = -1; this.versionPropertyIndex = -1; this.unloadProperties = new int[0]; @@ -653,6 +656,13 @@ public class BeanDescriptor implements BeanType, STreeType { return ebeanServer; } + /** + * Return true if this is an abstract type. + */ + public boolean isAbstractType() { + return abstractType; + } + /** * Return true if this is a "Doc Store only" entity bean. */ @@ -3433,11 +3443,11 @@ public class BeanDescriptor implements BeanType, STreeType { } public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException { - return jsonHelp.jsonRead(jsonRead, path); + return jsonHelp.jsonRead(jsonRead, path, true); } - protected T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException { - return jsonHelp.jsonReadObject(jsonRead, path); + public T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException { + return jsonHelp.jsonRead(jsonRead, path, false); } public List getUniqueProps() { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java index ca895e92a..a2355bdb9 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java @@ -3,6 +3,8 @@ package io.ebeaninternal.server.deploy; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.ebean.bean.EntityBean; import io.ebean.text.json.EJson; import io.ebeaninternal.api.json.SpiJsonReader; @@ -12,18 +14,18 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; -public class BeanDescriptorJsonHelp { +class BeanDescriptorJsonHelp { private final BeanDescriptor desc; private final InheritInfo inheritInfo; - public BeanDescriptorJsonHelp(BeanDescriptor desc) { + BeanDescriptorJsonHelp(BeanDescriptor desc) { this.desc = desc; this.inheritInfo = desc.inheritInfo; } - public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException { + void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException { writeJson.writeStartObject(key); @@ -42,13 +44,11 @@ public class BeanDescriptorJsonHelp { writeJson.writeEndObject(); } - protected void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) throws IOException { - + void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) { writeJson.writeBean(desc, bean); } - public void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { - + void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { if (inheritInfo == null) { jsonWriteDirtyProperties(writeJson, bean, dirtyProps); } else { @@ -56,7 +56,7 @@ public class BeanDescriptorJsonHelp { } } - protected void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { writeJson.writeStartObject(null); // render the dirty properties @@ -70,7 +70,7 @@ public class BeanDescriptorJsonHelp { } @SuppressWarnings("unchecked") - public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException { + T jsonRead(SpiJsonReader jsonRead, String path, boolean withInheritance) throws IOException { JsonParser parser = jsonRead.getParser(); //noinspection StatementWithEmptyBody @@ -87,43 +87,39 @@ public class BeanDescriptorJsonHelp { } } - if (desc.inheritInfo == null) { + if (desc.inheritInfo == null || !withInheritance) { return jsonReadObject(jsonRead, path); } + ObjectNode node = jsonRead.getObjectMapper().readTree(parser); + if (node.isNull()) { + return null; + } + JsonParser newParser = node.traverse(); + SpiJsonReader newReader = jsonRead.forJson(newParser, false); + // check for the discriminator value to determine the correct sub type String discColumn = inheritInfo.getRoot().getDiscriminatorColumn(); - - if (parser.nextToken() != JsonToken.FIELD_NAME) { - String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?"; - throw new JsonParseException(parser, msg, parser.getCurrentLocation()); - } - - String propName = parser.getCurrentName(); - if (!propName.equalsIgnoreCase(discColumn)) { - // just try to assume this is the correct bean type in the inheritance - BeanProperty property = desc.getBeanProperty(propName); - if (property != null) { - EntityBean bean = desc.createEntityBean(); - property.jsonRead(jsonRead, bean); - return jsonReadProperties(jsonRead, bean, path); + JsonNode discNode = node.get(discColumn); + if (discNode == null || discNode.isNull()) { + if (!desc.isAbstractType()) { + return desc.jsonReadObject(newReader, path); } - String msg = "Error reading inheritance discriminator, expected property [" + discColumn + "] but got [" + propName + "] ?"; - throw new JsonParseException(parser, msg, parser.getCurrentLocation()); + String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?"; + throw new JsonParseException(newParser, msg, parser.getCurrentLocation()); } - String discValue = parser.nextTextValue(); - return (T) inheritInfo.readType(discValue).desc().jsonReadObject(jsonRead, path); + return (T) inheritInfo.readType(discNode.asText()).desc().jsonReadObject(newReader, path); } - protected T jsonReadObject(SpiJsonReader readJson, String path) throws IOException { + private T jsonReadObject(SpiJsonReader readJson, String path) throws IOException { EntityBean bean = desc.createEntityBeanForJson(); return jsonReadProperties(readJson, bean, path); } @SuppressWarnings("unchecked") - protected T jsonReadProperties(SpiJsonReader readJson, EntityBean bean, String path) throws IOException { + private T jsonReadProperties(SpiJsonReader readJson, EntityBean bean, String path) throws IOException { if (path != null) { readJson.pushPath(path); diff --git a/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java b/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java index 8c536d6ca..6d112329b 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java +++ b/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java @@ -151,7 +151,6 @@ public class InheritInfo { * Set the descriptor for this node. */ public void setDescriptor(BeanDescriptor descriptor) { - this.descriptor = descriptor; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java index 241c56484..b35d0a32d 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.deploy.meta; +import io.ebean.bean.EntityBean; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanDescriptorMap; import io.ebeaninternal.server.deploy.BeanProperty; @@ -11,6 +12,7 @@ import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection; import io.ebeaninternal.server.deploy.InheritInfo; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import io.ebeaninternal.server.properties.BeanPropertySetter; import io.ebeaninternal.server.type.ScalarTypeString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +28,8 @@ public class DeployBeanPropertyLists { private static final Logger logger = LoggerFactory.getLogger(DeployBeanPropertyLists.class); + private static final NoopSetter NOOP_SETTER = new NoopSetter(); + private BeanProperty versionProperty; private BeanProperty unmappedJson; @@ -100,6 +104,7 @@ public class DeployBeanPropertyLists { discDeployProp.setDiscriminator(); discDeployProp.setName(discriminatorColumn); discDeployProp.setDbColumn(discriminatorColumn); + discDeployProp.setSetter(NOOP_SETTER); // only register it in the propertyMap. This might not be used if // an explicit property is mapped to the discriminator on the bean @@ -491,4 +496,17 @@ public class DeployBeanPropertyLists { return new BeanProperty(desc, deployProp); } + + private static class NoopSetter implements BeanPropertySetter { + + @Override + public void set(EntityBean bean, Object value) { + // do nothing + } + + @Override + public void setIntercept(EntityBean bean, Object value) { + // do nothing + } + } } diff --git a/src/test/java/org/tests/text/json/TestTextJsonInheritance.java b/src/test/java/org/tests/text/json/TestTextJsonInheritance.java index 570dd527b..288a61cc5 100644 --- a/src/test/java/org/tests/text/json/TestTextJsonInheritance.java +++ b/src/test/java/org/tests/text/json/TestTextJsonInheritance.java @@ -13,13 +13,24 @@ import org.tests.model.basic.VehicleDriver; import org.junit.Assert; import org.junit.Test; -import java.io.IOException; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; + public class TestTextJsonInheritance extends BaseTestCase { @Test - public void test() throws IOException { + public void parseJson_when_inheritanceType_outOfOrderDtype() { + + String fom = "{\"id\":90,\"name\":\"Frank\",\"vehicle\":{\"id\":42,\"licenseNumber\":\"T100\",\"capacity\":99.0,\"dtype\":\"T\"}}"; + + VehicleDriver driver1 = Ebean.json().toBean(VehicleDriver.class, fom); + assertThat(driver1.getVehicle()).isInstanceOf(Truck.class); + assertThat(driver1.getVehicle().getLicenseNumber()).isEqualTo("T100"); + } + + @Test + public void test() { setupData();