JSF 2.0 Fileupload

In this post I'll show you how to create a simple JSF component to upload files to the server. Normally I would use Richfaces 4 or Primefaces (which have better components and is easier to use). However, I had many problems using this components, So I decided to implement my own component

My development enviroment is something like that:

    - JSDK 6
    - Maven 3
    - Eclipse Helios + (plugin de maven)
    - JBoss AS 6
    - Seam 3
    - Apache commons fileupload

IMPORTANT: I'm going to asume that you have a previous knowledge developing JEE5 applications

Define a Filter to get all the Multipart requests (based on the book click="this.target='_blank'">Core Java Server Faces).
 

package com.carperea.web.jsf;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@WebFilter(filterName = "FileUploadFilter", urlPatterns = "/*") 
public class FileUploadFilter implements Filter {

	private static final String SIZE_THRESHOLD_PARAM = "com.carperea.web.upload.sizeThreshold";
	private static final String TEMP_DIRECTORY_PARAM = "com.carperea.web.upload.tempDirectory";

	private int sizeThreshold = 1024;
	private String tempDirectory;

	@Override
	public void init(FilterConfig config) throws ServletException {
		tempDirectory = config.getServletContext().getInitParameter(TEMP_DIRECTORY_PARAM);
		try {
			String paramValue = config.getServletContext().getInitParameter(SIZE_THRESHOLD_PARAM);
			if (paramValue != null) {
				sizeThreshold = Integer.parseInt(paramValue);
			}
		} catch (NumberFormatException e) {
			ServletException servletEx = new ServletException();
			servletEx.initCause(e);
			throw servletEx;
		}
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {

		if (!(request instanceof HttpServletRequest)) {
			chain.doFilter(request, response);
			return;
		}
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpRequest);
		if (!isMultipartContent) {
			chain.doFilter(request, response);
			return;
		}
		// Create a factory for disk-based file items
		DiskFileItemFactory factory = new DiskFileItemFactory();
		// Set factory constraints
		factory.setSizeThreshold(sizeThreshold);
		if (tempDirectory != null && !"".equalsIgnoreCase(tempDirectory)) {
			factory.setRepository(new File(tempDirectory));
		}
		// Create a new file upload handler
		ServletFileUpload upload = new ServletFileUpload(factory);
		try {
			List items = upload.parseRequest(httpRequest);
			final Map<String, String[]> map = new HashMap<String, String[]>();
			Iterator iter = items.iterator();
			while (iter.hasNext()) {
				FileItem item = (FileItem) iter.next();
				String str = item.getString();
				if (item.isFormField()) {
					map.put(item.getFieldName(), new String[] { str });
				} else {
					httpRequest.setAttribute(item.getFieldName(), item);
				}
			}

			chain.doFilter(new HttpServletRequestWrapper(httpRequest) {
				@SuppressWarnings("unchecked")
				public Map getParameterMap() {
					return map;
				}

				public String[] getParameterValues(String name) {
					Map map = getParameterMap();
					return (String[]) map.get(name);
				}

				public String getParameter(String name) {
					String[] params = getParameterValues(name);
					if (params == null)
						return null;
					return params[0];
				}

				@SuppressWarnings("unchecked")
				public Enumeration getParameterNames() {
					Map map = getParameterMap();
					return Collections.enumeration(map.keySet());
				}
			}, response);

		} catch (FileUploadException e) {
			ServletException servletEx = new ServletException();
			servletEx.initCause(e);
			throw servletEx;
		}

	}

	@Override
	public void destroy() {
	}

}

Define the Renderer

package com.carperea.web.jsf;

import java.io.IOException;

import javax.el.Valueexpression;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;

@FacesRenderer(componentFamily = FileUpload.COMPONENT_FAMILY, rendererType = FileUpload.RENDERER_TYPE)
public class FileUploadRenderer extends Renderer {

	@Override
	public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
		if (!component.isRendered()) {
			return;
		}
		ResponseWriter writer = context.getResponseWriter();
		String clientId = component.getClientId(context);
		writer.startElement("input", component);
		writer.writeAttribute("type", "file", "type");
		writer.writeAttribute("name", clientId, "clientId");
		String styleClass = (String) component.getAttributes().get("styleClass");
		if (styleClass != null) {
			writer.writeAttribute("class", styleClass, "styleClass");
		}
		writer.endElement("input");
		writer.flush();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void decode(FacesContext context, UIComponent component) {
		FileUpload fileUpload = (FileUpload) component;
		ExternalContext external = context.getExternalContext();
		HttpServletRequest request = (HttpServletRequest) external.getRequest();
		String clientId = fileUpload.getClientId(context);
		FileItem item = (FileItem) request.getAttribute(clientId);
		Valueexpression ve = fileUpload.getValueexpression("value");
		if (ve != null) {
			Class type = ve.getType(context.getELContext());
			if (type == FileItem.class) {
				fileUpload.setSubmittedValue(item);
			}
		}
	}
	
}

Modify the web.xml file to add some configuration paramaters

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

	...

	<!-- file upload parameters -->
	<context-param>
		<param-name>com.carperea.web.upload.sizeThreshold</param-name>
    	<param-value>10000</param-value>
	</context-param>
	<context-param>
		<param-name>com.carperea.web.upload.tempDirectory</param-name>
		<param-value>/tmp/imgs</param-value>
  	</context-param>

	...

</web-app>

define the component that represents the input of type file (<input type="file"/>)

package com.carperea.web.jsf;

import java.util.ArrayList;
import java.util.List;

import javax.el.Valueexpression;
import javax.faces.application.FacesMessage;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;

import org.apache.commons.fileupload.FileItem;

@FacesComponent(value = "HtmlInputFile")
public class FileUpload extends UIInput {

	public static final String COMPONENT_TYPE = "com.carperea.component.FileUpload";
	public static final String COMPONENT_FAMILY = "com.carperea.component";
	public static final String RENDERER_TYPE = "com.carperea.component.FileUploadRenderer";
	private static final String OPTIMIZED_PACKAGE = "com.carperea.component.";
	
	...

	private static final String ATTRIBUTES_THAT_ARE_SET = "javax.faces.component.UIComponentBase.attributesThatAreSet";
	
	protected enum PropertyKeys {

		allowTypes, sizeLimit, style, styleClass;

		String toString;

		PropertyKeys() {
		}

		PropertyKeys(String toString) {
			this.toString = toString;
		}

		public String toString() {
			return ((this.toString != null) ? this.toString : super.toString());
		}
	}

	public FileUpload() {
		setRendererType(RENDERER_TYPE);
	}

	@Override
	public String getFamily() {
		return COMPONENT_FAMILY;
	}

	public String getAllowTypes() {
		return (String) getStateHelper().eval(PropertyKeys.allowTypes, null);
	}

	public void setAllowTypes(String allowTypes) {
		getStateHelper().put(PropertyKeys.allowTypes, allowTypes);
		handleAttribute("allowTypes", allowTypes);
	}

	public Long getSizeLimit() {
		return (Long) getStateHelper().eval(PropertyKeys.sizeLimit, Long.MAX_VALUE);
	}

	public void setSizeLimit(Long sizeLimit) {
		getStateHelper().put(PropertyKeys.sizeLimit, sizeLimit);
		handleAttribute("sizeLimit", sizeLimit);
	}

	public String getStyle() {
		return (String) getStateHelper().eval(PropertyKeys.style, null);
	}

	public void setStyle(String style) {
		getStateHelper().put(PropertyKeys.style, style);
		handleAttribute("style", style);
	}

	public String getStyleClass() {
		return (String) getStateHelper().eval(PropertyKeys.styleClass, null);
	}

	public void setStyleClass(String styleClass) {
		getStateHelper().put(PropertyKeys.styleClass, styleClass);
		handleAttribute("styleClass", styleClass);
	}

	@SuppressWarnings("unchecked")
	private void handleAttribute(String name, Object value) {
		List<String> setAttributes = (List<String>) this.getAttributes().get(
				ATTRIBUTES_THAT_ARE_SET);
		if (setAttributes == null) {
			String cname = this.getClass().getName();
			if (cname != null && cname.startsWith(OPTIMIZED_PACKAGE)) {
				setAttributes = new ArrayList<String>(6);
				this.getAttributes().put(ATTRIBUTES_THAT_ARE_SET, setAttributes);
			}
		}
		if (setAttributes != null) {
			if (value == null) {
				Valueexpression ve = getValueexpression(name);
				if (ve == null) {
					setAttributes.remove(name);
				}
			} else if (!setAttributes.contains(name)) {
				setAttributes.add(name);
			}
		}
	}

	@Override
	protected void validateValue(FacesContext context, Object newValue) {
		super.validateValue(context, newValue);
		if (isValid()) {
			if (newValue instanceof FileItem) {
				FileItem tmp = (FileItem) newValue;
				String allowTypes = getAllowTypes();
				String fileName = tmp.getName();
				if (allowTypes != null && !"".equalsIgnoreCase(allowTypes) && fileName != null
						&& !"".equalsIgnoreCase(allowTypes)) {
					String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1);
					String[] exts = allowTypes.split(",");
					boolean validExt = false;
					for (String ext : exts) {
						validExt = validExt || ext.equalsIgnoreCase(fileExt);
					}
					if (validExt) {
						setValid(true);
					} else {
						setValid(false);
						FacesMessage message = MessageFactory.getMessage(INVALID_FILE_EXTENSION,
								FacesMessage.SEVERITY_ERROR);
						context.addMessage(getClientId(context), message);
					}
				}
				Long sizeLimit = getSizeLimit();
				if (sizeLimit != null) {
					if (tmp.getSize() <= sizeLimit) {
						setValid(isValid() && true); 
					} else {
						setValid(false);
						FacesMessage message = MessageFactory.getMessage(FILE_SIZE_EXCEEDED,
								FacesMessage.SEVERITY_ERROR);
						context.addMessage(getClientId(context), message);
					}
				}
			} else {
				setValid(false);
				FacesMessage message = MessageFactory.getMessage(DEFAULT_ERR,
						FacesMessage.SEVERITY_ERROR);
				context.addMessage(getClientId(context), message);
			}
		}
	}
}

Create the taglib file with the definition

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
	version="2.0">
	<namespace>http://carperea.com/jsf/html</namespace>
	<tag>
		<tag-name>inputFile</tag-name>
		<component>
			<component-type>HtmlInputFile</component-type>
		</component>
		<attribute>
			<description></description>
			<name>id</name>
			<required>false</required>
			<type>java.lang.String</type>
		</attribute>
		<attribute>
			<description></description>
			<name>rendered</name>
			<required>false</required>
			<type>java.lang.Boolean</type>
		</attribute>
		<attribute>
			<description></description>
			<name>binding</name>
			<required>false</required>
			<type>javax.faces.component.UIComponent</type>
		</attribute>
		<attribute>
			<description></description>
			<name>value</name>
			<required>false</required>
			<type>java.lang.Object</type>
		</attribute>
		<attribute>
			<description></description>
			<name>allowTypes</name>
			<required>false</required>
			<type>java.lang.String</type>
		</attribute>
		<attribute>
			<description></description>
			<name>sizeLimit</name>
			<required>false</required>
			<type>java.lang.Long</type>
		</attribute>
		<attribute>
			<description></description>
			<name>style</name>
			<required>false</required>
			<type>java.lang.String</type>
		</attribute>
		<attribute>
			<description></description>
			<name>styleClass</name>
			<required>false</required>
			<type>java.lang.String</type>
		</attribute>
	</tag>
</facelet-taglib>

Modify the web.xml file to add the TAG definition

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

	...

	<context-param>
		<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
		<param-value>/WEB-INF/file-taglib.xml</param-value>
	</context-param>

	...

</web-app>

Create a form with the defined component in a .xhtml file

<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:cp="http://carperea.com/jsf/html"
    template="/WEB-INF/templates/default.xhtml">

	...
	
	<ui:define name="content">
		<h:form enctype="multipart/form-data" >
			<cp:inputFile id="image" value="#{userProfile.fileItem}" 
				allowTypes="png,gif,jpg" sizeLimit="716800"/>
			<h:commandButton action="#{userProfile.loadImage}" value="Upload" />
		</h:form>
	</ui:define>

	...

</ui:composition>

finally, define a managed bean to get the loaded file

package com.carperea.project.web;

import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.fileupload.FileItem;

import com.carperea.project.core.UserRepository;
import com.carperea.project.model.User;

@Named
@RequestScoped
public class UserProfile {
	
	...
	
	private FileItem fileItem;
	
	public void loadImage() {
		String mimeType = fileItem.getContentType();
		String fileName = fileItem.getName();
		try {
			InputStream fileContenteAsInputStream = fileItem.getInputStream();
		} catch (IOException e) {
			...
		}
		byte[] fileContentAsByteArray = fileItem.get();
	}

	public FileItem getFileItem() {
		return fileItem;
	}

	public void setFileItem(FileItem fileItem) {
		this.fileItem = fileItem;
	}

	...

}


I hope this is useful for some of you...


 

Tip
blog comments powered by Disqus