/* * Copyright 2017 OmniFaces * * Licensed 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.omnifaces.taghandler; import static org.omnifaces.taghandler.DeferredTagHandlerHelper.collectDeferredAttributes; import static org.omnifaces.taghandler.DeferredTagHandlerHelper.createInstance; import static org.omnifaces.taghandler.DeferredTagHandlerHelper.getValueExpression; import static org.omnifaces.util.Components.getLabel; import java.io.IOException; import java.io.Serializable; import javax.el.ELContext; import javax.el.ValueExpression; import javax.faces.application.Application; import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.validator.ValidatorException; import javax.faces.view.facelets.ComponentHandler; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.TagAttribute; import javax.faces.view.facelets.TagHandlerDelegate; import javax.faces.view.facelets.ValidatorConfig; import javax.faces.view.facelets.ValidatorHandler; import org.omnifaces.taghandler.DeferredTagHandlerHelper.DeferredAttributes; import org.omnifaces.taghandler.DeferredTagHandlerHelper.DeferredTagHandler; import org.omnifaces.taghandler.DeferredTagHandlerHelper.DeferredTagHandlerDelegate; import org.omnifaces.util.Messages; /** * <p> * The <code><o:validator></code> is a taghandler that extends the standard <code><f:validator></code> tag * family with support for deferred value expressions in all attributes. In other words, the validator attributes are * not evaluated anymore on a per view build time basis, but just on every access like as with UI components and bean * properties. This has among others the advantage that they can be evaluated on a per-iteration basis inside an * iterating component, and that they can be set on a custom validator without needing to explicitly register it in a * tagfile. * * <h3>Usage</h3> * <p> * When you specify for example the standard <code><f:validateLongRange></code> by * <code>validatorId="javax.faces.LongRange"</code>, then you'll be able to use all its attributes such as * <code>minimum</code> and <code>maximum</code> as per its documentation, but then with the possibility to supply * deferred value expressions. * <pre> * <o:validator validatorId="javax.faces.LongRange" minimum="#{item.minimum}" maximum="#{item.maximum}" /> * </pre> * <p> * The validator ID of all standard JSF validators can be found in * <a href="http://docs.oracle.com/javaee/7/api/javax/faces/validator/package-summary.html">their javadocs</a>. * First go to the javadoc of the class of interest, then go to <code>VALIDATOR_ID</code> in its field summary * and finally click the Constant Field Values link to see the value. * <p> * It is also possible to specify the validator message on a per-validator basis using the <code>message</code> * attribute. Any "{0}" placeholder in the message will be substituted with the label of the referenced input component. * Note that this attribute is ignored when the parent component has already <code>validatorMessage</code> specified. * <pre> * <o:validator validatorId="javax.faces.LongRange" minimum="#{item.minimum}" maximum="#{item.maximum}" * message="Please enter between #{item.minimum} and #{item.maximum} characters" /> * </pre> * * @author Bauke Scholtz * @see DeferredTagHandlerHelper */ public class Validator extends ValidatorHandler implements DeferredTagHandler { // Constructors --------------------------------------------------------------------------------------------------- /** * The constructor. * @param config The validator config. */ public Validator(ValidatorConfig config) { super(config); } // Actions -------------------------------------------------------------------------------------------------------- /** * Create a {@link javax.faces.validator.Validator} based on the <code>binding</code> and/or * <code>validatorId</code> attributes as per the standard JSF <code><f:validator></code> implementation and * collect the render time attributes. Then create an anonymous <code>Validator</code> implementation which wraps * the created <code>Validator</code> and delegates the methods to it after setting the render time attributes only * and only if the <code>disabled</code> attribute evaluates <code>true</code> for the current request. Finally set * the anonymous implementation on the parent component. * @param context The involved facelet context. * @param parent The parent component to add the <code>Validator</code> to. * @throws IOException If something fails at I/O level. */ @Override public void apply(FaceletContext context, UIComponent parent) throws IOException { boolean insideCompositeComponent = UIComponent.getCompositeComponentParent(parent) != null; if (!ComponentHandler.isNew(parent) && !insideCompositeComponent) { // If it's not new nor inside a composite component, we're finished. return; } if (!(parent instanceof EditableValueHolder) || (insideCompositeComponent && getAttribute("for") == null)) { // It's inside a composite component and not reattached. TagHandlerDelegate will pickup it and pass the target component back if necessary. super.apply(context, parent); return; } addValidator(context, (EditableValueHolder) parent); } private void addValidator(FaceletContext context, EditableValueHolder parent) { final javax.faces.validator.Validator validator = createInstance(context, this, "validatorId"); final DeferredAttributes attributes = collectDeferredAttributes(context, this, validator); final ValueExpression disabled = getValueExpression(context, this, "disabled", Boolean.class); final ValueExpression message = getValueExpression(context, this, "message", String.class); parent.addValidator(new DeferredValidator() { private static final long serialVersionUID = 1L; @Override public void validate(FacesContext context, UIComponent component, Object value) { ELContext el = context.getELContext(); if (disabled == null || Boolean.FALSE.equals(disabled.getValue(el))) { attributes.invokeSetters(el, validator); try { validator.validate(context, component, value); } catch (ValidatorException e) { rethrowValidatorException(context, component, message, e); } } } private void rethrowValidatorException(FacesContext context, UIComponent component, ValueExpression message, ValidatorException e) { if (message != null) { String validatorMessage = (String) message.getValue(context.getELContext()); if (validatorMessage != null) { String label = getLabel(component); throw new ValidatorException(Messages.create(validatorMessage, label) .detail(validatorMessage, label).error().get(), e.getCause()); } } throw e; } }); } @Override @SuppressWarnings("unchecked") public <T> T create(Application application, String id) { return (T) application.createValidator(id); } @Override public TagAttribute getTagAttribute(String name) { return getAttribute(name); } @Override protected TagHandlerDelegate getTagHandlerDelegate() { return new DeferredTagHandlerDelegate(this, super.getTagHandlerDelegate()); } @Override public boolean isDisabled(FaceletContext context) { return false; // Let the deferred validator handle it. } // Nested classes ------------------------------------------------------------------------------------------------- /** * So that we can have a serializable validator. * * @author Bauke Scholtz */ protected abstract static class DeferredValidator implements javax.faces.validator.Validator, Serializable { private static final long serialVersionUID = 1L; } }