private boolean isResolvable(ValueReference ref, ValueExpression valueExpression) { Boolean result = null; String failureMessage = null; if (null == valueExpression) { failureMessage = "Unable to validate expression using Bean " + "Validation. Expression must not be null."; result = false; } else if (null == ref) { failureMessage = "Unable to validate expression " + valueExpression.getExpressionString() + " using Bean Validation. Unable to get value of expression."; result = false; } else { Class baseClass = ref.getBaseClass(); // case 1, base classes of Map, List, or Array are not resolvable if (null != baseClass) { 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."; result = false; } } } result = ((null != result) ? result : true); if (!result) { LOGGER.fine(failureMessage); } return result; }
/** * <span class="changed_modified_2_3">Verify</span> that the value is valid according to the Bean * Validation constraints. <div class="changed_added_2_0"> * * <p>Obtain a {@link ValidatorFactory} instance by calling {@link * javax.validation.Validation#buildDefaultValidatorFactory}. * * <p>Let <em>validationGroupsArray</em> be a <code>Class []</code> 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. * * <p>Let <em>valueExpression</em> be the return from calling {@link * UIComponent#getValueExpression} on the argument <em>component</em>, 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 <code> * ValueReference</code> from <em>valueExpression</em> and let <em>valueBaseClase</em> be the * return from calling <code>ValueReference.getBase()</code> and <em>valueProperty</em> be the * return from calling <code>ValueReference.getProperty()</code>. If an earlier version of the * Unified EL is present, use the appropriate methods to inspect <em>valueExpression</em> and * derive values for <em>valueBaseClass</em> and <em>valueProperty</em>. * * <p>If no <code>ValueReference</code> can be obtained, take no action and return. * * <p>If <code>ValueReference.getBase()</code> return <code>null</code>, take no action and * return. * * <p>Obtain the {@link ValidatorContext} from the {@link ValidatorFactory}. * * <p>Decorate the {@link MessageInterpolator} returned from {@link * ValidatorFactory#getMessageInterpolator} with one that leverages the <code>Locale</code> * returned from {@link javax.faces.component.UIViewRoot#getLocale}, and store it in the <code> * ValidatorContext</code> using {@link ValidatorContext#messageInterpolator}. * * <p>Obtain the {@link javax.validation.Validator} instance from the <code>validatorContext * </code>. * * <p>Obtain a <code>javax.validation.BeanDescriptor</code> from the <code> * javax.validation.Validator</code>. If <code>hasConstraints()</code> on the <code>BeanDescriptor * </code> returns false, take no action and return. Otherwise proceed. * * <p>Call {@link javax.validation.Validator#validateValue}, passing <em>valueBaseClass</em>, * <em>valueProperty</em>, the <em>value</em> argument, and <em>validatorGroupsArray</em> as * arguments. * * <p>If the returned <code>Set<{@link * ConstraintViolation}></code> is non-empty, for each element in the <code>Set</code>, create * a {@link FacesMessage} where the summary and detail are the return from calling {@link * ConstraintViolation#getMessage}. Capture all such <code>FacesMessage</code> instances into a * <code>Collection</code> and pass them to {@link * ValidatorException#ValidatorException(java.util.Collection)}. <span * class="changed_added_2_3">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 <code><f:validateWholeBean /></code> 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.</span> * * <p class="changed_added_2_3">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 <code> * <f:validateWholeBean /></code> component later in the tree is able to allow class-level * validation for the bean for which this particular field is a property. </div> * * @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; } ValidatorFactory validatorFactory; 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); } ValidatorContext validatorContext = validatorFactory.usingContext(); MessageInterpolator jsfMessageInterpolator = new JsfAwareMessageInterpolator(context, validatorFactory.getMessageInterpolator()); validatorContext.messageInterpolator(jsfMessageInterpolator); javax.validation.Validator beanValidator = validatorContext.getValidator(); Class[] validationGroupsArray = parseValidationGroups(getValidationGroups()); // PENDING(rlubke, driscoll): When EL 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)) { Set<ConstraintViolation<?>> violations = null; try { //noinspection unchecked violations = beanValidator.validateValue( valueReference.getBaseClass(), valueReference.getProperty(), value, validationGroupsArray); } catch (IllegalArgumentException iae) { String failureMessage = "Unable to validate expression " + valueExpression.getExpressionString() + " using Bean Validation. Unable to get value of expression. " + " Message from Bean Validation: " + iae.getMessage(); LOGGER.fine(failureMessage); } if (violations != null && !violations.isEmpty()) { ValidatorException toThrow; if (1 == violations.size()) { ConstraintViolation violation = violations.iterator().next(); toThrow = new ValidatorException( MessageFactory.getMessage( context, MESSAGE_ID, violation.getMessage(), MessageFactory.getLabel(context, component))); } else { Set<FacesMessage> messages = new LinkedHashSet<>(violations.size()); for (ConstraintViolation violation : violations) { messages.add( MessageFactory.getMessage( context, MESSAGE_ID, violation.getMessage(), MessageFactory.getLabel(context, component))); } toThrow = new ValidatorException(messages); } // Record the fact that this field failed validation, so that multi-field // validation is not attempted. if (MultiFieldValidationUtils.wholeBeanValidationEnabled(context, validationGroupsArray)) { Map<Object, Map<String, Map<String, Object>>> multiFieldCandidates = MultiFieldValidationUtils.getMultiFieldValidationCandidates(context, true); Object val = valueReference.getBase(); Map<String, Map<String, Object>> candidate = multiFieldCandidates.getOrDefault(val, new HashMap<>()); Map<String, Object> tuple = new HashMap<>(); tuple.put("component", component); tuple.put("value", FAILED_FIELD_LEVEL_VALIDATION); candidate.put(valueReference.getProperty(), tuple); multiFieldCandidates.putIfAbsent(val, candidate); } throw toThrow; } } // Record the fact that this field passed validation, so that multi-field // validation can be performed if desired if (MultiFieldValidationUtils.wholeBeanValidationEnabled(context, validationGroupsArray)) { Map<Object, Map<String, Map<String, Object>>> multiFieldCandidates = MultiFieldValidationUtils.getMultiFieldValidationCandidates(context, true); Object val = valueReference.getBase(); Map<String, Map<String, Object>> candidate = multiFieldCandidates.getOrDefault(val, new HashMap<>()); Map<String, Object> tuple = new HashMap<>(); // new ComponentValueTuple((EditableValueHolder) component, value); tuple.put("component", component); tuple.put("value", value); candidate.put(valueReference.getProperty(), tuple); multiFieldCandidates.putIfAbsent(val, candidate); } }