org.eclipse.persistence.internal.jpa.metadata.listeners.BeanValidationListener Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction 180e602
/*******************************************************************************
* Copyright (c) 2009, 2015 Sun Microsystems, Inc, IBM Corporation. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* 08/20/2014-2.5 Rick Curtis
* - 441890: Cache Validator instances.
* Marcel Valovy - 2.6 - skip validation of objects that are not constrained.
******************************************************************************/
package org.eclipse.persistence.internal.jpa.metadata.listeners;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import javax.validation.TraversableResolver;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.FetchGroupManager;
import org.eclipse.persistence.internal.jpa.metadata.beanvalidation.BeanValidationHelper;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.annotation.ElementType;
/**
* Responsible for performing automatic bean validation on call back events.
* @author Mitesh Meswani
*/
public class BeanValidationListener extends DescriptorEventAdapter {
private final ValidatorFactory validatorFactory;
private final Class[] groupPrePersit;
private final Class[] groupPreUpdate;
private final Class[] groupPreRemove;
private static final Class[] groupDefault = new Class[]{Default.class};
private final Map validatorMap;
private final BeanValidationHelper beanValidationHelper = new BeanValidationHelper();
public BeanValidationListener(ValidatorFactory validatorFactory, Class[] groupPrePersit, Class[] groupPreUpdate, Class[] groupPreRemove) {
this.validatorFactory = validatorFactory;
//For prePersit and preUpdate, default the group to validation group Default if user has not specified one
this.groupPrePersit = groupPrePersit != null ? groupPrePersit : groupDefault;
this.groupPreUpdate = groupPreUpdate != null ? groupPreUpdate : groupDefault;
//No validation performed on preRemove if user has not explicitly specified a validation group
this.groupPreRemove = groupPreRemove;
validatorMap = new ConcurrentHashMap<>();
}
@Override
public void prePersist (DescriptorEvent event) {
// since we are using prePersist to perform validation, invlid data may get inserted into database as shown by
// following example
// tx.begin()
// e = new MyEntity(...);
// em.perist(e); // prePersist validation happens here
// em.setXXX("invalid data");
// 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.
// This might be corrected in next iteration of spec
validateOnCallbackEvent(event, "prePersist", groupPrePersit);
}
@Override
public void preUpdate (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);
}
}
@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);
}
}
private void validateOnCallbackEvent(DescriptorEvent event, String callbackEventName, Class[] validationGroup) {
Object source = event.getSource();
boolean noOptimization = "true".equalsIgnoreCase((String) event.getSession().getProperty(PersistenceUnitProperties.BEAN_VALIDATION_NO_OPTIMISATION));
boolean shouldValidate = noOptimization || beanValidationHelper.isConstrained(source.getClass());
if (shouldValidate) {
Set> constraintViolations = getValidator(event).validate(source, validationGroup);
if (constraintViolations.size() > 0) {
// There were errors while call to validate above.
// Throw a ConstrainViolationException as required by the spec.
// The transaction would be rolled back automatically
// TODO need to I18N this.
throw new ConstraintViolationException(
"Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'" +
callbackEventName + "'. Please refer to embedded ConstraintViolations for details.",
(Set>) (Object) constraintViolations); /* Do not remove the explicit
cast. This issue is related to capture#a not being instance of capture#b. */
}
}
}
private Validator getValidator(DescriptorEvent event) {
ClassDescriptor descriptor = event.getDescriptor();
Validator res = validatorMap.get(descriptor);
if (res == null) {
TraversableResolver traversableResolver = new AutomaticLifeCycleValidationTraversableResolver(descriptor);
res = validatorFactory.usingContext().traversableResolver(traversableResolver).getValidator();
Validator t = validatorMap.put(descriptor, res);
if (t != null) {
// Threading collision, use existing
res = t;
}
}
return res;
}
/**
* This traversable resolver ensures that validation is not cascaded to any associations and no lazily loaded
* attribute is loaded as a side effect of validation
*/
private static class AutomaticLifeCycleValidationTraversableResolver implements TraversableResolver {
private final ClassDescriptor descriptor;
AutomaticLifeCycleValidationTraversableResolver(ClassDescriptor eventDescriptor) {
descriptor = eventDescriptor;
}
/**
* @return false for any lazily loaded property of root object being validated
*/
public boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class rootBeanType, Path pathToTraversableObject, ElementType elementType) {
boolean reachable = true;
String attributeName = null;
if (isRootObjectPath(pathToTraversableObject)) {
attributeName = traversableProperty.getName(); //Refer to section 4.2 of Bean Validation spec for more details about Path.Node
DatabaseMapping mapping = getMappingForAttributeName(attributeName);
if(mapping != null) {
if(mapping.isForeignReferenceMapping()) {
// For lazy relationships check whether it is instantiated
if(mapping.isLazy()) {
Object attributeValue = mapping.getAttributeAccessor().getAttributeValueFromObject(traversableObject);
reachable = ((ForeignReferenceMapping)mapping).getIndirectionPolicy().objectIsInstantiatedOrChanged(attributeValue);
}
} else {
// For lazy non relationship attributes, check whether it is fetched
FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager();
if (fetchGroupManager != null) {
reachable = fetchGroupManager.isAttributeFetched(traversableObject, attributeName);
}
}
}
}
return reachable;
}
/**
* Called only if isReachable returns true
* @return false for any associatons of root object being validated true otherwise
*/
public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class rootBeanType, Path pathToTraversableObject, ElementType elementType) {
boolean cascadable = true;
if (isRootObjectPath(pathToTraversableObject)) {
String attributeName = traversableProperty.getName(); //Refer to section 4.2 of Bean Validation spec for more details about Path
DatabaseMapping mapping = getMappingForAttributeName(attributeName);
if(mapping != null && mapping.isForeignReferenceMapping()) {
cascadable = false;
}
}
return cascadable;
}
/**
* @return DatabaseMapping for given attribute name
*/
private DatabaseMapping getMappingForAttributeName(String attributeName) {
return descriptor.getObjectBuilder().getMappingForAttributeName(attributeName);
}
/**
* @return true if given path corresponds to Root Object else false.
*/
private boolean isRootObjectPath(Path pathToTraversableObject) {
// From Bean Validation spec section 3.5.2
//
// pathToTraversableObject is the Path from the rootBeanType down to the traversableObject (it is composed of
// a single Node whose name is null if the root object is traversableObject). The path is described following the
// conventions described in Section 4.2 (getPropertyPath).
//
return pathToTraversableObject.iterator().next().getName() == null;
}
}
}