/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.integration.validation;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.test.AbstractPersistenceTestCase;
/**
* Tests the Bean Validation groups support as defined in the JPA 2.0 spec
* via the following scenarios:
*
* Verify default validation group on lifecycle events:
* 1a) PrePersist and PreUpdate validate with default validation group
* 1b) PreRemove does not validate with default validation group
* 1c) Specify the default group for PreRemove and verify that it validates with
* the default group.
* 1d) Verify validation for constraints using non-default validation groups
* does not occur.
* 1e) PrePersist does not validate with no validation group defined.
* 1f) PreUpdate does not validate when no validation group defined.
* 1g) PreUpdate only called when a pre-update validation group defined
* (ie. per-persist and pre-remove are disabled).
* 1h) PrePersist only called when pre-persist validation group defined
* (ie. per-persist and pre-remove are disabled).
*
* Verify validation occurs when specific validation groups are specified:
* 2a) Specify a non-default group for all lifecycle events.
* 2b) Specify multiple/mixed non-default groups for lifecycle events.
*
* Verify validation does not occur when no validation groups are specified:
* 3a) Specify an empty validation group for PrePersist and PreUpdate and
* verify validation does not occur on these events.
*
* @version $Rev$ $Date$
*/
public class TestValidationGroups extends AbstractPersistenceTestCase {
/**
* 1a) verify validation occurs using the default validation groups
* on pre-persist and pre-update on commit
*/
public void testDefaultValidationGroup() {
verifyDefaultValidationGroup(false);
}
/**
* 1af) verify validation occurs using the default validation groups
* on pre-persist and pre-update on flush
*/
public void testDefaultValidationGroupFlush() {
verifyDefaultValidationGroup(true);
}
/**
* 1b) verify validation does not occur using the default validation group
* on the PreRemove lifecycle event on commit.
*/
public void testDefaultPreRemove() {
verifyDefaultPreRemove(false);
}
/**
* 1bf) verify validation does not occur using the default validation group
* on the PreRemove lifecycle event on flush.
*/
public void testDefaultPreRemoveFlush() {
verifyDefaultPreRemove(true);
}
/**
* 1c) verify validation occurs on the default group when default is
* specified for pre-remove on commit
*/
public void testSpecifiedDefaultPreRemove() {
verifySpecifiedDefaultPreRemove(true);
}
/**
* 1cf) verify validation occurs on the default group when default is
* specified for pre-remove on flush
*/
public void testSpecifiedDefaultPreRemoveFlush() {
verifySpecifiedDefaultPreRemove(false);
}
/**
* 1e) PrePersist does not validate with no validation group defined.
*/
public void testPersistNoValidationGroup() {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"no-pre-persist-default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
DefGrpEntity dge = new DefGrpEntity();
dge.setDgName(null); // If default group was enabled for pre-persist, this would cause a CVE.
try {
em.getTransaction().begin();
em.persist(dge);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
fail("A ConstraintViolationException should not have been thrown " +
"on pre-persist");
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
} finally {
closeEM(em);
closeEMF(emf);
}
}
/**
* 1f) PreUpdate does not validate when no validation group defined.
*/
public void testUpdateNoValidationGroup() {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"no-pre-update-default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
DefGrpEntity dge = new DefGrpEntity();
dge.setDgName("NotNull");
try {
em.getTransaction().begin();
em.persist(dge);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
fail("A ConstraintViolationException should not have been thrown " +
"on pre-persist");
}
try {
em.getTransaction().begin();
dge.setDgName(null);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
fail("A ConstraintViolationException should not have been thrown " +
"on pre-update");
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
} finally {
closeEM(em);
closeEMF(emf);
}
}
/**
* 1g) PreUpdate only called when a pre-update validation group defined
* (ie. per-persist and pre-remove are disabled).
*/
public void testUpdateOnlyValidationGroup() {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"no-pre-persist-default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
DefGrpEntity dge = new DefGrpEntity();
dge.setDgName(null); // If default group enabled for pre-persist, this would cause a CVE.
try {
em.getTransaction().begin();
em.persist(dge);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
fail("A ConstraintViolationException should not have been thrown " +
"on pre-persist");
}
try {
em.getTransaction().begin();
dge.setDgName("NotNull");
dge.setDgName(null);
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-update");
} catch (ConstraintViolationException e) {
// expected
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
} finally {
closeEM(em);
closeEMF(emf);
}
}
/**
* 1h) PrePersist only called when pre-persist validation group defined
* (ie. per-persist and pre-remove are disabled).
*/
public void testPersistOnlyValidationGroup() {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"no-pre-update-default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
DefGrpEntity dge = new DefGrpEntity();
dge.setDgName(null);
try {
em.getTransaction().begin();
em.persist(dge);
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-persist");
} catch (ConstraintViolationException e) {
// Expected
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
// Fix the entity, persist with no CVE
try {
em.getTransaction().begin();
dge.setDgName("NotNull");
em.getTransaction().commit();
} catch (Exception e) {
fail("An Exception should not have been thrown " +
"on update");
}
// Update the entity with null value, should not case a CVE
try {
em.getTransaction().begin();
dge.setDgName(null);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
fail("A ConstraintViolationException should not have been thrown " +
"on pre-update");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
} finally {
closeEM(em);
closeEMF(emf);
}
}
/**
* 2a) verify non-default validation group for all lifecycle events on
* commit. default validation group constraints should not validate
*/
public void testNonDefaultValidationGroup() {
verifyNonDefaultValidationGroup(false);
}
/**
* 2af) verify non-default validation group for all lifecycle events on
* flush. default validation group constraints should not validate
*/
public void testNonDefaultValidationGroupFlush() {
verifyNonDefaultValidationGroup(true);
}
/**
* 2b1) verify multiple/mixed validation groups via persistence.xml
* @param flush
*/
public void testPesistenceXMLMultipleValidationGroups() {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"multi-validation-group-xml",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
verifyMultipleValidationGroups(emf);
}
/**
* 2b2) verify multiple/mixed validation groups via properties
* @param flush
*/
public void testMultipleValidationGroups() {
// Configure persistence properties via map
Map<String, Object> propMap = new HashMap<String, Object>();
propMap.put("javax.persistence.validation.group.pre-persist",
"org.apache.openjpa.integration.validation.ValGroup1," +
"org.apache.openjpa.integration.validation.ValGroup2");
propMap.put("javax.persistence.validation.group.pre-update",
"");
propMap.put("javax.persistence.validation.group.pre-remove",
"org.apache.openjpa.integration.validation.ValGroup2");
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"multi-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml",
propMap);
assertNotNull(emf);
verifyMultipleValidationGroups(emf);
}
private void verifyMultipleValidationGroups(OpenJPAEntityManagerFactorySPI emf) {
// create EM
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
MixedGrpEntity mge = new MixedGrpEntity();
// Assert vg1 and vg2 fire on pre-persist
try
{
em.getTransaction().begin();
em.persist(mge);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
checkCVE(e,
"vg1NotNull",
"vg2NotNull",
"vg12NotNull");
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
// Assert no validation occurs on pre-update
// Persist an entity. Default should not validate on pre-persist
em.getTransaction().begin();
mge.setVg1NotNull("Vg1");
mge.setVg2NotNull("Vg2");
mge.setVg12NotNull("Vg1&2");
em.persist(mge);
em.getTransaction().commit();
try {
em.getTransaction().begin();
mge.setDefNotNull(null);
mge.setVg12NotNull(null);
mge.setVg1NotNull(null);
mge.setVg2NotNull(null);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
fail("Update should have been successful." +
" Caught unexpected ConstraintViolationException.");
}
catch (Exception e) {
fail("Update should have been successful." +
" Caught unexpected exception.");
}
// Update the entity again so that it can be cleaned up by the
// emf cleanup facility. The update should not validate
em.getTransaction().begin();
mge.setVg2NotNull("Vg2NotNull");
mge.setVg12NotNull("Vg12NotNull");
em.getTransaction().commit();
// Assert vg2 and default groups validate on pre-remove
try {
em.getTransaction().begin();
mge.setDefNotNull(null);
mge.setVg1NotNull(null);
mge.setVg2NotNull(null);
mge.setVg12NotNull(null);
em.remove(mge);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
checkCVE(e,
"vg2NotNull",
"vg12NotNull");
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
finally {
closeEM(em);
closeEMF(emf);
}
}
/**
* 3a) No validation groups for pre-persist and pre-update and none for
* pre-remove by default. No validation should occur.
*/
public void testNoValidationGroups() {
// Configure persistence properties via map
Map<String, Object> propMap = new HashMap<String, Object>();
propMap.put("javax.persistence.validation.group.pre-persist","");
propMap.put("javax.persistence.validation.group.pre-update","");
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"multi-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml",
propMap);
assertNotNull(emf);
// create EM
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
MixedGrpEntity mge = new MixedGrpEntity();
try
{
// No validation on pre-persist
em.getTransaction().begin();
em.persist(mge);
em.getTransaction().commit();
// No validation on pre-update
em.getTransaction().begin();
mge.setVg12NotNull(null);
em.getTransaction().commit();
// No validation on pre-remove
em.getTransaction().begin();
em.remove(mge);
em.getTransaction().commit();
} catch (ConstraintViolationException e) {
fail("Operations should have been successful." +
" Caught unexpected ConstraintViolationException.");
}
catch (Exception e) {
fail("Operations should have been successful." +
" Caught unexpected exception.");
}
}
finally {
closeEM(em);
closeEMF(emf);
}
}
private void verifyDefaultValidationGroup(boolean flush) {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
getLog(emf).trace("verifyDefaultValidationGroup(" + flush + ")");
// create EM
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
DefGrpEntity dge = new DefGrpEntity();
// Test pre-persist with default group with flush after persist
// 1a) pre-persist
try {
em.getTransaction().begin();
em.persist(dge);
if (flush)
em.flush();
else
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-persist");
} catch (ConstraintViolationException e) {
checkCVE(e, "dgName");
// If flushing, tx should be marked for rollback
if (flush) {
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
}
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
// 1a) test pre-update with default group
// Add an entity with valid data (validation passes)
dge.setDgName("NonNullName");
em.getTransaction().begin();
em.persist(dge);
em.getTransaction().commit();
try {
// Update the entity with null value. pre-update
// validation should fail on flush or commit
em.getTransaction().begin();
dge.setDgName(null);
if (flush)
em.flush();
else
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-update");
} catch (ConstraintViolationException e) {
checkCVE(e, "dgName");
// If flushing, tx should be marked for rollback
if (flush) {
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
}
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
finally {
closeEM(em);
closeEMF(emf);
}
}
private void verifyNonDefaultValidationGroup(boolean flush) {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"non-default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
getLog(emf).trace("verifyNonDefaultValidationGroup(" + flush + ")");
// create EM
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
NonDefGrpEntity ndge = new NonDefGrpEntity();
// Test pre-persist with non-default group with flush after persist
try {
em.getTransaction().begin();
em.persist(ndge);
if (flush)
em.flush();
else
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-persist");
} catch (ConstraintViolationException e) {
checkCVE(e, "ndgName");
getLog(emf).trace("Caught expected exception");
// If flushing, tx should be marked for rollback
if (flush) {
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
}
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
// pre-update with non-default group. default group
// validation should not occur
// Add an entity with valid data (validation passes)
try {
ndge.setNdgName("NonNullName");
ndge.setDgName(null);
em.getTransaction().begin();
em.persist(ndge);
em.getTransaction().commit();
getLog(emf).trace("Entity was persisted. As expected, no " +
"validation took place on pre-persist with default group.");
}
catch (ConstraintViolationException e) {
fail("Caught unexpected exception");
if (em.getTransaction().isActive())
em.getTransaction().rollback();
}
try {
// Update the entity with null value. pre-update
// validation should fail on flush or commit
em.getTransaction().begin();
ndge.setNdgName(null);
if (flush)
em.flush();
else
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-update");
} catch (ConstraintViolationException e) {
checkCVE(e, "ndgName");
// If flushing, tx should be marked for rollback
if (flush) {
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
}
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
// Merge the entity so that it can be removed.
em.getTransaction().begin();
ndge.setDgName(null);
ndge.setNdgName("Some name");
ndge = em.merge(ndge);
em.getTransaction().commit();
try {
// Update the entity with null value and remove the entity.
// validation should not fail on pre-remove
em.getTransaction().begin();
ndge.setNdgName(null);
em.remove(ndge);
if (flush)
em.flush();
else
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-remove");
} catch (ConstraintViolationException e) {
checkCVE(e, "ndgName");
// If flushing, tx should be marked for rollback
if (flush) {
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
}
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
}
finally {
closeEM(em);
closeEMF(emf);
}
}
/**
* verify validation does not occur using the default validation group
* on the PreRemove lifecycle event.
*/
public void verifyDefaultPreRemove(boolean flush) {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
getLog(emf).trace("verifyDefaultPreRemove(" + flush + ")");
// create EM
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
// Add an entity
DefGrpEntity dge = new DefGrpEntity();
dge.setDgName("NonNullName");
em.getTransaction().begin();
em.persist(dge);
em.getTransaction().commit();
try {
// Update the entity with null value and remove the entity.
// validation should not fail on pre-remove
em.getTransaction().begin();
dge.setDgName(null);
em.remove(dge);
if (flush)
em.flush();
else
em.getTransaction().commit();
getLog(emf).trace("Entity was removed. As expected, no " +
"validation took place on pre-remove.");
} catch (ConstraintViolationException e) {
fail("Should not have caught a ConstraintViolationException");
getLog(emf).trace("Caught expected exception");
}
catch (Exception e) {
fail("Should not have caught an Exception");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
finally {
closeEM(em);
closeEMF(emf);
}
}
/**
* verify validation occurs when the default validation group
* is specified for the PreRemove lifecycle event via the
* "javax.persistence.validation.group.pre-remove" property.
*/
public void verifySpecifiedDefaultPreRemove(boolean flush) {
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI)
OpenJPAPersistence.createEntityManagerFactory(
"pre-remove-default-validation-group",
"org/apache/openjpa/integration/validation/persistence.xml");
assertNotNull(emf);
getLog(emf).trace("verifySpecifiedDefaultPreRemove(" + flush + ")");
// create EM
OpenJPAEntityManager em = emf.createEntityManager();
assertNotNull(em);
try {
// Add an entity
DefGrpEntity dge = new DefGrpEntity();
dge.setDgName("NonNullName");
em.getTransaction().begin();
em.persist(dge);
em.getTransaction().commit();
try {
// Update the entity with null value and remove the entity.
// validation should not fail on pre-remove
em.getTransaction().begin();
dge.setDgName(null);
em.remove(dge);
if (flush)
em.flush();
else
em.getTransaction().commit();
fail("A ConstraintViolationException should have been thrown " +
"on pre-remove");
} catch (ConstraintViolationException e) {
checkCVE(e, "dgName");
// If flushing, tx should be marked for rollback
if (flush) {
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
}
}
catch (Exception e) {
fail("Should have caught a ConstraintViolationException");
}
finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
finally {
closeEM(em);
closeEMF(emf);
}
}
private void checkCVE(ConstraintViolationException e,
String... vioProperties) {
Set<ConstraintViolation<?>>cvs = e.getConstraintViolations();
if (vioProperties.length == 0 && cvs == null)
return;
assertEquals(vioProperties.length, cvs.size());
Iterator<ConstraintViolation<?>> i =
(Iterator<ConstraintViolation<?>>) cvs.iterator();
while (i.hasNext()) {
ConstraintViolation<?> v = (ConstraintViolation<?>)i.next();
boolean found = false;
for (String vio : vioProperties) {
if (v.getPropertyPath().toString().compareTo(vio) == 0) {
found = true;
break;
}
}
if (!found) {
fail("Unexpected ConstraintViolation for: " +
v.getPropertyPath());
}
}
}
/**
* Internal convenience method for getting the OpenJPA logger
*
* @return Log
*/
private Log getLog(OpenJPAEntityManagerFactorySPI emf) {
return emf.getConfiguration().getLog("Tests");
}
}