/* * 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); } }