Coming Up for Air

Reintroducing the JSFTemplating FileStreamer

Tuesday, March 18, 2008 |

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.// more

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
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:

1
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

1
2
3
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):

1
2
3
4
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:

1
/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.

Search

    Quotes

    Sample quote

    Quote source

    About

    My name is Jason Lee. I am a software developer living in the middle of Oklahoma. I’ve been a professional developer since 1997, using a variety of languages, including Java, Javascript, PHP, Python, Delphi, and even a bit of C#. I currently work for Red Hat on the WildFly/EAP team, where, among other things, I maintain integrations for some MicroProfile specs, OpenTelemetry, Micrometer, Jakarta Faces, and Bean Validation. (Full resume here. LinkedIn profile)

    I am the president of the Oklahoma City JUG, and an occasional speaker at the JUG and a variety of technical conferences.

    On the personal side, I’m active in my church, and enjoy bass guitar, running, fishing, and a variety of martial arts. I’m also married to a beautiful woman, and have two boys, who, thankfully, look like their mother.

    My Links

    Publications