问题
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";
}
}
回答1:
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.
来源:https://stackoverflow.com/questions/23725629/includeviewparams-true-converts-null-model-value-to-empty-string-in-query-string