Components redisplaying model values when validation fails

Abraham Duffidall, modified 6 Years ago. New Member Posts: 4 Join Date: 6/19/14 Recent Posts
Hi all,

I am experiencing an issue that can be reproduced by deploying the demo

JSF CDI Applicant Portlet

to liferay-ce-portal-7.1.0-ga1.

As I want to use bean validation in an upcoming project,
I added the context parameter to the web.xml and deployed it to LR for testing...

[code]<context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
</context-param>

Now, if validation fails for a required input value, the (old) model value is redisplayed.

Steps to reproduce:
1) Enter only "first name"
2) Submit
3) Remove value from "first name" again
4) Submit

Now the old value (from model) is redisplayed.
I am pretty sure that this behavior is related to the following issue:

https://github.com/javaee/javaserverfaces-spec/issues/671
https://stackoverflow.com/questions/3933786/jsf-2-bean-validation-validation-failed-empty-values-are-replaced-with-las

But none of the workarounds worked for me.
Any ideas?

Thanks
Abraham
thumbnail
Kyle Joseph Stiemann, modified 6 Years ago. Liferay Master Posts: 760 Join Date: 1/14/13 Recent Posts
Hi Abraham,
As you noted, this is a bug in the JSF spec that has been fixed in JSF 2.3. There's no easy fix without changing Mojarra, but it may be possible to work around this issue using JSF extension points. Perhaps you could add a custom ResponseWriterWrapper that renders the "value" property as an empty string when appropriate:

public final class CustomResponseWriter extends ResponseWriterWrapper{

    private final ResponseWriter wrappedResponseWriter;
    private final LinkedList<editablevalueholder> editableValueHolders;

    public CustomResponseWriter(ResponseWriter responseWriter) {

        this.wrappedResponseWriter = responseWriter;
        this.editableValueHolders = new LinkedList<editablevalueholder>();
    }

    @Override
    public void writeText(Object text, UIComponent uiComponent, String property) throws IOException {

        if (shouldRenderAsEmptyString(property)) {
            super.writeText("", uiComponent, property);
        }
        else {
            super.writeText(text, uiComponent, property);
        }
    }

    @Override
    public void writeText(Object text, String property) throws IOException {

        if (shouldRenderAsEmptyString(property)) {
            super.writeText("", property);
        }
        else {
            super.writeText(text, property);
        }
    }

    @Override
    public void writeURIAttribute(String name, Object value, String property) throws IOException {

        if (shouldRenderAsEmptyString(property)) {
            super.writeURIAttribute(name, "", property);
        }
        else {
            super.writeURIAttribute(name, value, property);
        }
    }

    @Override
    public void writeAttribute(String name, Object value, String property) throws IOException {

        if (shouldRenderAsEmptyString(property)) {
            super.writeAttribute(name, "", property);
        }
        else {
            super.writeAttribute(name, value, property);
        }
    }

    @Override
    public void startElement(String name, UIComponent uiComponent) throws IOException {

        if (uiComponent instanceof EditableValueHolder) {
            editableValueHolders.push((EditableValueHolder) uiComponent);
        }
        else {
            editableValueHolders.push(null);
        }

        super.startElement(name, uiComponent);
    }

    @Override
    public void endElement(String name) throws IOException {

        super.endElement(name);
        editableValueHolders.pop();
    }

    private boolean shouldRenderAsEmptyString(String property) {
        return "value".equalsIgnoreCase(property) &amp;&amp;
                editableValueHolders.peek() != null &amp;&amp;
                editableValueHolders.peek().getSubmittedValue() == null &amp;&amp;
                !editableValueHolders.peek().isValid();
    }

    @Override
    public ResponseWriter getWrapped() {
        return wrappedResponseWriter;
    }
}</editablevalueholder></editablevalueholder>

You'll need to add this ResponseWriter using a custom RenderKit (example custom RenderKit in Liferay Faces) which is registered in your faces-config.xml file:

    <render-kit>
        <render-kit-id>HTML_BASIC</render-kit-id>
        <render-kit-class>your.custom.renderkit.CustomRenderKit</render-kit-class>
    </render-kit>

I haven't tested this code, and there are potential problems with this solution: certain renderers may omit the "value" property so it may not be possible to tell when they are rendering their value, this ResponseWriter may interfere with other ResponseWriters  (and vice-versa) etc. But this is the best workaround I could come up with. Let me know if it works for you.

​​​​​​- Kyle