/* * 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.validator; import static java.lang.String.format; import static org.omnifaces.util.Components.getLabel; import static org.omnifaces.util.FacesLocal.getMessageBundle; import static org.omnifaces.util.Messages.createError; import java.util.ResourceBundle; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.component.UISelectBoolean; import javax.faces.context.FacesContext; import javax.faces.validator.FacesValidator; import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; /** * <p> * The <code>omnifaces.RequiredCheckboxValidator</code> is intented to solve a peculiar problem with * <code>required="true"</code> attribute of {@link UISelectBoolean} components like * <code><h:selectBooleanCheckbox></code>. If you want to require the user to tick the desired checkbox, you would * expect that setting <code>required="true"</code> is sufficient. But it is not, the validation wil always pass. * <p> * As for every other {@link UIInput} component the default <code>required="true"</code> validator would * only check if the value is actually filled and been sent to the server side, i.e. the value is not null nor empty. * In case of a <code><h:selectBooleanCheckbox></code>, which accepts <code>Boolean</code> or <code>boolean</code> * properties only, EL will coerce the unchecked value to <code>Boolean.FALSE</code> during apply request values phase * right before validations phase. This value is not <code>null</code> nor empty! Thus, the required attribute of the * <code><h:selectBooleanCheckbox></code> is fairly pointless. It would always pass the validation and thus never * display the desired required message in case of an unticked checkbox. * * <h3>Usage</h3> * <p> * This validator is available by validator ID <code>omnifaces.RequiredCheckboxValidator</code>. Just specify it as * <code><f:validator></code> of the boolean selection component: * <pre> * <h:selectBooleanCheckbox id="agree" value="#{bean.agree}" requiredMessage="You must agree!"> * <f:validator validatorId="omnifaces.RequiredCheckboxValidator" /> * </h:selectBooleanCheckbox> * </pre> * <p> * The validator will use the message as specified in <code>requiredMessage</code>. If it's absent, then it will use * the default required message as specified in custom <code><message-bundle></code> in * <code>faces-config.xml</code>. If it's absent, then it will default to * <blockquote>{0}: a tick is required"</blockquote> * * @author Bauke Scholtz */ @FacesValidator("omnifaces.RequiredCheckboxValidator") public class RequiredCheckboxValidator implements Validator { // Constants ------------------------------------------------------------------------------------------------------ private static final String DEFAULT_REQUIRED_MESSAGE = "{0}: a tick is required"; private static final String ERROR_WRONG_COMPONENT = "RequiredCheckboxValidator must be registered on a component" + " of type UISelectBoolean. Encountered component of type '%s'."; // Actions -------------------------------------------------------------------------------------------------------- @Override public void validate(FacesContext context, UIComponent component, Object value) { if (!(component instanceof UISelectBoolean)) { throw new IllegalArgumentException(format(ERROR_WRONG_COMPONENT, component.getClass().getName())); } if (!Boolean.TRUE.equals(value)) { String requiredMessage = ((UIInput) component).getRequiredMessage(); if (requiredMessage == null) { ResourceBundle messageBundle = getMessageBundle(context); if (messageBundle != null) { requiredMessage = messageBundle.getString(UIInput.REQUIRED_MESSAGE_ID); } } if (requiredMessage == null) { requiredMessage = DEFAULT_REQUIRED_MESSAGE; } throw new ValidatorException(createError(requiredMessage, getLabel(component))); } } }