All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.faces.validator.BeanValidator Maven / Gradle / Ivy

There is a newer version: 3.1.0.SP02
Show newest version
/*
 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package javax.faces.validator;

import static javax.faces.validator.MessageFactory.getLabel;
import static javax.faces.validator.MessageFactory.getMessage;
import static javax.faces.validator.MultiFieldValidationUtils.FAILED_FIELD_LEVEL_VALIDATION;
import static javax.faces.validator.MultiFieldValidationUtils.getMultiFieldValidationCandidates;
import static javax.faces.validator.MultiFieldValidationUtils.wholeBeanValidationEnabled;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.PartialStateHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;


/**
 * 

A Validator that delegates * validation of the bean property to the Bean Validation API.

* @since 2.0 */ public class BeanValidator implements Validator, PartialStateHolder { private static final Logger LOGGER = Logger.getLogger("javax.faces.validator", "javax.faces.LogStrings"); private String validationGroups; private transient Class[] cachedValidationGroups; /** *

The standard validator id for this * validator, as defined by the Jakarta Server Face specification.

*/ public static final String VALIDATOR_ID = "javax.faces.Bean"; /** *

The message identifier of the {@link javax.faces.application.FacesMessage} to be created if * a constraint failure is found. The message format string for * this message may optionally include the following placeholders: *

    *
  • {0} replaced by the interpolated message from Bean Validation.
  • *
  • {1} replaced by a String whose value * is the label of the input component that produced this message.
  • *
*

The message format string provided by the default implementation should be a the placeholder {0}, * thus fully delegating the message handling to Bean Validation. A developer can override this message * format string to make it conform to other Jakarta Server Face validator messages (i.e., by including the component label)

*/ public static final String MESSAGE_ID = "javax.faces.validator.BeanValidator.MESSAGE"; /** *

The name of the Jakarta Servlet context * attribute which holds the object used by Jakarta Server Faces to obtain Validator * instances. If the Jakarta Servlet context attribute is missing or * contains a null value, Jakarta Server Faces is free to use this Jakarta Servlet context * attribute to store the ValidatorFactory bootstrapped by this * validator.

*/ public static final String VALIDATOR_FACTORY_KEY = "javax.faces.validator.beanValidator.ValidatorFactory"; /** *

The delimiter that is used to * separate the list of fully-qualified group names as strings.

*/ public static final String VALIDATION_GROUPS_DELIMITER = ","; /** *

The regular expression pattern that * identifies an empty list of validation groups.

*/ public static final String EMPTY_VALIDATION_GROUPS_PATTERN = "^[\\W" + VALIDATION_GROUPS_DELIMITER + "]*$"; /** *

If this param is defined, and * calling toLowerCase().equals(“true”) on a * String representation of its value returns * true, the runtime must not automatically add the * validator with validator-id equal to the value of the symbolic * constant {@link #VALIDATOR_ID} to the list of default validators. * Setting this parameter to true will have the effect * of disabling the automatic installation of Bean Validation to * every input component in every view in the application, though * manual installation is still possible.

* */ public static final String DISABLE_DEFAULT_BEAN_VALIDATOR_PARAM_NAME = "javax.faces.validator.DISABLE_DEFAULT_BEAN_VALIDATOR"; /** *

If this param is set, and calling * toLowerCase().equals("true") on a * String representation of its value returns {@code true} take * the additional actions relating to <validateWholeBean /> * specified in {@link #validate}.

* * @since 2.3 */ public static final String ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME = "javax.faces.validator.ENABLE_VALIDATE_WHOLE_BEAN"; //----------------------------------------------------------- multi-field validation /** *

A comma-separated list of validation * groups which are used to filter which validations get checked by * this validator. If the validationGroupsArray attribute is omitted or * is empty, the validation groups will be inherited from the branch * defaults or, if there are no branch defaults, the {@link * javax.validation.groups.Default} group will be used.

* * @param validationGroups comma-separated list of validation groups * (string with only spaces and commas treated as null) */ public void setValidationGroups(String validationGroups) { clearInitialState(); String newValidationGroups = validationGroups; // treat empty list as null if (newValidationGroups != null && newValidationGroups.matches(EMPTY_VALIDATION_GROUPS_PATTERN)) { newValidationGroups = null; } // only clear cache of validation group classes if value is changing if (newValidationGroups == null && validationGroups != null) { cachedValidationGroups = null; } if (newValidationGroups != null && validationGroups != null && !newValidationGroups.equals(validationGroups)) { cachedValidationGroups = null; } if (newValidationGroups != null && validationGroups == null) { cachedValidationGroups = null; } this.validationGroups = newValidationGroups; } /** *

Return the validation groups passed * to the Validation API when checking constraints. If the * validationGroupsArray attribute is omitted or empty, the validation * groups will be inherited from the branch defaults, or if there * are no branch defaults, the {@link * javax.validation.groups.Default} group will be used.

* * @return the value of the {@code validatinGroups} attribute. */ public String getValidationGroups() { return validationGroups; } /** *

Verify * that the value is valid according to the Bean Validation constraints.

* *
*

Obtain a {@link ValidatorFactory} instance by calling {@link * javax.validation.Validation#buildDefaultValidatorFactory}.

*

Let validationGroupsArray be a Class [] * representing validator groups set on the component by the tag * handler for this validator. The first search component * terminates the search for the validation groups value. If no * such value is found use the class name of {@link * javax.validation.groups.Default} as the value of the validation * groups.

*

Let valueExpression be the return from calling {@link * UIComponent#getValueExpression} on the argument * component, passing the literal string * “value” (without the quotes) as an argument. If this * application is running in an environment with a Unified EL * Implementation for Java EE6 or later, obtain the * ValueReference from valueExpression and let * valueBaseClase be the return from calling * ValueReference.getBase() and valueProperty * be the return from calling * ValueReference.getProperty(). If an earlier version * of Jakarta Expression Language is present, use the appropriate methods to * inspect valueExpression and derive values for * valueBaseClass and valueProperty.

*

If no ValueReference can be obtained, take no * action and return.

*

If ValueReference.getBase() return * null, take no action and return.

*

Obtain the {@link ValidatorContext} from the {@link * ValidatorFactory}.

*

Decorate the {@link MessageInterpolator} returned from {@link * ValidatorFactory#getMessageInterpolator} with one that leverages * the Locale returned from {@link * javax.faces.component.UIViewRoot#getLocale}, and store it in the * ValidatorContext using {@link * ValidatorContext#messageInterpolator}.

*

Obtain the {@link javax.validation.Validator} instance from * the validatorContext.

*

Obtain a javax.validation.BeanDescriptor from the * javax.validation.Validator. If * hasConstraints() on the BeanDescriptor * returns false, take no action and return. Otherwise proceed.

*

Call {@link javax.validation.Validator#validateValue}, passing * valueBaseClass, valueProperty, the * value argument, and validatorGroupsArray as * arguments.

*

If the returned Set<{@link * ConstraintViolation}> is non-empty, for each element in * the Set, create a {@link FacesMessage} where the * summary and detail are the return from calling {@link * ConstraintViolation#getMessage}. Capture all such * FacesMessage instances into a * Collection and pass them to {@link * ValidatorException#ValidatorException(java.util.Collection)}. * If the {@link * #ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME} application parameter is * enabled and this {@code Validator} instance has validation groups * other than or in addition to the {@code Default} group, record * the fact that this field failed validation so that any * <f:validateWholeBean /> component later in the tree * is able to skip class-level validation for the bean for which this * particular field is a property. Regardless of * whether or not {@link #ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME} is * set, throw the new exception.

* *

If the returned {@code Set} is * empty, the {@link #ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME} * application parameter is enabled and this {@code Validator} * instance has validation groups other than or in addition to the * {@code Default} group, record the fact that this field passed * validation so that any <f:validateWholeBean /> component later in the tree * is able to allow class-level validation for the bean for which this particular * field is a property.

* *
* * @param context {@inheritDoc} * @param component {@inheritDoc} * @param value {@inheritDoc} * * @throws ValidatorException {@inheritDoc} */ @Override public void validate(FacesContext context, UIComponent component, Object value) { if (context == null) { throw new NullPointerException(); } if (component == null) { throw new NullPointerException(); } ValueExpression valueExpression = component.getValueExpression("value"); if (valueExpression == null) { return; } javax.validation.Validator beanValidator = getBeanValidator(context); Class[] validationGroupsArray = parseValidationGroups(getValidationGroups()); // PENDING(rlubke, driscoll): When Jakarta Expression Language 1.3 is present, we won't need // this. ValueExpressionAnalyzer expressionAnalyzer = new ValueExpressionAnalyzer(valueExpression); ValueReference valueReference = expressionAnalyzer.getReference(context.getELContext()); if (valueReference == null) { return; } if (isResolvable(valueReference, valueExpression)) { @SuppressWarnings("rawtypes") Set violationsRaw = null; try { violationsRaw = beanValidator.validateValue( valueReference.getBaseClass(), valueReference.getProperty(), value, validationGroupsArray); } catch (IllegalArgumentException iae) { LOGGER.fine( "Unable to validate expression " + valueExpression.getExpressionString() + " using Bean Validation. Unable to get value of expression. " + " Message from Bean Validation: " + iae.getMessage()); } @SuppressWarnings("unchecked") Set> violations = violationsRaw; if (violations != null && !violations.isEmpty()) { ValidatorException toThrow; if (violations.size() == 1) { ConstraintViolation violation = violations.iterator().next(); toThrow = new ValidatorException(getMessage(context, MESSAGE_ID, violation.getMessage(), getLabel(context, component))); } else { Set messages = new LinkedHashSet<>(violations.size()); for (ConstraintViolation violation : violations) { messages.add(getMessage(context, MESSAGE_ID, violation.getMessage(), getLabel(context, component))); } toThrow = new ValidatorException(messages); } // Record the fact that this field failed validation, so that multi-field // validation is not attempted. if (wholeBeanValidationEnabled(context, validationGroupsArray)) { recordValidationResult(context, component, valueReference.getBase(), valueReference.getProperty(), FAILED_FIELD_LEVEL_VALIDATION); } throw toThrow; } } // Record the fact that this field passed validation, so that multi-field // validation can be performed if desired if (wholeBeanValidationEnabled(context, validationGroupsArray)) { recordValidationResult(context, component, valueReference.getBase(), valueReference.getProperty(), value); } } private boolean isResolvable(ValueReference valueReference, ValueExpression valueExpression) { Boolean resolvable = null; String failureMessage = null; if (valueExpression == null) { failureMessage = "Unable to validate expression using Bean " + "Validation. Expression must not be null."; resolvable = false; } else if (valueReference == null) { failureMessage = "Unable to validate expression " + valueExpression.getExpressionString() + " using Bean Validation. Unable to get value of expression."; resolvable = false; } else { Class baseClass = valueReference.getBaseClass(); // case 1, base classes of Map, List, or Array are not resolvable if (baseClass != null) { if (Map.class.isAssignableFrom(baseClass) || Collection.class.isAssignableFrom(baseClass) || Array.class.isAssignableFrom(baseClass)) { failureMessage = "Unable to validate expression " + valueExpression.getExpressionString() + " using Bean Validation. Expression evaluates to a Map, List or array."; resolvable = false; } } } resolvable = ((null != resolvable) ? resolvable : true); if (!resolvable) { LOGGER.fine(failureMessage); } return resolvable; } private Class[] parseValidationGroups(String validationGroupsStr) { if (cachedValidationGroups != null) { return cachedValidationGroups; } if (validationGroupsStr == null) { cachedValidationGroups = new Class[] { Default.class }; return cachedValidationGroups; } List> validationGroupsList = new ArrayList<>(); String[] classNames = validationGroupsStr.split(VALIDATION_GROUPS_DELIMITER); for (String className : classNames) { className = className.trim(); if (className.length() == 0) { continue; } if (className.equals(Default.class.getName())) { validationGroupsList.add(Default.class); } else { try { validationGroupsList.add(Class.forName(className, false, Thread.currentThread().getContextClassLoader())); } catch (ClassNotFoundException e1) { try { validationGroupsList.add(Class.forName(className)); } catch (ClassNotFoundException e2) { throw new FacesException("Validation group not found: " + className); } } } } cachedValidationGroups = validationGroupsList.toArray(new Class[validationGroupsList.size()]); return cachedValidationGroups; } // ----------------------------------------------------- StateHolder Methods @Override public Object saveState(FacesContext context) { if (context == null) { throw new NullPointerException(); } if (!initialStateMarked()) { Object values[] = new Object[1]; values[0] = validationGroups; return values; } return null; } @Override public void restoreState(FacesContext context, Object state) { if (context == null) { throw new NullPointerException(); } if (state != null) { Object values[] = (Object[]) state; validationGroups = (String) values[0]; } } private boolean initialState; @Override public void markInitialState() { initialState = true; } @Override public boolean initialStateMarked() { return initialState; } @Override public void clearInitialState() { initialState = false; } private boolean transientValue; @Override public boolean isTransient() { return this.transientValue; } @Override public void setTransient(boolean transientValue) { this.transientValue = transientValue; } // ----------------------------------------------------- Private helper methods for bean validation // MOJARRA IMPLEMENTATION NOTE: identical code exists in Mojarra's com.sun.faces.util.BeanValidation private static javax.validation.Validator getBeanValidator(FacesContext context) { ValidatorFactory validatorFactory = getValidatorFactory(context); ValidatorContext validatorContext = validatorFactory.usingContext(); MessageInterpolator jsfMessageInterpolator = new JsfAwareMessageInterpolator(context, validatorFactory.getMessageInterpolator()); validatorContext.messageInterpolator(jsfMessageInterpolator); return validatorContext.getValidator(); } private static ValidatorFactory getValidatorFactory(FacesContext context) { ValidatorFactory validatorFactory = null; Object cachedObject = context.getExternalContext() .getApplicationMap() .get(VALIDATOR_FACTORY_KEY); if (cachedObject instanceof ValidatorFactory) { validatorFactory = (ValidatorFactory) cachedObject; } else { try { validatorFactory = Validation.buildDefaultValidatorFactory(); } catch (ValidationException e) { throw new FacesException("Could not build a default Bean Validator factory", e); } context.getExternalContext() .getApplicationMap() .put(VALIDATOR_FACTORY_KEY, validatorFactory); } return validatorFactory; } private static class JsfAwareMessageInterpolator implements MessageInterpolator { private FacesContext context; private MessageInterpolator delegate; public JsfAwareMessageInterpolator(FacesContext context, MessageInterpolator delegate) { this.context = context; this.delegate = delegate; } @Override public String interpolate(String message, MessageInterpolator.Context context) { Locale locale = this.context.getViewRoot().getLocale(); if (locale == null) { locale = Locale.getDefault(); } return delegate.interpolate(message, context, locale); } @Override public String interpolate(String message, MessageInterpolator.Context context, Locale locale) { return delegate.interpolate(message, context, locale); } } // ----------------------------------------------------- Private helper methods for whole bean validation private void recordValidationResult(FacesContext context, UIComponent component, Object wholeBean, String propertyName, Object propertyValue) { Map>> multiFieldCandidates = getMultiFieldValidationCandidates(context, true); Map> candidate = multiFieldCandidates.getOrDefault(wholeBean, new HashMap<>()); Map tuple = new HashMap<>(); // new ComponentValueTuple((EditableValueHolder) component, value); tuple.put("component", component); tuple.put("value", propertyValue); candidate.put(propertyName, tuple); multiFieldCandidates.putIfAbsent(wholeBean, candidate); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy