diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index ec4a2cddb..0ad522236 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -49,7 +49,7 @@ io.ebean ebean-annotation - 7.3 + 7.4-SNAPSHOT diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 1cdabc9f8..0fca00f82 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -47,7 +47,14 @@ import io.ebean.bean.PersistenceContext.WithOption; import io.ebean.bean.SingleBeanLoader; import io.ebean.cache.ServerCacheManager; import io.ebean.common.CopyOnFirstWriteList; -import io.ebean.config.*; +import io.ebean.config.CurrentTenantProvider; +import io.ebean.config.DatabaseConfig; +import io.ebean.config.EncryptKeyManager; +import io.ebean.config.QueryPlanCapture; +import io.ebean.config.QueryPlanListener; +import io.ebean.config.SlowQueryEvent; +import io.ebean.config.SlowQueryListener; +import io.ebean.config.TenantMode; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.event.BeanPersistController; import io.ebean.event.ShutdownManager; @@ -649,7 +656,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ @Override public T createEntityBean(Class type) { - return descriptor(type).createBean(); + final BeanDescriptor desc = descriptor(type); + if (desc == null) { + throw new IllegalArgumentException("No bean type " + type.getName() + " registered"); + } + return desc.createBean(); } /** 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 eee73e4c0..3c949b6e1 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 @@ -87,10 +87,11 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private final BootupClasses bootupClasses; private final String serverName; private final List> elementDescriptors = new ArrayList<>(); - private final Map, BeanTable> beanTableMap = new HashMap<>(); + private final Map beanTableMap = new HashMap<>(); private final Map> descMap = new HashMap<>(); private final Map> descQueueMap = new HashMap<>(); private final Map> beanManagerMap = new HashMap<>(); + private final Map descAliases = new HashMap<>(); private final Map>> tableToDescMap = new HashMap<>(); private final Map>> tableToViewDescMap = new HashMap<>(); private final DbIdentity dbIdentity; @@ -116,7 +117,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private final Map draftTableMap = new HashMap<>(); // temporary collections used during startup and then cleared - private Map, DeployBeanInfo> deployInfoMap = new HashMap<>(); + private Map> deployInfoMap = new HashMap<>(); private Set> embeddedIdTypes = new HashSet<>(); private List> embeddedBeans = new ArrayList<>(); @@ -234,14 +235,22 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } @Override - @SuppressWarnings("unchecked") public BeanDescriptor descriptor(Class entityType) { - return (BeanDescriptor) descMap.get(entityType.getName()); + BeanDescriptor ret = descriptorByClassName(entityType.getName()); + if (ret == null) { + for (Class iface : entityType.getInterfaces()) { + ret = descriptorByClassName(iface.getName()); + if (ret != null) { + return ret; + } + } + } + return ret; } @SuppressWarnings("unchecked") public BeanDescriptor descriptorByClassName(String entityClassName) { - return (BeanDescriptor) descMap.get(entityClassName); + return (BeanDescriptor) descMap.get(resolveAlias(entityClassName)); } @Override @@ -287,6 +296,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy try { createListeners(); readEntityDeploymentInitial(); + readOverridesAndAliases(); readXmlMapping(mappings); readEntityBeanTable(); readEntityDeploymentAssociations(); @@ -336,7 +346,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy return; } - DeployBeanInfo info = deployInfoMap.get(entityClass); + DeployBeanInfo info = deploy(entityClass); if (info == null) { log.error("No entity bean for ebean.xml entry " + entityClassName); @@ -550,7 +560,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } public BeanTable beanTable(Class type) { - return beanTableMap.get(type); + return beanTableMap.get(resolveAlias(type.getName())); } /** @@ -566,7 +576,12 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } private BeanManager beanManager(String beanClassName) { - return beanManagerMap.get(beanClassName); + return beanManagerMap.get(resolveAlias(beanClassName)); + } + + private String resolveAlias(String name) { + String altName = descAliases.get(name); + return altName == null ? name : altName; } /** @@ -591,7 +606,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy */ @SuppressWarnings("unchecked") public DeployBeanInfo deploy(Class cls) { - return (DeployBeanInfo) deployInfoMap.get(cls); + return (DeployBeanInfo) deployInfoMap.get(resolveAlias(cls.getName())); } private void registerDescriptor(DeployBeanInfo info) { @@ -616,7 +631,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private void readEntityDeploymentInitial() { for (Class entityClass : bootupClasses.getEntities()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); - deployInfoMap.put(entityClass, info); + deployInfoMap.put(entityClass.getName(), info); Class embeddedIdType = info.getEmbeddedIdType(); if (embeddedIdType != null) { embeddedIdTypes.add(embeddedIdType); @@ -624,7 +639,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } for (Class entityClass : bootupClasses.getEmbeddables()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); - deployInfoMap.put(entityClass, info); + deployInfoMap.put(entityClass.getName(), info); if (embeddedIdTypes.contains(entityClass)) { // register embeddedId types early - scalar properties only // and needed for creating BeanTables (id properties) @@ -650,7 +665,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private void readEntityBeanTable() { for (DeployBeanInfo info : deployInfoMap.values()) { BeanTable beanTable = createBeanTable(info); - beanTableMap.put(beanTable.getBeanType(), beanTable); + beanTableMap.put(beanTable.getBeanType().getName(), beanTable); } // register non-id embedded beans (after bean tables are created) for (DeployBeanInfo info : embeddedBeans) { @@ -674,7 +689,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy DeployBeanDescriptor descriptor = info.getDescriptor(); InheritInfo inheritInfo = descriptor.getInheritInfo(); if (inheritInfo != null && !inheritInfo.isRoot()) { - DeployBeanInfo rootBeanInfo = deployInfoMap.get(inheritInfo.getRoot().getType()); + DeployBeanInfo rootBeanInfo = deploy(inheritInfo.getRoot().getType()); PlatformIdGenerator rootIdGen = rootBeanInfo.getDescriptor().getIdGenerator(); if (rootIdGen != null) { descriptor.setIdGenerator(rootIdGen); @@ -715,13 +730,79 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } } + /** + * Finds the overrides by priority. Computes a map with the base class as key + * and the descriptor, that shoud replace that class. Modifies the deployInfoMap + * and descAliases accordingly. + */ + private void readOverridesAndAliases() { + Map, DeployBeanInfo> overrides = new HashMap<>(); + + for (DeployBeanInfo info : deployInfoMap.values()) { + DeployBeanDescriptor desc = info.getDescriptor(); + Integer prio = desc.getOverridePriority(); + if (prio != null) { + Class base = desc.getBaseBeanType(); + Class child = desc.getBeanType(); + log.debug("{} overridden by {} with priority {}", base.getName(), child.getName(), prio); + + // check, if we already have found an override for this base and take the one + // with + // lowest prio + DeployBeanInfo override = overrides.get(base); + if (override == null) { + overrides.put(base, info); + } else { + int delta = override.getDescriptor().getOverridePriority().compareTo(prio); + if (delta > 0) { + overrides.put(base, info); + } else if (delta == 0) { + throw new IllegalStateException("There are two or more implementations for " + base.getName() + + " with priority " + prio + ". Conflicting entity: " + desc.getBeanType().getName() + " - " + + override.getDescriptor().getBeanType().getName()); + } + } + } + } + + overrides.forEach((key, value) -> { + deployInfoMap.remove(key.getName()); // remove the original descriptor + + DeployBeanDescriptor newDesc = value.getDescriptor(); + newDesc.setOverridePriority(null); // clear priority (so no discard will occur) + + deployInfoMap.put(newDesc.getBeanType().getName(), value); + descAliases.put(key.getName(), newDesc.getBeanType().getName()); + }); + + + // remove all left overrides classes (they are discarded) + deployInfoMap.values().removeIf(info -> info.getDescriptor().getOverridePriority() != null); + + for (DeployBeanInfo info : deployInfoMap.values()) { + for (Class iface : info.getDescriptor().getInterfaces()) { + String conflict = descAliases.put(iface.getName(), info.getDescriptor().getBeanType().getName()); + if (conflict != null) { + throw new IllegalStateException("There are two or more implementations for " + iface + + ". Conflicting entity: " + conflict + " - " + info.getDescriptor().getBeanType().getName()); + } + } + } + + // output alias map + if (log.isInfoEnabled()) { + descAliases.forEach((key, value) -> log.info("{} alias for {}", key, value)); + } + } + + /** * Sets the inheritance info. */ private void setInheritanceInfo(DeployBeanInfo info) { for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { if (!oneProp.isTransient()) { - DeployBeanInfo assoc = deployInfoMap.get(oneProp.getTargetType()); + DeployBeanInfo assoc = deploy(oneProp.getTargetType()); if (assoc != null) { oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } @@ -729,7 +810,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } for (DeployBeanPropertyAssocMany manyProp : info.getDescriptor().propertiesAssocMany()) { if (!manyProp.isTransient()) { - DeployBeanInfo assoc = deployInfoMap.get(manyProp.getTargetType()); + DeployBeanInfo assoc = deploy(manyProp.getTargetType()); if (assoc != null) { manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } @@ -786,7 +867,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private DeployBeanDescriptor targetDescriptor(DeployBeanPropertyAssoc prop) { Class targetType = prop.getTargetType(); - DeployBeanInfo info = deployInfoMap.get(targetType); + DeployBeanInfo info = deploy(targetType); if (info == null) { throw new PersistenceException("Can not find descriptor [" + targetType + "] for " + prop.getFullBeanName()); } @@ -878,7 +959,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy final InheritInfo targetInheritInfo = targetDesc.getInheritInfo(); if (targetInheritInfo != null) { for (InheritInfo child : targetInheritInfo.getChildren()) { - final DeployBeanDescriptor childDescriptor = deployInfoMap.get(child.getType()).getDescriptor(); + final DeployBeanDescriptor childDescriptor = deploy(child.getType()).getDescriptor(); childDescriptor.setOrderColumn(orderProperty); } } @@ -1143,11 +1224,12 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy // set bean controller, finder and listener setBeanControllerFinderListener(desc); deplyInherit.process(desc); - desc.checkInheritanceMapping(); createProperties.createProperties(desc); DeployBeanInfo info = new DeployBeanInfo<>(deployUtil, desc); readAnnotations.readInitial(info); + + desc.checkInheritanceMapping(); return info; } @@ -1424,7 +1506,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy String baseTable = prop.getDesc().getBaseTable(); DeployTableJoin inverse = prop.getTableJoin().createInverse(baseTable); TableJoin inverseJoin = new TableJoin(inverse, prop.getForeignKey()); - DeployBeanInfo target = deployInfoMap.get(prop.getTargetType()); + DeployBeanInfo target = deploy(prop.getTargetType()); target.setPrimaryKeyJoin(inverseJoin); } 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 e670dd136..f6282db2d 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 @@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy.meta; import io.ebean.annotation.Cache; import io.ebean.annotation.DocStore; import io.ebean.annotation.DocStoreMode; +import io.ebean.annotation.EntityOverride; import io.ebean.annotation.Identity; import io.ebean.config.DatabaseConfig; import io.ebean.config.TableName; @@ -16,6 +17,7 @@ import io.ebean.event.BeanPostLoad; import io.ebean.event.BeanQueryAdapter; import io.ebean.event.changelog.ChangeLogFilter; import io.ebean.text.PathProperties; +import io.ebean.util.AnnotationUtil; import io.ebean.util.SplitName; import io.ebeaninternal.api.ConcurrencyMode; import io.ebeaninternal.server.core.CacheOptions; @@ -44,9 +46,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * Describes Beans including their deployment information. @@ -152,6 +156,10 @@ public class DeployBeanDescriptor { private TableJoin primaryKeyJoin; private Object jacksonAnnotatedClass; + private final Set> interfaces = new HashSet<>(); + + private Integer overridePriority; + /** * Construct the BeanDescriptor. */ @@ -957,7 +965,7 @@ public class DeployBeanDescriptor { * Check the mapping for class inheritance */ public void checkInheritanceMapping() { - if (inheritInfo == null) { + if (inheritInfo == null && overridePriority == null) { checkInheritance(getBeanType()); } } @@ -1130,4 +1138,46 @@ public class DeployBeanDescriptor { } return jacksonAnnotatedClass; } + + /** + * Adds a concrete interface, that will be registered as alias for a concrete implementation. + */ + public void addInterface(Class iface) { + if (!iface.isAssignableFrom(beanType)) { + throw new ClassCastException("Cannot cast " + iface.getName() + " to " + beanType.getName()); + } + interfaces.add(iface); + } + + /** + * Returns the interfaces. + */ + public Set> getInterfaces() { + return interfaces; + } + + /** + * Sets the override priority. + */ + public void setOverridePriority(Integer overridePriority) { + this.overridePriority = overridePriority; + } + + /** + * returns the override priority, or null if this is a base entity. + */ + public Integer getOverridePriority() { + return overridePriority; + } + + /** + * Returns the base bean type, if {@link EntityOverride} was used. + */ + public Class getBaseBeanType() { + Class base = getBeanType(); + while (AnnotationUtil.has(base, EntityOverride.class)) { + base = base.getSuperclass(); + } + return base; + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java index cd015da92..d9061e9e9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -6,6 +6,8 @@ import io.ebean.annotation.DbPartition; import io.ebean.annotation.DocStore; import io.ebean.annotation.Draftable; import io.ebean.annotation.DraftableElement; +import io.ebean.annotation.EntityImplements; +import io.ebean.annotation.EntityOverride; import io.ebean.annotation.History; import io.ebean.annotation.Identity; import io.ebean.annotation.Index; @@ -14,6 +16,7 @@ import io.ebean.annotation.ReadAudit; import io.ebean.annotation.StorageEngine; import io.ebean.annotation.View; import io.ebean.config.TableName; +import io.ebean.util.AnnotationUtil; import io.ebeaninternal.api.CoreLog; import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType; import io.ebeaninternal.server.deploy.IndexDefinition; @@ -29,6 +32,7 @@ import javax.persistence.IdClass; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.UniqueConstraint; +import java.util.Set; import static io.ebean.util.AnnotationUtil.typeGet; @@ -84,7 +88,7 @@ final class AnnotationClass extends AnnotationParser { */ private void setTableName() { if (descriptor.isBaseTableType()) { - Class beanType = descriptor.getBeanType(); + Class beanType = descriptor.getBaseBeanType(); InheritInfo inheritInfo = descriptor.getInheritInfo(); if (inheritInfo != null) { beanType = inheritInfo.getRoot().getType(); @@ -188,6 +192,18 @@ final class AnnotationClass extends AnnotationParser { for (NamedQuery namedQuery : annotationClassNamedQuery(cls)) { descriptor.addNamedQuery(namedQuery.name(), namedQuery.query()); } + + Set entityImplements = AnnotationUtil.typeGetAll(cls, EntityImplements.class); + for (EntityImplements ann : entityImplements) { + for (Class iface : ann.value()) { + descriptor.addInterface(iface); + } + } + + EntityOverride entityOverride = AnnotationUtil.typeGet(cls, EntityOverride.class); + if (entityOverride != null) { + descriptor.setOverridePriority(entityOverride.priority()); + } } } diff --git a/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson1.java b/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson1.java new file mode 100644 index 000000000..ffe781ecf --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson1.java @@ -0,0 +1,25 @@ +package org.tests.model.interfaces; + +import io.ebean.annotation.EntityImplements; +import io.ebean.annotation.EntityOverride; + +import javax.persistence.Entity; + +@Entity() +@EntityImplements(IExtPerson1.class) +@EntityOverride(priority = 30) +public class ExtPerson1 extends Person implements IExtPerson1 { + + private int myField1; + + @Override + public int getMyField1() { + return myField1; + } + + @Override + public void setMyField1(int myField1) { + this.myField1 = myField1; + } + +} diff --git a/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson1and2.java b/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson1and2.java new file mode 100644 index 000000000..edc7486f2 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson1and2.java @@ -0,0 +1,26 @@ +package org.tests.model.interfaces; + +import io.ebean.annotation.EntityImplements; +import io.ebean.annotation.EntityOverride; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity() +@Table(name = "person") +@EntityImplements(IExtPerson2.class) +@EntityOverride(priority = -30) +public class ExtPerson1and2 extends ExtPerson1 implements IExtPerson2 { + + private int myField2; + + @Override + public int getMyField2() { + return myField2; + } + + @Override + public void setMyField2(int myField2) { + this.myField2 = myField2; + } +} diff --git a/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson2.java b/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson2.java new file mode 100644 index 000000000..d1866ba4a --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/model/interfaces/ExtPerson2.java @@ -0,0 +1,27 @@ +package org.tests.model.interfaces; + +import io.ebean.annotation.EntityImplements; +import io.ebean.annotation.EntityOverride; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@EntityOverride(priority = 20) +@EntityImplements(IExtPerson2.class) +@Table(name = "person") +public class ExtPerson2 extends Person implements IExtPerson2 { + + private int myField2; + + @Override + public int getMyField2() { + return myField2; + } + + @Override + public void setMyField2(int myField2) { + this.myField2 = myField2; + } + +} diff --git a/ebean-test/src/test/java/io/ebean/model/interfaces/IExtPerson1.java b/ebean-test/src/test/java/io/ebean/model/interfaces/IExtPerson1.java new file mode 100644 index 000000000..b81a5266d --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/model/interfaces/IExtPerson1.java @@ -0,0 +1,7 @@ +package org.tests.model.interfaces; + +public interface IExtPerson1 extends IPerson { + int getMyField1(); + + void setMyField1(int myField1); +} diff --git a/ebean-test/src/test/java/io/ebean/model/interfaces/IExtPerson2.java b/ebean-test/src/test/java/io/ebean/model/interfaces/IExtPerson2.java new file mode 100644 index 000000000..49bf60133 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/model/interfaces/IExtPerson2.java @@ -0,0 +1,7 @@ +package org.tests.model.interfaces; + +public interface IExtPerson2 extends IPerson { + int getMyField2(); + + void setMyField2(int myField2); +} diff --git a/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java b/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java index 7f601523a..eab5cf6ae 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java +++ b/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java @@ -5,27 +5,51 @@ import io.ebean.DB; import org.junit.jupiter.api.Test; import org.tests.model.basic.ResetBasicData; import org.tests.model.interfaces.Address; +import org.tests.model.interfaces.ExtPerson1and2; import org.tests.model.interfaces.IAddress; +import org.tests.model.interfaces.IExtPerson1; +import org.tests.model.interfaces.IExtPerson2; import org.tests.model.interfaces.IPerson; import org.tests.model.interfaces.Person; +import static org.assertj.core.api.Assertions.assertThat; + public class TestManyOneInterface extends BaseTestCase { @Test - public void test() { - + public void testEntityOverrideEntityImplements() { ResetBasicData.reset(); - IAddress a = new Address("hello"); + IAddress a = DB.getDefault().createEntityBean(IAddress.class); + assertThat(a).isInstanceOf(Address.class); - IPerson p = new Person(); + IPerson p = DB.getDefault().createEntityBean(Person.class); + assertThat(p).isInstanceOf(ExtPerson1and2.class); + p = DB.getDefault().getPluginApi().createEntityBean(IPerson.class); + assertThat(p).isInstanceOf(ExtPerson1and2.class); p.setDefaultAddress(a); - DB.save(a); + IAddress ea1 = DB.getDefault().createEntityBean(IAddress.class); + IAddress ea2 = DB.getDefault().createEntityBean(IAddress.class); + + p.getExtraAddresses().add(ea1); + p.getExtraAddresses().add(ea2); + DB.save(p); - //Assert.assertTrue(); + IAddress a2 = DB.find(IAddress.class, a.getOid()); + IPerson p2 = DB.find(IPerson.class, p.getOid()); + + assertThat(a2).isInstanceOf(Address.class); + assertThat(p2).isNotNull().isInstanceOf(ExtPerson1and2.class); + assertThat(p2.getDefaultAddress()).isInstanceOf(Address.class); + + // some more checks + IExtPerson1 pe1 = DB.getDefault().getPluginApi().createEntityBean(IExtPerson1.class); + IExtPerson2 pe2 = DB.getDefault().getPluginApi().createEntityBean(IExtPerson2.class); + assertThat(pe1).isInstanceOf(ExtPerson1and2.class); + assertThat(pe2).isInstanceOf(ExtPerson1and2.class); } } diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/Address.java b/ebean-test/src/test/java/org/tests/model/interfaces/Address.java index 052ef63f8..0e5f7a32e 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/Address.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/Address.java @@ -1,10 +1,14 @@ package org.tests.model.interfaces; +import io.ebean.annotation.EntityImplements; + import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.ManyToOne; import javax.persistence.Version; @Entity +@EntityImplements(IAddress.class) public class Address implements IAddress { @Id @@ -15,6 +19,9 @@ public class Address implements IAddress { private String street; + @ManyToOne + private Person extraAddress; + public Address(String street) { this.street = street; } diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java b/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java index 78b6d2005..9097d23d7 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java @@ -1,6 +1,9 @@ package org.tests.model.interfaces; public interface IAddress { + + long getOid(); + String getStreet(); void setStreet(String s); diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java b/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java index 0d07ecc6b..3c1366185 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java @@ -1,7 +1,16 @@ package org.tests.model.interfaces; +import java.util.List; + public interface IPerson { + + long getOid(); + IAddress getDefaultAddress(); void setDefaultAddress(IAddress address); + + List getExtraAddresses(); + + List getAddressLinks(); } diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/Person.java b/ebean-test/src/test/java/org/tests/model/interfaces/Person.java index 5edbb68c7..50d6b9330 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/Person.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/Person.java @@ -1,11 +1,19 @@ package org.tests.model.interfaces; +import io.ebean.annotation.EntityImplements; + +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.Version; +import java.util.ArrayList; +import java.util.List; @Entity +@EntityImplements(IPerson.class) public class Person implements IPerson { @Id private long oid; @@ -13,9 +21,15 @@ public class Person implements IPerson { @Version private int version; - @ManyToOne(targetEntity = Address.class) + @ManyToOne(cascade = CascadeType.PERSIST) private IAddress defaultAddress; + @OneToMany(cascade = CascadeType.PERSIST, orphanRemoval = true) + private List extraAddresses = new ArrayList<>(); + + @ManyToMany(cascade = CascadeType.PERSIST) + private List addressLinks = new ArrayList<>(); + @Override public IAddress getDefaultAddress() { return defaultAddress; @@ -42,4 +56,14 @@ public class Person implements IPerson { this.version = version; } + @Override + public List getExtraAddresses() { + return extraAddresses; + } + + @Override + public List getAddressLinks() { + return addressLinks; + } + } diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java b/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java index 974d7aeef..d27280ac8 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java @@ -30,7 +30,7 @@ public class TestTargetEntity extends BaseTestCase { private Person setup() { Address address = new Address("street"); DB.save(address); - Person person = new Person(); + Person person = DB.getDefault().createEntityBean(Person.class); person.setDefaultAddress(address); DB.save(person); return person;