OPENJPA-1984 - Skip non-root object validation when performing remove(root object). Let's validation provider to perform root object validation so that the contraint violation can report the correct root bean.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1096791 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Albert Lee 2011-04-26 16:00:52 +00:00
parent d1d356c151
commit 9ea0354f1b
6 changed files with 510 additions and 2 deletions

View File

@ -0,0 +1,68 @@
/*
* 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 javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* Image entity which makes use of several BV constraints.
*/
@Entity
public class Image {
private int id;
private String fileName;
private Location location;
@Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NotNull(message="Image file name must not be null.")
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
@Valid
@Embedded
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public String toString() {
return "[Image:id=" + id + ",filename=" + fileName + "," + location + ']';
}
}

View File

@ -0,0 +1,96 @@
/*
* 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 javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* Location embeddable with several BV constraints applied.
*/
@Embeddable
public class Location {
@NotNull(message="City must be specified.")
private String city;
private String street;
private String state;
@NotNull(message="Country must be specified.")
@Size(message="Country must be 50 characters or less.", max=50)
@Column(length=50)
private String country;
@Size(message="Zip code must be 10 characters or less.", max=10)
@Pattern(message="Zip code must be 5 digits or use the 5+4 format.",
regexp="^\\d{5}(([\\-]|[\\+])\\d{4})?$")
@Column(length=10)
private String zipCode;
public void setCity(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setStreet(String street) {
this.street = street;
}
public String getStreet() {
return street;
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setCountry(String country) {
this.country = country;
}
public String getCountry() {
return country;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getZipCode() {
return zipCode;
}
public String toString() {
return "[Location:city=" + city + ",street=" + street + ",state=" + state + ",country=" + country + ",zipCode="
+ zipCode + ']';
}
}

View File

@ -0,0 +1,309 @@
/*
* 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.Map;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.test.AbstractPersistenceTestCase;
public class TestConstraintViolation extends AbstractPersistenceTestCase {
EntityManagerFactory emf2 = null;
Log log = null;
public void setUp() {
Map<String, String> props = new HashMap<String, String>();
props.put("javax.persistence.validation.group.pre-remove", "javax.validation.groups.Default");
// This test case uses a different persistence xml file because validation require 2.0 xsd.
emf2 = OpenJPAPersistence.createEntityManagerFactory("ConstraintViolationTestPU",
"org/apache/openjpa/integration/validation/persistence.xml", props);
log = ((OpenJPAEntityManagerFactorySPI)emf2).getConfiguration().getLog("Tests");
EntityManager em = emf2.createEntityManager();
Image img = em.find(Image.class, 1);
if (img != null) {
em.getTransaction().begin();
em.remove(img);
em.getTransaction().commit();
}
em.close();
}
public void testPersistNormalValidation() {
EntityManager em = emf2.createEntityManager();
// Persist with successful validations
Location loc = new Location();
loc.setCity("Rochester");
loc.setStreet(null);
loc.setState("MN");
loc.setZipCode("55901");
loc.setCountry("USA");
Image img = new Image();
img.setId(1);
img.setFileName("Winter_01.gif");
img.setLocation(loc);
try {
em.getTransaction().begin();
log.trace("------------------------------------------------");
log.trace("** Persist with successful validations");
em.persist(img);
} catch (ConstraintViolationException cve) {
// Transaction was marked for rollback, roll it back and start a new TX
Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
for (ConstraintViolation<?> cv : cvs) {
log.trace("Message: " + cv.getMessage());
log.trace("RootBean: " + cv.getRootBean());
log.trace("LeafBean: " + cv.getLeafBean());
log.trace("PropertyPath: " + cv.getPropertyPath());
log.trace("Invalid value: " + cv.getInvalidValue());
}
fail();
} finally {
em.getTransaction().rollback();
em.close();
emf2.close();
}
}
public void testPersistImageNullValidation() {
EntityManager em = emf2.createEntityManager();
// Persist with null filename in Image
Location loc = new Location();
loc.setCity("Rochester");
loc.setStreet("3605 Hwy 52 N");
loc.setState("MN");
loc.setZipCode("55901");
loc.setCountry("USA");
Image img = new Image();
img.setId(1);
img.setFileName(null);
img.setLocation(loc);
try {
em.getTransaction().begin();
log.trace("------------------------------------------------");
log.trace("** Persist with null filename in Image");
em.persist(img);
fail();
} catch (ConstraintViolationException cve) {
// Transaction was marked for rollback, roll it back and start a new TX
Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
assertEquals(1, cvs.size());
for (ConstraintViolation<?> cv : cvs) {
log.trace("Message: " + cv.getMessage());
log.trace("RootBean: " + cv.getRootBean());
log.trace("LeafBean: " + cv.getLeafBean());
log.trace("PropertyPath: " + cv.getPropertyPath());
log.trace("Invalid value: " + cv.getInvalidValue());
assertEquals("Image file name must not be null.", cv.getMessage());
assertEquals("Image", cv.getRootBeanClass().getSimpleName());
assertEquals("Image", cv.getLeafBean().getClass().getSimpleName());
assertTrue(cv.getLeafBean().getClass() == cv.getRootBeanClass());
assertEquals("fileName", cv.getPropertyPath().toString());
assertNull(cv.getInvalidValue());
}
} finally {
em.getTransaction().rollback();
em.close();
emf2.close();
}
}
public void testPersistEmbedCityNullValidation() {
EntityManager em = emf2.createEntityManager();
// Persist with null city name in location
Location loc = new Location();
loc.setCity(null);
loc.setStreet("3605 Hwy 52 N");
loc.setState("MN");
loc.setZipCode("55901");
loc.setCountry("USA");
Image img = new Image();
img.setId(1);
img.setFileName("Winter_01.gif");
img.setLocation(loc);
try {
em.getTransaction().begin();
log.trace("------------------------------------------------");
log.trace("** Persist with null city name in location" );
em.persist(img);
fail();
} catch (ConstraintViolationException cve) {
// Transaction was marked for rollback, roll it back and start a new TX
Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
assertEquals(1, cvs.size());
for (ConstraintViolation<?> cv : cvs) {
log.trace("Message: " + cv.getMessage());
log.trace("RootBean: " + cv.getRootBean());
log.trace("LeafBean: " + cv.getLeafBean());
log.trace("PropertyPath: " + cv.getPropertyPath());
log.trace("Invalid value: " + cv.getInvalidValue());
assertEquals("City must be specified.", cv.getMessage());
assertEquals("Image", cv.getRootBeanClass().getSimpleName());
// The violation occurred on a leaf bean (embeddable)
assertEquals("Location", cv.getLeafBean().getClass().getSimpleName());
assertTrue(cv.getLeafBean().getClass() != cv.getRootBeanClass());
assertEquals("location.city", cv.getPropertyPath().toString());
assertNull(cv.getInvalidValue());
}
} finally {
em.getTransaction().rollback();
em.close();
emf2.close();
}
}
public void testRemoveEmbedCityNullValidation() {
EntityManager em = emf2.createEntityManager();
// Remove with null city name in location
Location loc = new Location();
loc.setCity("Rochester");
loc.setStreet("3605 Hwy 52 N");
loc.setState("MN");
loc.setZipCode("55901");
loc.setCountry("USA");
Image img = new Image();
img.setId(1);
img.setFileName("Winter_01.gif");
img.setLocation(loc);
try {
em.getTransaction().begin();
log.trace("------------------------------------------------");
log.trace("** Create normal Image/location" );
em.persist(img);
em.getTransaction().commit();
} catch (ConstraintViolationException cve) {
fail();
}
try {
em.getTransaction().begin();
log.trace("** set null city name in location and remove" );
img.getLocation().setCity(null);
em.remove(img);
fail();
} catch (ConstraintViolationException cve) {
// Transaction was marked for rollback, roll it back and
// start a new TX
Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
assertEquals(1, cvs.size());
for (ConstraintViolation<?> cv : cvs) {
log.trace("Message: " + cv.getMessage());
log.trace("RootBean: " + cv.getRootBean());
log.trace("LeafBean: " + cv.getLeafBean());
log.trace("PropertyPath: " + cv.getPropertyPath());
log.trace("Invalid value: " + cv.getInvalidValue());
assertEquals("City must be specified.", cv.getMessage());
assertEquals("Image", cv.getRootBeanClass().getSimpleName());
// The violation occurred on a leaf bean (embeddable)
assertEquals("Location", cv.getLeafBean().getClass().getSimpleName());
assertTrue(cv.getLeafBean().getClass() != cv.getRootBeanClass());
assertEquals("location.city", cv.getPropertyPath().toString());
assertNull(cv.getInvalidValue());
}
} finally {
em.getTransaction().rollback();
em.close();
emf2.close();
}
}
public void testFlushEmbedCityNullValidation() {
EntityManager em = emf2.createEntityManager();
// set invalid zipCode in location and flush testing pre-update
Location loc = new Location();
loc.setCity("Rochester");
loc.setStreet("3605 Hwy 52 N");
loc.setState("MN");
loc.setZipCode("55901");
loc.setCountry("USA");
Image img = new Image();
img.setId(1);
img.setFileName("Winter_01.gif");
img.setLocation(loc);
try {
em.getTransaction().begin();
log.trace("------------------------------------------------");
log.trace("** Create normal Image/location" );
em.persist(img);
em.getTransaction().commit();
} catch (ConstraintViolationException cve) {
fail();
}
try {
em.getTransaction().begin();
log.trace("** set invalid zipCode and flush testing pre-update" );
img.getLocation().setZipCode("abcde");
em.flush();
fail();
} catch (ConstraintViolationException cve) {
// Transaction was marked for rollback, roll it back and start a new TX
Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
assertEquals(1, cvs.size());
for (ConstraintViolation<?> cv : cvs) {
log.trace("Message: " + cv.getMessage());
log.trace("RootBean: " + cv.getRootBean());
log.trace("LeafBean: " + cv.getLeafBean());
log.trace("PropertyPath: " + cv.getPropertyPath());
log.trace("Invalid value: " + cv.getInvalidValue());
assertEquals("Zip code must be 5 digits or use the 5+4 format.", cv.getMessage());
assertEquals("Image", cv.getRootBeanClass().getSimpleName());
// The violation occurred on a leaf bean (embeddable)
assertEquals("Location", cv.getLeafBean().getClass().getSimpleName());
assertTrue(cv.getLeafBean().getClass() != cv.getRootBeanClass());
assertEquals("location.zipCode", cv.getPropertyPath().toString());
assertEquals("abcde", cv.getInvalidValue());
}
} finally {
em.getTransaction().rollback();
em.close();
emf2.close();
}
}
}

View File

@ -210,4 +210,16 @@
</properties>
</persistence-unit>
<!-- ============================================================ -->
<!-- Persistence unit for testing ConstraintViolation object -->
<persistence-unit name="ConstraintViolationTestPU">
<class>org.apache.openjpa.integration.validation.Image</class>
<class>org.apache.openjpa.integration.validation.Location</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<validation-mode>CALLBACK</validation-mode>
<properties>
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
</properties>
</persistence-unit>
</persistence>

View File

@ -96,6 +96,7 @@ import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;
import org.apache.openjpa.util.WrappedException;
import org.apache.openjpa.validation.ValidatingLifecycleEventManager;
/**
* Concrete {@link Broker}. The broker handles object-level behavior,
@ -2759,7 +2760,18 @@ public class BrokerImpl
throw newDetachedException(obj, "delete");
if ((action & OpCallbacks.ACT_CASCADE) != 0) {
if (!sm.isEmbedded() || !sm.getDereferencedEmbedDependent()) {
sm.cascadeDelete(call);
if (ValidatingLifecycleEventManager.class.isAssignableFrom(_lifeEventManager.getClass())) {
ValidatingLifecycleEventManager _validatingLCEventManager =
(ValidatingLifecycleEventManager) _lifeEventManager;
boolean saved = _validatingLCEventManager.setValidationEnabled(false);
try {
sm.cascadeDelete(call);
} finally {
_validatingLCEventManager.setValidationEnabled(saved);
}
} else {
sm.cascadeDelete(call);
}
}
}
sm.delete();

View File

@ -37,6 +37,7 @@ public class ValidatingLifecycleEventManager extends LifecycleEventManager
private OpenJPAConfiguration _conf = null;
private Validator _validator = null;
protected boolean _validationEnabled = true;
/**
* Constructor which accepts a reference to the validator to use. If null,
@ -118,7 +119,7 @@ public class ValidatingLifecycleEventManager extends LifecycleEventManager
// If a validator is provided and the source object should be validated,
// validate it and return any exceptions
if (_validator != null && _validator.validating(source, type)) {
if (_validationEnabled && _validator != null && _validator.validating(source, type)) {
ValidationException vex = _validator.validate(source, type);
if (vex != null) {
if (evx == null || evx.length == 0) {
@ -137,4 +138,14 @@ public class ValidatingLifecycleEventManager extends LifecycleEventManager
}
return evx;
}
public boolean isValidationEnabled() {
return _validationEnabled;
}
public boolean setValidationEnabled(boolean enabled) {
boolean val = _validationEnabled;
_validationEnabled = enabled;
return val;
}
}