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

jakarta.faces.component.UIInput Maven / Gradle / Ivy

/*
 * Copyright (c) 1997, 2020 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 jakarta.faces.component;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import jakarta.el.ELException;
import jakarta.el.ValueExpression;
import jakarta.faces.FacesException;
import jakarta.faces.application.Application;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.ExceptionHandler;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.convert.Converter;
import jakarta.faces.convert.ConverterException;
import jakarta.faces.event.ExceptionQueuedEvent;
import jakarta.faces.event.ExceptionQueuedEventContext;
import jakarta.faces.event.PhaseId;
import jakarta.faces.event.PostValidateEvent;
import jakarta.faces.event.PreValidateEvent;
import jakarta.faces.event.ValueChangeEvent;
import jakarta.faces.event.ValueChangeListener;
import jakarta.faces.render.Renderer;
import jakarta.faces.validator.BeanValidator;
import jakarta.faces.validator.Validator;
import jakarta.faces.validator.ValidatorException;

/**
 * 

* UIInput is a {@link UIComponent} that represents a component that both * displays output to the user (like {@link UIOutput} components do) and processes request parameters on the subsequent * request that need to be decoded. There are no restrictions on the data type of the local value, or the object * referenced by the value binding expression (if any); however, individual {@link jakarta.faces.render.Renderer}s will * generally impose restrictions on the type of data they know how to display. *

* *

* During the Apply Request Values phase of the request processing lifecycle, the decoded value of this * component, usually but not necessarily a String, must be stored - but not yet converted - using * setSubmittedValue(). If the component wishes to indicate that no particular value was submitted, it can * either do nothing, or set the submitted value to null. *

* *

* By default, during the Process Validators phase of the request processing lifecycle, the submitted value * will be converted to a typesafe object, and, if validation succeeds, stored as a local value using * setValue(). However, if the immediate property is set to true, this processing * will occur instead at the end of the Apply Request Values phase. *

* *

* During the Render Response phase of the request processing lifecycle, conversion for output occurs as for * {@link UIOutput}. *

* *

* When the validate() method of this {@link UIInput} detects that a value change has actually occurred, * and that all validations have been successfully passed, it will queue a {@link ValueChangeEvent}. Later on, the * broadcast() method will ensure that this event is broadcast to all interested listeners. This event will * be delivered by default in the Process Validators phase, but can be delivered instead during Apply * Request Values if the immediate property is set to true. * If the validation fails, the implementation must call * {@link FacesContext#validationFailed}. *

* *

* By default, the rendererType property must be set to "Text". This value can be changed by * calling the setRendererType() method. *

*/ public class UIInput extends UIOutput implements EditableValueHolder { private static final String BEANS_VALIDATION_AVAILABLE = "jakarta.faces.private.BEANS_VALIDATION_AVAILABLE"; // ------------------------------------------------------ Manifest Constants /** *

* The standard component type for this component. *

*/ public static final String COMPONENT_TYPE = "jakarta.faces.Input"; /** *

* The standard component family for this component. *

*/ public static final String COMPONENT_FAMILY = "jakarta.faces.Input"; /** *

* The message identifier of the {@link jakarta.faces.application.FacesMessage} to be created if a conversion error * occurs, and neither the page author nor the {@link ConverterException} provides a message. *

*/ public static final String CONVERSION_MESSAGE_ID = "jakarta.faces.component.UIInput.CONVERSION"; /** *

* The message identifier of the {@link jakarta.faces.application.FacesMessage} to be created if a required check fails. *

*/ public static final String REQUIRED_MESSAGE_ID = "jakarta.faces.component.UIInput.REQUIRED"; /** *

* The message identifier of the {@link jakarta.faces.application.FacesMessage} to be created if a model update error * occurs, and the thrown exception has no message. *

*/ public static final String UPDATE_MESSAGE_ID = "jakarta.faces.component.UIInput.UPDATE"; /** *

* The name of a context parameter that indicates how empty values should be handled with respect to validation. See * {@link #validateValue} for the allowable values and specification of how they should be interpreted. *

*/ public static final String VALIDATE_EMPTY_FIELDS_PARAM_NAME = "jakarta.faces.VALIDATE_EMPTY_FIELDS"; /** *

* The name of a context parameter that indicates how empty strings need to be interpreted. *

*/ public static final String EMPTY_STRING_AS_NULL_PARAM_NAME = "jakarta.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL"; /** *

* If this param is set, and calling toLowerCase().equals("true") on a String representation of its value returns true, * validation must be performed, even when there is no corresponding value for this component in the incoming request. * See {@link #validate}. *

*/ public static final String ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE = "jakarta.faces.ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE"; private static final Validator[] EMPTY_VALIDATOR = new Validator[0]; private transient Boolean emptyStringIsNull; private transient Boolean validateEmptyFields; private transient Boolean isSetAlwaysValidateRequired; enum PropertyKeys { /** *

* The "localValueSet" state for this component. */ localValueSet, /** *

* If the input is required or not. *

*/ required, /** *

* Custom message to be displayed if input is required but non was submitted. *

*/ requiredMessage, /** *

* Custom message to be displayed when conversion fails. *

*/ converterMessage, /** *

* Custom message to be displayed when validation fails. *

*/ validatorMessage, /** *

* Flag indicating whether or not this component is valid. *

*/ valid, /** *

* Flag indicating when conversion/validation should occur. *

*/ immediate, } // ------------------------------------------------------------ Constructors /** *

* Create a new {@link UIInput} instance with default property values. *

*/ public UIInput() { super(); setRendererType("jakarta.faces.Text"); } // -------------------------------------------------------------- Properties @Override public String getFamily() { return COMPONENT_FAMILY; } /** *

* The submittedValue value of this {@link UIInput} component. *

*/ private transient Object submittedValue = null; /** *

* Return the submittedValue value of this {@link UIInput} component. This method should only be used by the * decode() and validate() method of this component, or its corresponding {@link Renderer}. *

*/ @Override public Object getSubmittedValue() { if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) { // JAVASERVERFACES_SPEC_PUBLIC-671 return ""; } else { return submittedValue; } } /** *

* Set the submittedValue value of this {@link UIInput} component. This method should only be used by the * decode() and validate() method of this component, or its corresponding {@link Renderer}. *

* * @param submittedValue The new submitted value */ @Override public void setSubmittedValue(Object submittedValue) { this.submittedValue = submittedValue; } /** *

* If there is a local value, return it, otherwise return the result of calling {@code super.getVaue()}. *

* * @since 2.2 */ @Override public Object getValue() { return isLocalValueSet() ? getLocalValue() : super.getValue(); } @Override public void setValue(Object value) { super.setValue(value); // Mark the local value as set. setLocalValueSet(true); } /** *

* Convenience method to reset this component's value to the un-initialized * state. This method does the following: *

* *

* Call {@link UIOutput#setValue}. *

* *

* Call {@link #setSubmittedValue} passing null. *

* *

* Clear state for property localValueSet. *

* *

* Clear state for property valid. *

* *

* Upon return from this call if the instance had a ValueBinding associated with it for the "value" * property, this binding is evaluated when {@link UIOutput#getValue} is called. Otherwise, null is * returned from getValue(). *

*/ @Override public void resetValue() { super.resetValue(); setSubmittedValue(null); getStateHelper().remove(PropertyKeys.localValueSet); getStateHelper().remove(PropertyKeys.valid); } /** * Return the "local value set" state for this component. Calls to setValue() automatically reset this * property to true. */ @Override public boolean isLocalValueSet() { return (Boolean) getStateHelper().eval(PropertyKeys.localValueSet, false); } /** * Sets the "local value set" state for this component. */ @Override public void setLocalValueSet(boolean localValueSet) { getStateHelper().put(PropertyKeys.localValueSet, localValueSet); } /** *

* Return the "required field" state for this component. *

*/ @Override public boolean isRequired() { return (Boolean) getStateHelper().eval(PropertyKeys.required, false); } /** *

* If there has been a call to {@link #setRequiredMessage} on this instance, return the message. Otherwise, call * {@link #getValueExpression} passing the key "requiredMessage", get the result of the expression, and return it. Any * {@link ELException}s thrown during the call to getValue() must be wrapped in a {@link FacesException} * and rethrown. * * @return the required message. */ public String getRequiredMessage() { return (String) getStateHelper().eval(PropertyKeys.requiredMessage); } /** *

* Override any {@link ValueExpression} set for the "requiredMessage" with the literal argument provided to this method. * Subsequent calls to {@link #getRequiredMessage} will return this value; *

* * @param message the literal message value to be displayed in the event the user hasn't supplied a value and one is * required. */ public void setRequiredMessage(String message) { getStateHelper().put(PropertyKeys.requiredMessage, message); } /** *

* If there has been a call to {@link #setConverterMessage} on this instance, return the message. Otherwise, call * {@link #getValueExpression} passing the key "converterMessage", get the result of the expression, and return it. Any * {@link ELException}s thrown during the call to getValue() must be wrapped in a {@link FacesException} * and rethrown. * * @return the converter message. */ public String getConverterMessage() { return (String) getStateHelper().eval(PropertyKeys.converterMessage); } /** *

* Override any {@link ValueExpression} set for the "converterMessage" with the literal argument provided to this * method. Subsequent calls to {@link #getConverterMessage} will return this value; *

* * @param message the literal message value to be displayed in the event conversion fails. */ public void setConverterMessage(String message) { getStateHelper().put(PropertyKeys.converterMessage, message); } /** *

* If there has been a call to {@link #setValidatorMessage} on this instance, return the message. Otherwise, call * {@link #getValueExpression} passing the key "validatorMessage", get the result of the expression, and return it. Any * {@link ELException}s thrown during the call to getValue() must be wrapped in a {@link FacesException} * and rethrown. * * @return the validator message. */ public String getValidatorMessage() { return (String) getStateHelper().eval(PropertyKeys.validatorMessage); } /** *

* Override any {@link ValueExpression} set for the "validatorMessage" with the literal argument provided to this * method. Subsequent calls to {@link #getValidatorMessage} will return this value; *

* * @param message the literal message value to be displayed in the event validation fails. */ public void setValidatorMessage(String message) { getStateHelper().put(PropertyKeys.validatorMessage, message); } @Override public boolean isValid() { return (Boolean) getStateHelper().eval(PropertyKeys.valid, true); } @Override public void setValid(boolean valid) { getStateHelper().put(PropertyKeys.valid, valid); } /** *

* Set the "required field" state for this component. *

* * @param required The new "required field" state */ @Override public void setRequired(boolean required) { getStateHelper().put(PropertyKeys.required, required); } @Override public boolean isImmediate() { return (Boolean) getStateHelper().eval(PropertyKeys.immediate, false); } @Override public void setImmediate(boolean immediate) { getStateHelper().put(PropertyKeys.immediate, immediate); } // ----------------------------------------------------- UIComponent Methods /** *

* In addition to the actions taken in {@link UIOutput} when {@link PartialStateHolder#markInitialState()} is called, * check if any of the installed {@link Validator}s are PartialStateHolders and if so, call * {@link jakarta.faces.component.PartialStateHolder#markInitialState()} as appropriate. *

*/ @Override public void markInitialState() { super.markInitialState(); if (validators != null) { validators.markInitialState(); } } @Override public void clearInitialState() { if (initialStateMarked()) { super.clearInitialState(); if (validators != null) { validators.clearInitialState(); } } } /** *

* Specialized decode behavior on top of that provided by the superclass. In addition to the standard * processDecodes behavior inherited from {@link UIComponentBase}, calls validate() if the the * immediate property is true; if the component is invalid afterwards or a RuntimeException is * thrown, calls {@link FacesContext#renderResponse}. *

* * @throws NullPointerException {@inheritDoc} */ @Override public void processDecodes(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Skip processing if our rendered flag is false if (!isRendered()) { return; } super.processDecodes(context); if (isImmediate()) { executeValidate(context); } } /** *

* In addition to the standard processValidators behavior * inherited from {@link UIComponentBase}, calls validate() if the immediate property is false * (which is the default); if the component is invalid afterwards, calls {@link FacesContext#renderResponse}. * To ensure the {@code PostValidateEvent} is published at the proper time, this * component must be validated first, followed by the component's children and facets. If a * RuntimeException is thrown during validation processing, calls {@link FacesContext#renderResponse} and * re-throw the exception. *

* * @throws NullPointerException {@inheritDoc} */ @Override public void processValidators(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Skip processing if our rendered flag is false if (!isRendered()) { return; } pushComponentToEL(context, this); if (!isImmediate()) { Application application = context.getApplication(); application.publishEvent(context, PreValidateEvent.class, this); executeValidate(context); application.publishEvent(context, PostValidateEvent.class, this); } for (Iterator i = getFacetsAndChildren(); i.hasNext();) { i.next().processValidators(context); } popComponentFromEL(context); } /** *

* In addition to the standard processUpdates behavior inherited from {@link UIComponentBase}, calls * updateModel(). If the component is invalid afterwards, calls {@link FacesContext#renderResponse}. If a * RuntimeException is thrown during update processing, calls {@link FacesContext#renderResponse} and * re-throw the exception. *

* * @throws NullPointerException {@inheritDoc} */ @Override public void processUpdates(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Skip processing if our rendered flag is false if (!isRendered()) { return; } super.processUpdates(context); pushComponentToEL(context, this); try { updateModel(context); } catch (RuntimeException e) { context.renderResponse(); throw e; } finally { popComponentFromEL(context); } if (!isValid()) { context.renderResponse(); } } /** * @throws NullPointerException {@inheritDoc} */ @Override public void decode(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Force validity back to "true" setValid(true); super.decode(context); } /** *

* Perform the following algorithm to update the model data associated with * this {@link UIInput}, if any, as appropriate. *

*
    *
  • If the valid property of this component is false, take no further action.
  • *
  • If the localValueSet property of this component is false, take no further action.
  • *
  • If no {@link ValueExpression} for value exists, take no further action.
  • *
  • Call setValue() method of the {@link ValueExpression} to update the value that the * {@link ValueExpression} points at.
  • *
  • If the setValue() method returns successfully: *
      *
    • Clear the local value of this {@link UIInput}.
    • *
    • Set the localValueSet property of this {@link UIInput} to false.
    • *
    *
  • *
  • If the setValue() method throws an Exception: *
      *
    • Enqueue an error message. Create a {@link FacesMessage} with the id * {@link #UPDATE_MESSAGE_ID}. Create a {@link UpdateModelException}, passing the FacesMessage and the * caught exception to the constructor. Create an {@link ExceptionQueuedEventContext}, passing the * FacesContext, the UpdateModelException, this component instance, and * {@link PhaseId#UPDATE_MODEL_VALUES} to its constructor. Call {@link FacesContext#getExceptionHandler} and then call * {@link ExceptionHandler#processEvent}, passing the ExceptionQueuedEventContext.
    • *
    • Set the valid property of this {@link UIInput} to false.
    • *
    * The exception must not be re-thrown. This enables tree traversal to continue for this lifecycle phase, as in all the * other lifecycle phases.
  • *
* * @param context {@link FacesContext} for the request we are processing * @throws NullPointerException if context is null */ public void updateModel(FacesContext context) { if (context == null) { throw new NullPointerException(); } if (!isValid() || !isLocalValueSet()) { return; } ValueExpression ve = getValueExpression("value"); if (ve != null) { Throwable caught = null; FacesMessage message = null; try { ve.setValue(context.getELContext(), getLocalValue()); resetValue(); } catch (ELException e) { caught = e; String messageStr = e.getMessage(); Throwable result = e.getCause(); while (null != result && result.getClass().isAssignableFrom(ELException.class)) { messageStr = result.getMessage(); result = result.getCause(); } if (null == messageStr) { message = MessageFactory.getMessage(context, UPDATE_MESSAGE_ID, MessageFactory.getLabel(context, this)); } else { message = new FacesMessage(FacesMessage.SEVERITY_ERROR, messageStr, messageStr); } setValid(false); } catch (Exception e) { caught = e; message = MessageFactory.getMessage(context, UPDATE_MESSAGE_ID, MessageFactory.getLabel(context, this)); setValid(false); } if (caught != null) { assert message != null; // PENDING(edburns): verify this is in the spec. @SuppressWarnings({ "ThrowableInstanceNeverThrown" }) UpdateModelException toQueue = new UpdateModelException(message, caught); ExceptionQueuedEventContext eventContext = new ExceptionQueuedEventContext(context, toQueue, this, PhaseId.UPDATE_MODEL_VALUES); context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, eventContext); } } } // ------------------------------------------------------ Validation Methods /** *

* Perform the following algorithm * to validate the local value of this {@link UIInput}. *

* *
    * *
  • Retrieve the submitted value with {@link #getSubmittedValue}. If this returns null, * and the value of the {@link #ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE} * context-param is true (ignoring case), examine the value of the "required" property. If the value of "required" is * true, continue as below. If the value of "required" is false or the required attribute is not set, exit without * further processing. If the context-param is not set, or is set to false (ignoring case), exit without further * processing. (This indicates that no value was submitted for this component.)
  • * *
  • If the * jakarta.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL context parameter value is * true (ignoring case), and getSubmittedValue() returns a zero-length String * call {@link #setSubmittedValue}, passing null as the argument and continue processing using * null as the current submitted value.
  • * *
  • Convert the submitted value into a "local value" of the appropriate data type by calling * {@link #getConvertedValue}.
  • *
  • If conversion fails: *
      *
    • Enqueue an appropriate error message by calling the addMessage() method on the * FacesContext.
    • *
    • Set the valid property on this component to false
    • *
    *
  • * *
  • Validate the property by calling {@link #validateValue}.
  • * *
  • If the valid property of this component is still true, retrieve the previous value of * the component (with getValue()), store the new local value using setValue(), and reset the * submitted value to null with a call to {@link #setSubmittedValue} passing * {@code null} as the argument. If the local value is different from the previous value of this component, * as determined by a call to {@link #compareValues}, fire a * {@link ValueChangeEvent} to be broadcast to all interested listeners.
  • * *
*

* Application components implementing {@link UIInput} that wish to perform validation with logic embedded in the * component should perform their own correctness checks, and then call the super.validate() method to * perform the standard processing described above. *

* * @param context The {@link FacesContext} for the current request * @throws NullPointerException if context is null */ public void validate(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Submitted value == null means "the component was not submitted // at all". Object submittedValue = getSubmittedValue(); if (submittedValue == null) { if (isRequired() && isSetAlwaysValidateRequired(context)) { // continue as below } else { if (this instanceof UIViewParameter && considerEmptyStringNull(context)) { // https://github.com/eclipse-ee4j/mojarra/issues/4550 // https://github.com/eclipse-ee4j/mojarra/issues/4716 validateValue(context, getConvertedValue(context, submittedValue)); } return; } } // If non-null, an instanceof String, and we're configured to treat // zero-length Strings as null: // call setSubmittedValue(null) if ((considerEmptyStringNull(context) && submittedValue instanceof String && ((String) submittedValue).length() == 0)) { setSubmittedValue(null); submittedValue = null; } Object newValue = null; try { newValue = getConvertedValue(context, submittedValue); } catch (ConverterException ce) { addConversionErrorMessage(context, ce); setValid(false); if (submittedValue == null) { setSubmittedValue(""); } } validateValue(context, newValue); // If our value is valid, store the new value, erase the // "submitted" value, and emit a ValueChangeEvent if appropriate if (isValid()) { Object previous = getValue(); setValue(newValue); setSubmittedValue(null); if (compareValues(previous, newValue)) { queueEvent(new ValueChangeEvent(context, this, previous, newValue)); } } } /* * Respecting the fact that someone may have decorated FacesContextFactory and thus skipped our saving of this init * param, look for the init param and return its value. The return is saved in a transient ivar to provide performance * while not perturbing state saving. */ private boolean isSetAlwaysValidateRequired(FacesContext context) { if (null != isSetAlwaysValidateRequired) { return isSetAlwaysValidateRequired; } Boolean bool = (Boolean) context.getAttributes().get(ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE); if (null != bool) { isSetAlwaysValidateRequired = bool; } else { String val = context.getExternalContext().getInitParameter(ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE); isSetAlwaysValidateRequired = Boolean.valueOf(val); } return isSetAlwaysValidateRequired; } /** *

* Convert the submitted value into a "local value" of the appropriate data type, if necessary. Employ the following * algorithm to do so: *

*
    *
  • If a Renderer is present, call getConvertedValue() to convert the submitted value.
  • *
  • If no Renderer is present, and the submitted value is a String, locate a {@link Converter} as * follows: *
      *
    • If getConverter() returns a non-null {@link Converter}, use that instance.
    • *
    • Otherwise, if a value binding for value exists, call getType() on it. *
        *
      • If this call returns null, assume the output type is String and perform no * conversion.
      • *
      • Otherwise, call Application.createConverter(Class) to locate any registered {@link Converter} * capable of converting data values of the specified type.
      • *
      *
    • *
    *
  • If a {@link Converter} instance was located, call its getAsObject() method to perform the * conversion. If conversion fails, the Converter will have thrown * a ConverterException which is declared as a checked exception on this method, and thus must be handled * by the caller.
  • *
  • Otherwise, use the submitted value without any conversion
  • *
*

* This method can be overridden by subclasses for more specific behavior. *

* * @param context the Faces context. * @param newSubmittedValue the new submitted value. * @return the converted value. */ protected Object getConvertedValue(FacesContext context, Object newSubmittedValue) throws ConverterException { Renderer renderer = getRenderer(context); Object newValue; if (renderer != null) { newValue = renderer.getConvertedValue(context, this, newSubmittedValue); } else if (newSubmittedValue instanceof String) { // If there's no Renderer, and we've got a String, // run it through the Converter (if any) Converter converter = getConverterWithType(context); if (converter != null) { newValue = converter.getAsObject(context, this, (String) newSubmittedValue); } else { newValue = newSubmittedValue; } } else { newValue = newSubmittedValue; } return newValue; } /** *

* Set the "valid" property according to the below algorithm. *

* *
    * *
  • * *

    * If the valid property on this component is still true, and the required * property is also true, ensure that the local value is not empty (where "empty" is defined as * null or a zero-length String). If the local value is empty: *

    * *
      * *
    • *

      * Enqueue an appropriate error message by calling the addMessage() method on the FacesContext * instance for the current request. If the {@link #getRequiredMessage} returns non-null, use the value as * the summary and detail in the {@link FacesMessage} that is enqueued on the * FacesContext, otherwise use the message for the {@link #REQUIRED_MESSAGE_ID}.

    • * *
    • Set the valid property on this component to false.
    • * *
    • *

      * If calling {@link ValidatorException#getFacesMessages} returns non-null, each message should be added to * the FacesContext. Otherwise the single message returned from {@link ValidatorException#getFacesMessage} * should be added. *

      *
    • * *
    * *
  • * *
  • * *

    * Otherwise, if the valid property on this component is still true, take the following action * to determine if validation of this component should proceed. *

    * *
      * *
    • *

      * If the value is not empty, validation should proceed. *

      *
    • * *
    • *

      * If the value is empty, but the system has been directed to validate empty fields, validation should proceed. The * implementation must obtain the init parameter Map from the ExternalContext and inspect the * value for the key given by the value of the symbolic constant {@link #VALIDATE_EMPTY_FIELDS_PARAM_NAME}. If there is * no value under that key, use the same key and look in the application map from the ExternalContext. If * the value is null or equal to the string “auto” (without the quotes) take * appropriate action to determine if Bean Validation is present in the runtime environment. If not, validation should * not proceed. If so, validation should proceed. If the value is equal (ignoring case) to * “true” (without the quotes) validation should proceed. Otherwise, validation should not * proceed. *

      *
    • * *
    * *

    * If the above determination indicates that validation should proceed, call the validate() method of each * {@link Validator} registered for this {@link UIInput}, followed by the method pointed at by the * validatorBinding property (if any). If any of these validators or the method throws a * {@link ValidatorException}, catch the exception, add its message (if any) to the {@link FacesContext}, and set the * valid property of this component to false.

  • * *
* * @param context the Faces context. * @param newValue the new value. */ protected void validateValue(FacesContext context, Object newValue) { // If our value is valid, enforce the required property if present if (isValid() && isRequired() && isEmpty(newValue)) { String requiredMessageStr = getRequiredMessage(); FacesMessage message; if (null != requiredMessageStr) { message = new FacesMessage(FacesMessage.SEVERITY_ERROR, requiredMessageStr, requiredMessageStr); } else { message = MessageFactory.getMessage(context, REQUIRED_MESSAGE_ID, MessageFactory.getLabel(context, this)); } context.addMessage(getClientId(context), message); setValid(false); } // If our value is valid and not empty or empty w/ validate empty fields enabled, call all validators if (isValid() && (!isEmpty(newValue) || validateEmptyFields(context))) { if (validators != null) { Validator[] validators = this.validators.asArray(Validator.class); for (Validator validator : validators) { try { validator.validate(context, this, newValue); } catch (ValidatorException ve) { // If the validator throws an exception, we're // invalid, and we need to add a message setValid(false); FacesMessage message; String validatorMessageString = getValidatorMessage(); if (null != validatorMessageString) { message = new FacesMessage(FacesMessage.SEVERITY_ERROR, validatorMessageString, validatorMessageString); message.setSeverity(FacesMessage.SEVERITY_ERROR); } else { Collection messages = ve.getFacesMessages(); if (null != messages) { message = null; String cid = getClientId(context); for (FacesMessage m : messages) { context.addMessage(cid, m); } } else { message = ve.getFacesMessage(); } } if (message != null) { context.addMessage(getClientId(context), message); } } } } } } /** *

* Return true if the new value is different from the previous value. First compare the two values by * passing value to the equals method on argument previous. If that method returns * true, return true. If that method returns false, and both arguments implement * java.lang.Comparable, compare the two values by passing value to the compareTo * method on argument previous. Return true if this method returns 0, * false otherwise. *

* * @param previous old value of this component (if any) * @param value new value of this component (if any) * @return true if the new value is different from the previous value, false otherwise. */ protected boolean compareValues(Object previous, Object value) { boolean result = true; if (previous == null) { result = value != null; } else if (value == null) { result = true; } else { boolean previousEqualsValue = previous.equals(value); if (!previousEqualsValue && previous instanceof Comparable && value instanceof Comparable) { try { result = !(0 == ((Comparable) previous).compareTo(value)); } catch (ClassCastException cce) { // Comparable throws CCE if the types prevent a comparison result = true; } } else { result = !previousEqualsValue; } } return result; } /** * Executes validation logic. */ private void executeValidate(FacesContext context) { try { validate(context); } catch (RuntimeException e) { context.renderResponse(); throw e; } if (!isValid()) { context.validationFailed(); context.renderResponse(); } } /** *

* Is the value denoting an empty value. *

* *

* If the value is null, return true. If the value is a String and it is the empty string, return true. If the value is * an array and the array length is 0, return true. If the value is a List and the List is empty, return true. If the * value is a Collection and the Collection is empty, return true. If the value is a Map and the Map is empty, return * true. In all other cases, return false. *

* * @param value the value to check. * @return true if it is, false otherwise. */ public static boolean isEmpty(Object value) { if (value == null) { return true; } else if (value instanceof String && ((String) value).length() < 1) { return true; } else if (value.getClass().isArray()) { if (0 == java.lang.reflect.Array.getLength(value)) { return true; } } else if (value instanceof List) { if (((List) value).isEmpty()) { return true; } } else if (value instanceof Collection) { if (((Collection) value).isEmpty()) { return true; } } else if (value instanceof Map && ((Map) value).isEmpty()) { return true; } return false; } /** *

* The set of {@link Validator}s associated with this UIComponent. *

*/ AttachedObjectListHolder validators; /** *

* Add a {@link Validator} instance to the set associated with this {@link UIInput}. *

* * @param validator The {@link Validator} to add * @throws NullPointerException if validator is null */ @Override public void addValidator(Validator validator) { if (validator == null) { throw new NullPointerException(); } if (validators == null) { validators = new AttachedObjectListHolder<>(); } validators.add(validator); } /** *

* Return the set of registered {@link Validator}s for this {@link UIInput} instance. If there are no registered * validators, a zero-length array is returned. *

*/ @Override public Validator[] getValidators() { return validators != null ? validators.asArray(Validator.class) : EMPTY_VALIDATOR; } /** *

* Remove a {@link Validator} instance from the set associated with this {@link UIInput}, if it was previously * associated. Otherwise, do nothing. *

* * @param validator The {@link Validator} to remove */ @Override public void removeValidator(Validator validator) { if (validator == null) { return; } if (validators != null) { validators.remove(validator); } } // ------------------------------------------------ Event Processing Methods /** *

* Add a new {@link ValueChangeListener} to the set of listeners interested in being notified when * {@link ValueChangeEvent}s occur. *

* * @param listener The {@link ValueChangeListener} to be added * @throws NullPointerException if listener is null */ @Override public void addValueChangeListener(ValueChangeListener listener) { addFacesListener(listener); } /** *

* Return the set of registered {@link ValueChangeListener}s for this {@link UIInput} instance. If there are no * registered listeners, a zero-length array is returned. *

*/ @Override public ValueChangeListener[] getValueChangeListeners() { return (ValueChangeListener[]) getFacesListeners(ValueChangeListener.class); } /** *

* Remove an existing {@link ValueChangeListener} (if any) from the set of listeners interested in being notified when * {@link ValueChangeEvent}s occur. *

* * @param listener The {@link ValueChangeListener} to be removed * @throws NullPointerException if listener is null */ @Override public void removeValueChangeListener(ValueChangeListener listener) { removeFacesListener(listener); } // ----------------------------------------------------- StateHolder Methods @Override public Object saveState(FacesContext context) { if (context == null) { throw new NullPointerException(); } Object[] result = null; Object superState = super.saveState(context); Object validatorsState = validators != null ? validators.saveState(context) : null; if (superState != null || validatorsState != null) { result = new Object[] { superState, validatorsState }; } return result; } @Override public void restoreState(FacesContext context, Object state) { if (context == null) { throw new NullPointerException(); } if (state == null) { return; } Object[] values = (Object[]) state; super.restoreState(context, values[0]); if (values[1] != null) { if (validators == null) { validators = new AttachedObjectListHolder<>(); } validators.restoreState(context, values[1]); } } private Converter getConverterWithType(FacesContext context) { Converter converter = getConverter(); if (converter != null) { return converter; } ValueExpression valueExpression = getValueExpression("value"); if (valueExpression == null) { return null; } Class converterType; try { converterType = valueExpression.getType(context.getELContext()); } catch (ELException e) { throw new FacesException(e); } // if converterType is null, String, or Object, assume // no conversion is needed if (converterType == null || converterType == String.class || converterType == Object.class) { return null; } // if getType returns a type for which we support a default // conversion, acquire an appropriate converter instance. try { Application application = context.getApplication(); return application.createConverter(converterType); } catch (Exception e) { return null; } } private void addConversionErrorMessage(FacesContext context, ConverterException ce) { FacesMessage message; String converterMessageString = getConverterMessage(); if (null != converterMessageString) { message = new FacesMessage(FacesMessage.SEVERITY_ERROR, converterMessageString, converterMessageString); } else { message = ce.getFacesMessage(); if (message == null) { message = MessageFactory.getMessage(context, CONVERSION_MESSAGE_ID); if (message.getDetail() == null) { message.setDetail(ce.getMessage()); } } } context.addMessage(getClientId(context), message); } private boolean considerEmptyStringNull(FacesContext ctx) { if (emptyStringIsNull == null) { String val = ctx.getExternalContext().getInitParameter(EMPTY_STRING_AS_NULL_PARAM_NAME); emptyStringIsNull = Boolean.valueOf(val); } return emptyStringIsNull; } private boolean validateEmptyFields(FacesContext ctx) { if (validateEmptyFields == null) { ExternalContext extCtx = ctx.getExternalContext(); String val = extCtx.getInitParameter(VALIDATE_EMPTY_FIELDS_PARAM_NAME); if (null == val) { val = (String) extCtx.getApplicationMap().get(VALIDATE_EMPTY_FIELDS_PARAM_NAME); } if (val == null || "auto".equals(val)) { validateEmptyFields = isBeansValidationAvailable(ctx); } else { validateEmptyFields = Boolean.valueOf(val); } } return validateEmptyFields; } private boolean isBeansValidationAvailable(FacesContext context) { boolean result = false; Map appMap = context.getExternalContext().getApplicationMap(); if (appMap.containsKey(BEANS_VALIDATION_AVAILABLE)) { result = (Boolean) appMap.get(BEANS_VALIDATION_AVAILABLE); } else { try { new BeanValidator(); appMap.put(BEANS_VALIDATION_AVAILABLE, result = true); } catch (Throwable t) { appMap.put(BEANS_VALIDATION_AVAILABLE, Boolean.FALSE); } } return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy