Skip to content

Commit 37d396f

Browse files
committed
[2.7] Close #1806 - Have to the ability to dynamically set the Validation Group during a transaction. Add tests, update copyright headers.
1 parent 116033f commit 37d396f

File tree

9 files changed

+240
-15
lines changed

9 files changed

+240
-15
lines changed

foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/EntityManagerProperties.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -319,6 +319,21 @@ public class EntityManagerProperties {
319319
* )
320320
*/
321321
public static final String COMPOSITE_UNIT_PROPERTIES = PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES;
322+
323+
/**
324+
* Overrides the Bean Validation Group(s) that will execute during a prePersist event. This should be a class or class[].
325+
*/
326+
public static final String VALIDATION_GROUP_PRE_PERSIST = "eclipselink.validation.group.prePersist";
327+
328+
/**
329+
* Overrides the Bean Validation Group(s) that will execute during a preUpdate event. This should be a class or class[].
330+
*/
331+
public static final String VALIDATION_GROUP_PRE_UPDATE = "eclipselink.validation.group.preUpdate";
332+
333+
/**
334+
* Overrides the Bean Validation Group(s) that will execute during a preRemove event. This should be a class or class[].
335+
*/
336+
public static final String VALIDATION_GROUP_PRE_REMOVE = "eclipselink.validation.group.preRemove";
322337

323338
private static final Set<String> supportedProperties = new HashSet<String>() {
324339

@@ -344,6 +359,9 @@ public class EntityManagerProperties {
344359
add(PERSISTENCE_CONTEXT_COMMIT_ORDER);
345360
add(FLUSH_CLEAR_CACHE);
346361
add(COMPOSITE_UNIT_PROPERTIES);
362+
add(VALIDATION_GROUP_PRE_PERSIST);
363+
add(VALIDATION_GROUP_PRE_UPDATE);
364+
add(VALIDATION_GROUP_PRE_REMOVE);
347365
}
348366
};
349367

jpa/eclipselink.jpa.test/resource/eclipselink-beanvalidation-model/persistence.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!--
22
3-
Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
3+
Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
44
55
This program and the accompanying materials are made available under the
66
terms of the Eclipse Public License v. 2.0 which is available at
@@ -18,6 +18,7 @@
1818
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Employee</class>
1919
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Project</class>
2020
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Address</class>
21+
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Phone</class>
2122
<validation-mode>CALLBACK</validation-mode>
2223
</persistence-unit>
2324
</persistence>

jpa/eclipselink.jpa.test/resource/eclipselink-beanvalidation-model/server/persistence.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!--
22
3-
Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
3+
Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
44
55
This program and the accompanying materials are made available under the
66
terms of the Eclipse Public License v. 2.0 which is available at
@@ -19,6 +19,7 @@
1919
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Employee</class>
2020
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Project</class>
2121
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Address</class>
22+
<class>org.eclipse.persistence.testing.models.jpa.beanvalidation.Phone</class>
2223
<validation-mode>CALLBACK</validation-mode>
2324
<properties>
2425
<property name="eclipselink.target-server" value="@server-platform@"/>

jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/models/jpa/beanvalidation/Employee.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -50,6 +50,10 @@ public class Employee {
5050
@Valid
5151
@Embedded
5252
private Address adress;
53+
54+
@Valid
55+
@Embedded
56+
private Phone phone;
5357

5458
// ===========================================================
5559
// getters and setters for the state fields
@@ -105,4 +109,11 @@ void m1() {
105109
return "Employee {Id:" + id + " name:" + name + "}";
106110
}
107111

112+
public Phone getPhone() {
113+
return phone;
114+
}
115+
116+
public void setPhone(Phone phone) {
117+
this.phone = phone;
118+
}
108119
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2009, 2025 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0,
7+
* or the Eclipse Distribution License v. 1.0 which is available at
8+
* http://www.eclipse.org/org/documents/edl-v10.php.
9+
*
10+
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
11+
*/
12+
13+
// Contributors:
14+
15+
package org.eclipse.persistence.testing.models.jpa.beanvalidation;
16+
17+
public interface GermanPhone {
18+
19+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2009, 2025 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0,
7+
* or the Eclipse Distribution License v. 1.0 which is available at
8+
* http://www.eclipse.org/org/documents/edl-v10.php.
9+
*
10+
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
11+
*/
12+
13+
// Contributors:
14+
15+
package org.eclipse.persistence.testing.models.jpa.beanvalidation;
16+
17+
import javax.persistence.Embeddable;
18+
import javax.validation.constraints.Pattern;
19+
import javax.validation.constraints.Size;
20+
21+
@Embeddable
22+
public class Phone {
23+
@Pattern(regexp = "^\\+1 \\([0-9]{3}\\) [0-9]{3}-[0-9]{4}$", groups = { USPhone.class })
24+
@Pattern(regexp = "^\\+49 \\([1-9]\\d{1,4}\\) \\d{3,8}-\\d{4}$", groups = { GermanPhone.class })
25+
String phone;
26+
27+
public Phone() {}
28+
29+
public Phone(String phone) {
30+
this.phone = phone;
31+
}
32+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2009, 2025 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0,
7+
* or the Eclipse Distribution License v. 1.0 which is available at
8+
* http://www.eclipse.org/org/documents/edl-v10.php.
9+
*
10+
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
11+
*/
12+
13+
// Contributors:
14+
15+
package org.eclipse.persistence.testing.models.jpa.beanvalidation;
16+
17+
public interface USPhone {
18+
19+
}

jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/tests/jpa/beanvalidation/BeanValidationJunitTest.java

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -27,13 +27,17 @@
2727
import javax.validation.ConstraintViolation;
2828
import javax.validation.ConstraintViolationException;
2929

30+
import org.eclipse.persistence.config.EntityManagerProperties;
3031
import org.eclipse.persistence.logging.SessionLog;
3132
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
3233
import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
3334
import org.eclipse.persistence.testing.models.jpa.beanvalidation.Address;
3435
import org.eclipse.persistence.testing.models.jpa.beanvalidation.BeanValidationTableCreator;
3536
import org.eclipse.persistence.testing.models.jpa.beanvalidation.Employee;
37+
import org.eclipse.persistence.testing.models.jpa.beanvalidation.GermanPhone;
38+
import org.eclipse.persistence.testing.models.jpa.beanvalidation.Phone;
3639
import org.eclipse.persistence.testing.models.jpa.beanvalidation.Project;
40+
import org.eclipse.persistence.testing.models.jpa.beanvalidation.USPhone;
3741

3842
import junit.framework.Test;
3943
import junit.framework.TestSuite;
@@ -396,6 +400,96 @@ public void testValidateChangedData() {
396400
}
397401
}
398402

403+
public void testPersistIrishPhoneNoGroups() {
404+
EntityManager em = createEntityManager();
405+
boolean gotConstraintViolations = false;
406+
try {
407+
// Persist an object with Irish phone number (valid default-phone)
408+
beginTransaction(em);
409+
Employee e = new Employee();
410+
e.setPhone(new Phone("+353 (1) 555-5678"));
411+
em.persist(e);
412+
} catch (ConstraintViolationException e) {
413+
assertTrue("Transaction not marked for roll back when ConstraintViolation is thrown", getRollbackOnly(em));
414+
gotConstraintViolations = true;
415+
} finally {
416+
if (isTransactionActive(em)) {
417+
rollbackTransaction(em);
418+
}
419+
closeEntityManager(em);
420+
}
421+
assertFalse("Did not expect Constraint Violation while persisting IrishPhone without groups", gotConstraintViolations);
422+
}
423+
424+
public void testPersistUSPhoneWithGermanGroup() {
425+
Map<String, Object> props = new HashMap<>();
426+
props.put(EntityManagerProperties.VALIDATION_GROUP_PRE_PERSIST, new Class[]{ GermanPhone.class });
427+
EntityManager em = createEntityManager(props);
428+
boolean gotConstraintViolations = false;
429+
try {
430+
// Persist an object with US phone number
431+
beginTransaction(em);
432+
Employee e = new Employee();
433+
e.setPhone(new Phone("+1 (316) 555-5555"));
434+
em.persist(e);
435+
} catch (ConstraintViolationException e) {
436+
assertTrue("Transaction not marked for roll back when ConstraintViolation is thrown", getRollbackOnly(em));
437+
gotConstraintViolations = true;
438+
} finally {
439+
if (isTransactionActive(em)) {
440+
rollbackTransaction(em);
441+
}
442+
closeEntityManager(em);
443+
}
444+
assertTrue("Did not get Constraint Violation while persisting USPhone with German group active", gotConstraintViolations);
445+
}
446+
447+
public void testPersistGermanPhoneWithUSGroup() {
448+
Map<String, Object> props = new HashMap<>();
449+
props.put(EntityManagerProperties.VALIDATION_GROUP_PRE_PERSIST, new Class[]{ USPhone.class });
450+
EntityManager em = createEntityManager(props);
451+
boolean gotConstraintViolations = false;
452+
try {
453+
// Persist an object with German phone number (invalid without German group)
454+
beginTransaction(em);
455+
Employee e = new Employee();
456+
e.setPhone(new Phone("+49 (221) 1555-5678"));
457+
em.persist(e);
458+
} catch (ConstraintViolationException e) {
459+
assertTrue("Transaction not marked for roll back when ConstraintViolation is thrown", getRollbackOnly(em));
460+
gotConstraintViolations = true;
461+
} finally {
462+
if (isTransactionActive(em)) {
463+
rollbackTransaction(em);
464+
}
465+
closeEntityManager(em);
466+
}
467+
assertTrue("Did not get Constraint Violation while persisting GermanPhone with US group active", gotConstraintViolations);
468+
}
469+
470+
public void testPersistGermanPhoneWithGermanGroup() {
471+
Map<String, Object> props = new HashMap<>();
472+
props.put(EntityManagerProperties.VALIDATION_GROUP_PRE_PERSIST, new Class[]{ GermanPhone.class });
473+
EntityManager em = createEntityManager(props);
474+
boolean gotConstraintViolations = false;
475+
try {
476+
// Persist an object with German phone number (valid DE-phone)
477+
beginTransaction(em);
478+
Employee e = new Employee();
479+
e.setPhone(new Phone("+49 (221) 1555-5678"));
480+
em.persist(e);
481+
} catch (ConstraintViolationException e) {
482+
assertTrue("Transaction not marked for roll back when ConstraintViolation is thrown", getRollbackOnly(em));
483+
gotConstraintViolations = true;
484+
} finally {
485+
if (isTransactionActive(em)) {
486+
rollbackTransaction(em);
487+
}
488+
closeEntityManager(em);
489+
}
490+
assertFalse("Did not expect Constraint Violation while persisting GermanPhone with German group active", gotConstraintViolations);
491+
}
492+
399493
//--------------------Helper Methods ---------------//
400494
private boolean isInstantiated(Object entityObject, String attributeName, org.eclipse.persistence.sessions.Project project) {
401495
ForeignReferenceMapping mapping = (ForeignReferenceMapping) project.getDescriptor(Employee.class).getObjectBuilder().getMappingForAttributeName(attributeName);

jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2020 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -36,17 +36,20 @@
3636
import javax.validation.ValidatorFactory;
3737
import javax.validation.groups.Default;
3838

39+
import org.eclipse.persistence.config.EntityManagerProperties;
3940
import org.eclipse.persistence.config.PersistenceUnitProperties;
4041
import org.eclipse.persistence.descriptors.ClassDescriptor;
4142
import org.eclipse.persistence.descriptors.DescriptorEvent;
4243
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
4344
import org.eclipse.persistence.descriptors.FetchGroupManager;
45+
import org.eclipse.persistence.exceptions.BeanValidationException;
4446
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
4547
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
4648
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
4749
import org.eclipse.persistence.mappings.DatabaseMapping;
4850
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
4951

52+
5053
/**
5154
* Responsible for performing automatic bean validation on call back events.
5255
* @author Mitesh Meswani
@@ -72,7 +75,7 @@ public BeanValidationListener(ValidatorFactory validatorFactory, Class[] groupPr
7275

7376
@Override
7477
public void prePersist (DescriptorEvent event) {
75-
// since we are using prePersist to perform validation, invlid data may get inserted into database as shown by
78+
// since we are using prePersist to perform validation, invalid data may get inserted into database as shown by
7679
// following example
7780
// tx.begin()
7881
// e = new MyEntity(...);
@@ -81,19 +84,38 @@ public void prePersist (DescriptorEvent event) {
8184
// tx.commit();
8285
// "invalid data" would get inserted into database.
8386
//
84-
// preInsert can be used to work around above issue. Howerver, the JPA spec does not itent it.
87+
// preInsert can be used to work around above issue. However, the JPA spec does not itent it.
8588
// This might be corrected in next iteration of spec
86-
validateOnCallbackEvent(event, "prePersist", groupPrePersit);
89+
Object overrideGroups = event.getSession().getParent().getProperties().get(EntityManagerProperties.VALIDATION_GROUP_PRE_PERSIST);
90+
if (overrideGroups != null) {
91+
if (overrideGroups instanceof Class) {
92+
overrideGroups = new Class[] { (Class) overrideGroups };
93+
} else if (!(overrideGroups instanceof Class[])) {
94+
throw new BeanValidationException("prePersist validation group must be a Class or Class Array:" + overrideGroups);
95+
}
96+
validateOnCallbackEvent(event, "prePersist", (Class[]) overrideGroups);
97+
} else {
98+
validateOnCallbackEvent(event, "prePersist", groupPrePersit);
99+
}
87100
}
88101

89-
@Override
90102
public void aboutToUpdate(DescriptorEvent event) {
91103
Object source = event.getSource();
92104
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl )event.getSession();
93105
// preUpdate is also generated for deleted objects that were modified in this UOW.
94106
// Do not perform preUpdate validation for such objects as preRemove would have already been called.
95-
if(!unitOfWork.isObjectDeleted(source)) {
96-
validateOnCallbackEvent(event, "preUpdate", groupPreUpdate);
107+
if (!unitOfWork.isObjectDeleted(source)) {
108+
Object overrideGroups = event.getSession().getParent().getProperties().get(EntityManagerProperties.VALIDATION_GROUP_PRE_UPDATE);
109+
if (overrideGroups != null) {
110+
if (overrideGroups instanceof Class) {
111+
overrideGroups = new Class[] { (Class) overrideGroups };
112+
} else if (!(overrideGroups instanceof Class[])) {
113+
throw new BeanValidationException("preUpdate validation group must be a Class or Class Array:" + overrideGroups);
114+
}
115+
validateOnCallbackEvent(event, "preUpdate", (Class[]) overrideGroups);
116+
} else {
117+
validateOnCallbackEvent(event, "preUpdate", groupPreUpdate);
118+
}
97119
}
98120
}
99121

@@ -104,9 +126,17 @@ public void preUpdateWithChanges(DescriptorEvent event) {
104126

105127
@Override
106128
public void preRemove (DescriptorEvent event) {
107-
if(groupPreRemove != null) { //No validation performed on preRemove if user has not explicitly specified a validation group
108-
validateOnCallbackEvent(event, "preRemove", groupPreRemove);
109-
}
129+
Object overrideGroups = event.getSession().getParent().getProperties().get(EntityManagerProperties.VALIDATION_GROUP_PRE_REMOVE);
130+
if (overrideGroups != null) {
131+
if (overrideGroups instanceof Class) {
132+
overrideGroups = new Class[] { (Class) overrideGroups };
133+
} else if (!(overrideGroups instanceof Class[])) {
134+
throw new BeanValidationException("preRemove validation group must be a Class or Class Array:" + overrideGroups);
135+
}
136+
validateOnCallbackEvent(event, "preRemove", (Class[]) overrideGroups);
137+
} else if (groupPreRemove != null) { // No validation performed on preRemove if user has not explicitly specified a validation group
138+
validateOnCallbackEvent(event, "preRemove", groupPreRemove);
139+
}
110140
}
111141

112142
private void validateOnCallbackEvent(DescriptorEvent event, String callbackEventName, Class[] validationGroup) {

0 commit comments

Comments
 (0)