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