Thursday, December 10, 2009

MATE File Upload Service Invoker

If you are working with Adobe Flex technology you probably heard of MATE Flex Framework. It is “a tag-based, event-driven Flex framework” with IoC container, view auto wiring and other convenient features. In one of my recent projects there was a requirement to import local data files in XML format. Unfortunately, Flash Player 9 was a constraint as it does not allow to load local files into Flex application. This feature comes with Flash Player 10 through FileReference.load method. In this scenario you have to first upload file on to the server and download it back to Flex client application. MATE framework provides a concept of Service Invokers for remote communication, configurable through its Event Maps in MXML format. There are Service Invokers for RemoteObject, HTTPService and WebService available out of the box. It seemed logical to handle this file upload scenario the same way. Unfortunately, current version of MATE does not provide such a Service Invoker, so I decided to build it myself.
It consists of two classes: FileUploadServiceInvoker and FileUploadHandlers. You can see them below:
1: package components {
2:  import com.asfusion.mate.actionLists.IScope;
3:  import com.asfusion.mate.actions.IAction;
4:  import com.asfusion.mate.actions.builders.ServiceInvoker;
5:  import com.asfusion.mate.core.ISmartObject;
6:  import com.asfusion.mate.events.UnhandledFaultEvent;
7: 
8:  import flash.events.DataEvent;
9:  import flash.events.IOErrorEvent;
10:  import flash.net.FileReference;
11:  import flash.net.URLRequest;
12: 
13:  public class FileUploadServiceInvoker extends ServiceInvoker implements IAction {
14:   private var _url:String;
15:   private var _fileReference:Object;
16: 
17:   public function FileUploadServiceInvoker() {
18:   }
19: 
20:   public function get url():Object {
21:    return _url;
22:   }
23: 
24:   public function set url(value:Object):void {
25:    if (value is String) {
26:     _url = String(value);
27:    }
28:   }
29: 
30:   public function get fileReference():Object {
31:    return _fileReference;
32:   }
33: 
34:   public function set fileReference(value:Object):void {
35:    _fileReference = value;
36:   }
37: 
38:   override protected function run(scope:IScope):void {
39:    var uploadRequest:URLRequest = new URLRequest(_url);
40:    getRealFileReference(scope).upload(uploadRequest);
41:   }
42: 
43:   private function getRealFileReference(scope:IScope):FileReference {
44:    var value:FileReference = fileReference is ISmartObject ? ISmartObject(fileReference).getValue(scope) as FileReference : FileReference(fileReference);
45:    return value;
46:   }
47: 
48:   override protected function complete(scope:IScope):void {
49:    innerHandlersDispatcher = getRealFileReference(scope);
50:    if (resultHandlers && resultHandlers.length > 0) {
51:     var resultHandlersInstance:FileUploadHandlers = createInnerHandlers(scope, DataEvent.UPLOAD_COMPLETE_DATA, resultHandlers, FileUploadHandlers) as FileUploadHandlers;
52:     resultHandlersInstance.validateNow();
53:    }
54:    if ((faultHandlers && faultHandlers.length > 0) || scope.dispatcher.hasEventListener(UnhandledFaultEvent.FAULT)) {
55:     var faultHandlersInstance:FileUploadHandlers = createInnerHandlers(scope, IOErrorEvent.IO_ERROR, faultHandlers, FileUploadHandlers) as FileUploadHandlers;
56:     faultHandlersInstance.validateNow();
57:    }
58:   }
59:  }
60: }


1: package components {
2:  import com.asfusion.mate.actionLists.EventHandlers;
3:  import com.asfusion.mate.actionLists.IScope;
4:  import com.asfusion.mate.actionLists.ServiceScope;
5:  import com.asfusion.mate.events.UnhandledFaultEvent;
6: 
7:  import flash.events.DataEvent;
8:  import flash.events.Event;
9:  import flash.events.IOErrorEvent;
10: 
11:  import mx.rpc.Fault;
12: 
13:  public class FileUploadHandlers extends EventHandlers {
14:   public function FileUploadHandlers(inheritedScope:IScope = null) {
15:    super();
16:    this.inheritedScope = inheritedScope;
17:   }
18: 
19:   override protected function fireEvent(event:Event):void {
20:    if (actions && actions.length > 0) {
21:     var currentScope:ServiceScope = new ServiceScope(inheritedScope.event, debug, inheritedScope);
22:     currentScope.owner = this;
23: 
24:     if (event is DataEvent && event.type == DataEvent.UPLOAD_COMPLETE_DATA) {
25:      currentScope.result = DataEvent(event).data;
26:     } else if (event is IOErrorEvent) {
27:      currentScope.fault = new Fault("", IOErrorEvent(event).text);
28:     }
29: 
30:     setScope(currentScope);
31:     runSequence(currentScope, actions);
32:    } else if (event is IOErrorEvent) {
33:     var faultEvent:UnhandledFaultEvent = new UnhandledFaultEvent(UnhandledFaultEvent.FAULT);
34:     faultEvent.fault = new Fault("", IOErrorEvent(event).text);
35:     inheritedScope.dispatcher.dispatchEvent(faultEvent);
36:    }
37:   }
38:  }
39: }

FileUploadServiceInvoker prepares URLRequest and calls FileReference.upload in its run() method. FileUploadHandlers handles DataEvent.UPLOAD_COMPLETE_DATA and IOErrorEvent events in its fireEvent() method. Everything else is MATE specific code like scope management etc.

Usage of this service invoker is exactly the same as other service invokers provided by MATE. To give a short example, lets suppose we need to import local XML data file and bind it to DataGrid. Here you can see main.mxml file, FileUploadEvent even class which holds FileReference object, MainEventMap which configures Handlers/Injectors and server side import.jsp which simply echoes uploaded file back to Flex client.

main.mxml

1: <?xml version="1.0" encoding="utf-8"?>
2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
3:     layout="vertical"
4:     xmlns:maps="maps.*"
5:     xmlns:net="flash.net.*">
6:  <mx:Script>
7:   <![CDATA[
8:    import mx.controls.Alert;
9:    import mx.rpc.Fault;
10:    import components.FileUploadEvent;
11: 
12:    [Bindable]
13:    private var xmlData:XML;
14: 
15:    private function selectFile():void {
16:     fileRef.addEventListener(Event.SELECT, onFileSelect);
17:     fileRef.browse();
18:    }
19: 
20:    private function onFileSelect(event:Event):void {
21:     var uploadEvent:FileUploadEvent = new FileUploadEvent(FileUploadEvent.FILE_UPLOAD, true);
22:     uploadEvent.fileReference = fileRef;
23:     dispatchEvent(uploadEvent);
24:    }
25: 
26:    public function onResult(data:*):void {
27:     xmlData = new XML(data);
28:    }
29: 
30:    public function onFail(fault:Fault):void {
31:     Alert.show(fault.faultString);
32:    }
33:   ]]>
34:  </mx:Script>
35:  <maps:MainEventMap />
36: 
37:  <mx:Button label="Select File" click="selectFile()" />
38:  <net:FileReference id="fileRef" />
39:  <mx:DataGrid dataProvider="{xmlData.member}">
40:   <mx:columns>
41:    <mx:DataGridColumn dataField="@name" headerText="Name" />
42:    <mx:DataGridColumn dataField="@email" headerText="Email" />
43:    <mx:DataGridColumn dataField="@department" headerText="Department" />
44:   </mx:columns>
45:  </mx:DataGrid>
46: </mx:Application>

FileUploadEvent.as

1: package components {
2:  import flash.events.Event;
3:  import flash.net.FileReference;
4: 
5:  public class FileUploadEvent extends Event {
6:   public static const FILE_UPLOAD:String = "fileUpload";
7: 
8:   public var fileReference:FileReference;
9: 
10:   public function FileUploadEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
11:    super(type, bubbles, cancelable);
12:   }
13:  }
14: }

MainEventMap.mxml

1: <?xml version="1.0" encoding="utf-8"?>
2: <EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/" xmlns:components="components.*">
3:  <mx:Script>
4:   <![CDATA[
5:    import components.FileUploadEvent;
6:   ]]>
7:  </mx:Script>
8:  <EventHandlers type="{FileUploadEvent.FILE_UPLOAD}">
9:   <components:FileUploadServiceInvoker url="import.jsp" fileReference="{event.fileReference}">
10:    <components:resultHandlers>
11:     <CallBack method="onResult" arguments="{resultObject}" />
12:    </components:resultHandlers>
13:    <components:faultHandlers>
14:     <CallBack method="onFail" arguments="{fault}" />
15:    </components:faultHandlers>
16:   </components:FileUploadServiceInvoker>
17:  </EventHandlers>
18: </EventMap>

import.jsp

1: <%@page import="org.apache.commons.fileupload.FileItem"%>
2: <%@page import="java.util.List"%>
3: <%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>
4: <%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%>
5: <%  
6: 
7: DiskFileItemFactory factory = new DiskFileItemFactory();
8: ServletFileUpload upload = new ServletFileUpload(factory);
9: //@SuppressWarnings("unchecked")
10: List items = upload.parseRequest(request);
11: for(int i= 0; i <items.size(); i++){
12:  FileItem item = (FileItem)items.get(i);
13:  if(!item.isFormField())
14:  {
15:   out.write(new String(item.get(), "UTF-8"));
16:  }
17: }
18: %>

No comments:

Post a Comment