Implementing a file upload portlet in IBM WebSphere Portal

The file upload feature is an essential interaction service that is offered by many portals. This article shows an example of how you can implement the file upload feature in IBM® WebSphere® Portal in a similar way as you implement it in a traditional Java™ Platform, Enterprise Edition Web application. No open source or third-party packages are used in the samples provided.

Ahmed Abbass (aabbass@eg.ibm.com), IT Architect, IBM

Ahmed Abbas is an IT Architect at Cairo Technology Development Center. Through his involvement in lab services, Ahmed has been interested in bridging the gap between the theories behind technology and the practicality of implementations to serve customer business needs. You can reach him at aabbass@eg.ibm.com.



Salma El-Sherbini (salmas@eg.ibm.com), Software Engineer, IBM

Salma El-Sherbini is a software engineer at IBM Egypt Global Delivery Center - Global Business Services in Cairo. She has three years of experience in developing, deploying, and administering applications on IBM WebSphere Portal versions 5.0, 5.1, and 6.0. You can reach her at salmas@eg.ibm.com.



22 July 2008

HTTP supports sending files from a client, typically a Web browser, to a Web server as part of a multipart request. Figure 1 shows a sample request of that type. The parts of a multipart request are marked by a delimiter highlighted as "Multipart delimiter line" in the figure. For file uploading, a part of the request carries the contents of the file to be uploaded along with a header. That header includes the file name and the content type among other fields.

Figure 1. A sample part of a multipart request
A sample part of a multipart request

After the header, there are two line separators before the contents of the file to be uploaded start. The sample file here contains the sentence "This is a simple file." Binary files, such as pictures, can also be submitted in the same way as this sample text file. As the rest of the request parts, the block that contains the header and the file content is marked by two multipart delimiters. Other form fields also display within their own parts, such as the Upload input field shown in figure 1. These fields cannot be directly retrieved using request.getParameter("param_name"). Instead, they should be parsed from the stream. One of the options provided in this article shows how you can extract uploaded file information and content from the request. You can extract other field values or other files submitted in the request in a similar way.

Open source and commercial file upload libraries provide the capability to access different fields and parts of multipart requests using APIs, rather than forcing you to deal with the bare code stream.

Portlets use the same mechanism to handle file uploading as parts of a multipart request. The request scope is at a portlet level, though, as opposed to the HttpRequest that covers the request to a servlet in a Java Platform, Enterprise Edition Web application.

The following sections describe two sample implementations of the file upload feature. As shown in figure 2, the two samples provide the same user interface. The only difference is the way the functionality is implemented.

Figure 2. The two implementation options look exactly the same
The two implementation options look exactly the same

Sample portlet implementation

The sample portlet here is a JSR 168 portlet with a view mode that contains the HTML form, as shown in listing 1. The <form> tag has POST as its method and an attribute called enctype of the value multipart/form-data. This tag allows the portlet’s processAction() method to recognize the incoming stream as a multipart request that holds form data.

Listing 1. The form JSP of the sample portlet
<form name="FileUploadBasicForm" id="FileUploadBasicForm" method="POST" 
		action="<portlet:actionURL/>" enctype="multipart/form-data">

<table width="100%">
	<tr><td><b>Select a file to upload using basic custom code:</b></td></tr>
	<tr><td><input type="file" name="uploadfile" size="45"></td></tr>
	<tr><td><input type="submit" name="Upload" value="Upload"></td></tr>
</table>

</form>

Listing 2 shows the processAction() method, where an optional validation on the request size is completed prior to reading its contents.

Listing 2. The processAction() method of the file upload portlet
public void processAction(ActionRequest request, ActionResponse response)
		throws PortletException, java.io.IOException {

	/*
	 * Wrap the input stream into a buffered data stream to offload
	 * operating system-level I/O operations.
	 */
	final BufferedInputStream inputStream = new BufferedInputStream(
			new DataInputStream(request.getPortletInputStream()));

	if (inputStream.available() != -1) {
		
		/*  
		 * OPTIONAL - validation on maximum file size. Note that it 
		 * checks on the total bytes in the form, not only the
		 * file size, so it is a rough estimate of the file's size.
		 * It is better done here than after reading the
		 * whole file data from the stream.
		 */
		if (request.getContentLength() > 500000) {
			System.err.println("DEBUG - ERROR file length is too large");
			return;
		}

		/*
		 * Allocate a buffer to hold the whole request. The buffer can be of
		 * a smaller size, however you will need to read from the stream
		 * several times till you cover the whole request.
		 */
		final byte[] requestContent = new byte[request.getContentLength()];

		/* Read the request and store it as a String */
		final String requestContentAsString = new String(requestContent, 0,
				inputStream.read(requestContent));

		/* Close the stream, as you do not need it anymore! */
		inputStream.close();

		/*
		 * Calculate the position of end of the header. That is the first
		 * part of the request till a double line separator is encountered.
		 */
		final int endOfHeaderIndex = requestContentAsString
				.indexOf(END_OF_HEADER_SEPARATOR);

		/* Extract information on the uploaded file and store it in a bean. */
		final MultipartHeaderInfoBean headerBean = 
		extractMultipartHeaderInfo(requestContentAsString
				.substring(0, endOfHeaderIndex));

		/* OPTIONAL - validation on extension */
		if (headerBean.originalFileName.toLowerCase().indexOf(".jpg") >= 0) {
			System.err.println("DEBUG - ERROR JPG extension not allowed");
			return;
		}
		
		/* OPTIONAL - validation on ContentType */
		if ("video/mpeg".equals(headerBean.contentType)) {
			System.err.println("DEBUG - ERROR content type is invalid");
			return;
		}
		
		/*
		 * Calculate the position of the end of the file content. That is the
		 * first occurrence of the multipart delimiter after the end of the
		 * header.
		 */
		final int endOfFileContentIndex = requestContentAsString.indexOf(
				headerBean.multipartDelimiter, endOfHeaderIndex);

		/*
		 * Calculate the uploaded file length in bytes. Note that the last
		 * two bytes deducted are for the new line character at the end of
		 * the file content and before the multipart separator.
		 */
		final int fileContentLength = endOfFileContentIndex
				- endOfHeaderIndex - END_OF_HEADER_SEPARATOR.length() - 2;
		
		/* OPTIONAL - validation on minimum file size */
		if (fileContentLength < 2) {
			System.err.println("DEBUG - ERROR file length is insufficient");
			return;
		}

		/* Save the uploaded file. */
		writeToFile(requestContent, endOfHeaderIndex,
				headerBean.originalFileName, fileContentLength);
	}
}

As mentioned previously, the header of a multipart request part is terminated by two line separators. The END_OF_HEADER_SEPARATOR is a constant that is defined for this purpose in the sample portlet class, as shown in listing 3.

Listing 3. The header separator constant
public static final String END_OF_HEADER_SEPARATOR = System.
		getProperty("line.separator") + System.getProperty("line.separator");

The uploaded file information is extracted from the header in the method shown in listing 4. String manipulation can be done in a variety of ways. The implementation here is included only for demonstration purposes.

Listing 4. Extracting the uploaded file information from the header
private MultipartHeaderInfoBean extractMultipartHeaderInfo(
		final String _header_text) throws IOException {

	/*
	 * Read the string through a buffered reader for easier line-based
	 * processing.
	 */
	final BufferedReader reader = new BufferedReader(new StringReader(
			_header_text));

	final String multipartDelimiter, fileName, contentType;
	String lineRead;

	if ((lineRead = reader.readLine()) != null) {
		/* Extract the delimiter that separates different request parts. */
		multipartDelimiter = lineRead;
		if ((lineRead = reader.readLine()) != null) {
			/* Extract the original uploaded file name. */
			fileName = new File(lineRead.split(";")[2].split("=")[1]
					.replace('"', ' ').trim()).getName();
			if ((lineRead = reader.readLine()) != null) {
				/* Extract file content type. */
				contentType = lineRead.split(":")[1].trim();
				/* Go back with what you have! */
				return new MultipartHeaderInfoBean(multipartDelimiter,
						fileName, contentType);
			}
		}
	}
	return null;
}

The MultipartHeaderInfoBean is a plain Java bean that carries the delimiter used to separate different parts of the multipart request, the original name of the uploaded file (at the client side), and the content type of that file. Listing 5 shows the definition of the the MultipartHeaderInfoBean.

Listing 5. The MultipartHeaderInfoBean class
class MultipartHeaderInfoBean {
	
	private String multipartDelimiter;
	
	private String originalFileName;
	
	private String contentType;

	public MultipartHeaderInfoBean(String _multipart_delimiter,
			String _original_file_name, String _content_type) {
		multipartDelimiter = _multipart_delimiter;
		originalFileName = _original_file_name;
		contentType = _content_type;
	}	
}

Given the uploaded file information, such as the file extension and the headers encoded within the input stream, you can execute some optional validations by comparing the content type and the file extension, for example.

The rest of the processAction() method calculates the start position of the contents of the file within the request and their length. Based on that calculated file content length, additional file size checks can be executed at this point, before writing the content to a file on the server.

Finally, the processAction() method writes the uploaded file contents to a file on the server, as shown in Listing 6.

Listing 6. Writing the uploaded file to a local file
private void writeToFile(byte[] _whole_request_content,
		int _end_of_header_index, final String _original_file_name,
		final int _file_content_length) throws FileNotFoundException,
		IOException {
	final FileOutputStream fileOut = new FileOutputStream(new String(
			"C:\\UploadTrials\\Basic-" + _original_file_name));
	
	/* Write only the part that covers the file content. */
	fileOut.write(_whole_request_content, _end_of_header_index
			+ END_OF_HEADER_SEPARATOR.length(), _file_content_length);
	fileOut.flush();
	fileOut.close();
}

File contents do not have to be stored as a file. Storing them as database binary large objects (BLOB) is a valid alternative for binary files, for example.


Another implementation using JavaServer Faces

A JavaServer Faces component called File Upload is available in IBM Rational® Application Developer, as shown in figure 3.

Figure 3. The File Upload JSF component
The File Upload JSF component

The File Upload component simplifies file uploading by isolating developers from the multipart request. Some validations, such as file extension and content type, are available without customization, as shown in figure 4.

Figure 4. The File Upload JSF component validations
The File Upload JSF component validations

The JSP file looks like the code shown in listing 7.

Listing 7. The JSP file of the portlet
<f:view>
<hx:scriptCollector id="scriptCollector1">
	<h:form id="FileUploadJSFForm"><table width="100%">
		<tr><td>
			<b>Select a file to upload using the JSF portlet component: </b>
		</td></tr>
		<tr><td>
			<hx:fileupload id="fileUploadInput" size="45">
				<hx:fileProp name="fileName" />
				<hx:fileProp name="contentType" />
			</hx:fileupload>
			<h:message for="fileUploadInput"></h:message>
		</td></tr>
		<tr><td>
			<hx:commandExButton 
				type="submit" 
				value="Upload" 
				id="submitJSFFormButton"
			action="#{pc_FileUploadJSFView.doSubmitJSFFormButtonAction}">
			</hx:commandExButton>
		</td></tr>
	</table></h:form>
</hx:scriptCollector>
</f:view>

For this sample, we added an Upload button with an action that writes the contents of the uploaded file to a local file. This implementation is shown in listing 8.

Listing 8. JSF button action code
public String doSubmitJSFFormButtonAction() {
	
	try {

		ContentElement content = (ContentElement) getFileUploadInput().getValue();

		byte[] fileBytes = content.getContentValue(); // file byte array
		String filename = getFileUploadInput().getFilename(); // file name

		/* Extracting the file name in case the complete path was received */
		filename = new File(filename).getName();
		
		/* Writing file bytes to an output stream */
		filename = "C:\\UploadTrials\\JSF-" + filename;
		FileOutputStream fileOut = new FileOutputStream(filename);
		fileOut.write(fileBytes, 0, fileBytes.length);
		fileOut.flush();
		fileOut.close();

		System.out.println("DEBUG - File saved at >> " + filename);

	} catch (Exception e) {
		e.printStackTrace();
	}
	return "SamePage";
}

This code runs at server side, and you can add more programmatic validations to it.


Conclusion

Implementing the file upload feature in portlets is similar to its implementation in Java Platform, Enterprise Edition Web applications, except for the use of portlet APIs instead of servlet APIs. In this article, you saw two sample implementations that do not rely on any open source or third-party packages. You can choose the first implementation, if you want to have more control over how the request handling is done. The second implementation is more appropriate if your design already adopts JavaServer Faces as its basis.


Acknowledgments

The authors would like to express gratitude to Tamer Mahfouz, Hassan Ali, and Noha Tarek for their valuable comments.

Resources

Learn

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=322197
ArticleTitle=Implementing a file upload portlet in IBM WebSphere Portal
publish-date=07222008