Coming Up for Air

Reintroducing the JSFTemplating FileStreamer

In a blog entry last year, Ken Paulsen gave a short introduction to the FileStreamer utility in JSFTemplating. Since Scales is now using JSFTemplating to make the component authoring process easier, I have been able to use this facility, allowing me to deprecate some custom code. In the process of making the migration, I’ve made changes to JSFTemplating that will be of benefit to all. In this entry, I’d like to highlight those changes, and show you how you, too, can use this great facility.

Prior to my changes, JSFTemplating offered two ways to use the FileStreamer. One way is as a Servlet. This approach is intended for non-JSF users, though JSF users can certainly use it as well — many other frameworks have "resource servlets" as well. Another method is a ViewHandler-based approached, the approach Ken uses in his blog entry, which can only be used if the JSFTemplating ViewHandler is in play. Since I want to avoid as much external configuration as possible, and I don’t want to dictate a JSFTemplating-only approach to JSF app authoring (not that there’s anything wrong with that), I needed another method, so I created a PhaseListener-based approach: FileStreamerPhaseListener. Using a PhaseListener, we are able to process the request from inside the JSF lifecycle, giving us access to application state, etc. For my purposes (namely, modifying the FileDownload component to use the FileStreamer), that is very important. The FileDownload component currently works by stuffing a reference to itself in the HttpSession, which is then retrieved when the resource request is made, a process made difficult, though not impossible in a Servlet-based approach.

While the PhaseListener solves how to handle the incoming request, it doesn’t solve the problem of getting the data from the component. This is where FileStreamer really shines. As Ken notes in his blog, FileStreamer supports the idea of a ContentSource, which is a class that handles getting the content of a source (if you can imagine that) from an arbitrary location. His examples show getting the resource from the file-system as well as a remote server, all through the same API. Leveraging that capability, I added to Scales the FileDownloadContentSource:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class FileDownloadContentSource implements ContentSource {
    public final static String CONTENT_SOURCE_ID = "fileDownloadCs";
    public String getId() {
        return CONTENT_SOURCE_ID;
    }
    public InputStream getInputStream(Context context) throws IOException {
        InputStream in = (InputStream) context.getAttribute("inputStream");
        if (in != null) {
            return in;
        }
        String componentId = (String) context.getAttribute(Context.FILE_PATH);
        FacesContext fc = FacesContext.getCurrentInstance();
        FileDownload comp = (FileDownload) fc.getExternalContext()
            .getSessionMap().get("HtmlDownload-" + componentId);
        if (comp != null) {
            Object value = comp.getData();
            if (value != null) {
                String mimeType = comp.getMimeType();
                context.setAttribute(Context.CONTENT_TYPE, mimeType);
                if (FileDownload.METHOD_DOWNLOAD.equals(comp.getMethod())) {
                    context.setAttribute(Context.CONTENT_DISPOSITION, comp.getMethod());
                } else {
                    context.setAttribute(Context.CONTENT_DISPOSITION, "inline");
                }
                byte[] data = null;
                if (value instanceof byte[]) {
                    in = new ByteArrayInputStream ((byte[]) value);
                } else if (value instanceof ByteArrayOutputStream) {
                    in = new ByteArrayInputStream (((ByteArrayOutputStream) value)
                        .toByteArray()); // EEEK!
                } else if (value instanceof InputStream) {
                    in = (InputStream)value;
                } else {
                    throw new FacesException(
                        "HtmlDownload: an unsupported data type was found:  " +
                        value.getClass().getName());
                }
            }
        }
        context.setAttribute("inputStream", in);
        return in;
    }
    public void cleanUp(Context context) {
        InputStream is = (InputStream) context.getAttribute("inputStream");
        // Close the InputStream
        if (is != null) {
            try {
                is.close();
            } catch (Exception ex) {
                // Ignore...
            }
        }
        context.removeAttribute("inputStream");
    }
    public long getLastModified(Context context) {
        return -1; // We don't/can't know so make it redownload every time
    }
}-----

The really interesting part is in `getInputStream()`.  We query the request for the component ID, then look it up in the session.  If it is found, we then query the data returned to determine its type, then return an `InputStream` which the `PhaseListener` will use to stream the data to the client.  I really like this `ContentSource` approach.  It's very simple and elegant, and, as you can see, very easy to implement.  We still have two problems remaining, though:  how do I tell JSFTemplating about this new `ContentSource`, and how do I generate a URL to access the data?

In Ken's example, he used an `init-param` in `web.xml` to register his `ContentSource`s.  Again, I wasn't too comfortable with that, as it requires more external configuration.  I want people to be able to drop this component on a page and not have to worry about configuration (though there's still the `PhaseListener` registration, which I'm working on).  After some discussion with Ken, I developed, with much help from him, a mechanism by which JSFTemplating will register `ContentSources` based on a properties file.  This file, `META-INF/jsftempalting/fileStreamer.properties`, looks something like this:

[source,xml,linenums]

contentSources=com.sun.mojarra.scales.util.FileDownloadContentSource

The `FileStreamer` constructor finds any of these files that might be in any JAR in the web application's lib directory (`WEB-INF/lib`) and registers the `ContentSource`s specified in the comma-delimited list (i.e., `contentSources=org.example.contentSources.ExampleContentSource, org.example.contentSources.ProxyContentSource`).  For the performance sensitive, this happens only once during a web app's lifecycle, as the `FileStreamer` reference is a singleton.  At any rate, for the curious, here's how the files are found, a new public and static method on `FileUtil` called `getJarResource()` that can be used by any application:

[source,java,linenums]

public static List<Tuple> getJarResources(FacesContext facesContext, String resourcePath, String…​ searchPaths) throws IOException { if (searchPaths == null) { // Use default jar search path…​ searchPaths = DEFAULT_SEARCH_PATH; } List<Tuple> entries = new ArrayList<Tuple>(); ExternalContext ec = facesContext.getExternalContext(); for (String searchPath : searchPaths) { Set<String> paths = ec.getResourcePaths(searchPath); for (String path : paths) { if ("jar".equalsIgnoreCase(path.substring(path.length() - 3))) { JarFile jarFile = new JarFile(new File(ec.getResource(path).getFile())); JarEntry jarEntry = jarFile.getJarEntry(resourcePath); if (jarEntry != null) { entries.add(new Tuple(jarFile, jarEntry)); } } } } return entries; }

The return from this method is tuple containing the `JarFile` and `JarEntry` for the properties file, which `FileStreamer` then loops through and processes.
The only remaining issue, then, is URL creation.  The new `FileStreamerPhaseListener` has a utility method to handle that for us:

[source,java,linenums]

public static String createResourceUrl(FacesContext context, String contentSourceId, String path)

If `contentSourceId` is null, the default `ContentSource` is used, which is JSFTemplating's `ResourceContentSource`.  In Scales' case, though, we want to use our custom `ContentSource`, so our call to this method looks like this (from `FileDownloadRenderer`):

[source,java,linenums]

protected String generateUri(FacesContext context, FileDownload comp) { return FileStreamerPhaseListener.createResourceUrl(context, FileDownloadContentSource.CONTENT_SOURCE_ID, comp.getClientId(context)); }

That results in a URL like this:

[source,html,linenums]

/mojarra-scales-demo-facelets/jsft_resource.jsf?contentSourceId=fileDownloadCs&filename=j_id5

The browser can request that URL, and the `FileStreamerPhaseListener` will recognize that it should process it, determine and acquire the `ContentSource`, then query that for the data, setting the mime type, etc., as it streams the data to the client.  I am also now using this exact approach, though with the default `ContentSource`, to serve up from the Scales jar file the Javascript and CSS needed for the components, demonstrating clearly, I think, the power and flexibility of this great facility.

Quotes

Sample quote

Quote source