jakarta.faces.component.UIViewParameter 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.io.IOException;
import java.util.Iterator;
import jakarta.el.ValueExpression;
import jakarta.faces.FactoryFinder;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.convert.Converter;
import jakarta.faces.convert.ConverterException;
import jakarta.faces.render.RenderKit;
import jakarta.faces.render.RenderKitFactory;
import jakarta.faces.render.Renderer;
import jakarta.faces.validator.RequiredValidator;
import jakarta.faces.validator.Validator;
/**
*
* UIViewParameter represents a binding between a
* request parameter and a model property or {@link UIViewRoot} property. This is a bi-directional binding.
*
*
*
*
*
* The {@link jakarta.faces.view.ViewDeclarationLanguage} implementation must cause an instance of this component to
* appear in the view for each occurrence of an <f:viewParam />
element placed inside of an
* <f:metadata />
element. The user must place this facet within the UIViewRoot
.
*
*
*
* Because this class extends UIInput
any actions that one would normally take on a UIInput
* instance are valid for instances of this class. Instances of this class participate in the regular Jakarta Server
* Faces lifecycle, including on Ajax requests.
*
*
*
*
* @since 2.0
*/
public class UIViewParameter extends UIInput {
// ------------------------------------------------------ Manifest Constants
/**
*
* The standard component type for this component.
*
*/
public static final String COMPONENT_TYPE = "jakarta.faces.ViewParameter";
/**
*
* The standard component family for this component.
*
*/
public static final String COMPONENT_FAMILY = "jakarta.faces.ViewParameter";
enum PropertyKeys {
name, submittedValue
}
// ------------------------------------------------------ Instance Variables
private Renderer inputTextRenderer = null;
private transient Boolean emptyStringIsNull;
// ------------------------------------------------------------ Constructors
/**
*
* Create a new {@link UIViewParameter} instance with default property values.
*
*/
public UIViewParameter() {
super();
setRendererType(null);
}
// ------------------------------------------------------ Instance Variables
/**
*
* The raw value is the "implicit" binding for this view parameter. This property maintains the submitted value of the
* view parameter for the duration of the request. If the view parameter does not explicitly specify a value expression,
* then when the request ends, this value is stored with the state of this component to use as the submitted value on an
* ensuing postback.
*
*/
private String rawValue;
// -------------------------------------------------------------- Properties
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
/**
*
* Return the request parameter name from which the value is retrieved.
*
*
* @return the name.
* @since 2.0
*/
public String getName() {
return (String) getStateHelper().eval(PropertyKeys.name);
}
/**
*
* Set the request parameter name from which the value is retrieved.
*
*
* @param name The new request parameter name.
* @since 2.0
*/
public void setName(String name) {
getStateHelper().put(PropertyKeys.name, name);
}
/**
*
* Return false
. The immediate setting is not relevant for view parameters and must be assumed to be
* false
.
*
*
* @return true
if immediate, false
otherwise.
* @since 2.0
*/
@Override
public boolean isImmediate() {
return false;
}
/**
*
* Assume that the submitted value is always a string,
* but the return type from this method is Object
..
*
*
* @return the submitted value.
* @since 2.0
*/
@Override
public Object getSubmittedValue() {
return getStateHelper().get(PropertyKeys.submittedValue);
}
/**
* PENDING (docs) Interesting that submitted value isn't saved by the parent
*
* @param submittedValue The new submitted value
*/
@Override
public void setSubmittedValue(Object submittedValue) {
getStateHelper().put(PropertyKeys.submittedValue, submittedValue);
}
// ----------------------------------------------------- UIComponent Methods
// This is the "Apply Request Phase" step
// QUESTION should we just override processDecodes() directly?
// ANSWER: In this case, no. We don't want to take responsibility for
// traversing any children we may have in the future.
/**
*
* Override behavior from superclass to pull a value from the incoming request parameter map under the name given by
* {@link #getName} and store it with a call to {@link UIInput#setSubmittedValue}.
*
*
* @since 2.0
*/
@Override
public void decode(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// QUESTION can we move forward and support an array? no different than UISelectMany; perhaps need to know
// if the value expression is single or multi-valued
// ANSWER: I'd rather not right now.
String paramValue = context.getExternalContext().getRequestParameterMap().get(getName());
// submitted value will stay as previous value (null on initial request) if a parameter is absent
if (paramValue != null) {
setSubmittedValue(paramValue);
}
rawValue = (String) getSubmittedValue();
setValid(true);
}
/**
*
* Specialize superclass behavior to treat null
differently. In
* this class, a null
value along with the "required" flag being set to true
will cause a
* validation failure. Otherwise, If the {@link UIInput#EMPTY_STRING_AS_NULL_PARAM_NAME}
* context parameter is true and the value is {@code null}, call {@link UIInput#setSubmittedValue} passing the empty
* string as the argument. This will cause the normal validation processing to happen, including bean validation.
*
*
* @param context the Faces context.
* @since 2.0
*/
@Override
public void processValidators(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
Object submittedValue = getSubmittedValue();
// we have to override since UIInput assumes that a null value means don't check
if (submittedValue == null && myIsRequired()) {
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);
context.validationFailed();
context.renderResponse();
} else {
if (myConsiderEmptyStringNull(context)) {
// JAVASERVERFACES_SPEC_PUBLIC-1329: If the EMPTY_STRING_SUBMITTED_VALUES_AS_NULL
// config is set, ensure that logic gets a chance to be executed
// in UIInput.processValidators().
if (null == submittedValue) {
setSubmittedValue("");
}
}
super.processValidators(context);
}
}
private boolean myConsiderEmptyStringNull(FacesContext ctx) {
if (emptyStringIsNull == null) {
String val = ctx.getExternalContext().getInitParameter(EMPTY_STRING_AS_NULL_PARAM_NAME);
emptyStringIsNull = Boolean.valueOf(val);
}
return emptyStringIsNull;
}
private boolean myIsRequired() {
return super.isRequired() || isRequiredViaNestedRequiredValidator();
}
/*
* JAVASERVERFACES-3058. Handle the nested requiredValidator case explicitly in the case of .
*
*/
private boolean isRequiredViaNestedRequiredValidator() {
boolean result = false;
if (null == validators) {
return result;
}
Iterator iter = validators.iterator();
while (iter.hasNext()) {
if (iter.next() instanceof RequiredValidator) {
// See JAVASERVERFACES-2526. Note that we can assume
// that at this point the validator is not disabled,
// so the mere existence of the validator implies it is
// enabled.
result = true;
Object submittedValue = getSubmittedValue();
if (submittedValue == null) {
// JAVASERVERFACES-3058 asserts that view parameters
// should be treated differently than form parameters
// if they are not submitted. I'm not sure if that's
// correct, but let's put this in and see how
// the community responds.
setSubmittedValue("");
}
break;
}
}
return result;
}
/**
*
* Call through to superclass {@link UIInput#updateModel} then take the additional action of pushing the value into
* request scope if and only if the value is not a value expression, is valid, and the local value was set on this
* lifecycle execution.
*
*
* @since 2.0
*/
@Override
public void updateModel(FacesContext context) {
super.updateModel(context);
if (!hasValueExpression() && isValid() && isLocalValueSet()) {
// QUESTION should this be done even when a value expression is present?
// ANSWER: I don't see why not.
context.getExternalContext().getRequestMap().put(getName(), getLocalValue());
}
}
// This is called during the real "Render Response" phase
/**
*
* Called specially by {@link UIViewRoot#encodeEnd}, this method simply sets the submitted value to be the return from
* {@link #getStringValue}.
*
*
* @throws IOException when an I/O error occurs.
* @since 2.0
*/
@Override
public void encodeAll(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
// if there is a value expression, update view parameter w/ latest value after render
// QUESTION is it okay that a null string value may be suppressing the view parameter value?
// ANSWER: I'm not sure.
setSubmittedValue(getStringValue(context));
}
/**
*
* If the value of this parameter comes from a ValueExpression
return the value of the expression,
* otherwise, return the local value.
*
*
* @param context the Faces context.
* @return the string value.
* @since 2.0
*/
public String getStringValue(FacesContext context) {
String result = null;
if (hasValueExpression()) {
result = getStringValueFromModel(context);
} else {
result = null != rawValue ? rawValue : (String) getValue();
}
return result;
}
/**
*
* Manually perform standard conversion steps to get a string value from the value expression.
*
*
* @param context the Faces context.
* @return the string value from the model.
* @since 2.0
*/
public String getStringValueFromModel(FacesContext context) throws ConverterException {
ValueExpression ve = getValueExpression("value");
if (ve == null) {
return null;
}
Object currentValue = ve.getValue(context.getELContext());
// If there is a converter attribute, use it to to ask application
// instance for a converter with this identifer.
Converter c = getConverter();
if (c == null) {
// if value is null and no converter attribute is specified, then
// return null (null has meaning for a view parameters; it means remove it).
if (currentValue == null) {
return null;
}
// Do not look for "by-type" converters for Strings
if (currentValue instanceof String) {
return (String) currentValue;
}
// if converter attribute set, try to acquire a converter
// using its class type.
Class converterType = currentValue.getClass();
c = context.getApplication().createConverter(converterType);
// if there is no default converter available for this identifier,
// assume the model type to be String.
if (c == null) {
return currentValue.toString();
}
}
return c.getAsString(context, this, currentValue);
}
/**
*
* Because this class has no {@link Renderer}, leverage the one from the standard HTML_BASIC {@link RenderKit} with
* component-family: jakarta.faces.Input
and renderer-type: jakarta.faces.Text
and call its
* {@link Renderer#getConvertedValue} method.
*
*
* @param submittedValue the submitted value.
* @return the converted value.
* @since 2.0
*/
@Override
protected Object getConvertedValue(FacesContext context, Object submittedValue) throws ConverterException {
return getInputTextRenderer(context).getConvertedValue(context, this, submittedValue);
}
private Renderer getInputTextRenderer(FacesContext context) {
if (null == inputTextRenderer) {
RenderKitFactory rkf = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
RenderKit standard = rkf.getRenderKit(context, RenderKitFactory.HTML_BASIC_RENDER_KIT);
inputTextRenderer = standard.getRenderer("jakarta.faces.Input", "jakarta.faces.Text");
}
assert null != inputTextRenderer;
return inputTextRenderer;
}
// ----------------------------------------------------- Helper Methods
private boolean hasValueExpression() {
return null != getValueExpression("value");
}
/**
*
* Inner class to encapsulate a UIViewParameter
instance so that it may be safely referenced regardless of
* whether or not the current view is the same as the view in which this UIViewParameter
resides.
*
*
* @since 2.0
*/
public static class Reference {
private StateHolderSaver saver;
private int indexInParent;
private String viewIdAtTimeOfConstruction;
/**
*
* Construct a reference to a UIViewParameter
. This constructor cause the {@link StateHolder#saveState}
* method to be called on argument UIViewParameter
.
*
*
* @param context the FacesContext
for this request
* @param param the UIViewParameter.
* @param indexInParent the index of the UIViewParameter
in its parent UIPanel
.
* @param viewIdAtTimeOfConstruction the viewId of the view in which the UIViewParameter
is included. This
* may not be the same as the viewId from the context
argument.
*
* @since 2.0
*/
public Reference(FacesContext context, UIViewParameter param, int indexInParent, String viewIdAtTimeOfConstruction) {
saver = new StateHolderSaver(context, param);
this.indexInParent = indexInParent;
this.viewIdAtTimeOfConstruction = viewIdAtTimeOfConstruction;
}
/**
*
* Return the UIViewParameter
to which this instance refers. If the current viewId is the same as the
* viewId passed to our constructor, use the index passed to the constructor to find the actual
* UIViewParameter
instance and return it. Otherwise, call {@link StateHolder#restoreState} on the saved
* state and return the result.
*
*
* @param context the FacesContext
for this request
* @return the UIViewParameter.
* @since 2.0
*/
public UIViewParameter getUIViewParameter(FacesContext context) {
UIViewParameter result = null;
UIViewRoot root = context.getViewRoot();
// If the view root is the same as when we were constructed...
if (viewIdAtTimeOfConstruction.equals(root.getViewId())) {
// get the actual view parameter from the tree...
UIComponent metadataFacet = root.getFacet(UIViewRoot.METADATA_FACET_NAME);
result = (UIViewParameter) metadataFacet.getChildren().get(indexInParent);
} else {
// otherwise, use the saved one
result = (UIViewParameter) saver.restore(context);
}
return result;
}
}
}