18

I want to embed a link in a JSF message, is this possible?

When I try it, the rendered html of the h:messages tag escapes the html characters. I tried setting the escape attribute of the h:messages tag to false, but that didn't help.

Brian Leathem
  • 4,609
  • 1
  • 24
  • 44

2 Answers2

23

Unfortunately, this is not possible in the standard JSF implementation. The component and the renderer doesn't officially support this attribute. You can however homegrow a renderer which handles this.

Since this is a pretty common requirement/wish, I thought to take a look what's all possible.

First some background information: JSF by default uses ResponseWriter#writeText() to write the tag body, which escapes HTML by default. We'd like to let it use ResponseWriter#write() instead like as with <h:outputText escape="false" />. We'd like to extend the MessagesRenderer of the standard JSF implementation and override the encodeEnd() method accordingly. But since the MessagesRenderer#encodeEnd() contains pretty a lot of code (~180 lines) which we prefer not to copypaste to just change one or two lines after all, I found it better to replace the ResponseWriter with a custom implementation with help of ResponseWriterWrapper wherein the writeText() is been overriden to handle the escaping.

So, I ended up with this:

package com.example;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import javax.faces.render.FacesRenderer;

import com.sun.faces.renderkit.html_basic.MessagesRenderer;

@FacesRenderer(componentFamily="javax.faces.Messages", rendererType="javax.faces.Messages")
public class EscapableMessagesRenderer extends MessagesRenderer {

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        final ResponseWriter originalResponseWriter = context.getResponseWriter();

        try {
            context.setResponseWriter(new ResponseWriterWrapper() {

                @Override
                public ResponseWriter getWrapped() {
                    return originalResponseWriter;
                }

                @Override
                public void writeText(Object text, UIComponent component, String property) throws IOException {
                    String string = String.valueOf(text);
                    String escape = (String) component.getAttributes().get("escape");
                    if (escape != null && !Boolean.valueOf(escape)) {
                        super.write(string);
                    } else {
                        super.writeText(string, component, property);
                    }
                }
            });

            super.encodeEnd(context, component); // Now, render it!
        } finally {
            context.setResponseWriter(originalResponseWriter); // Restore original writer.
        }
    }
}

In spite of the @FacesRenderer annotation, it get overriden by the default MessagesRenderer implementation. I suspect here a bug, so I reported issue 1748. To get it to work anyway, we have to fall back to the faces-config.xml:

<render-kit>
    <renderer>
        <component-family>javax.faces.Messages</component-family>
        <renderer-type>javax.faces.Messages</renderer-type>
        <renderer-class>com.example.EscapableMessagesRenderer</renderer-class>
    </renderer>
</render-kit>

Then, to trigger it, just do:

<h:messages escape="false" />

And it works! :)


Note: the above affects <h:messages> only. To do the same for <h:message>, just do the same, but replace anywhere "Messages" by "Message" (component family, renderer type and classnames).

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks BalusC, I arrived at the same solution today as well (and filed the same bug https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=1750) I used the delegator pattern for my response writer, but I think I prefer what you did with the wrapper. Thanks! – Brian Leathem Jul 29 '10 at 03:57
  • A new link for the reported issue: http://java.net/jira/browse/JAVASERVERFACES-1750 – Brian Leathem Dec 12 '11 at 13:57
  • This code does not works wits the MyFaces JSF 2 implementation right? – John John Pichler Sep 06 '14 at 00:06
  • 2
    @Mr.Pichler: You'd indeed extend its own one. These days there's `` as alternative. http://showcase.omnifaces.org/components/messages – BalusC Sep 06 '14 at 06:53
  • With this solution, we are replacing the `ResponseWritter` globally. So will there be a case that right after you set the new `ResponseWriter` some user will able to see the result that is not escaped where it should? – Will Feb 03 '15 at 19:14
  • I guess my question is this: before we set the `ResponseWritter` to original, will there be a case that other classes call getResponseWriter and get the "modified one"? – Will Feb 03 '15 at 19:31
  • 1
    Only component's original `encodeEnd()` (or error pages, I've edited the answer to fix that). – BalusC Feb 03 '15 at 19:37
  • @BalusC I get error `Attribute escape according invalid for tag messages to TLD` at the line ``. Do you know what the problem could be? – user1766169 Oct 05 '16 at 09:04
  • @user1766169: It's the IDE who's pretending to be smarter than it is. Just ignore it. It runs fine, right? – BalusC Oct 05 '16 at 09:05
  • 1
    @user1766169: perhaps you're unexpectedly using legacy JSP instead of its in 2009 introduced successor Facelets. All JSF 2.x answers here are targeted on Facelets as JSP has been deprecated since then. – BalusC Oct 05 '16 at 09:07
  • @BalusC Ok. Thanks! Thats probebly the problem. – user1766169 Oct 05 '16 at 09:13
2

The escape="false" attributed you need is provided by the OmniFaces <o:messages> component. The OmniFaces utility library is available for JSF 2.

I posted this solution mentioned by @BalusC's comment as an answer since this is the most straightforward solution.

Adam
  • 1,926
  • 23
  • 21