Замените нулевые или пустые строки указанным значением при выводе с использованием конвертера JSF

Ниже представлен конвертер, в основном предназначенный для обрезания пробелов в верхнем и заднем пробелах и замены более одного пробела между словами в предложении или тексте с одним пространством. Преобразователь теперь модифицируется для замены null или пустых строк с помощью "Недоступно" (при необходимости может быть локализовано динамически).

@FacesConverter(forClass = String.class)
public class StringTrimmer implements Converter {

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        return Boolean.TRUE.equals(component.getAttributes().get("skipConverter")) ? value : value == null ? null : value.trim().replaceAll("\\s+", " ");
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return Boolean.TRUE.equals(component.getAttributes().get("skipConverter")) ? value == null ? null : value.toString() : value == null || ((String) value).trim().length() == 0 ? "Not available" : ((String) value).trim().replaceAll("\\s+", " ");
    }
}

Поскольку преобразователи не вызывают, когда значение модели null на основе предыдущего вопроса, com.sun.faces.renderkit.html_basic.TextRenderer было расширено с целью вызова конвертеров, когда значение свойства в связанной модели равно null.

public final class HtmlBasicRenderer extends TextRenderer {

    @Override
    public String getCurrentValue(FacesContext context, UIComponent component) {

        if (component instanceof UIInput) {
            Object submittedValue = ((UIInput) component).getSubmittedValue();

            if (submittedValue != null) {
                return submittedValue.toString();
            }
        }

        return getFormattedValue(context, component, getValue(component));
    }
}

следующий условный тест был удален, поэтому метод getFormattedValue() может быть вызван, даже если встречается значение null.

Object currentObj = getValue(component);

if (currentObj != null) {
    currentValue = getFormattedValue(context, component, currentObj);
}

Это зарегистрировано в faces-config.xml следующим образом.

<render-kit>
    <renderer>
        <component-family>javax.faces.Output</component-family>
        <renderer-type>javax.faces.Text</renderer-type>
        <renderer-class>com.example.renderer.HtmlBasicRenderer</renderer-class>
    </renderer>
</render-kit>

Преобразователь StringTrimmer все еще не вызывается (getAsString()), когда значение свойства в целевой модели возвращает null.

Включение условного теста в EL, как #{empty bean.value ? 'Not available' : bean.value} повсюду в приложении, является безумием. Любое предложение?

Это Mojarra 2.2.12.


Обновление:

Конвертируемые значения доступны, когда один из операторов return внутри метода getFormattedValue() возвращает пустую строку "", когда currentValue is null, изменяется для возврата преобразованного значения при вызове

javax.​faces.​convert.​Converter.getAsString(FacesContext context, UIComponent component, Object value)

внутри этого метода getFormattedValue().

Таким образом, после,

if(currentValue == null) {
    return "";
}

необходимо заменить на

if (currentValue == null) {
    converter = Util.getConverterForClass("".getClass(), context);
    return converter == null ? "" : converter.getAsString(context, component, currentValue);
}

(Требует предложений).

Ответ 1

Во-первых, a Converter никогда не намерен применять "значение по умолчанию".

Независимо от вопроса, что бы вы ни делали в getAsString(), вы должны гарантировать, что полученный String может быть преобразован обратно в исходный Object, когда вы передадите его обратно через getAsObject(). Ваш конвертер не делает этого. Несмотря на то, что вы вряд ли когда-либо использовали его, технически конвертер необходимо изменить, чтобы преобразовать точную строку "Not available" обратно в null. Другими словами, ваш конвертер должен быть сконструирован таким образом, чтобы getAsObject() и getAsString() могли успешно передавать друг другу результат в бесконечном цикле и каждый раз возвращать тот же результат.

Что касается конкретного функционального требования для обеспечения значения по умолчанию, а не в Converter, вы должны сделать это либо в модели, либо в представлении, в зависимости от того, откуда происходит фактическое значение по умолчанию. В вашем конкретном случае вы просто хотите иметь текст/метку заполнителя по умолчанию в пользовательском интерфейсе, когда нет такого значения. Это относится к мнению.

Постановка условного теста в EL, как #{empty bean.value ? 'Not available' : bean.value} повсюду в приложении, является безумием.

Я полностью согласен с тем, что это безумие, если вы уже на полпути разрабатываете приложение и имеете сотни из них во всех местах. Однако это не безумие, если вы уже учли это с самого начала. Если вы этого не сделали, тогда, хорошо, выучите урок, кусайте пулю и регулярно исправляйте код соответственно. Достойные IDE имеют regex based find & replace, которые могут помочь в этом. Все были там и делали такое безумие, даже я. Чтобы уменьшить шаблон, оберните его в EL-функцию или файл тегов.

Что касается конкретной проблемы Converter, не вызываемой, когда значение модели null, которое я лично полностью согласен с непредвиденным поведением, это когда-либо сообщалось как Моджарра номер 630. Позднее он закрыт как WONTFIX, потому что он вызвал сбои в тестовом случае и после этого лучше будет сообщаться как спецификация спецификации JSF. Это было сделано как спецификация JSF спецификации 475 (во время JSF 1.2 уже). Я проверил MyFaces 2.2.9 на этом, и он также не запускает конвертер и, таким образом, предоставляет ту же проблему.

Однако техническая проблема понятна. Значение null не имеет разумного getClass(), поэтому конвертер не может быть просмотрен классом значений таким образом. Он работает только тогда, когда конвертер явно зарегистрирован на компоненте.

<h:outputText value="#{bean.potentiallyNullValue}" converter="stringTrimmer" />

Он должен работать, когда значение представляет собой пустую строку "", которая относительно тривиальна для реализации в getValue() пользовательского рендеринга, расширяющего Mojarra TextRenderer.

@Override
protected Object getValue(UIComponent component) {
    Object value = super.getValue(component);
    return (value != null) ? value : "";
}

Однако, когда я попробовал это сам, он все равно не удался. Что получается, конвертер по-классу полностью пропускается, когда значение является экземпляром String. Он работает только тогда, когда конвертер явно зарегистрирован на компоненте. Скорее всего, это был надзор во время реализации спецификации 131 для JSF 1.2 (до этой версии конвертеры для String.class вообще не поддерживались, и это проблема только исправлена ​​для декодирования, а не для кодирования).

Это может быть переопределено в том же настраиваемом рендерере (с вышеперечисленным getValue() override) с нижним переопределением getFormattedValue(), при котором конвертер явно просматривается.

@Override
protected String getFormattedValue(FacesContext context, UIComponent component, Object currentValue) throws ConverterException {
    Converter converter = ((UIOutput) component).getConverter();

    if (converter == null) {
        converter = context.getApplication().createConverter(currentValue.getClass());
    }

    return super.getFormattedValue(context, component, "".equals(currentValue) ? null : currentValue, converter);
}

Обратите внимание, что вам не нужно проверять UIInput, поскольку вы явно зарегистрировали свой собственный рендерер только в javax.faces.Output/javax.faces.Text семействе компонентов/типе (т.е. он будет работать только на компонентах <h:outputText>).

Тем не менее, лучшим решением по-прежнему является создание EL-функции или файла тегов для этого.

<h:outputText value="#{empty bean.value ? 'Not available' : bean.value}" />
<h:outputText value="#{of:coalesce(bean.value, 'Not Available')}" />
<h:outputText value="#{of:coalesce(bean.value, i18n.na)}" />
<h:outputText value="#{your:value(bean.value)}" />
<your:text value="#{bean.value}" />