| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 javax.faces.component; |
| |
| import org.apache.myfaces.core.api.shared.SharedRendererUtils; |
| import org.apache.myfaces.core.api.shared.ComponentUtils; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFListener; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam; |
| |
| import javax.el.ValueExpression; |
| import javax.faces.application.FacesMessage; |
| import javax.faces.application.ProjectStage; |
| import javax.faces.context.ExternalContext; |
| import javax.faces.context.FacesContext; |
| import javax.faces.convert.Converter; |
| import javax.faces.convert.ConverterException; |
| import javax.faces.event.AbortProcessingException; |
| import javax.faces.event.ExceptionQueuedEvent; |
| import javax.faces.event.ExceptionQueuedEventContext; |
| import javax.faces.event.FacesEvent; |
| 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.Validator; |
| import javax.faces.webapp.FacesServlet; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFExclude; |
| import org.apache.myfaces.core.api.shared.BeanValidationUtils; |
| import org.apache.myfaces.core.api.shared.ExternalSpecifications; |
| import org.apache.myfaces.core.api.shared.MessageUtils; |
| |
| /** |
| * UICommand is a base abstraction for components that implement ActionSource. |
| * <p> |
| * See the javadoc for this class in the <a href="http://java.sun.com/j2ee/javaserverfaces/1.2/docs/api/index.html">JSF |
| * Specification</a> for further details. |
| * <p> |
| */ |
| @JSFComponent(defaultRendererType = "javax.faces.Text") |
| public class UIInput extends UIOutput implements EditableValueHolder |
| { |
| public static final String COMPONENT_TYPE = "javax.faces.Input"; |
| public static final String COMPONENT_FAMILY = "javax.faces.Input"; |
| |
| public static final String CONVERSION_MESSAGE_ID = "javax.faces.component.UIInput.CONVERSION"; |
| public static final String REQUIRED_MESSAGE_ID = "javax.faces.component.UIInput.REQUIRED"; |
| public static final String UPDATE_MESSAGE_ID = "javax.faces.component.UIInput.UPDATE"; |
| |
| /** |
| * Force validation on empty fields (By default is auto, which means it is only |
| * enabled when Bean Validation binaries are available on the current classpath). |
| */ |
| @JSFWebConfigParam(defaultValue="auto", expectedValues="auto, true, false", since="2.0", group="validation") |
| public static final String VALIDATE_EMPTY_FIELDS_PARAM_NAME = "javax.faces.VALIDATE_EMPTY_FIELDS"; |
| |
| /** |
| * Submitted values are decoded as null values instead empty strings. |
| * |
| * <p>Note this param is ignored for components extending from UISelectOne/UISelectMany.</p> |
| **/ |
| @JSFWebConfigParam(defaultValue="false", expectedValues="true, false", since="2.0", group="validation") |
| public static final String EMPTY_STRING_AS_NULL_PARAM_NAME |
| = "javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL"; |
| |
| /** |
| * When CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY is enabled, input fields will be cleared |
| * when null or empty values are submitted. When disabled, and INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL |
| * is enabled, submitting null or empty values will cause the previous model value to be restored |
| * to the input field. |
| **/ |
| @JSFWebConfigParam(defaultValue="true", expectedValues="true, false", since="2.3.0", group="validation") |
| private static final String CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY_PARAM_NAME |
| = "org.apache.myfaces.CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY"; |
| |
| /** |
| * If set to true, validation is always performed when required is true. |
| */ |
| @JSFWebConfigParam(defaultValue="false", expectedValues="true, false", since="2.3", group="validation") |
| public static final String ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE |
| = "javax.faces.ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE"; |
| |
| // our own, cached key |
| private static final String MYFACES_EMPTY_VALUES_AS_NULL_PARAM_NAME = |
| "org.apache.myfaces.UIInput.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL"; |
| |
| /** |
| * Extended debug info is stored under this key in the request |
| * map for every UIInput component when in Development mode. |
| * ATTENTION: this constant is duplicate in org.apache.myfaces.renderkit.ErrorPageWriter |
| */ |
| private static final String DEBUG_INFO_KEY = "org.apache.myfaces.debug.DEBUG_INFO"; |
| |
| private final static String BEAN_BEFORE_JSF_PROPERTY = "oam.beanBeforeJsf"; |
| |
| private static final Validator[] EMPTY_VALIDATOR_ARRAY = new Validator[0]; |
| |
| private _DeltaList<Validator> _validatorList; |
| |
| /** |
| * Construct an instance of the UIInput. |
| */ |
| public UIInput() |
| { |
| setRendererType("javax.faces.Text"); |
| } |
| |
| @Override |
| public String getFamily() |
| { |
| return COMPONENT_FAMILY; |
| } |
| |
| /** |
| * Store the specified object as the "local value" of this component. The value-binding named "value" (if any) is |
| * ignored; the object is only stored locally on this component. During the "update model" phase, if there is a |
| * value-binding named "value" then this local value will be stored via that value-binding and the "local value" |
| * reset to null. |
| */ |
| @Override |
| public void setValue(Object value) |
| { |
| FacesContext facesContext = getFacesContext(); |
| if (facesContext != null && facesContext.isProjectStage(ProjectStage.Development)) |
| { |
| // extended debug-info when in Development mode |
| _createFieldDebugInfo(facesContext, "localValue", |
| getLocalValue(), value, 1); |
| } |
| setLocalValueSet(true); |
| super.setValue(value); |
| } |
| |
| /** |
| * Return the current value of this component. |
| * <p> |
| * If a submitted value has been converted but not yet pushed into the |
| * model, then return that locally-cached value (see isLocalValueSet). |
| * </p> |
| * Otherwise, evaluate an EL expression to fetch a value from the model. |
| */ |
| @JSFExclude |
| @JSFProperty(clientEvent="valueChange") |
| @Override |
| public Object getValue() |
| { |
| if (isLocalValueSet()) |
| { |
| return super.getLocalValue(); |
| } |
| return super.getValue(); |
| } |
| |
| /** |
| * Set the "submitted value" of this component from the relevant data in the current servlet request object. |
| * <p> |
| * If this component is not rendered, then do nothing; no output would have been sent to the client so no input is |
| * expected. |
| * <p> |
| * Invoke the inherited functionality, which typically invokes the renderer associated with this component to |
| * extract and set this component's "submitted value". |
| * <p> |
| * If this component is marked "immediate", then immediately apply validation to the submitted value found. On |
| * error, call context method "renderResponse" which will force processing to leap to the "render |
| * response" phase as soon as the "decode" step has completed for all other components. |
| */ |
| @Override |
| public void processDecodes(FacesContext context) |
| { |
| if (context == null) |
| { |
| throw new NullPointerException("context"); |
| } |
| |
| try |
| { |
| setCachedFacesContext(context); |
| pushComponentToEL(context, this); |
| if (!isRendered()) |
| { |
| return; |
| } |
| } |
| finally |
| { |
| setCachedFacesContext(null); |
| popComponentFromEL(context); |
| } |
| |
| super.processDecodes(context); |
| |
| try |
| { |
| setCachedFacesContext(context); |
| pushComponentToEL(context, this); |
| if (isImmediate()) |
| { |
| //Pre validation event dispatch for component |
| context.getApplication().publishEvent(context, PreValidateEvent.class, getClass(), this); |
| try |
| { |
| validate(context); |
| } |
| catch (RuntimeException e) |
| { |
| context.renderResponse(); |
| throw e; |
| } |
| finally |
| { |
| context.getApplication().publishEvent(context, PostValidateEvent.class, getClass(), this); |
| } |
| |
| if (!isValid()) |
| { |
| context.renderResponse(); |
| } |
| } |
| } |
| finally |
| { |
| setCachedFacesContext(null); |
| popComponentFromEL(context); |
| } |
| } |
| |
| @Override |
| public void processValidators(FacesContext context) |
| { |
| if (context == null) |
| { |
| throw new NullPointerException("context"); |
| } |
| |
| try |
| { |
| setCachedFacesContext(context); |
| pushComponentToEL(context, this); |
| if (!isRendered()) |
| { |
| return; |
| } |
| } |
| finally |
| { |
| setCachedFacesContext(null); |
| popComponentFromEL(context); |
| } |
| |
| // Call the processValidators() method of all facets and children of this UIComponent, in the order |
| // determined by a call to getFacetsAndChildren(). |
| if (getFacetCount() > 0) |
| { |
| for (UIComponent facet : getFacets().values()) |
| { |
| facet.processValidators(context); |
| } |
| } |
| |
| for (int i = 0, childCount = getChildCount(); i < childCount; i++) |
| { |
| UIComponent child = getChildren().get(i); |
| child.processValidators(context); |
| } |
| |
| try |
| { |
| setCachedFacesContext(context); |
| pushComponentToEL(context, this); |
| if (!isImmediate()) |
| { |
| //Pre validation event dispatch for component |
| context.getApplication().publishEvent(context, PreValidateEvent.class, getClass(), this); |
| try |
| { |
| validate(context); |
| } |
| catch (RuntimeException e) |
| { |
| context.renderResponse(); |
| throw e; |
| } |
| finally |
| { |
| context.getApplication().publishEvent(context, PostValidateEvent.class, getClass(), this); |
| } |
| |
| if (!isValid()) |
| { |
| context.validationFailed(); |
| context.renderResponse(); |
| } |
| } |
| } |
| finally |
| { |
| setCachedFacesContext(null); |
| popComponentFromEL(context); |
| } |
| } |
| |
| @Override |
| public void processUpdates(FacesContext context) |
| { |
| if (context == null) |
| { |
| throw new NullPointerException("context"); |
| } |
| try |
| { |
| setCachedFacesContext(context); |
| pushComponentToEL(context, this); |
| if (!isRendered()) |
| { |
| return; |
| } |
| } |
| finally |
| { |
| setCachedFacesContext(null); |
| popComponentFromEL(context); |
| } |
| |
| super.processUpdates(context); |
| |
| try |
| { |
| setCachedFacesContext(context); |
| pushComponentToEL(context, this); |
| try |
| { |
| updateModel(context); |
| } |
| catch (RuntimeException e) |
| { |
| context.renderResponse(); |
| throw e; |
| } |
| |
| if (!isValid()) |
| { |
| context.renderResponse(); |
| } |
| } |
| finally |
| { |
| setCachedFacesContext(null); |
| popComponentFromEL(context); |
| } |
| } |
| |
| @Override |
| public void decode(FacesContext context) |
| { |
| // We (re)set to valid, so that component automatically gets (re)validated |
| setValid(true); |
| super.decode(context); |
| } |
| |
| @Override |
| public void broadcast(FacesEvent event) throws AbortProcessingException |
| { |
| // invoke standard listeners attached to this component first |
| super.broadcast(event); |
| } |
| |
| public void updateModel(FacesContext context) |
| { |
| if (context == null) |
| { |
| throw new NullPointerException(); |
| } |
| if (!isValid() || !isLocalValueSet()) |
| { |
| return; |
| } |
| |
| ValueExpression expression = getValueExpression("value"); |
| if (expression == null) |
| { |
| return; |
| } |
| |
| try |
| { |
| expression.setValue(context.getELContext(), getLocalValue()); |
| setValue(null); |
| setLocalValueSet(false); |
| } |
| catch (Exception e) |
| { |
| // Enqueue an error message |
| //context.getExternalContext().log(e.getMessage(), e); |
| |
| // Create a FacesMessage with the id UPDATE_MESSAGE_ID |
| FacesMessage facesMessage = MessageUtils.getMessage(context, |
| context.getViewRoot().getLocale(), FacesMessage.SEVERITY_ERROR, UPDATE_MESSAGE_ID, |
| new Object[] { MessageUtils.getLabel(context, this) }); |
| |
| // create an UpdateModelException and enqueue it since |
| // we are not allowed to throw it directly here |
| // spec javadoc: The exception must not be re-thrown. This enables tree traversal to |
| // continue for this lifecycle phase, as in all the other lifecycle phases. |
| UpdateModelException updateModelException = new UpdateModelException(facesMessage, e); |
| ExceptionQueuedEventContext exceptionQueuedContext |
| = new ExceptionQueuedEventContext(context, updateModelException, this, PhaseId.UPDATE_MODEL_VALUES); |
| |
| // spec javadoc says we should call context.getExceptionHandler().processEvent(exceptionQueuedContext), |
| // which is not just syntactically wrong, but also stupid!! |
| context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, exceptionQueuedContext); |
| |
| // Set the valid property of this UIInput to false |
| setValid(false); |
| } |
| } |
| |
| protected void validateValue(FacesContext context, Object convertedValue) |
| { |
| if (!isValid()) |
| { |
| return; |
| } |
| |
| // If our value is empty, check the required property |
| boolean isEmpty = isEmpty(convertedValue); |
| |
| if (isRequired() && isEmpty) |
| { |
| if (getRequiredMessage() != null) |
| { |
| String requiredMessage = getRequiredMessage(); |
| context.addMessage(this.getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, |
| requiredMessage, requiredMessage)); |
| } |
| else |
| { |
| MessageUtils.addErrorMessage(context, this, REQUIRED_MESSAGE_ID, |
| new Object[] { MessageUtils.getLabel(context, this) }); |
| } |
| setValid(false); |
| return; |
| } |
| |
| if (!isEmpty || shouldValidateEmptyFields(context)) |
| { |
| ComponentUtils.callValidators(context, this, convertedValue); |
| } |
| } |
| |
| /** |
| * Checks if the <code>validate()</code> should interpret an empty |
| * submitted value should be handle as <code>NULL</code> |
| * |
| * @return a (cached) boolean to identify the interpretation as null |
| */ |
| private boolean shouldInterpretEmptyStringSubmittedValuesAsNull(FacesContext context) |
| { |
| ExternalContext ec = context.getExternalContext(); |
| Boolean interpretEmptyStringAsNull |
| = (Boolean)ec.getApplicationMap().get(MYFACES_EMPTY_VALUES_AS_NULL_PARAM_NAME); |
| |
| // not yet cached... |
| if (interpretEmptyStringAsNull == null) |
| { |
| // parses the web.xml to get the "javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL" value |
| String param = ec.getInitParameter(EMPTY_STRING_AS_NULL_PARAM_NAME); |
| |
| // evaluate the param |
| interpretEmptyStringAsNull = "true".equalsIgnoreCase(param); |
| |
| // cache the parsed value |
| ec.getApplicationMap().put(MYFACES_EMPTY_VALUES_AS_NULL_PARAM_NAME, interpretEmptyStringAsNull); |
| } |
| |
| return interpretEmptyStringAsNull; |
| } |
| |
| /** |
| * <p>Return <code>true</code> if the value is an empty <code>String</code>.</p> |
| */ |
| private boolean isEmptyString(Object value) |
| { |
| return ((value instanceof String) && (((String) value).length() == 0)); |
| } |
| |
| |
| private boolean shouldValidateEmptyFields(FacesContext context) |
| { |
| ExternalContext ec = context.getExternalContext(); |
| Boolean validateEmptyFields = (Boolean) ec.getApplicationMap().get(VALIDATE_EMPTY_FIELDS_PARAM_NAME); |
| |
| if (validateEmptyFields == null) |
| { |
| String param = ec.getInitParameter(VALIDATE_EMPTY_FIELDS_PARAM_NAME); |
| |
| // null means the same as auto. |
| if (param == null) |
| { |
| param = "auto"; |
| } |
| else |
| { |
| // The environment variables are case insensitive. |
| param = param.toLowerCase(); |
| } |
| |
| if (param.equals("auto") && ExternalSpecifications.isBeanValidationAvailable()) |
| { |
| validateEmptyFields = true; |
| } |
| else if (param.equals("true")) |
| { |
| validateEmptyFields = true; |
| } |
| else |
| { |
| validateEmptyFields = false; |
| } |
| |
| // cache the parsed value |
| ec.getApplicationMap().put(VALIDATE_EMPTY_FIELDS_PARAM_NAME, validateEmptyFields); |
| } |
| |
| return validateEmptyFields; |
| } |
| |
| /** |
| * Get the value of context parameter |
| * org.apache.myfaces.CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY from the web.xml |
| * |
| * @return the value of the context parameter |
| */ |
| private boolean shouldClearInputWhenSubmittedValueIsNullOrEmpty(FacesContext context) |
| { |
| ExternalContext ec = context.getExternalContext(); |
| Boolean clearInputWhenSubmittedValueIsNullOrEmpty = |
| (Boolean) ec.getApplicationMap().get(CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY_PARAM_NAME); |
| |
| if (clearInputWhenSubmittedValueIsNullOrEmpty == null) |
| { |
| // parses the web.xml to get the |
| // "org.apache.myfaces.CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY" value |
| String param = ec.getInitParameter(CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY_PARAM_NAME); |
| |
| // evaluate the param |
| // NOTE: On JSF 2.3, this value will be set to true by default. |
| if (param == null) |
| { |
| clearInputWhenSubmittedValueIsNullOrEmpty = true; // default |
| } |
| else if ("false".equalsIgnoreCase(param)) |
| { |
| clearInputWhenSubmittedValueIsNullOrEmpty = false; |
| } |
| else |
| { |
| clearInputWhenSubmittedValueIsNullOrEmpty = true; |
| } |
| |
| // cache the parsed value |
| ec.getApplicationMap().put(CLEAR_INPUT_WHEN_SUBMITTED_VALUE_IS_NULL_OR_EMPTY_PARAM_NAME, clearInputWhenSubmittedValueIsNullOrEmpty); |
| } |
| |
| return clearInputWhenSubmittedValueIsNullOrEmpty; |
| } |
| |
| private boolean shouldAlwaysPerformValidationWhenRequiredTrue(FacesContext context) |
| { |
| ExternalContext ec = context.getExternalContext(); |
| Boolean alwaysPerformValidationWhenRequiredTrue = (Boolean) ec.getApplicationMap().get( |
| ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE); |
| |
| if (alwaysPerformValidationWhenRequiredTrue == null) |
| { |
| String param = ec.getInitParameter(ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE); |
| |
| // null means the same as auto. |
| if (param == null) |
| { |
| param = "false"; |
| } |
| else |
| { |
| // The environment variables are case insensitive. |
| param = param.toLowerCase(); |
| } |
| |
| if (param.equals("true")) |
| { |
| alwaysPerformValidationWhenRequiredTrue = true; |
| } |
| else |
| { |
| alwaysPerformValidationWhenRequiredTrue = false; |
| } |
| |
| // cache the parsed value |
| ec.getApplicationMap().put(ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE, |
| alwaysPerformValidationWhenRequiredTrue); |
| } |
| |
| return alwaysPerformValidationWhenRequiredTrue; |
| } |
| |
| /** |
| * Determine whether the new value is valid, and queue a ValueChangeEvent if necessary. |
| * <p> |
| * The "submitted value" is converted to the necessary type; conversion failure is reported as an error and |
| * validation processing terminates for this component. See documentation for method getConvertedValue for details |
| * on the conversion process. |
| * <p> |
| * Any validators attached to this component are then run, passing the converted value. |
| * <p> |
| * The old value of this component is then fetched (possibly involving the evaluation of a value-binding expression, |
| * ie invoking a method on a user object). The old value is compared to the new validated value, and if they are |
| * different then a ValueChangeEvent is queued for later processing. |
| * <p> |
| * On successful completion of this method: |
| * <ul> |
| * <li>isValid() is true |
| * <li>isLocalValueSet() is true |
| * <li>submittedValue is reset to null |
| * <li>a ValueChangeEvent is queued if the new value != old value |
| * </ul> |
| */ |
| public void validate(FacesContext context) |
| { |
| if (context == null) |
| { |
| throw new NullPointerException("context"); |
| } |
| |
| Object submittedValue = getSubmittedValue(); |
| if (submittedValue == null) |
| { |
| if (isRequired() && shouldAlwaysPerformValidationWhenRequiredTrue(context)) |
| { |
| // continue |
| } |
| else |
| { |
| return; |
| } |
| } |
| |
| // Begin new JSF 2.0 requirement (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL) |
| if (shouldInterpretEmptyStringSubmittedValuesAsNull(context) && isEmptyString(submittedValue)) |
| { |
| setSubmittedValue(null); |
| |
| if (!shouldClearInputWhenSubmittedValueIsNullOrEmpty(context)) |
| { |
| submittedValue = null; |
| } |
| } |
| // End new JSF 2.0 requirement (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL) |
| |
| Object convertedValue; |
| try |
| { |
| if (shouldClearInputWhenSubmittedValueIsNullOrEmpty(context)) |
| { |
| // don't use local variable submittedValue because of |
| // (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL) |
| convertedValue = getConvertedValue(context, getSubmittedValue()); |
| } |
| else |
| { |
| convertedValue = getConvertedValue(context, submittedValue); |
| } |
| } |
| catch (ConverterException e) |
| { |
| String converterMessage = getConverterMessage(); |
| if (converterMessage != null) |
| { |
| context.addMessage(getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, |
| converterMessage, converterMessage)); |
| } |
| else |
| { |
| FacesMessage facesMessage = e.getFacesMessage(); |
| if (facesMessage != null) |
| { |
| context.addMessage(getClientId(context), facesMessage); |
| } |
| else |
| { |
| MessageUtils.addErrorMessage(context, this, CONVERSION_MESSAGE_ID, |
| new Object[] { MessageUtils.getLabel(context, this) }); |
| } |
| } |
| setValid(false); |
| if (shouldClearInputWhenSubmittedValueIsNullOrEmpty(context)) |
| { |
| // set submitted value again if is invalid |
| // because of (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL) |
| setSubmittedValue(submittedValue); |
| } |
| return; |
| } |
| |
| validateValue(context, convertedValue); |
| |
| if (!isValid()) |
| { |
| if (shouldClearInputWhenSubmittedValueIsNullOrEmpty(context)) |
| { |
| // set submitted value again if is invalid |
| // because of (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL) |
| setSubmittedValue(submittedValue); |
| } |
| return; |
| } |
| |
| Object previousValue = getValue(); |
| setValue(convertedValue); |
| setSubmittedValue(null); |
| if (compareValues(previousValue, convertedValue)) |
| { |
| queueEvent(new ValueChangeEvent(this, previousValue, convertedValue)); |
| } |
| } |
| |
| /** |
| * Convert the provided object to the desired value. |
| * <p> |
| * If there is a renderer for this component, then call the renderer's getConvertedValue method. While this can of |
| * course be implemented in any way the renderer desires, it typically performs exactly the same processing that |
| * this method would have done anyway (ie that described below for the no-renderer case). |
| * <p> |
| * Otherwise: |
| * <ul> |
| * <li>If the submittedValue is not a String then just return the submittedValue unconverted. |
| * <li>If there is no "value" value-binding then just return the submittedValue unconverted. |
| * <li>Use introspection to determine the type of the target property specified by the value-binding, and then use |
| * Application.createConverter to find a converter that can map from String to the required type. Apply the |
| * converter to the submittedValue and return the result. |
| * </ul> |
| */ |
| protected Object getConvertedValue(FacesContext context, Object submittedValue) throws ConverterException |
| { |
| Renderer renderer = getRenderer(context); |
| if (renderer != null) |
| { |
| return renderer.getConvertedValue(context, this, submittedValue); |
| } |
| else if (submittedValue instanceof String) |
| { |
| Converter converter = SharedRendererUtils.findUIOutputConverter(context, this); |
| if (converter != null) |
| { |
| return converter.getAsObject(context, this, (String) submittedValue); |
| } |
| } |
| return submittedValue; |
| } |
| |
| protected boolean compareValues(Object previous, Object value) |
| { |
| return previous == null ? (value != null) : (!previous.equals(value)); |
| } |
| |
| /** |
| * @since 1.2 |
| */ |
| @Override |
| public void resetValue() |
| { |
| super.resetValue(); |
| setSubmittedValue(null); |
| setLocalValueSet(false); |
| setValid(true); |
| } |
| |
| /** |
| * A boolean value that identifies the phase during which action events should fire. |
| * <p> |
| * During normal event processing, action methods and action listener methods are fired during the |
| * "invoke application" phase of request processing. If this attribute is set to "true", these methods are fired |
| * instead at the end of the "apply request values" phase. |
| * </p> |
| */ |
| @JSFProperty |
| @Override |
| public boolean isImmediate() |
| { |
| return (Boolean) getStateHelper().eval(PropertyKeys.immediate, Boolean.FALSE); |
| } |
| |
| @Override |
| public void setImmediate(boolean immediate) |
| { |
| getStateHelper().put(PropertyKeys.immediate, immediate ); |
| } |
| |
| /** |
| * A boolean value that indicates whether an input value is required. |
| * <p> |
| * If this value is true and no input value is provided by a postback operation, then the "requiredMessage" text is |
| * registered as a FacesMessage for the request, and validation fails. |
| * </p> |
| * <p> |
| * Default value: false. |
| * </p> |
| */ |
| @JSFProperty(defaultValue = "false") |
| @Override |
| public boolean isRequired() |
| { |
| return (Boolean) getStateHelper().eval(PropertyKeys.required, Boolean.FALSE); |
| } |
| |
| @Override |
| public void setRequired(boolean required) |
| { |
| getStateHelper().put(PropertyKeys.required, required ); |
| } |
| |
| /** |
| * Text to be displayed to the user as an error message when conversion of a submitted value to the target type |
| * fails. |
| * <p> |
| * </p> |
| */ |
| @JSFProperty |
| public String getConverterMessage() |
| { |
| return (String) getStateHelper().eval(PropertyKeys.converterMessage); |
| } |
| |
| public void setConverterMessage(String converterMessage) |
| { |
| getStateHelper().put(PropertyKeys.converterMessage, converterMessage ); |
| } |
| |
| /** |
| * Text to be displayed to the user as an error message when this component is marked as "required" but no input |
| * data is present during a postback (ie the user left the required field blank). |
| */ |
| @JSFProperty |
| public String getRequiredMessage() |
| { |
| return (String) getStateHelper().eval(PropertyKeys.requiredMessage); |
| } |
| |
| public void setRequiredMessage(String requiredMessage) |
| { |
| getStateHelper().put(PropertyKeys.requiredMessage, requiredMessage ); |
| } |
| |
| /** See getValidator. */ |
| @Override |
| public void addValidator(Validator validator) |
| { |
| if (validator == null) |
| { |
| throw new NullPointerException("validator"); |
| } |
| |
| if (_validatorList == null) |
| { |
| _validatorList = new _DeltaList<>(3); |
| } |
| |
| _validatorList.add(validator); |
| } |
| |
| /** See getValidator. */ |
| @Override |
| public void removeValidator(Validator validator) |
| { |
| if (validator == null || _validatorList == null) |
| { |
| return; |
| } |
| |
| _validatorList.remove(validator); |
| } |
| |
| /** See getValidator. */ |
| @Override |
| public Validator[] getValidators() |
| { |
| if (ExternalSpecifications.isBeanValidationAvailable() |
| && Boolean.TRUE.equals(this.getAttributes().containsKey(BEAN_BEFORE_JSF_PROPERTY))) |
| { |
| int bvIndex = -1; |
| for (int i = 0; i < _validatorList.size(); i++) |
| { |
| Validator v = _validatorList.get(i); |
| if (BeanValidationUtils.isBeanValidator(v)) |
| { |
| bvIndex = i; |
| break; |
| } |
| } |
| if (bvIndex != -1) |
| { |
| Validator[] array = new Validator[_validatorList.size()]; |
| for (int i = 0; i < _validatorList.size(); i++) |
| { |
| if (i == bvIndex) |
| { |
| array[0] = _validatorList.get(i); |
| bvIndex = -1; |
| } |
| else |
| { |
| array[i+1] = _validatorList.get(i); |
| } |
| } |
| return array; |
| } |
| else |
| { |
| return _validatorList == null |
| ? EMPTY_VALIDATOR_ARRAY |
| : _validatorList.toArray(new Validator[_validatorList.size()]); |
| } |
| } |
| else |
| { |
| return _validatorList == null |
| ? EMPTY_VALIDATOR_ARRAY |
| : _validatorList.toArray(new Validator[_validatorList.size()]); |
| } |
| } |
| |
| /** |
| * Text which will be shown if validation fails. |
| */ |
| @JSFProperty |
| public String getValidatorMessage() |
| { |
| return (String) getStateHelper().eval(PropertyKeys.validatorMessage); |
| } |
| |
| public void setValidatorMessage(String validatorMessage) |
| { |
| getStateHelper().put(PropertyKeys.validatorMessage, validatorMessage ); |
| } |
| |
| /** |
| * Specifies whether the component's value is currently valid, ie whether the validators attached to this component |
| * have allowed it. |
| */ |
| @JSFProperty(defaultValue = "true", tagExcluded = true) |
| @Override |
| public boolean isValid() |
| { |
| Object value = getStateHelper().get(PropertyKeys.valid); |
| if (value != null) |
| { |
| return (Boolean) value; |
| } |
| return true; |
| } |
| |
| @Override |
| public void setValid(boolean valid) |
| { |
| // default value for valid is true, so if the intention is to save the default |
| // value when nothing else was set before, don't do it. This is done in order to |
| // reduce the size of the saved state of the state helper. Default values won't be |
| // included in the saved state. |
| if (getStateHelper().get(PropertyKeys.valid) != null || !valid) |
| { |
| getStateHelper().put(PropertyKeys.valid, valid ); |
| } |
| } |
| |
| /** |
| * Specifies whether a local value is currently set. |
| * <p> |
| * If false, values are being retrieved from any attached ValueBinding. |
| */ |
| @JSFProperty(defaultValue = "false", tagExcluded = true) |
| @Override |
| public boolean isLocalValueSet() |
| { |
| Object value = getStateHelper().get(PropertyKeys.localValueSet); |
| if (value != null) |
| { |
| return (Boolean) value; |
| } |
| return false; |
| } |
| |
| @Override |
| public void setLocalValueSet(boolean localValueSet) |
| { |
| // default value for localValueSet is false, so if the intention is to save the default |
| // value when nothing else was set before, don't do it. This is done in order to |
| // reduce the size of the saved state of the state helper. Default values won't be |
| // included in the saved state. |
| if (getStateHelper().get(PropertyKeys.localValueSet) != null || localValueSet) |
| { |
| getStateHelper().put(PropertyKeys.localValueSet, localValueSet ); |
| } |
| } |
| |
| /** |
| * Gets the current submitted value. This value, if non-null, is set by the Renderer to store a possibly invalid |
| * value for later conversion or redisplay, and has not yet been converted into the proper type for this component |
| * instance. This method should only be used by the decode() and validate() method of this component, or its |
| * corresponding Renderer; however, user code may manually set it to null to erase any submitted value. |
| */ |
| @Override |
| @JSFProperty(tagExcluded = true) |
| public Object getSubmittedValue() |
| { |
| return getStateHelper().get(PropertyKeys.submittedValue); |
| } |
| |
| @Override |
| public void setSubmittedValue(Object submittedValue) |
| { |
| FacesContext facesContext = getFacesContext(); |
| if (facesContext != null && facesContext.isProjectStage(ProjectStage.Development)) |
| { |
| // extended debug-info when in Development mode |
| _createFieldDebugInfo(facesContext, "submittedValue", |
| getStateHelper().get(PropertyKeys.submittedValue), submittedValue, 1); |
| } |
| getStateHelper().put(PropertyKeys.submittedValue, submittedValue ); |
| } |
| |
| @Override |
| public void addValueChangeListener(ValueChangeListener listener) |
| { |
| addFacesListener(listener); |
| } |
| |
| @Override |
| public void removeValueChangeListener(ValueChangeListener listener) |
| { |
| removeFacesListener(listener); |
| } |
| |
| /** |
| * The valueChange event is delivered when the value attribute |
| * is changed. |
| */ |
| @JSFListener(event="javax.faces.event.ValueChangeEvent") |
| @Override |
| public ValueChangeListener[] getValueChangeListeners() |
| { |
| return (ValueChangeListener[]) getFacesListeners(ValueChangeListener.class); |
| } |
| |
| enum PropertyKeys |
| { |
| immediate |
| , required |
| , converterMessage |
| , requiredMessage |
| , validator |
| , validatorListSet |
| , validatorMessage |
| , valueChangeListener |
| , valid |
| , localValueSet |
| , submittedValue |
| } |
| |
| private static final Object[] INITIAL_STATE_PROPERTIES = new |
| Object[]{ |
| UIOutput.PropertyKeys.value, |
| null, |
| UIInput.PropertyKeys.localValueSet, |
| false, |
| UIInput.PropertyKeys.submittedValue, |
| null, |
| UIInput.PropertyKeys.valid, |
| true |
| }; |
| |
| @Override |
| public void markInitialState() |
| { |
| StateHelper helper = getStateHelper(false); |
| if (helper != null && helper instanceof _DeltaStateHelper) |
| { |
| ((_DeltaStateHelper)helper).markPropertyInInitialState(INITIAL_STATE_PROPERTIES); |
| } |
| super.markInitialState(); |
| if (_validatorList != null) |
| { |
| _validatorList.markInitialState(); |
| } |
| } |
| |
| @Override |
| public void clearInitialState() |
| { |
| if (initialStateMarked()) |
| { |
| super.clearInitialState(); |
| if (_validatorList != null) |
| { |
| _validatorList.clearInitialState(); |
| } |
| } |
| } |
| |
| @Override |
| public Object saveState(FacesContext facesContext) |
| { |
| if (initialStateMarked()) |
| { |
| Object parentSaved = super.saveState(facesContext); |
| Object validatorListSaved = saveValidatorList(facesContext); |
| if (parentSaved == null && validatorListSaved == null) |
| { |
| //No values |
| return null; |
| } |
| |
| Object[] values = new Object[2]; |
| values[0] = parentSaved; |
| values[1] = validatorListSaved; |
| return values; |
| } |
| else |
| { |
| Object[] values = new Object[2]; |
| values[0] = super.saveState(facesContext); |
| values[1] = saveValidatorList(facesContext); |
| return values; |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void restoreState(FacesContext facesContext, Object state) |
| { |
| if (state == null) |
| { |
| return; |
| } |
| |
| Object[] values = (Object[])state; |
| super.restoreState(facesContext,values[0]); |
| if (values[1] instanceof _AttachedDeltaWrapper) |
| { |
| //Delta |
| if (_validatorList != null) |
| { |
| ((StateHolder)_validatorList).restoreState(facesContext, |
| ((_AttachedDeltaWrapper) values[1]).getWrappedStateObject()); |
| } |
| } |
| else if (values[1] != null || !initialStateMarked()) |
| { |
| //Full |
| _validatorList = (_DeltaList<Validator>) |
| restoreAttachedState(facesContext,values[1]); |
| } |
| } |
| |
| private Object saveValidatorList(FacesContext facesContext) |
| { |
| PartialStateHolder holder = (PartialStateHolder) _validatorList; |
| if (initialStateMarked() && _validatorList != null && holder.initialStateMarked()) |
| { |
| Object attachedState = holder.saveState(facesContext); |
| if (attachedState != null) |
| { |
| return new _AttachedDeltaWrapper(_validatorList.getClass(), attachedState); |
| } |
| |
| //_validatorList instances once is created never changes, we can return null |
| return null; |
| } |
| else |
| { |
| return saveAttachedState(facesContext,_validatorList); |
| } |
| } |
| |
| /** |
| * Returns the debug-info Map for this component. |
| * @return |
| */ |
| @SuppressWarnings("unchecked") |
| private Map<String, List<Object[]>> _getDebugInfoMap() |
| { |
| Map<String, Object> requestMap = getFacesContext().getExternalContext().getRequestMap(); |
| return (Map<String, List<Object[]>>) |
| requestMap.computeIfAbsent(DEBUG_INFO_KEY + getClientId(), k -> new HashMap<>()); |
| } |
| |
| /** |
| * Returns the field's debug-infos from the component's debug-info Map. |
| * @param field |
| * @return |
| */ |
| private List<Object[]> _getFieldDebugInfos(final String field) |
| { |
| Map<String, List<Object[]>> debugInfo = _getDebugInfoMap(); |
| return debugInfo.computeIfAbsent(field, k -> new ArrayList<Object[]>()); |
| } |
| |
| /** |
| * Creates the field debug-info for the given field, which changed |
| * from oldValue to newValue. |
| * |
| * @param facesContext |
| * @param field |
| * @param oldValue |
| * @param newValue |
| * @param skipStackTaceElements How many StackTraceElements should be skipped |
| * when the calling function will be determined. |
| */ |
| private void _createFieldDebugInfo(FacesContext facesContext, |
| final String field, Object oldValue, |
| Object newValue, final int skipStackTaceElements) |
| { |
| if (oldValue == null && newValue == null) |
| { |
| // both values are null, not interesting and can |
| // happen a lot in UIData with saving and restoring state |
| return; |
| } |
| |
| if (facesContext.getViewRoot() == null) |
| { |
| // No viewRoot set, it is creating component, |
| // so it is not possible to calculate the clientId, |
| // abort processing because the interesting part will |
| // happen later. |
| return; |
| } |
| |
| if (getParent() == null || !isInView()) |
| { |
| //Skip if no parent or is not in view |
| return; |
| } |
| |
| // convert Array values into a more readable format |
| if (oldValue != null && oldValue.getClass().isArray() && Object[].class.isAssignableFrom(oldValue.getClass())) |
| { |
| oldValue = Arrays.deepToString((Object[]) oldValue); |
| } |
| if (newValue != null && newValue.getClass().isArray() && Object[].class.isAssignableFrom(newValue.getClass())) |
| { |
| newValue = Arrays.deepToString((Object[]) newValue); |
| } |
| |
| // use Throwable to get the current call stack |
| Throwable throwableHelper = new Throwable(); |
| StackTraceElement[] stackTraceElements = throwableHelper.getStackTrace(); |
| List<StackTraceElement> debugStackTraceElements = new LinkedList<>(); |
| |
| // + 1 because this method should also be skipped |
| for (int i = skipStackTaceElements + 1; i < stackTraceElements.length; i++) |
| { |
| debugStackTraceElements.add(stackTraceElements[i]); |
| |
| if (FacesServlet.class.getCanonicalName().equals(stackTraceElements[i].getClassName())) |
| { |
| // stop after the FacesServlet |
| break; |
| } |
| } |
| |
| // create the debug-info array |
| // structure: |
| // - 0: phase |
| // - 1: old value |
| // - 2: new value |
| // - 3: StackTraceElement List |
| // NOTE that we cannot create a class here to encapsulate this data, |
| // because this is not on the spec and the class would not be available in impl. |
| Object[] debugInfo = new Object[4]; |
| debugInfo[0] = facesContext.getCurrentPhaseId(); |
| debugInfo[1] = oldValue; |
| debugInfo[2] = newValue; |
| debugInfo[3] = debugStackTraceElements; |
| |
| // add the debug info |
| _getFieldDebugInfos(field).add(debugInfo); |
| } |
| |
| /** |
| * Check if a value is empty or not. Since we don't know the class of |
| * value we have to check and deal with it properly. |
| * |
| * @since 2.0 |
| * @param value |
| * @return |
| */ |
| public static boolean isEmpty(Object value) |
| { |
| if (value == null) |
| { |
| return true; |
| } |
| else if (value instanceof String) |
| { |
| if (((String) value).trim().length() <= 0) |
| { |
| return true; |
| } |
| } |
| else if (value instanceof Collection) |
| { |
| if (((Collection) value).isEmpty()) |
| { |
| return true; |
| } |
| } |
| else if (value.getClass().isArray()) |
| { |
| if (java.lang.reflect.Array.getLength(value) <= 0) |
| { |
| return true; |
| } |
| } |
| else if (value instanceof Map) |
| { |
| if (((Map) value).isEmpty()) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |