user3027786
user3027786

Reputation: 185

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?

Upvotes: 4

Views: 5594

Answers (2)

cocorossello
cocorossello

Reputation: 1357

My solution for JSF 2.2 and custom URLStream Handler

public class DatabaseResourceHandlerWrapper extends ResourceHandlerWrapper {

    private ResourceHandler wrapped;

    @Inject
    UserSessionBean userBeean;

    public DatabaseResourceHandlerWrapper(ResourceHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public Resource createResource(String resourceName, String libraryName) {
        return super.createResource(resourceName, libraryName); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public ViewResource createViewResource(FacesContext context, String resourceName) {
        if (resourceName.startsWith("/dynamic.xhtml?")) {
            try {
                String query = resourceName.substring("/dynamic.xhtml?".length());
                Map<String, String> params = splitQuery(query);
                //do some query to get content
                String content = "<ui:composition"
                        + " xmlns='http://www.w3.org/1999/xhtml' xmlns:ui='http://java.sun.com/jsf/facelets'"
                        + " xmlns:h='http://java.sun.com/jsf/html'> MY CONTENT"
                        + "</ui:composition>";

                final URL url = new URL(null, "string://helloworld", new MyCustomHandler(content));
                return new ViewResource() {
                    @Override
                    public URL getURL() {
                        return url;
                    }
                };
            } catch (IOException e) {
                throw new FacesException(e);
            }
        }

        return super.createViewResource(context, resourceName);
    }

    public static Map<String, String> splitQuery(String query) throws UnsupportedEncodingException {
        Map<String, String> params = new LinkedHashMap<>();
        String[] pairs = query.split("&");
        for (String pair : pairs) {
            int idx = pair.indexOf("=");
            params.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
        }
        return params;
    }

    @Override
    public ResourceHandler getWrapped() {
        return wrapped;
    }

    static class MyCustomHandler extends URLStreamHandler {

        private String content;

        public MyCustomHandler(String content) {
            this.content = content;
        }

        @Override
        protected URLConnection openConnection(URL u) throws IOException {
            return new UserURLConnection(u, content);
        }

        private static class UserURLConnection extends URLConnection {

            private String content;

            public UserURLConnection(URL url, String content) {
                super(url);
                this.content = content;
            }

            @Override
            public void connect() throws IOException {
            }

            @Override
            public InputStream getInputStream() throws IOException {
                return new ByteArrayInputStream(content.getBytes("UTF-8"));
            }
        }

    }

}

Upvotes: 0

BalusC
BalusC

Reputation: 1109262

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().

Upvotes: 5

Related Questions