From 2f397610bd2c21bab1d77aaee4bc1a0a23ee1245 Mon Sep 17 00:00:00 2001
From: "Jonathan S. Fisher" <exabrial@gmail.com>
Date: Tue, 28 Nov 2023 17:20:23 -0600
Subject: [PATCH] Close #1806 - Have to the ability to dynamically set the
 Validation Group during a transaction

---
 .../config/EntityManagerProperties.java       | 18 +++++++
 .../listeners/BeanValidationListener.java     | 48 +++++++++++++++----
 2 files changed, 57 insertions(+), 9 deletions(-)

diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/EntityManagerProperties.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/EntityManagerProperties.java
index b13537c346c..d485e29cf66 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/EntityManagerProperties.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/EntityManagerProperties.java
@@ -319,6 +319,21 @@ public class EntityManagerProperties {
      * )
      */
     public static final String COMPOSITE_UNIT_PROPERTIES = PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES;
+    
+    /**
+     * Overrides the Bean Validation Group(s) that will execute during a prePersist event. This should be a class or class[].
+     */
+    public static final String VALIDATION_GROUP_PRE_PERSIST = "eclipselink.validation.group.prePersist";
+    
+    /**
+     * Overrides the Bean Validation Group(s) that will execute during a preUpdate event. This should be a class or class[].
+     */
+    public static final String VALIDATION_GROUP_PRE_UPDATE = "eclipselink.validation.group.preUpdate";
+    
+    /**
+     * Overrides the Bean Validation Group(s) that will execute during a preRemove event. This should be a class or class[].
+     */
+    public static final String VALIDATION_GROUP_PRE_REMOVE = "eclipselink.validation.group.preRemove";
 
     private static final Set<String> supportedProperties = new HashSet<String>() {
 
@@ -344,6 +359,9 @@ public class EntityManagerProperties {
             add(PERSISTENCE_CONTEXT_COMMIT_ORDER);
             add(FLUSH_CLEAR_CACHE);
             add(COMPOSITE_UNIT_PROPERTIES);
+            add(VALIDATION_GROUP_PRE_PERSIST);
+            add(VALIDATION_GROUP_PRE_UPDATE);
+            add(VALIDATION_GROUP_PRE_REMOVE);
         }
     };
 
diff --git a/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java b/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java
index f92b68239f3..b52d7d97005 100644
--- a/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java
+++ b/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java
@@ -36,17 +36,20 @@
 import javax.validation.ValidatorFactory;
 import javax.validation.groups.Default;
 
+import org.eclipse.persistence.config.EntityManagerProperties;
 import org.eclipse.persistence.config.PersistenceUnitProperties;
 import org.eclipse.persistence.descriptors.ClassDescriptor;
 import org.eclipse.persistence.descriptors.DescriptorEvent;
 import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
 import org.eclipse.persistence.descriptors.FetchGroupManager;
+import org.eclipse.persistence.exceptions.BeanValidationException;
 import org.eclipse.persistence.internal.localization.ExceptionLocalization;
 import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
 import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
 import org.eclipse.persistence.mappings.DatabaseMapping;
 import org.eclipse.persistence.mappings.ForeignReferenceMapping;
 
+
 /**
  * Responsible for performing automatic bean validation on call back events.
  * @author Mitesh Meswani
@@ -72,7 +75,7 @@ public BeanValidationListener(ValidatorFactory validatorFactory, Class[] groupPr
 
     @Override
     public void prePersist (DescriptorEvent event) {
-        //  since we are using prePersist to perform validation, invlid data may get inserted into database as shown by
+        //  since we are using prePersist to perform validation, invalid data may get inserted into database as shown by
         // following example
         //    tx.begin()
         //    e = new MyEntity(...);
@@ -81,19 +84,38 @@ public void prePersist (DescriptorEvent event) {
         //    tx.commit();
         //  "invalid data" would get inserted into database.
         //
-        //  preInsert can be used to work around above issue. Howerver, the JPA spec does not itent it.
+        //  preInsert can be used to work around above issue. However, the JPA spec does not itent it.
         //  This might be corrected in next iteration of spec
-        validateOnCallbackEvent(event, "prePersist", groupPrePersit);
+        Object overrideGroups = event.getSession().getParent().getProperties().get(EntityManagerProperties.VALIDATION_GROUP_PRE_PERSIST);
+        if (overrideGroups != null) {
+            if (overrideGroups instanceof Class) {
+                overrideGroups = new Class[] { (Class) overrideGroups };
+            } else if (!(overrideGroups instanceof Class[])) {
+                throw new BeanValidationException("prePersist validation group must be a Class or Class Array:" + overrideGroups);
+            }
+            validateOnCallbackEvent(event, "prePersist", (Class[]) overrideGroups);
+        } else {
+            validateOnCallbackEvent(event, "prePersist", groupPrePersit);
+        }
     }
 
-    @Override
     public void aboutToUpdate(DescriptorEvent event) {
         Object source = event.getSource();
         UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl )event.getSession();
         // preUpdate is also generated for deleted objects that were modified in this UOW.
         // Do not perform preUpdate validation for such objects as preRemove would have already been called.
-        if(!unitOfWork.isObjectDeleted(source)) {
-            validateOnCallbackEvent(event, "preUpdate", groupPreUpdate);
+        if (!unitOfWork.isObjectDeleted(source)) {
+          Object overrideGroups = event.getSession().getParent().getProperties().get(EntityManagerProperties.VALIDATION_GROUP_PRE_UPDATE);
+          if (overrideGroups != null) {
+              if (overrideGroups instanceof Class) {
+                  overrideGroups = new Class[] { (Class) overrideGroups };
+              } else if (!(overrideGroups instanceof Class[])) {
+                  throw new BeanValidationException("preUpdate validation group must be a Class or Class Array:" + overrideGroups);
+              }
+              validateOnCallbackEvent(event, "preUpdate", (Class[]) overrideGroups);
+          } else {
+              validateOnCallbackEvent(event, "preUpdate", groupPreUpdate);
+          }
         }
     }
 
@@ -104,9 +126,17 @@ public void preUpdateWithChanges(DescriptorEvent event) {
 
     @Override
     public void preRemove (DescriptorEvent event) {
-        if(groupPreRemove != null) { //No validation performed on preRemove if user has not explicitly specified a validation group
-           validateOnCallbackEvent(event, "preRemove", groupPreRemove);
-        }
+      Object overrideGroups = event.getSession().getParent().getProperties().get(EntityManagerProperties.VALIDATION_GROUP_PRE_REMOVE);
+      if (overrideGroups != null) {
+          if (overrideGroups instanceof Class) {
+              overrideGroups = new Class[] { (Class) overrideGroups };
+          } else if (!(overrideGroups instanceof Class[])) {
+              throw new BeanValidationException("preRemove validation group must be a Class or Class Array:" + overrideGroups);
+          }
+          validateOnCallbackEvent(event, "preRemove", (Class[]) overrideGroups);
+      } else if (groupPreRemove != null) { // No validation performed on preRemove if user has not explicitly specified a validation group
+          validateOnCallbackEvent(event, "preRemove", groupPreRemove);
+      }
     }
 
     private void validateOnCallbackEvent(DescriptorEvent event, String callbackEventName, Class[] validationGroup) {