Serving resources using Resource PhaseListener

PhaseListener designed to serve resources like css, javascript, images, pdf etc.. from jar file

ResourcePhaseListener.java

All required files can be downloaded here.

package com.gregbugaj.jsfdump.console;
 
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
 
import javax.activation.MimetypesFileTypeMap;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
 
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
 
import com.gregbugaj.jsfdump.util.JarUtil;
import com.gregbugaj.jsfdump.util.XMLUtil;
/**
 * Serve resources from jar file  back to the user by specifying resource name in resource-config.xml
 * 
 * This works with following syntax if faces servlet is *.jsf  /jsfdump/resource/script.js.jsf
 * or  /jsfdump/resource/script.js if faces servlet is *.*
 * 
 * @author devil
 *
 */
@SuppressWarnings("serial")
public class ResourcePhaseListener implements PhaseListener {
	//This is how the resource will be accessed ex /jsfdump/resource/script.js
	private static final String RESOURCE_PREFIX = "/jsfdump/resource/";
	//Location of where the js, img, css etc files reside inside the jar, we could also placed them in META-INF folder
	private static final String RESOURCE_PATH = "/com/gregbugaj/jsfdump/resources/";
 
	private static Map<String, String> resources=new HashMap<String,String>();
	private boolean isLoaded;
 
	@Override
	public void afterPhase(PhaseEvent event) {
		FacesContext facesContext=event.getFacesContext();
		String rootId=facesContext.getViewRoot().getViewId();
		//Clean up key
		String key=rootId.replace(RESOURCE_PREFIX, "");
		key=key.replace(".xhtml", "");
		key=key.replace(".jsf", "");
		if(!rootId.startsWith(RESOURCE_PREFIX)){
			return; 
		}
 
		//Lazy loading
		if(!isLoaded){
			isLoaded=initResources();
		}	
		String resourceName=resources.get(key);
		//Location of resources inside the jar file
		String fileName=RESOURCE_PATH+resourceName;
		InputStream resourceStream=JarUtil.getStreamFromJar(fileName);
		HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
		response.setCharacterEncoding("UTF-8");
		ServletOutputStream sos = null;
		try {
			sos = response.getOutputStream();
			if(resourceStream!=null){
				response.setStatus(HttpServletResponse.SC_OK);
				//Resolve mime type, required that we have activation.jar loaded
				//Additional mime types can be defined in /META-INF/mimes.types  {@link http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/activation/MimetypesFileTypeMap.html }
				String contentType = new MimetypesFileTypeMap().getContentType(fileName);
				response.setContentType(contentType);
				byte[] buffer= new byte[1024];
				for (int bytesRead = 0; (bytesRead = resourceStream.read(buffer, 0, buffer.length)) > 0;)
				{
					sos.write(buffer, 0, bytesRead);
				}
			}else{
				//Resource not found
				response.setStatus(HttpServletResponse.SC_NOT_FOUND);
				response.setContentType("text/html");
			}
			sos.flush();
			sos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		facesContext.responseComplete();
	}
 
	@Override
	public void beforePhase(PhaseEvent event) {
		//Do nothing
	}
 
	/**
	 * Load resource mapping from resource-config.xml
	 * @return true if we successfully loded resource
	 */
	private boolean initResources() {
		boolean retVal=true;
		InputStream stream=null;	
		try {
			stream=JarUtil.getStreamFromJar("/META-INF/resource-config.xml");
			Document document=XMLUtil.getXmlDocument(stream);
			NodeList resourceNodes=XMLUtil.extract("/resources/resource", document);
			for(int i=0;i<resourceNodes.getLength();i++){
				Node node=resourceNodes.item(i);
				String name=XMLUtil.attributeText(node, "name");
				String src=XMLUtil.attributeText(node, "src");
				//No forward slash in the resource name
				//ex js/scriptname.js not /js/script.js
				if(src.startsWith("/")){
					src=src.replaceFirst("/", "");
				}
				resources.put(name, src);
			}
		}  catch (IOException e) {
			retVal=false;
			e.printStackTrace();
		}
		catch (Exception e) {
			retVal=false;
			e.printStackTrace();
		}
		return retVal;
	}
 
	@Override
	public PhaseId getPhaseId() {
		return PhaseId.RESTORE_VIEW;
	}
}

resource-config.xml

This is where we define resources that we will be serving. One reasons we use xml configuration is to prevent security breaches.

  <?xml version="1.0" encoding="UTF-8"?>
  <resources>
	<resource name="proto.js" src="js/proto.js" />
	<resource name="logo.png" src="images/a.png" />
	<resource name="main.css" src="css/hello.css" />
  </resources>

Two common ways to access the resources are
If faces servlet is *.jsf /jsfdump/resource/script.js.jsf
Of /jsfdump/resource/script.js if faces servlet is *.*

META-INF/mime.types

This is where we add additional mime types, requires that we have activation.jar loaded More info

This file is not required but it helps, for example png files resolve to application/octet-stream rather than image/x-png

Examples

1 thought on “Serving resources using Resource PhaseListener”

Leave a Comment

Your email address will not be published. Required fields are marked *