diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index 15de32146..3af896938 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -1825,12 +1825,12 @@ public interface EbeanServer { * The values are published from the draft to the live bean. *

* + * @param the type of the entity bean * @param beanType the type of the entity bean * @param id the id of the entity bean * @param transaction the transaction the publish process should use - * @param the type of the entity bean */ - void publish(Class beanType, Object id, Transaction transaction); + T publish(Class beanType, Object id, Transaction transaction); /** * Publish the beans that match the query. @@ -1838,10 +1838,10 @@ public interface EbeanServer { * The values are published from the draft beans to the live beans. *

* + * @param the type of the entity bean * @param query the query used to select the draft beans to publish * @param transaction the transaction the publish process should use - * @param the type of the entity bean */ - void publish(Query query, Transaction transaction); + List publish(Query query, Transaction transaction); } diff --git a/src/main/java/com/avaje/ebean/bean/BeanCollection.java b/src/main/java/com/avaje/ebean/bean/BeanCollection.java index b915f1862..2ddbf242f 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanCollection.java +++ b/src/main/java/com/avaje/ebean/bean/BeanCollection.java @@ -31,6 +31,16 @@ public interface BeanCollection extends Serializable { ALL } + /** + * Add a bean to the list/set with modifyListen notification. + */ + void addBean(E bean); + + /** + * Remove a bean to the list/set with modifyListen notification. + */ + void removeBean(E bean); + /** * Reset the collection back to an empty state ready for reloading. *

diff --git a/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java b/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java index 778f2b34f..55a3804eb 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java +++ b/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java @@ -12,5 +12,5 @@ public interface BeanCollectionAdd { /** * Add a loaded bean to the collection. */ - void addBean(EntityBean bean); + void addEntityBean(EntityBean bean); } diff --git a/src/main/java/com/avaje/ebean/common/BeanList.java b/src/main/java/com/avaje/ebean/common/BeanList.java index 22fad26b0..f358c79f5 100644 --- a/src/main/java/com/avaje/ebean/common/BeanList.java +++ b/src/main/java/com/avaje/ebean/common/BeanList.java @@ -60,7 +60,7 @@ public final class BeanList extends AbstractBeanCollection implements List } @SuppressWarnings("unchecked") - public void addBean(EntityBean bean) { + public void addEntityBean(EntityBean bean) { list.add((E) bean); } @@ -206,6 +206,11 @@ public final class BeanList extends AbstractBeanCollection implements List list.add(index, element); } + @Override + public void addBean(E bean) { + add(bean); + } + public boolean add(E o) { checkReadOnly(); init(); @@ -319,6 +324,13 @@ public final class BeanList extends AbstractBeanCollection implements List return list.listIterator(index); } + @Override + public void removeBean(E bean) { + if (list.remove(bean)) { + getModifyHolder().modifyRemoval(bean); + } + } + public E remove(int index) { checkReadOnly(); init(); diff --git a/src/main/java/com/avaje/ebean/common/BeanMap.java b/src/main/java/com/avaje/ebean/common/BeanMap.java index 71a30db23..0f2419474 100644 --- a/src/main/java/com/avaje/ebean/common/BeanMap.java +++ b/src/main/java/com/avaje/ebean/common/BeanMap.java @@ -272,6 +272,16 @@ public final class BeanMap extends AbstractBeanCollection implements Ma map.putAll(puts); } + @Override + public void addBean(E bean) { + throw new IllegalStateException("Method not allowed on Map. Please use List instead."); + } + + @Override + public void removeBean(E bean) { + throw new IllegalStateException("Method not allowed on Map. Please use List instead."); + } + public E remove(Object key) { checkReadOnly(); init(); diff --git a/src/main/java/com/avaje/ebean/common/BeanSet.java b/src/main/java/com/avaje/ebean/common/BeanSet.java index 7ceefcdbd..23d030372 100644 --- a/src/main/java/com/avaje/ebean/common/BeanSet.java +++ b/src/main/java/com/avaje/ebean/common/BeanSet.java @@ -53,7 +53,7 @@ public final class BeanSet extends AbstractBeanCollection implements Set extends AbstractBeanCollection implements Set cols = draftTable.getColumns().values(); + for (MColumn col: cols) { + if (col.getForeignKeyName() != null) { + // Note that we adjust the 'references' table later in a second pass + // after we know all the tables that are 'draftable' + //col.setReferences(refTable + "." + refColumn); + col.setForeignKeyName(foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount)); + + String[] indexCols = {col.getName()}; + col.setForeignKeyIndex(foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount)); + } + } + + addTable(draftTable); + } + } diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java index b64e5177e..926302b45 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java @@ -42,6 +42,11 @@ public class ModelBuildIntersectionTable { } buildFkConstraints(); + + if (manyProp.getTargetDescriptor().isDraftable()) { + ctx.createDraft(intersectionTable); + } + } private void buildFkConstraints() { diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java index 4f75c4d59..8a72d21d0 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -112,24 +112,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { private void addDraftTable() { if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) { // create a 'Draft' table which looks very similar (change PK, FK etc) - MTable draftTable = table.createDraftTable(); - draftTable.setPkName(ctx.primaryKeyName(draftTable.getName())); - - int fkCount = 0; - int ixCount = 0; - Collection cols = draftTable.getColumns().values(); - for (MColumn col: cols) { - if (col.getForeignKeyName() != null) { - // Note that we adjust the 'references' table later in a second pass - // after we know all the tables that are 'draftable' - //col.setReferences(refTable + "." + refColumn); - col.setForeignKeyName(ctx.foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount)); - - String[] indexCols = {col.getName()}; - col.setForeignKeyIndex(ctx.foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount)); - } - } - ctx.addTable(draftTable); + ctx.createDraft(table); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 3d56e8e32..076dbf6db 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1625,14 +1625,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - public void publish(Query query, Transaction transaction) { + public List publish(Query query, Transaction transaction) { TransWrapper wrap = initTransIfRequired(transaction); try { SpiTransaction trans = wrap.transaction; - persister.publish(query, trans); + List liveBeans = persister.publish(query, trans); wrap.commitIfCreated(); + return liveBeans; + } catch (RuntimeException e) { wrap.rollbackIfCreated(); throw e; @@ -1640,10 +1642,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void publish(Class beanType, Object id, Transaction transaction) { + public T publish(Class beanType, Object id, Transaction transaction) { Query query = find(beanType).setId(id); - publish(query, transaction); + List liveBeans = publish(query, transaction); + return (liveBeans.size() == 1) ? liveBeans.get(0) : null; } private EntityBean checkEntityBean(Object bean) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index 3fc05528c..d66a85425 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -231,7 +231,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } public boolean isNotify() { - this.notifyCache = beanDescriptor.isCacheNotify(); + this.notifyCache = beanDescriptor.isCacheNotify(publish); return notifyCache || isNotifyPersistListener(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java index b939219d8..56994062b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.core; import java.util.Collection; +import java.util.List; import com.avaje.ebean.CallableSql; import com.avaje.ebean.Query; @@ -90,6 +91,6 @@ public interface Persister { /** * Publish the draft beans matching the given query. */ - void publish(Query query, Transaction transaction); + List publish(Query query, Transaction transaction); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java index c459a8e4c..31bb60815 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java @@ -51,8 +51,8 @@ public class BeanCascadeInfo { /** * Set to true if delete should cascade. */ - public void setDelete(boolean isDelete) { - this.delete = isDelete; + public void setDelete(boolean delete) { + this.delete = delete; } /** * Return true if save should cascade. @@ -60,5 +60,13 @@ public class BeanCascadeInfo { public boolean isSave() { return save; } - + + /** + * Set cascade save and delete settings. + */ + public void setSaveDelete(boolean save, boolean delete) { + this.save = save; + this.delete = delete; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 364ceb51f..e040a7566 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -572,16 +572,21 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { * * @param asOfTableMap the map of base tables to associated 'with history' tables * @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name + * @param draftTableMap the map of base tables to associated 'draft' tables. */ - public void initialiseOther(Map asOfTableMap, String asOfViewSuffix) { + public void initialiseOther(Map asOfTableMap, String asOfViewSuffix, Map draftTableMap) { + + for (int i = 0; i < propertiesManyToMany.length; i++) { + // register associated draft table for M2M intersection + propertiesManyToMany[i].registerDraftIntersectionTable(draftTableMap); + } if (historySupport) { // history support on this bean so check all associated intersection tables // and if they are not excluded register the associated 'with history' table for (int i = 0; i < propertiesManyToMany.length; i++) { + // register associated history table for M2M intersection if (!propertiesManyToMany[i].isExcludedFromHistory()) { - // this intersection table has history support so also register - // it into the asOfTableMap TableJoin intersectionTableJoin = propertiesManyToMany[i].getIntersectionTableJoin(); String intersectionTableName = intersectionTableJoin.getTable(); asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix); @@ -849,7 +854,11 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { /** * Return true if the persist request needs to notify the cache. */ - public boolean isCacheNotify() { + public boolean isCacheNotify(boolean publish) { + if (draftable && !publish) { + // no caching when editing draft beans + return false; + } return cacheHelp.isCacheNotify(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorDraftHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorDraftHelp.java index 86ade6568..6f62f6185 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorDraftHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorDraftHelp.java @@ -31,13 +31,22 @@ public final class BeanDescriptorDraftHelp { EntityBean live = (EntityBean)liveBean; BeanProperty idProperty = desc.getIdProperty(); - idProperty.publish(draft, live); + if (idProperty != null) { + idProperty.publish(draft, live); + } BeanProperty[] props = desc.propertiesNonMany(); for (int i = 0; i < props.length; i++) { props[i].publish(draft, live); } + BeanPropertyAssocMany[] many = desc.propertiesMany(); + for (int i = 0; i < many.length; i++) { + if (many[i].getTargetDescriptor().isDraftable()) { + many[i].publishMany(draft, live); + } + } + return liveBean; } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java index 72c6b1a8a..71a05588a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -5,6 +5,7 @@ import com.avaje.ebean.Model; import com.avaje.ebean.RawSql; import com.avaje.ebean.RawSqlBuilder; import com.avaje.ebean.annotation.ConcurrencyMode; +import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebean.config.EncryptKey; @@ -423,7 +424,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // also look for intersection tables with // associated history support and register them // into the asOfTableMap - d.initialiseOther(asOfTableMap, asOfViewSuffix); + d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap); } // create BeanManager for each non-embedded entity bean @@ -910,7 +911,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap { */ private void checkMappedByOneToMany(DeployBeanInfo info, DeployBeanPropertyAssocMany prop) { - // get the bean descriptor that holds the mappedBy property + DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); + + if (targetDesc.isDraftableElement()) { + // automatically turning on orphan removal and CascadeType.ALL + prop.setModifyListenMode(BeanCollection.ModifyListenMode.REMOVALS); + prop.getCascadeInfo().setSaveDelete(true, true); + } if (prop.getMappedBy() == null) { if (!findMappedBy(prop)) { @@ -924,7 +931,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { String mappedBy = prop.getMappedBy(); // get the mappedBy property - DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { @@ -960,6 +966,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // get the bean descriptor that holds the mappedBy property String mappedBy = prop.getMappedBy(); if (mappedBy == null) { + if (getTargetDescriptor(prop).isDraftable()) { + prop.setIntersectionDraftTable(); + } return; } @@ -1007,6 +1016,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap { DeployTableJoin inverseJoin = new DeployTableJoin(); mappedIntJoin.copyTo(inverseJoin, false, intTableName); prop.setInverseJoin(inverseJoin); + + if (targetDesc.isDraftable()) { + prop.setIntersectionDraftTable(); + } } private void setBeanControllerFinderListener(DeployBeanDescriptor descriptor) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java index 143c2db2e..cfc413e83 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java @@ -84,7 +84,7 @@ public final class BeanMapHelp implements BeanCollectionHelp { this.map = map; } - public void addBean(EntityBean bean) { + public void addEntityBean(EntityBean bean) { Object keyValue = beanProperty.getValue(bean); map.put(keyValue, bean); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 6b5f6d9c4..80094dc70 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -39,6 +40,8 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { * Join for manyToMany intersection table. */ private final TableJoin intersectionJoin; + private final String intersectionPublishTable; + private final String intersectionDraftTable; /** * For ManyToMany this is the Inverse join used to build reference queries. @@ -109,6 +112,13 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { this.mapKey = deploy.getMapKey(); this.fetchOrderBy = deploy.getFetchOrderBy(); this.intersectionJoin = deploy.createIntersectionTableJoin(); + if (intersectionJoin != null) { + this.intersectionPublishTable = intersectionJoin.getTable(); + this.intersectionDraftTable = deploy.getIntersectionDraftTable(); + } else { + this.intersectionPublishTable = null; + this.intersectionDraftTable = null; + } this.inverseJoin = deploy.createInverseTableJoin(); this.modifyListenMode = deploy.getModifyListenMode(); this.jsonHelp = new BeanPropertyAssocManyJsonHelp(this); @@ -798,22 +808,39 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { return row; } - public IntersectionRow buildManyToManyDeleteChildren(EntityBean parentBean) { + public IntersectionRow buildManyToManyDeleteChildren(EntityBean parentBean, boolean publish) { - IntersectionRow row = new IntersectionRow(intersectionJoin.getTable()); + String tableName = publish ? intersectionPublishTable : intersectionDraftTable; + IntersectionRow row = new IntersectionRow(tableName); buildExport(row, parentBean); return row; } - public IntersectionRow buildManyToManyMapBean(EntityBean parent, EntityBean other) { - - IntersectionRow row = new IntersectionRow(intersectionJoin.getTable()); + public IntersectionRow buildManyToManyMapBean(EntityBean parent, EntityBean other, boolean publish) { + String tableName = publish ? intersectionPublishTable : intersectionDraftTable; + IntersectionRow row = new IntersectionRow(tableName); buildExport(row, parent); buildImport(row, other); return row; } + /** + * Register the mapping of intersection table to associated draft table. + */ + public void registerDraftIntersectionTable(Map draftTableMap) { + if (hasDraftIntersection()) { + draftTableMap.put(intersectionPublishTable, intersectionDraftTable); + } + } + + /** + * Return true if the relationship is a ManyToMany with the intersection having an associated draft table. + */ + private boolean hasDraftIntersection() { + return intersectionDraftTable != null && !intersectionDraftTable.equals(intersectionPublishTable); + } + private void buildExport(IntersectionRow row, EntityBean parentBean) { if (embeddedExportedProperties) { @@ -876,4 +903,57 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException { jsonHelp.jsonRead(readJson, parentBean); } + + public void publishMany(EntityBean draft, EntityBean live) { + + // collections will not be null due to enhancement + BeanCollection draftVal = (BeanCollection)getValueIntercept(draft); + BeanCollection liveVal = (BeanCollection)getValueIntercept(live); + + // Organise the existing live beans into map keyed by id + Map liveBeansAsMap = liveBeansAsMap(liveVal); + + // publish from each draft to live bean creating new live beans as required + draftVal.size(); + Collection actualDetails = draftVal.getActualDetails(); + for (T bean : actualDetails) { + Object id = targetDescriptor.getId((EntityBean) bean); + T liveBean = liveBeansAsMap.remove(id); + + if (isManyToMany()) { + if (liveBean == null) { + // add new relationship (Map not allowed here) + liveVal.addBean(targetDescriptor.createReference(Boolean.FALSE, id)); + } + + } else { + // recursively publish the OneToMany child bean + T newLive = targetDescriptor.publish(bean, liveBean); + if (liveBean == null) { + // Map not allowed here + liveVal.addBean(newLive); + } + } + } + + // anything remaining should be deleted (so remove from modify aware collection) + Collection values = liveBeansAsMap.values(); + for (T value : values) { + liveVal.removeBean(value); + } + } + + private Map liveBeansAsMap(BeanCollection liveVal) { + + liveVal.size(); + Collection liveBeans = liveVal.getActualDetails(); + Map liveMap = new LinkedHashMap(); + + for (Object liveBean : liveBeans) { + Object id = targetDescriptor.getId((EntityBean) liveBean); + liveMap.put(id, (T)liveBean); + } + return liveMap; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java index fbcde3bf2..e6d30d68f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java @@ -65,7 +65,7 @@ public class BeanPropertyAssocManyJsonHelp { // read the entire array break; } - add.addBean(detailBean); + add.addEntityBean(detailBean); if (parentBean != null && many.childMasterProperty != null) { // bind detail bean back to master via mappedBy property diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index c8c8ec4e7..282d4bf8a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -216,6 +216,7 @@ public class DeployBeanDescriptor { } public void setDraftableElement() { + draftable = true; draftableElement = true; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java index 5d61b2857..4e5967334 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java @@ -9,6 +9,11 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin; */ public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc { + /** + * The type of the many, set, list or map. + */ + final ManyType manyType; + ModifyListenMode modifyListenMode = ModifyListenMode.NONE; /** @@ -35,12 +40,9 @@ public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc { String mapKey; - /** - * The type of the many, set, list or map. - */ - final ManyType manyType; + String intersectionDraftTable; - /** + /** * Create this property. */ public DeployBeanPropertyAssocMany(DeployBeanDescriptor desc, Class targetType, ManyType manyType) { @@ -191,4 +193,17 @@ public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc { } } + /** + * Return a draft table for intersection between 2 @Draftable entities. + */ + public String getIntersectionDraftTable() { + return (intersectionDraftTable != null) ? intersectionDraftTable : intersectionJoin.getTable(); + } + + /** + * ManyToMany between 2 @Draftable entities to also need draft intersection table. + */ + public void setIntersectionDraftTable() { + this.intersectionDraftTable = intersectionJoin.getTable()+"_draft"; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java index 92ed1ea97..00afeace9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java @@ -58,6 +58,8 @@ import java.util.Set; */ public final class DefaultPersister implements Persister { + private static final Logger PUB = LoggerFactory.getLogger("org.avaje.ebean.PUB"); + private static final Logger logger = LoggerFactory.getLogger(DefaultPersister.class); /** @@ -125,15 +127,20 @@ public final class DefaultPersister implements Persister { } @Override - public void publish(Query query, Transaction transaction) { + public List publish(Query query, Transaction transaction) { query.asDraft(); Class beanType = query.getBeanType(); List draftBeans = server.findList(query, transaction); - BeanDescriptor desc = server.getBeanDescriptor(beanType); + if (draftBeans.isEmpty()) { + throw new IllegalArgumentException("No draft beans found to publish"); + } + + PUB.debug("publish [{}] count:{}", desc.getName(), draftBeans.size()); + // get the list of Id's List idList = new ArrayList(); for (T draftBean: draftBeans) { @@ -157,16 +164,21 @@ public final class DefaultPersister implements Persister { livePublish.add(liveBean); Type persistType = (existingLiveBean == null) ? Type.INSERT : Type.UPDATE; - PersistRequestBean request = createRequest(liveBean, transaction, null, mgr, persistType, false); + PersistRequestBean request = createRequest(liveBean, transaction, null, mgr, persistType, true); request.setPublish(); + PUB.trace("publish [{}] id[{}]", desc.getName(), draftID); + if (persistType == Type.INSERT) { insert(request); } else { update(request); } + + PUB.debug("publish complete for type:{}", desc.getName()); } + return livePublish; } /** @@ -242,10 +254,13 @@ public final class DefaultPersister implements Persister { } } - private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode) { + private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) { // determine insert or update taking into account stateless updates - PersistRequestBean request = createRequest(bean, t, parentBean, insertMode); + PersistRequestBean request = createRequestRecurse(bean, t, parentBean, insertMode, publish); + if (publish) { + request.setPublish(); + } if (request.isReference()) { // its a reference... @@ -586,7 +601,7 @@ public final class DefaultPersister implements Persister { if (!prop.isSaveRecurseSkippable(detailBean)) { t.depth(+1); prop.setParentBeanToChild(parentBean, detailBean); - saveRecurse(detailBean, t, parentBean, insertMode); + saveRecurse(detailBean, t, parentBean, insertMode, request.isPublish()); t.depth(-1); } } @@ -617,6 +632,7 @@ public final class DefaultPersister implements Persister { private final SpiTransaction transaction; private final boolean cascade; private final boolean deleteMissingChildren; + private final boolean publish; private SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { this.insertedParent = insertedParent; @@ -625,6 +641,7 @@ public final class DefaultPersister implements Persister { this.parentBean = parentBean; this.transaction = request.getTransaction(); this.deleteMissingChildren = request.isDeleteMissingChildren(); + this.publish = request.isPublish(); } private SaveManyPropRequest(BeanPropertyAssocMany many, EntityBean parentBean, SpiTransaction t) { @@ -634,6 +651,7 @@ public final class DefaultPersister implements Persister { this.transaction = t; this.cascade = true; this.deleteMissingChildren = false; + this.publish = false; } public boolean isSaveIntersection() { @@ -680,6 +698,10 @@ public final class DefaultPersister implements Persister { } c.modifyReset(); } + + public boolean isPublish() { + return publish; + } } private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) { @@ -817,7 +839,7 @@ public final class DefaultPersister implements Persister { } if (!skipSavingThisBean) { - saveRecurse(detail, t, parentBean, insertMode); + saveRecurse(detail, t, parentBean, insertMode, saveMany.isPublish()); } } } @@ -850,7 +872,7 @@ public final class DefaultPersister implements Persister { BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass()); BeanPropertyAssocMany prop = (BeanPropertyAssocMany) descriptor.getBeanProperty(propertyName); - return deleteAssocManyIntersection(ownerBean, prop, t); + return deleteAssocManyIntersection(ownerBean, prop, t, false); } public void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t) { @@ -884,7 +906,7 @@ public final class DefaultPersister implements Persister { int revertDepth = -1 * depth; trans.depth(depth); - saveRecurse(assocBean, t, parentBean, true); + saveRecurse(assocBean, t, parentBean, true, false); trans.depth(revertDepth); } else { @@ -915,7 +937,7 @@ public final class DefaultPersister implements Persister { if (vanillaCollection || deleteMissingChildren) { // delete all intersection rows and then treat all // beans in the collection as additions - deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t); + deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t, saveManyPropRequest.isPublish()); } Collection deletions = null; @@ -967,7 +989,7 @@ public final class DefaultPersister implements Persister { } else { // build a intersection row for 'insert' - IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherBean); + IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherBean, saveManyPropRequest.isPublish()); SqlUpdate sqlInsert = intRow.createInsert(server); executeSqlUpdate(sqlInsert, t); } @@ -983,7 +1005,7 @@ public final class DefaultPersister implements Persister { EntityBean otherDelete = (EntityBean) other; // the object from the 'other' side of the ManyToMany // build a intersection row for 'delete' - IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete); + IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete, saveManyPropRequest.isPublish()); SqlUpdate sqlDelete = intRow.createDelete(server); executeSqlUpdate(sqlDelete, t); } @@ -993,10 +1015,10 @@ public final class DefaultPersister implements Persister { t.depth(-1); } - private int deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany many, Transaction t) { + private int deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany many, Transaction t, boolean publish) { // delete all intersection rows for this bean - IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean); + IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean, publish); SqlUpdate sqlDelete = intRow.createDeleteChildren(server); return executeSqlUpdate(sqlDelete, t); @@ -1045,7 +1067,7 @@ public final class DefaultPersister implements Persister { for (int i = 0; i < manys.length; i++) { if (manys[i].isManyToMany()) { // delete associated rows from intersection table - deleteAssocManyIntersection(parentBean, manys[i], t); + deleteAssocManyIntersection(parentBean, manys[i], t, request.isPublish()); } else { @@ -1149,7 +1171,7 @@ public final class DefaultPersister implements Persister { && !request.isParent(detailBean)) { SpiTransaction t = request.getTransaction(); t.depth(-1); - saveRecurse(detailBean, t, null, insertMode); + saveRecurse(detailBean, t, null, insertMode, request.isPublish()); t.depth(+1); } } @@ -1247,15 +1269,21 @@ public final class DefaultPersister implements Persister { *

* This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type). */ - private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, boolean insertMode) { + private PersistRequestBean createRequestRecurse(T bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) { BeanManager mgr = getBeanManager(bean); if (mgr == null) { throw new PersistenceException(errNotRegistered(bean.getClass())); } BeanDescriptor desc = mgr.getBeanDescriptor(); EntityBean entityBean = (EntityBean) bean; - // determine Insert or Update based on bean state and insert flag - PersistRequest.Type type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE; + PersistRequest.Type type; + if (publish) { + // insert if it is a new bean (as publish created it) + type = entityBean._ebean_getIntercept().isNew() ? Type.INSERT : Type.UPDATE; + } else { + // determine Insert or Update based on bean state and insert flag + type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE; + } return createRequest(bean, t, parentBean, mgr, type, true); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 2017045e0..fee0b4223 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -899,11 +899,13 @@ public class DefaultOrmQuery implements SpiQuery { } public Boolean isUseBeanCache() { - return useBeanCache; + // not using L2 cache for asDraft() query + return isAsDraft() ? Boolean.FALSE : useBeanCache; } public boolean isUseQueryCache() { - return Boolean.TRUE.equals(useQueryCache); + // not using L2 cache for asDraft() query + return !isAsDraft() && Boolean.TRUE.equals(useQueryCache); } public DefaultOrmQuery setUseCache(boolean useBeanCache) { @@ -917,7 +919,8 @@ public class DefaultOrmQuery implements SpiQuery { } public boolean isLoadBeanCache() { - return loadBeanCache; + // not using L2 cache for asDraft() query + return !isAsDraft() && loadBeanCache; } public DefaultOrmQuery setLoadBeanCache(boolean loadBeanCache) { diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index b8c35da23..4d9041c4b 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -348,13 +348,13 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } @Override - public void publish(Class beanType, Object id, Transaction transaction) { - + public T publish(Class beanType, Object id, Transaction transaction) { + return null; } @Override - public void publish(Query query, Transaction transaction) { - + public List publish(Query query, Transaction transaction) { + return null; } @Override