Uploading files using Flex and J2EE Application Server (JBoss)

Quite often we need to upload some contents into a web server application. The standard way of doing this is by making use of the HTML Form Upload. Flex applications bridge with the browser and allow us to make use of Form Uploads. Another less common way is to make use of Adobe’s AMF Protocol (Action Message Format) that we can leverage through Open Source tools like BlazeDS.

Flex Form Uploads Advantages & Disadvantges on a J2EE server:
– Can talk to Servlets or REST services
– Does not support HTTPS (SSL/TLS)
– Does not support HTTP authentication
– HTTP Header can carry extra parameters

Flex AMF Uploads Advantages & Disadvantges on a J2EE server:
– Can not make use of Servlets or REST services
– Easily deployed on a Stateless EJB or vanilla POJO
– Support HTTPS (SSL/TLS)
– Support HTTP authentication
– Extra parameters can be passed in a normal function signature

Let’s have a look in an example for both cases.

The main application

This is a very simple application that contains two buttons to initiate the upload, one will trigger a form based upload and the other a AMF based upload. In addition a description field is added to demonstrate the passage of extra fields through the different upload flavors.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" 
			   minHeight="600">
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
	
	<fx:Script>
		<![CDATA[
			private static var serverAddress:String = 'http://localhost:8080/uploadmethods';

			private var formUploader:FormUploader = null;
			private var amfUploader:AMFUploader = null;

			protected function formButton_clickHandler(event:MouseEvent):void {
				formUploader = new FormUploader(serverAddress);
				
				var params:URLVariables = new URLVariables();
				params.Description = description.text;
				
				formUploader.upload(params);
			}

			protected function amfButton_clickHandler(event:MouseEvent):void {
				amfUploader = new AMFUploader(serverAddress);
				amfUploader.upload(description.text);
			}

		]]>
	</fx:Script>

	<s:Label text="File Description:" x="15" y="34"/>
	<s:TextInput id="description" x="13" y="54" width="188"/>
	<s:Button id="formButton" label="Form Upload" click="formButton_clickHandler(event)" x="12" y="83"/>
	<s:Button id="amfButton" label="AMF Upload" click="amfButton_clickHandler(event)" x="116" y="83"/>
	
</s:Application>

Flex Form Uploading

As you might have noticed in the main application MXML code the FormUploader class is the one responsible for uploading the data through the browser form upload mechanism. Once evoked, the upload method will start by getting the sessionid from the server since some browsers, such as Firefox, require the sessionid to be present in the upload url, otherwise the upload will simply fail. The next step is to request the user for the file to be uploaded and, once selected, the upload method is called. Extra parameters are passed as URLVariables to the URLRequest just before the FileReference upload.

FileUploader.as

package 
{
	import flash.events.Event;
	import flash.net.FileFilter;
	import flash.net.FileReference;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	
	import mx.controls.Alert;
	import mx.core.FlexGlobals;
	import mx.rpc.AsyncToken;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.http.HTTPService;

	public class FormUploader {
		private static var allFilter:FileFilter = new FileFilter('All Files (*.*)', '*.*');
		private static var allTypes:Array = [allFilter];
		
		private var fileReference:FileReference = null;
		private var _serverAddress:String = null;
		private var _sessionid:String = null;
		private var _params:URLVariables = null;

		public function FormUploader(serverAddress:String) {
			this._serverAddress = serverAddress;
		}
		
		private function browseFileReference():void {
			
			try {
				fileReference = new FileReference();
				fileReference.addEventListener(Event.SELECT, fileSelectedHandler);
				fileReference.addEventListener(Event.COMPLETE, fileCompleteHandler);
				fileReference.addEventListener(Event.CANCEL, fileCancelHandler);
				
				try {
					var success:Boolean = fileReference.browse(allTypes);
				} catch (error:Error) {
					trace(error.message);
				}
			} catch (error:Error) {
				trace(error.message);
			}
		}
		
		public function upload(params:URLVariables):void {
			if (!_serverAddress) return;
			_params = params;
			
			var httpService:HTTPService = new HTTPService();
			httpService.url = _serverAddress + "/sessionid";
			httpService.method = "GET";
			httpService.addEventListener("result", httpResult);
			httpService.addEventListener("fault", httpFault);
			httpService.send();

			browseFileReference();
		}
		
		private function httpResult(event:ResultEvent):void {
			_sessionid = event.result as String;
		}
		
		private function httpFault(event:FaultEvent):void {
			trace(event.fault.faultString);
		}
		
		private function fileSelectedHandler(event:Event):void {
			var request:URLRequest = 
				new URLRequest(_serverAddress + '/form' + (_sessionid && _sessionid.length > 0 ? "?jsessionid=" + _sessionid : ''));
			
			request.method = URLRequestMethod.POST;
			request.data = _params;
			
			fileReference.upload(request);
		}
		
		private function fileCancelHandler(event:Event):void {
			fileReference = null;
		}
		
		private function fileCompleteHandler(event:Event):void {
			fileReference.removeEventListener(Event.SELECT, fileSelectedHandler);
			fileReference.removeEventListener(Event.COMPLETE, fileCompleteHandler);
			fileReference.removeEventListener(Event.CANCEL, fileCancelHandler);
			Alert.show("You can download your file from the link bellow:\n" + _serverAddress + "/" + fileReference.name, "Success");
		}
		
	}
}

On the Application Server side the servlets will process the http request and, in the case of the upload servlet, the data will stored in the the war root directory. This application was only tested in JBoss but should work just fine with any other Servlet based web server such as Tomcat, Spring or any other J2EE based App Servers.

SessionIDServlet.java

package org.riaconnection.sessionid;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name="SessionIDServlet", urlPatterns={"/sessionid"})
public class SessionIDServlet extends HttpServlet {

	private static final long serialVersionUID = 582907274400470463L;

	@Override
	protected void
	doGet(HttpServletRequest req, HttpServletResponse resp)
	throws ServletException, IOException {
		resp.setContentType("text/plain");

	    PrintWriter writer = resp.getWriter();
	    writer.println(req.getSession().getId());
		writer.close();
	}
}

UploadServlet.java

package org.riaconnection.uploaders;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@WebServlet(name="UploadServlet", urlPatterns={"/form"})
@MultipartConfig
public class UploadServlet extends HttpServlet {
   
	private static final long serialVersionUID = -3432888638137950983L;

	@Override
	protected void 
	doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        
		response.setContentType("text/html;charset=UTF-8");
        
        PrintWriter out = response.getWriter();
        try {
            // get access to file that is uploaded from client
            Part p1 = request.getPart("Filedata");
            InputStream is = p1.getInputStream();

            // read filename which is sent as a part
            Part p2  = request.getPart("Filename");
            Scanner s = new Scanner(p2.getInputStream());
            String filename = s.nextLine();    // read filename from stream

            // read description which is sent as a part
            Part p3  = request.getPart("Description");
            s = new Scanner(p3.getInputStream());
            String description = s.nextLine();    // read description from stream

            // get filename to use on the server
            String outputfile = this.getServletContext().getRealPath(filename);  // get path on the server
            FileOutputStream os = new FileOutputStream (outputfile);
            
            // write bytes taken from uploaded file to target file
            int ch = is.read();
            long size = 0;
            while (ch != -1) {
                 os.write(ch);
                 ch = is.read();
                 size++;
            }
            
            os.close();
            out.println("<h3>File uploaded successfully!</h3>");
            out.println("Received details are:");
            out.println("Filename: " + filename);
            out.println("Description: " + description);
            out.println("File size: " + size);
        }
        catch(Exception ex) {
           out.println("Exception: " + ex.getMessage());
        }
        finally { 
            out.close();
        }
    } 
 }

For the form upload mechanism, you can use REST based interfaces as opposed to Servlets. For example, you could make use of either JAXRS or JBoss RESTEasy multipart handler methods.

Flex AMF Uploading

AMF based uploading is actually much easier than form based (provided that you know how to configure Adobe BlazeDS or Adobe Lifecycle)

In the main application you’ll notice that the AMFUploader class is the one responsible for uploading the data through the browser form upload mechanism. Once evoked, the upload method will request the user for the file to be uploaded and, once selected, the upload method is called. Extra parameters are passed as normal parameters like any other RemoteObject method signature.

AMFUploader.as

package 
{
	import flash.events.Event;
	import flash.net.FileFilter;
	import flash.net.FileReference;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	import flash.utils.ByteArray;
	
	import mx.controls.Alert;
	import mx.core.FlexGlobals;
	import mx.messaging.ChannelSet;
	import mx.messaging.config.ServerConfig;
	import mx.rpc.AsyncResponder;
	import mx.rpc.AsyncToken;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.http.HTTPService;
	import mx.rpc.remoting.RemoteObject;

	public class AMFUploader {
		private static var allFilter:FileFilter = new FileFilter('All Files (*.*)', '*.*');
		private static var allTypes:Array = [allFilter];
		
		private var fileReference:FileReference = null;
		private var _serverAddress:String = null;
		private var _description:String = null;

		private var fileStreamer:RemoteObject = null;

		public function AMFUploader(serverAddress:String) {
			this._serverAddress = serverAddress;
		}
		
		private function browseFileReference():void {
			
			try {
				fileReference = new FileReference();
				fileReference.addEventListener(Event.SELECT, fileSelectedHandler);
				fileReference.addEventListener(Event.COMPLETE, fileCompleteHandler);
				fileReference.addEventListener(Event.CANCEL, fileCancelHandler);
				
				try {
					var success:Boolean = fileReference.browse(allTypes);
				} catch (error:Error) {
					trace(error.message);
				}
			} catch (error:Error) {
				trace(error.message);
			}
		}
		
		public function upload(description:String):void {
			_description = description;

			browseFileReference();
		}
		
		private function fileSelectedHandler(event:Event):void {
			fileReference.load();			
		}
		
		private function fileCancelHandler(event:Event):void {
			fileReference = null;
		}
		
		private function fileCompleteHandler(event:Event):void {
			fileReference.removeEventListener(Event.SELECT, fileSelectedHandler);
			fileReference.removeEventListener(Event.COMPLETE, fileCompleteHandler);
			fileReference.removeEventListener(Event.CANCEL, fileCancelHandler);
			
			if (!fileStreamer) {
				fileStreamer = new RemoteObject();
				fileStreamer.destination = "amfUploader";
			}
			var data:ByteArray = new ByteArray(); 
			fileReference.data.readBytes(data, 0, fileReference.data.length); 
			
			var token:AsyncToken = fileStreamer.upload(data, fileReference.name, _description);
			token.data = event;
			token.addResponder(new AsyncResponder(uploadResultHandler, uploadFaultHandler));

		}
		
		private function uploadResultHandler(event:ResultEvent, token:AsyncToken):void {
			Alert.show("You can download your file from the link bellow:\n" + _serverAddress + "/" + fileReference.name, "Success");
		}
		
		private function uploadFaultHandler(event:FaultEvent, token:AsyncToken):void {
			trace(event.fault.faultString);
		}
	}
}

On the Application Server side a simple POJO will be used to process the received data:

AMFUploader.java

package org.riaconnection.uploaders;

import java.io.FileOutputStream;
import java.io.IOException;

import flex.messaging.FlexContext;

public class AMFUploader {

	public String  
	upload(byte[] data, String fileName, String description) 
	throws Exception {  
		try {
		    String outputfile = FlexContext.getServletContext().getRealPath(fileName);
		    
		    FileOutputStream out = new FileOutputStream(outputfile);
		    out.write(data);
		    out.close();
	    } catch (IOException e) {
			return "NOK";
		}
	    
	    return "OK";  
	}

}

Pretty clean and simple! Now, if you configure your server and BlazeDS/Lifecycle to make use of secure channels (HTTPS) then you notice that the Form version will no longer work but that the AMF version will continue to work without any issue.

Enjoy!

Advertisements

About CrazyPenguin

Software Engineer
This entry was posted in AS3 (ActionScript3), BlazeDS, FlashBuilder 4.5 (Burrito), Flex 3, Flex 4 (Spark), J2EE, JBoss, MXML and tagged . Bookmark the permalink.

3 Responses to Uploading files using Flex and J2EE Application Server (JBoss)

  1. Hi, very nice post. Very usefull

  2. Desky Natalio says:

    It is really work, nice post.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s