includeViewParams=true преобразует пустое значение модели в пустую строку в строке запроса

Учитывая <p:selectOneMenu> следующее.

<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"/>

Соответствующий управляемый компонент:

@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";
    }
}

Параметр как указано <f:viewParam> не является обязательным. Например, доступ к странице осуществляется с помощью URL-адреса следующим образом.

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

поскольку id является необязательным параметром, к URL-адресу прикрепляется пустой параметр (при изменении языка с <p:selectOneMenu>), если он не поставляется следующим образом.

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

Этого не должно быть. Пустой параметр не должен добавляться, если он не указан, а URL должен выглядеть как первый.

Есть ли способ предотвратить добавление пустого параметра к URL, если он не был передан?


Это связано только с конвертером, как указано в <f:viewParam> - javax.faces.Long ,

Если этот преобразователь удаляется, параметры не добавляются к URL-адресу, если параметры не указаны.

Хотя указывать преобразователь, как показано здесь, совершенно не нужно, у меня есть преобразователи, как показано ниже для преобразования id хотя URL-адрес передан в качестве параметра строки запроса в объект JPA.

@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() : "";
    }
}

Этот конвертер теперь должен быть указан явно с <f:viewParam> следующее.

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

И связанный управляемый компонент должен быть изменен следующим образом.

@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 ответ

Решение

Это, вероятно, упущение в реализации Мохарры по умолчанию UIViewParameter#getStringValueFromModel() чей источник для справки скопирован ниже:

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    }

Этот метод вызывается для каждого UIViewParameter (компонент пользовательского интерфейса позади <f:viewParam>) во время построения строки запроса для includeViewParams=true, Мы видим в источнике, что он вызывает конвертер независимо от того, currentValue является null или нет. Другими словами, даже если значение модели null, он по-прежнему вызывает конвертер с ним.

В соответствии с Javadoc Converter#getAsString() преобразователи по спецификации должны возвращать строку нулевой длины, если значение равно null:

getAsString

...

Возвращает: Строка нулевой длины, если значение null в противном случае результат преобразования

Таким образом, конвертеры фактически никогда не возвращаются null на getAsString(), Затем они возвращают пустую строку. В случае просмотра параметров в строке запроса это крайне нежелательно. Разница между значением пустой строки и полным отсутствием в строке запроса действительно значительна.

Я сообщил об этом парням из Моджарры как вопрос 3288. Затем они должны решить эту проблему следующим образом:

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

В то же время, я совершил решение для OmniFaces. <o:viewParam> был расширен с этим исправлением. Это доступно согласно сегодняшнему снимку 1.8.

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

Обновление: они решили не исправить это. В любом случае, есть OmniFaces.

Другие вопросы по тегам