Pages

Monday, January 11, 2010

Multipart Requests and JSF

Multipart requests are used when client needs to upload files to server. A multipart request has a different encoding and requires to be parsed.
JSF2 doesn't come with any fileupload component nor it has any support with multipart requests. A multipart request to JSF (mojarra impl. 2.0.2) just executes the 'restore view' and 'render response' phases, simply because JSF can't extract the request parameters from the multipart request.
Here is what I did to implement a file upload scenerio in JSF:
Define the view
I defined my view a form with multipart encoding and file input:
1:      <h:form enctype="multipart/form-data">
2: User : <h:inputText value="#{join.userName}"/>
3: Wall : <h:inputText value="#{join.wallName}"/>
4: Avatar : <input id="avatar" name="avatar" type="file" />
5: <h:commandButton value="Login" action="#{join.create}"/>
6: </h:form>
Wrap & Parse The Request
I used commons-fileupload to parse the request and used CDI event dispatching to assign the uploaded file to beans :
1:  public class MultipartRequestWrapper extends HttpServletRequestWrapper {
2:
3: private Hashtable<String, String[]> params = new Hashtable<String, String[]>();
4:
5: MultipartRequestWrapper(HttpServletRequest request, Event<FileItem> uploadEvents) {
6: super(request);
7: DiskFileItemFactory factory = new DiskFileItemFactory();
8: factory.setSizeThreshold(2097152);
9: ServletFileUpload upload = new ServletFileUpload(factory);
10: upload.setSizeMax(2097152);
11:
12: try {
13: List<FileItem> items = upload.parseRequest(request);
14: for (FileItem item : items) {
15: if (item.isFormField()) {
16: params.put(item.getFieldName(), new String[]{new String(item.get())});
17: } else {
18: uploadEvents.fire(item);
19: }
20: }
21: } catch (FileUploadException e) {
22: throw new RuntimeException(e);
23: }
24: }
25:
26: @Override
27: public String getParameter(String name) {
28: String [] values = getParameterValues(name);
29: if(values == null || values.length == 0) {
30: return null;
31: }
32:
33: return values[0];
34: }
35:
36: @Override
37: public Map<String, String[]> getParameterMap() {
38: return params;
39: }
40:
41: @Override
42: public Enumeration<String> getParameterNames() {
43: return params.keys();
44: }
45:
46: @Override
47: public String[] getParameterValues(String name) {
48: return params.get(name);
49: }
50:
51: }
Line 16 parses and extracts the request parameters and and line 8 publishes the file item to the listening beans. A simple filter checks the request and if it's a multipart request it wraps the request with this wrapper.

7 comments:

  1. any way to achieve this in a jsf-centric approach? with a PL for instance...

    ReplyDelete
  2. I had to do a similar thing for PrimeFaces FileUpload as well and glad that servlet 3.0 fixes this mess with supporting fileuploads out of the box.

    ReplyDelete
  3. @mert you need to wrap the request on a filter apart from that you can make PL version I guess. @cagataycivici I didn't know about servlet 3.0's fileupload support, will check it first thing.

    ReplyDelete
  4. Mert, I used a PL in Scales to do this:

    http://kenai.com/projects/scales/sources/mercurial/content/upload/src/main/java/com/sun/mojarra/scales/util/MultiFileUploadPhaseListener.java?rev=429

    I've never been real happy with how that works, so I think I'm going to rework that, possibly using Servlet 3.0, but that should at least show the technique above in action with a PL.

    Better look at the URL quick, BTW. Oracle's killing kenai. :|

    ReplyDelete
  5. I already made a Servlet 3.0, check it out here : http://code.google.com/p/teknoatolye/source/browse/trunk/iwall/src/main/java/org/mca/iwall/web/filters/MultipartRequestWrapper.java

    ReplyDelete
  6. Hmmm, how do I hook this in to make it useful?

    ReplyDelete
  7. How or where do you define, that MultipartRequestWrapper will be used?

    ReplyDelete