javax.faces.component.UIInput Maven / Gradle / Ivy
Show all versions of javax.faces-api Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package javax.faces.component;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.el.MethodBinding;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;
import javax.faces.event.PhaseId;
import javax.faces.event.PostValidateEvent;
import javax.faces.event.PreValidateEvent;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;
import javax.faces.render.Renderer;
import javax.faces.validator.BeanValidator;
import javax.faces.validator.Validator;
import javax.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 javax.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 =
"javax.faces.private.BEANS_VALIDATION_AVAILABLE";
// ------------------------------------------------------ Manifest Constants
/**
* The standard component type for this component.
*/
public static final String COMPONENT_TYPE = "javax.faces.Input";
/**
* The standard component family for this component.
*/
public static final String COMPONENT_FAMILY = "javax.faces.Input";
/**
* The message identifier of the
* {@link javax.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 =
"javax.faces.component.UIInput.CONVERSION";
/**
* The message identifier of the
* {@link javax.faces.application.FacesMessage} to be created if
* a required check fails.
*/
public static final String REQUIRED_MESSAGE_ID =
"javax.faces.component.UIInput.REQUIRED";
/**
* The message identifier of the
* {@link javax.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 =
"javax.faces.component.UIInput.UPDATE";
/**
* The name of an 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 =
"javax.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 =
"javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL";
private static final Validator[] EMPTY_VALIDATOR = new Validator[0];
private transient Boolean emptyStringIsNull;
private transient Boolean validateEmptyFields;
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("javax.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() {
return (this.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();
this.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);
}
/**
* Return a MethodBinding
pointing at a
* method that will be called during Process Validations
* phase of the request processing lifecycle, to validate the current
* value of this component.
*
* @deprecated {@link #getValidators} should be used instead.
*/
@Override
public MethodBinding getValidator() {
MethodBinding result = null;
Validator[] curValidators = getValidators();
// go through our lisetners list and find the one and only
// MethodBindingValidator instance, if present.
if (null != curValidators) {
for (int i = 0; i < curValidators.length; i++) {
// We are guaranteed to have at most one instance of
// MethodBindingValidator in the curValidators list.
if (MethodBindingValidator.class ==
curValidators[i].getClass()) {
result = ((MethodBindingValidator) curValidators[i]).
getWrapped();
break;
}
}
}
return result;
}
/**
* Set a MethodBinding
pointing at a
* method that will be called during Process Validations
* phase of the request processing lifecycle, to validate the current
* value of this component.
*
* Any method referenced by such an expression must be public, with
* a return type of void
, and accept parameters of type
* {@link FacesContext}, {@link UIComponent}, and Object
.
*
* @param validatorBinding The new MethodBinding
instance
* @deprecated Use {@link #addValidator} instead, obtaining the
* argument {@link Validator} by creating an instance of {@link
* javax.faces.validator.MethodExpressionValidator}.
*/
@Override
public void setValidator(MethodBinding validatorBinding) {
Validator[] curValidators = getValidators();
// see if we need to null-out, or replace an existing validator
if (null != curValidators) {
for (int i = 0; i < curValidators.length; i++) {
// if we want to remove the validatorBinding
if (null == validatorBinding) {
// We are guaranteed to have at most one instance of
// MethodBindingValidator in the curValidators
// list.
if (MethodBindingValidator.class ==
curValidators[i].getClass()) {
removeValidator(curValidators[i]);
return;
}
}
// if we want to replace the validatorBinding
else //noinspection ObjectEquality
if (validatorBinding == curValidators[i]) {
removeValidator(curValidators[i]);
break;
}
}
}
addValidator(new MethodBindingValidator(validatorBinding));
}
@Override
public MethodBinding getValueChangeListener() {
MethodBinding result = null;
ValueChangeListener[] curListeners = getValueChangeListeners();
// go through our lisetners list and find the one and only
// MethodBindingValueChangeListener instance, if present.
if (null != curListeners) {
for (int i = 0; i < curListeners.length; i++) {
// We are guaranteed to have at most one instance of
// MethodBindingValueChangeListener in the curListeners list.
if (MethodBindingValueChangeListener.class ==
curListeners[i].getClass()) {
result = ((MethodBindingValueChangeListener) curListeners[i]).
getWrapped();
break;
}
}
}
return result;
}
/**
* {@inheritDoc}
*
* @param valueChangeListener the value change listener.
* @deprecated Use {@link #addValueChangeListener} instead, obtaining the
* argument {@link ValueChangeListener} by creating an instance of {@link
* javax.faces.event.MethodExpressionValueChangeListener}.
*/
@Override
public void setValueChangeListener(MethodBinding valueChangeListener) {
ValueChangeListener[] curListeners = getValueChangeListeners();
// see if we need to null-out, or replace an existing listener
if (null != curListeners) {
for (int i = 0; i < curListeners.length; i++) {
// if we want to remove the valueChangeListener
if (null == valueChangeListener) {
// We are guaranteed to have at most one instance of
// MethodBindingValueChangeListener in the curListeners
// list.
if (MethodBindingValueChangeListener.class ==
curListeners[i].getClass()) {
removeFacesListener(curListeners[i]);
return;
}
}
// if we want to replace the valueChangeListener
else //noinspection ObjectEquality
if (valueChangeListener == curListeners[i]) {
removeFacesListener(curListeners[i]);
break;
}
}
}
addValueChangeListener(new MethodBindingValueChangeListener(valueChangeListener));
}
// ----------------------------------------------------- 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 javax.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
, exit without further
* processing. (This indicates that no value was submitted for this
* component.)
* - If the
*
javax.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) {
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);
}
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));
}
}
}
/**
* 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((Comparable) 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;
}
}