diff --git a/ebean-api/src/main/java/io/ebean/DB.java b/ebean-api/src/main/java/io/ebean/DB.java
index b5438eb08..f834c0354 100644
--- a/ebean-api/src/main/java/io/ebean/DB.java
+++ b/ebean-api/src/main/java/io/ebean/DB.java
@@ -477,6 +477,19 @@ public final class DB {
public static int saveAll(Object... beans) throws OptimisticLockException {
return getDefault().saveAll(beans);
}
+
+ /**
+ * This will visit all beans in the persist graph on a given object
+ * start. It will call the visitor for each dirty bean that would
+ * be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can
+ * use this method to implement custom validations.
+ *
+ * @param start could be a bean, a list of beans or a map of beans.
+ * @param visitor the visitor
+ */
+ public static void visitSave(Object start, PersistVisitor visitor) {
+ getDefault().visitSave(start, visitor);
+ }
/**
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java
index d2344db52..66e1d0cf8 100644
--- a/ebean-api/src/main/java/io/ebean/Database.java
+++ b/ebean-api/src/main/java/io/ebean/Database.java
@@ -1017,6 +1017,17 @@ public interface Database {
* Save all the beans.
*/
int saveAll(Object... beans) throws OptimisticLockException;
+
+ /**
+ * This will visit all beans in the persist graph on a given object
+ * start. It will call the visitor for each dirty bean that would
+ * be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can
+ * use this method to implement custom validations.
+ *
+ * @param start could be a bean, a list of beans or a map of beans.
+ * @param visitor the visitor
+ */
+ void visitSave(Object start, PersistVisitor visitor);
/**
* Delete the bean.
diff --git a/ebean-api/src/main/java/io/ebean/PersistVisitor.java b/ebean-api/src/main/java/io/ebean/PersistVisitor.java
new file mode 100644
index 000000000..b5f1398d0
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/PersistVisitor.java
@@ -0,0 +1,29 @@
+package io.ebean;
+
+import java.util.Collection;
+import java.util.Map;
+
+import io.ebean.bean.EntityBean;
+import io.ebean.plugin.Property;
+
+@FunctionalInterface
+public interface PersistVisitor {
+
+ PersistVisitor visitBean(EntityBean bean);
+
+ default PersistVisitor visitProperty(Property prop) {
+ return this;
+ };
+
+ default PersistVisitor visitCollection(Collection> collection) {
+ return this;
+ };
+
+ default PersistVisitor visitMap(Map, ?> map) {
+ return this;
+ };
+
+ default void visitEnd() {
+ }
+
+}
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 84cf05a34..7750de089 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
@@ -18,6 +18,7 @@ import io.ebean.FutureRowCount;
import io.ebean.MergeOptions;
import io.ebean.MergeOptionsBuilder;
import io.ebean.PagedList;
+import io.ebean.PersistVisitor;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
@@ -1628,6 +1629,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
persister.save(checkEntityBean(bean), transaction);
}
+ @Override
+ public void visitSave(Object start, PersistVisitor visitor) {
+ new VisitHandler(descriptorManager).visit(start, visitor);
+ }
+
@Override
public void markAsDirty(Object bean) {
if (!(bean instanceof EntityBean)) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java
new file mode 100644
index 000000000..46dcd66ab
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java
@@ -0,0 +1,121 @@
+package io.ebeaninternal.server.core;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import io.ebean.PersistVisitor;
+import io.ebean.bean.EntityBean;
+import io.ebean.bean.EntityBeanIntercept;
+import io.ebeaninternal.server.deploy.BeanDescriptor;
+import io.ebeaninternal.server.deploy.BeanDescriptorManager;
+import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
+import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
+
+/**
+ * Handler to process persist graphs. It will allow you to visit a persist
+ * action (insert/update) and get information about all beans that will be
+ * affected by that action. This allows you to do validation and other things on
+ * a set of beans.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+class VisitHandler {
+
+ private final Set