From 5f6c93cc2ac2bd9ee5382154a0d65ee3580a1bc1 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 17 May 2022 13:47:08 +1200 Subject: [PATCH] Investigate allowing an entity to extend entity ( ~"Customization" ) --- .../server/deploy/BeanDescriptorManager.java | 47 ++++++++++++++- .../deploy/meta/DeployBeanDescriptor.java | 60 +++++++++---------- .../server/deploy/parse/DeployBeanInfo.java | 12 ++++ .../tests/model/basic/cache/OCachedApp.java | 2 + .../model/basic/cache/OCachedAppCustom.java | 28 +++++++++ .../model/basic/cache/TestCustomExtends.java | 35 +++++++++++ 6 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 ebean-test/src/test/java/org/tests/model/basic/cache/OCachedAppCustom.java create mode 100644 ebean-test/src/test/java/org/tests/model/basic/cache/TestCustomExtends.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index af0edfef1f..3d8c4acc66 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -119,6 +119,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private Map, DeployBeanInfo> deployInfoMap = new HashMap<>(); private Set> embeddedIdTypes = new HashSet<>(); private List> embeddedBeans = new ArrayList<>(); + private final List customizedEntities = new ArrayList<>(); /** * Create for a given database dbConfig. @@ -511,6 +512,11 @@ private void initialiseAll() { checkForValidEmbeddedId(d); } } + //for (CustomPair pair : customizedEntities) { + //BeanManager newDesc = beanManagerMap.get(pair.overrideType.getName()); + //BeanManager oldDesc = beanManagerMap.get(pair.originalType.getName()); + // System.out.println("replaced " + oldDesc + " with " + newDesc); + //} } private void checkForValidEmbeddedId(BeanDescriptor d) { @@ -618,7 +624,16 @@ public DeployBeanInfo deploy(Class cls) { private void registerDescriptor(DeployBeanInfo info) { BeanDescriptor desc = new BeanDescriptor<>(this, info.getDescriptor()); + if (info.isCustomized()) { + // register for persisting via BeanManager which will use this descriptor + descMap.put("customized:" + desc.type().getName(), desc); + return; + } descMap.put(desc.type().getName(), desc); + Class originalType = info.getOriginalType(); + if (originalType != null) { + descMap.put(originalType.getName(), desc); + } if (desc.isDocStoreMapped()) { descQueueMap.put(desc.docStoreQueueId(), desc); } @@ -657,6 +672,10 @@ private void readEntityDeploymentInitial() { embeddedBeans.add(info); } } + for (CustomPair pair : customizedEntities) { + DeployBeanInfo info = deployInfoMap.get(pair.originalType); + info.markAsCustomized(); + } } private void registerEmbeddedBean(DeployBeanInfo info) { @@ -671,8 +690,14 @@ private void registerEmbeddedBean(DeployBeanInfo info) { */ private void readEntityBeanTable() { for (DeployBeanInfo info : deployInfoMap.values()) { - BeanTable beanTable = createBeanTable(info); - beanTableMap.put(beanTable.getBeanType(), beanTable); + if (!info.isCustomized()) { + BeanTable beanTable = createBeanTable(info); + beanTableMap.put(beanTable.getBeanType(), beanTable); + Class originalType = info.getOriginalType(); + if (originalType != null) { + beanTableMap.put(originalType, beanTable); + } + } } // register non-id embedded beans (after bean tables are created) for (DeployBeanInfo info : embeddedBeans) { @@ -1506,6 +1531,13 @@ public List queryPlanInit(QueryPlanInit request) { return list; } + /** + * Register that entity overrideType extends/customizes the originalType entity. + */ + public void addCustomizedEntity(Class overrideType, Class originalType) { + customizedEntities.add(new CustomPair(overrideType, originalType)); + } + /** * Comparator to sort the BeanDescriptors by name. */ @@ -1518,4 +1550,15 @@ public int compare(BeanDescriptor o1, BeanDescriptor o2) { return o1.name().compareTo(o2.name()); } } + + private static final class CustomPair { + + final Class overrideType; + final Class originalType; + + CustomPair(Class overrideType, Class originalType) { + this.overrideType = overrideType; + this.originalType = originalType; + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index ac6c76c5a2..9ffd0bab6e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -8,31 +8,14 @@ import io.ebean.config.TableName; import io.ebean.config.dbplatform.IdType; import io.ebean.config.dbplatform.PlatformIdGenerator; -import io.ebean.event.BeanFindController; -import io.ebean.event.BeanPersistController; -import io.ebean.event.BeanPersistListener; -import io.ebean.event.BeanPostConstructListener; -import io.ebean.event.BeanPostLoad; -import io.ebean.event.BeanQueryAdapter; +import io.ebean.event.*; import io.ebean.event.changelog.ChangeLogFilter; import io.ebean.text.PathProperties; import io.ebean.util.SplitName; import io.ebeaninternal.api.ConcurrencyMode; import io.ebeaninternal.server.core.CacheOptions; import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType; -import io.ebeaninternal.server.deploy.BeanDescriptorManager; -import io.ebeaninternal.server.deploy.ChainedBeanPersistController; -import io.ebeaninternal.server.deploy.ChainedBeanPersistListener; -import io.ebeaninternal.server.deploy.ChainedBeanPostConstructListener; -import io.ebeaninternal.server.deploy.ChainedBeanPostLoad; -import io.ebeaninternal.server.deploy.ChainedBeanQueryAdapter; -import io.ebeaninternal.server.deploy.DeployPropertyParserMap; -import io.ebeaninternal.server.deploy.IdentityMode; -import io.ebeaninternal.server.deploy.IndexDefinition; -import io.ebeaninternal.server.deploy.InheritInfo; -import io.ebeaninternal.server.deploy.PartitionMeta; -import io.ebeaninternal.server.deploy.TableJoin; -import io.ebeaninternal.server.deploy.TablespaceMeta; +import io.ebeaninternal.server.deploy.*; import io.ebeaninternal.server.deploy.parse.DeployBeanInfo; import io.ebeaninternal.server.idgen.UuidV1IdGenerator; import io.ebeaninternal.server.idgen.UuidV1RndIdGenerator; @@ -41,13 +24,7 @@ import javax.persistence.Entity; import javax.persistence.MappedSuperclass; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Describes Beans including their deployment information. @@ -118,6 +95,8 @@ public int compare(DeployBeanProperty o1, DeployBeanProperty o2) { * The EntityBean type used to create new EntityBeans. */ private final Class beanType; + private Class originalType; + private boolean customized; private final List persistControllers = new ArrayList<>(); private final List persistListeners = new ArrayList<>(); private final List queryAdapters = new ArrayList<>(); @@ -254,7 +233,7 @@ public PartitionMeta getPartitionMeta() { } return partitionMeta; } - + public void setTablespaceMeta(TablespaceMeta tablespaceMeta) { this.tablespaceMeta = tablespaceMeta; } @@ -312,11 +291,9 @@ public boolean isScalaObject() { } public DeployBeanTable createDeployBeanTable() { - - DeployBeanTable beanTable = new DeployBeanTable(getBeanType()); + DeployBeanTable beanTable = new DeployBeanTable(beanType); beanTable.setBaseTable(baseTable); beanTable.setIdProperty(idProperty()); - return beanTable; } @@ -392,6 +369,20 @@ public void setProperties(String[] props) { this.properties = props; } + public boolean isCustomized() { + return customized; + } + + public void markAsCustomized() { + this.customized = true; + this.entityType = EntityType.VIEW; + this.dependentTables = new String[]{baseTable}; + } + + public Class getOriginalType() { + return originalType; + } + /** * Return the class type this BeanDescriptor describes. */ @@ -976,15 +967,18 @@ public void checkInheritanceMapping() { * Check valid mapping annotations on the class hierarchy. */ private void checkInheritance(Class beanType) { - Class parent = beanType.getSuperclass(); if (parent == null || Object.class.equals(parent)) { // all good return; } if (parent.isAnnotationPresent(Entity.class)) { - String msg = "Checking " + getBeanType() + " and found " + parent + " that has @Entity annotation rather than MappedSuperclass?"; - throw new IllegalStateException(msg); + // String msg = "Checking " + getBeanType() + " and found " + parent + " that has @Entity annotation rather than MappedSuperclass?"; + // throw new IllegalStateException(msg); + // allow an entity to extend an entity ... as "customization" + originalType = parent; + manager.addCustomizedEntity(beanType, parent); + return; } if (parent.isAnnotationPresent(MappedSuperclass.class)) { // continue checking diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java index 70a7e666a4..875df1a63b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java @@ -77,4 +77,16 @@ public Class getEmbeddedIdType() { public boolean isEmbedded() { return descriptor.isEmbedded(); } + + public Class getOriginalType() { + return descriptor.getOriginalType(); + } + + public void markAsCustomized() { + descriptor.markAsCustomized(); + } + + public boolean isCustomized() { + return descriptor.isCustomized(); + } } diff --git a/ebean-test/src/test/java/org/tests/model/basic/cache/OCachedApp.java b/ebean-test/src/test/java/org/tests/model/basic/cache/OCachedApp.java index 4ee852d90e..974268596b 100644 --- a/ebean-test/src/test/java/org/tests/model/basic/cache/OCachedApp.java +++ b/ebean-test/src/test/java/org/tests/model/basic/cache/OCachedApp.java @@ -3,10 +3,12 @@ import io.ebean.annotation.Cache; import javax.persistence.Entity; +import javax.persistence.Table; import javax.persistence.UniqueConstraint; @Cache(naturalKey = "appName") @Entity +@Table(name="ocached_app") @UniqueConstraint(name="uq_ocached_app", columnNames = "app_name") public class OCachedApp extends OCacheBase { diff --git a/ebean-test/src/test/java/org/tests/model/basic/cache/OCachedAppCustom.java b/ebean-test/src/test/java/org/tests/model/basic/cache/OCachedAppCustom.java new file mode 100644 index 0000000000..67b7728483 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/basic/cache/OCachedAppCustom.java @@ -0,0 +1,28 @@ +package org.tests.model.basic.cache; + +import io.ebean.annotation.Cache; + +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +@Cache(naturalKey = "appName") +@Entity +@Table(name = "ocached_app") +@UniqueConstraint(name = "uq_ocached_app", columnNames = "app_name") +public class OCachedAppCustom extends OCachedApp { + + String custom; + + public OCachedAppCustom(String appName) { + super(appName); + } + + public String getCustom() { + return custom; + } + + public void setCustom(String custom) { + this.custom = custom; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/basic/cache/TestCustomExtends.java b/ebean-test/src/test/java/org/tests/model/basic/cache/TestCustomExtends.java new file mode 100644 index 0000000000..1e756d1f45 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/basic/cache/TestCustomExtends.java @@ -0,0 +1,35 @@ +package org.tests.model.basic.cache; + +import io.ebean.DB; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestCustomExtends { + + @Test + void insert_find() { + OCachedApp orig = new OCachedApp("orig"); + DB.save(orig); + + OCachedAppCustom custom = new OCachedAppCustom("custom"); + custom.setCustom("someCustomValue"); + DB.save(custom); + + OCachedAppCustom c1 = DB.find(OCachedAppCustom.class, custom.getId()); + OCachedApp c2 = DB.find(OCachedApp.class, custom.getId()); + + OCachedAppCustom o1 = DB.find(OCachedAppCustom.class, orig.getId()); + OCachedApp o2 = DB.find(OCachedApp.class, orig.getId()); + + assertThat(c1).isNotNull(); + assertThat(c2).isNotNull(); + assertThat(c2.getAppName()).isEqualTo(c1.getAppName()); + assertThat(c2).isNotNull().isInstanceOf(OCachedAppCustom.class); + + assertThat(o1).isNotNull(); + assertThat(o2).isNotNull(); + assertThat(o2.getAppName()).isEqualTo(o1.getAppName()); + assertThat(o2).isNotNull().isInstanceOf(OCachedAppCustom.class); + } +}