blob: ea06495f23017c460be989393b081e7d00a391f1 [file] [log] [blame]
/*
* 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.persistence.validation;
import java.security.AccessController;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.ValidationMode;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.event.LifecycleEvent;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.persistence.JPAProperties;
import org.apache.openjpa.validation.AbstractValidator;
import org.apache.openjpa.validation.ValidationException;
public class ValidatorImpl extends AbstractValidator {
private static final Localizer _loc = Localizer.forPackage(ValidatorImpl.class);
private ValidatorFactory _validatorFactory = null;
private Validator _validator = null;
private ValidationMode _mode = ValidationMode.AUTO;
private OpenJPAConfiguration _conf = null;
private transient Log _log = null;
// A map storing the validation groups to use for a particular event type
private Map<Integer, Class<?>[]> _validationGroups = new HashMap<Integer,Class<?>[]>();
// Lookup table for event to group property mapping
private static HashMap<String, Integer> _vgMapping = new HashMap<String, Integer> ();
static {
_vgMapping.put(JPAProperties.VALIDATE_PRE_PERSIST, LifecycleEvent.BEFORE_PERSIST);
_vgMapping.put(JPAProperties.VALIDATE_PRE_REMOVE, LifecycleEvent.BEFORE_DELETE);
_vgMapping.put(JPAProperties.VALIDATE_PRE_UPDATE, LifecycleEvent.BEFORE_UPDATE);
}
/**
* Default constructor. Builds a default validator factory, if available
* and creates the validator.
* Returns an Exception if a Validator could not be created.
*/
public ValidatorImpl() {
initialize();
}
public ValidatorImpl(Configuration conf) {
if (conf instanceof OpenJPAConfiguration) {
_conf = (OpenJPAConfiguration)conf;
_log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
Object validatorFactory = _conf.getValidationFactoryInstance();
String mode = _conf.getValidationMode();
_mode = Enum.valueOf(ValidationMode.class, mode);
if (validatorFactory != null) {
if (validatorFactory instanceof ValidatorFactory) {
_validatorFactory = (ValidatorFactory)validatorFactory;
} else {
// Supplied object was not an instance of a ValidatorFactory
throw new IllegalArgumentException(
_loc.get("invalid-factory").getMessage());
}
}
}
initialize();
}
/**
* Type-specific constructor
* Returns an Exception if a Validator could not be created.
* @param validatorFactory Instance of validator factory to use. Specify
* null to use the default factory.
* @param mode ValdiationMode enum value
*/
public ValidatorImpl(ValidatorFactory validatorFactory,
ValidationMode mode) {
if (mode != null) {
_mode = mode;
}
if (validatorFactory != null) {
_validatorFactory = validatorFactory;
}
initialize();
}
/**
* Common setup code factored out of the constructors
*/
private void initialize() {
// only try setting up a validator if mode is not NONE
if (_mode != ValidationMode.NONE) {
if (_validatorFactory == null) {
_validatorFactory = getDefaultValidatorFactory();
}
if (_validatorFactory != null) {
// use our TraversableResolver instead of BV provider one
_validator = _validatorFactory.usingContext().
traversableResolver(new TraversableResolverImpl()).
getValidator();
} else {
// A default ValidatorFactory could not be created.
throw new RuntimeException(
_loc.get("no-default-factory").getMessage());
}
// throw an exception if we have no Validator
if (_validator == null) {
// A Validator provider could not be created.
throw new RuntimeException(
_loc.get("no-validator").getMessage());
}
if (_conf != null) {
addValidationGroup(JPAProperties.VALIDATE_PRE_PERSIST, _conf.getValidationGroupPrePersist());
addValidationGroup(JPAProperties.VALIDATE_PRE_UPDATE, _conf.getValidationGroupPreUpdate());
addValidationGroup(JPAProperties.VALIDATE_PRE_REMOVE, _conf.getValidationGroupPreRemove());
} else {
// add in default validation groups, which can be over-ridden later
addDefaultValidationGroups();
}
} else {
// A Validator should not be created based on the supplied ValidationMode.
throw new RuntimeException(
_loc.get("no-validation").getMessage());
}
}
/**
* Add a validation group for the specific property. The properties map
* to a specific lifecycle event. To disable validation for a group, set
* the validation group to null.
*
* @param validationGroupName
* @param vgs
*/
public void addValidationGroup(String validationGroupName, Class<?>...vgs) {
Integer event = findEvent(validationGroupName);
if (event != null) {
_validationGroups.put(event, vgs);
return;
} else {
// There were no events found for group "{0}".
throw new IllegalArgumentException(
_loc.get("no-group-events", validationGroupName).getMessage());
}
}
/**
* Add a validation group for a specified event. Event definitions
* are defined in LifecycleEvent. To disable validation for a group, set
* the validation group to null.
*
* @param event
* @param validationGroup
*/
public void addValidationGroup(Integer event, Class<?>... validationGroup) {
_validationGroups.put(event, validationGroup);
}
/**
* Add the validation group(s) for the specified event. Event definitions
* are defined in LifecycleEvent
* @param event
* @param group
*/
public void addValidationGroup(String validationGroupName, String group) {
Integer event = findEvent(validationGroupName);
if (event != null) {
Class<?>[] vgs = getValidationGroup(validationGroupName, group);
if (vgs != null) {
addValidationGroup(event, vgs);
}
} else {
// There were no events found for group "{0}".
throw new IllegalArgumentException(
_loc.get("no-group-events", validationGroupName).getMessage());
}
}
/**
* Converts a comma separated list of validation groups into an array
* of classes.
* @param group
*/
private Class<?>[] getValidationGroup(String vgName, String group) {
Class<?>[] vgGrp = null;
if (group == null || group.trim().length() == 0) {
return null;
}
String[] strClasses = group.split(",");
if (strClasses.length > 0) {
vgGrp = new Class<?>[strClasses.length];
for (int i = 0; i < strClasses.length; i++) {
try {
vgGrp[i] = Class.forName(StringUtil.trim(strClasses[i]));
} catch (Throwable t) {
throw new IllegalArgumentException(
_loc.get("invalid-validation-group", StringUtil.trim(strClasses[i]),
vgName).getMessage(), t);
}
}
}
return vgGrp;
}
/**
* Return the validation groups to be validated for a specified event
* @param event Lifecycle event id
* @return An array of validation groups
*/
public Class<?>[] getValidationGroup(Integer event) {
return _validationGroups.get(event);
}
/**
* Returns whether the Validator is validating for the
* specified event. Based on whether validation groups are specified for
* the event.
*
* @param event the event to check for validation
* @return returns true if validating for this particular event
*/
public boolean isValidating(Integer event) {
return (_validationGroups.get(event) != null);
}
/**
* Returns the validation constraints for the specified class
*
* @param cls Class for which constraints to return
* @return The validation bean descriptor
*/
public BeanDescriptor getConstraintsForClass(Class<?> cls) {
return _validator.getConstraintsForClass(cls);
}
/**
* Validates a given instance
*
* @param <T> The instance to validate
* @param arg0 The class, of type T to validate
* @param event The event id
* @return ValidationException if the validator produces one or more
* constraint violations.
*/
@Override
public <T> ValidationException validate(T arg0, int event) {
if (!isValidating(event))
return null;
Set<ConstraintViolation<T>> violations = AccessController.doPrivileged(
J2DoPrivHelper.validateAction(_validator, arg0, getValidationGroup(event)));
if (violations != null && violations.size() > 0) {
return new ValidationException(
new ConstraintViolationException(
// A validation constraint failure occurred for class "{0}".
_loc.get("validate-failed",
arg0.getClass().getName()).getMessage(),
(Set)violations),
true);
}
return null;
}
/**
* Validates a property of a given instance
*
* @param <T> The instance to validate
* @param arg0 The property to validate
* @param property The property to validate
* @param event The event id
* @return ValidationException if the validator produces one or more
* constraint violations.
*/
@Override
public <T> ValidationException validateProperty(T arg0, String property,
int event) {
if (!isValidating(event))
return null;
Set<ConstraintViolation<T>> violations =
_validator.validateProperty(arg0, property,
getValidationGroup(event));
if (violations != null && violations.size() > 0) {
return new ValidationException(
new ConstraintViolationException(
// A validation constraint failure occurred for
// property "{1}" in class "{0}".
_loc.get("valdiate-property-failed",
arg0.getClass().getName(),property).getMessage(),
(Set)violations),
true);
}
return null;
}
/**
* Validates a value based upon the constraints applied to a given class
* attribute.
* @param <T> The instance type to base validation upon
* @param arg0 The class of type T to validate
* @param arg1 The property to validate
* @param arg2 The property value to validate
* @param event The event id
* @return ValidationException if the validator produces one or more
* constraint violations.
*/
@Override
public <T> ValidationException validateValue(Class<T> arg0,
String arg1, Object arg2, int event) {
if (!isValidating(event))
return null;
Set<ConstraintViolation<T>> violations =
_validator.validateValue(arg0, arg1, arg2,
getValidationGroup(event));
if (violations != null && violations.size() > 0) {
return new ValidationException(
new ConstraintViolationException(
// A validation constraint failure occurred for
// value "{2}" of property "{1}" in class "{0}".
_loc.get("validate-value-failed", arg0.getClass().getName(),
arg1, arg2.toString()).getMessage(),
(Set)violations),
true);
}
return null;
}
/**
* Returns whether validation is active for the given event.
*
* @param <T>
* @param arg0 Type being validated
* @param event event type
* @return true if validation is active for the specified event
*/
@Override
public <T> boolean validating(T arg0, int event) {
// TODO: This method will also make a determination based upon which
// groups are validating and the group defined on the class
return isValidating(event);
}
// Lookup the lifecycle event id for the validationProperty
private Integer findEvent(String validationProperty) {
return _vgMapping.get(validationProperty);
}
// Get the default validator factory
private ValidatorFactory getDefaultValidatorFactory() {
ValidatorFactory factory = null;
try {
factory = AccessController.doPrivileged(J2DoPrivHelper.buildDefaultValidatorFactoryAction());
} catch (javax.validation.ValidationException e) {
if (_log != null && _log.isTraceEnabled())
_log.trace(_loc.get("factory-create-failed"), e);
}
return factory;
}
// Per JSR-317, the pre-persist and pre-update groups will validate using
// the default validation group and pre-remove will not validate (no
// validation group)
private void addDefaultValidationGroups() {
addValidationGroup(JPAProperties.VALIDATE_PRE_PERSIST, javax.validation.groups.Default.class);
addValidationGroup(JPAProperties.VALIDATE_PRE_UPDATE, javax.validation.groups.Default.class);
}
}