Search code examples
jsf-2faceletsjsf-2.2dynamic-content

Include dynamic content containing JSF tags/components from stream


I am working on an application where I would like to include dynamic XHTML content from a stream. To handle this I wrote a taghandler extension which dumps the dynamic XHTML content to output component as

UIOutput htmlChild = (UIOutput) ctx.getFacesContext().getApplication().createComponent(UIOutput.COMPONENT_TYPE);
htmlChild.setValue(new String(outputStream.toByteArray(), "utf-8"));

This works fine for XHTML content which has no JSF tags. If I have JSF tags in my dynamic XHTML content like <h:inputText value="#{bean.item}"/>, then they're printed as plain text. I want them to render as input fields. How can I achieve this?


Solution

  • Essentially, you should be using an <ui:include> in combination with a custom ResourceHandler which is able to return the resource in flavor of an URL. So when having an OutputStream, you should really be writing it to a (temp) file so that you can get an URL out of it.

    E.g.

    <ui:include src="/dynamic.xhtml" />
    

    with

    public class DynamicResourceHandler extends ResourceHandlerWrapper {
    
        private ResourceHandler wrapped;
    
        public DynamicResourceHandler(ResourceHandler wrapped) {
            this.wrapped = wrapped;
        }
    
        @Override
        public ViewResource createViewResource(FacesContext context, String resourceName) {
            if (resourceName.equals("/dynamic.xhtml")) {
                try {
                    File file = File.createTempFile("dynamic-", ".xhtml");
    
                    try (Writer writer = new FileWriter(file)) {
                        writer
                            .append("<ui:composition")
                            .append(" xmlns:ui='http://java.sun.com/jsf/facelets'")
                            .append(" xmlns:h='http://java.sun.com/jsf/html'")
                            .append(">")
                            .append("<p>Hello from a dynamic include!</p>")
                            .append("<p>The below should render as a real input field:</p>")
                            .append("<p><h:inputText /></p>")
                            .append("</ui:composition>");
                    }
    
                    final URL url = file.toURI().toURL();
                    return new ViewResource(){
                        @Override
                        public URL getURL() {
                            return url;
                        }
                    };
                }
                catch (IOException e) {
                    throw new FacesException(e);
                }
            }
    
            return super.createViewResource(context, resourceName);
        }
    
        @Override
        public ResourceHandler getWrapped() {
            return wrapped;
        }
    
    }
    

    (warning: basic kickoff example! this creates a new temp file on every request, a reuse/cache system should be invented on your own)

    which is registered in faces-config.xml as follows

    <application>
        <resource-handler>com.example.DynamicResourceHandler</resource-handler>
    </application>
    

    Note: all of above is JSF 2.2 targeted. For JSF 2.0/2.1 users stumbling upon this answer, you should use ResourceResolver instead for which an example is available in this answer: Obtaining Facelets templates/files from an external filesystem or database. Important note: ResourceResolver is deprecated in JSF 2.2 in favor of ResourceHandler#createViewResource().