Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
951 views
in Technique[技术] by (71.8m points)

jsf - includeViewParams=true converts null model value to empty string in query string

Given a <p:selectOneMenu> as follows.

<f:metadata>
    <f:viewParam name="id" value="#{testManagedBean.id}" converter="javax.faces.Long"/>
</f:metadata>

<p:selectOneMenu value="#{localeBean.language}" onchange="changeLanguage();">
    <f:selectItem itemValue="en" itemLabel="English" />
    <f:selectItem itemValue="hi" itemLabel="Hindi" />
</p:selectOneMenu>

<p:remoteCommand action="#{testManagedBean.submitAction}"
                 name="changeLanguage"
                 process="@this"
                 update="@none"/>

The corresponding managed bean :

@ManagedBean
@RequestScoped
public final class TestManagedBean {

    private Long id; //Getter and setter.

    public TestManagedBean() {}

    public String submitAction() {
        return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?faces-redirect=true&includeViewParams=true";
    }
}

The parameter as indicated by <f:viewParam> is optional. A page, for example is accessed using a URL as follows.

https://localhost:8181/Project-war/private_resources/Test.jsf

Since id is an optional parameter, an empty parameter is attached to the URL (when a language is changed from <p:selectOneMenu>), in case it is not supplied as follows.

https://localhost:8181/Project-war/private_resources/Test.jsf?id=

This should not happen. An empty parameter should not be appended, if it is not supplied and the URL should look like the first one.

Is there a way to prevent an empty parameter from being appended to the URL, when it is not passed?


This is only associated with the converter as specified with <f:viewParam> - javax.faces.Long.

If this converter is removed then, parameters are not appended to the URL, in case no parameters are supplied.

Although specifying a converter as demonstrated here is completely unnecessary, I have converters as shown below to convert an id passed though the URL as a query-string parameter to a JPA entity.

@ManagedBean
@RequestScoped
public final class ZoneConverter implements Converter {

    @EJB
    private final SharableBeanLocal sharableService = null;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        try {
            long parsedValue = Long.parseLong(value);

            if (parsedValue <= 0) {
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Message Summary", "Message"));
            }

            ZoneTable entity = sharableService.findZoneById(parsedValue);
            if (entity == null) {
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_WARN, "Message Summary", "Message"));
            }

            return entity;
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Message Summary", "Message"), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return value instanceof ZoneTable ? ((ZoneTable) value).getZoneId().toString() : "";
    }
}

This converter is now required to be specified explicitly with <f:viewParam> as follows.

<f:viewParam name="id" 
             value="#{testManagedBean.id}"
             converter="#{zoneConverter}"
             rendered="#{not empty param.id}"/>

And the associated managed bean needs to be changed as follows.

@ManagedBean
@RequestScoped
public final class TestManagedBean {

    private ZoneTable id;  //Getter and setter.

    public TestManagedBean() {}

    public String submitAction() {
        return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?faces-redirect=true&includeViewParams=true";
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is likely an oversight in Mojarra's default implementation of UIViewParameter#getStringValueFromModel() whose source is for reference copypasted below:

384    public String getStringValueFromModel(FacesContext context)
385        throws ConverterException {
386        ValueExpression ve = getValueExpression("value");
387        if (ve == null) {
388            return null;
389        }
390
391        Object currentValue = ve.getValue(context.getELContext());
392
393        // If there is a converter attribute, use it to to ask application
394        // instance for a converter with this identifer.
395        Converter c = getConverter();
396
397        if (c == null) {
398            // if value is null and no converter attribute is specified, then
399            // return null (null has meaning for a view parameters; it means remove it).
400            if (currentValue == null) {
401                return null;
402            }
403            // Do not look for "by-type" converters for Strings
404            if (currentValue instanceof String) {
405                return (String) currentValue;
406            }
407
408            // if converter attribute set, try to acquire a converter
409            // using its class type.
410
411            Class converterType = currentValue.getClass();
412            c = context.getApplication().createConverter(converterType);
413
414            // if there is no default converter available for this identifier,
415            // assume the model type to be String.
416            if (c == null) {
417                return currentValue.toString();
418            }
419        }
420
421        return c.getAsString(context, this, currentValue);
422    }

This method is called for every UIViewParameter (the UI component behind <f:viewParam>) during building the query string for includeViewParams=true. We see in the source that it calls the converter regardless of whether currentValue is null or not. In other words, even if the model value is null, it still calls the converter with it.

As per the javadoc of Converter#getAsString() converters are by specification required to return a zero-length String if value is null:

getAsString

...

Returns: a zero-length String if value is null, otherwise the result of the conversion

So, converters are actually supposed to never return null on getAsString(). They return an empty string then. In case of view parameters in query string, this is highly undesirable. The difference between an empty string value and a complete absence in query string is really significant.

I've reported it to Mojarra guys as issue 3288. They should then fix this problem as follows:

391        Object currentValue = ve.getValue(context.getELContext());
392
393        if (currentValue == null) {
394            return null;
395        }

In the meanwhile, I've committed a solution to OmniFaces. The <o:viewParam> has been extended with this fix. It's available as per today's 1.8 snapshot.

<f:metadata>
    <o:viewParam name="id" value="#{testManagedBean.id}" converter="javax.faces.Long"/>
</f:metadata>

Update: they decided to not fix it. In any case, there's OmniFaces.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...