Thursday, June 12, 2008

How can you implement Web-based File Uploading


How can you implement Web-based File Uploading?

The implementation starts with a GUI, which will prompt the user to either enter the path of the file or to select it from the hierarchical filesystem at the client machine. When the user selects/enters the file name and submits the form then the contents of the browser encodes the entire form data including the contents of the file and its name/path. The browser sends the entire encoded data to the Web Server, which in turn passes the data to the script (either a Servlet or a JSP in case of a Web Application) specified in the ACTION attribute of the FORM element. This is how we do it:-

<FORM ENCTYPE="multipart/form-data" METHOD="POST" ACTION="/jsp/jspName.jsp">

<INPUT TYPE="FILE" NAME="fileUpload">
......

<FORM>

You would have noticed a new encoding type specified with the FORM element by now. This is mandatory to do as the default encoding of the form data is not suitable for file uploading. Similarly, GET method is also not suitable, so we must use the method type as 'POST' for file uploading. Servlet or JSP specifications don't require them to interpret the 'multipart/form-data' encoding style, hence we need to use a parser which can do that task for the Servlet/JSP. One of the common third party Java package used for this task is org.apache.commons.fileUpload, which contains a class named DiskFileUpload. This class has a method called parseRequest(), which accepts an HttpServletRequest as parameter and it returns a List of instances of type org.apache.commons.fileUpload.FileItem and the encoded form data coming to the Web Server via the HttpServletRequest can now be read from the stream returned by the getInputStream() method on each of these FileItem instances. Remember that these FileItem instances not only contain the uploaded files, but also the other parameters attached to the HttpServletRequest, which is passed to the parseRequest() method of the DiskFileUpload class.

Now that we have got access to the parsed form data, we need a way to retrieve the contents so that we can save the uploaded file(s) on the server machine. Servlets provide two methods to fetch data - getParameter() and getParameterValues(). Both these methods are useless here as they work not for file-data, but for the regular constructs like radio buttons, checkbixes, drop down lists, text fiels, etc. This problem is solved by the using the features of the Servlet API - Filters and Request Wrappers.

If we are using JSP RI (Reference Implementation), which is the official JSF implementation from Sun, then we can use MyFaces extensions from Apache for the File Uploading feature. This can be done by putting the myfaces-extension.jar, commons-fileupload-1.0.jar, and other regular JSF RI JAR files in the WEB-INF/lib directory of the web application deployed on the Web Server.

MyFaces extensions has a class named MultipartRequestWrapper, which extends the HttpServletRequestWrapper class and overrides the get methods to make them work properly for the 'multipart/form-data' encoding style as well. This class provides two methods - getFileItem() and getFileItems() to directly access the files uploaded read by the FileItem interface. In fact, we don't even need to parse the 'multipart/form-data' type data in case we use the MyFaces as it automatically (though it does like that only) handles all this makes the Web-based File Upload functionality quite easier to implement. To enable the automatic parsing, we just need to add an entry for the ExtensionsFilter in the web.xml (Deployment Descriptor of the web application) file and this will intercept all the incoming Http Requests before they reach the FacesSevlet of JSF RI. This is how the entry of ExtensionsFilter would look like:-

<filter>
<filter-name>ExtensionsFilter</filter-name>
<filter-class> org.apache.myfaces.component.html.util.ExtensionsFilter </filter-class>
<init-param>
<param-name>uploadMaxFileSize</param-name>
<param-value>size in bytes... say 5m</param-value>
</init-param>
<init-param>
<param-name>uploadThresholdSize</param-name>
<param-value>size in bytes... say 500k</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>ExtensionsFilter</filter-name>
<servlet-name>FacesServlet</servlet-name>
</filter-mapping>

If we do it using MyFaces, then we will use its File Upload component named inputFileUpload to provide the UI (what we did with INPUT TYPE = "file" in simple HTML). This element has many features like - we can bind the UploadedFile instance directly to a specified property of the backing bean, we can specify whether the storage type is 'memory' or 'file', etc.

Okay... how does the two storage type 'memory' and 'file' differ from each other? 'memory' is the default one and in this case the contents of the uploaded file, its name, size, content type, etc. all these info gets stored in the private fields in memory as well, even though the file gets stored on the disk. So, it's kind of wastage of storage, but keeping everything in memory will certainly make the access to the file faster. Needless to mention now that the 'file' storage type differs in the sense it doesn't get contents and other file info stored into the memory. MyFaces has two implementations of the UploadedFile interface to support these two storage types - UploadedFileDefaultMemoryImpl and UploadedFileDefaultFileImpl. If no storage type is specified (or if we explicitly specify 'memory') then by default the former implementation is used as 'memory' is the default storage type.



Share/Save/Bookmark


No comments: