Monday, June 22, 2009

Flex + BlazeDS + Google App Engine + Java + Eclipse

Why?

Because you want to write a Flex application that requires that the server be able to push data to the client, you want a highly scalable server so you decided on the Google App Engine, when provided with the option of Python versus Java you found Java more accessible, and Eclipse is an easy way to link it all together.

  • Flex – used for the graphical user interface
  • BlazeDS – used for Flex to Server remote objects and provides the ability for the server to push data to the Flex client
  • Google App Engine – used for hosting the server portion of the application
  • Java – the language used for constructing the server potion of the application
  • Eclipse – an easy way to build, manage, write code, and deploy

There are lots of other options though, in general with Flex you are aiming for a way to deal with remote objects and/or server data push + some way to persist data on a server that ties to some language + some IDE. This is just one of many possible configurations with an emphasis on relatively free parts.

It is also apparently difficult to get all of things working together, so I thought it would help for others if I shared the steps.

In this example I basically combined the two following projects into one to use the Google App Engine to show basic remote objects and messaging:

If you want more details into the how remote objects and messaging works see those examples.

Requirements

1. Creating the Server

1.1 Press the “New Web Application” button in the Eclipse toolbar

image

1.2 In the “New Web Application Project” window specify a project name, the package, and for the rest use the default settings then press the “Finish” button.

image

1.3 If you are like me and already run another server on port 8080, then you need to change the project settings to use a different port. You can do this by opening the debug menu and selecting “Open Debug Dialog…”

image

1.4 Specify a new port to be used and save these settings.

image

2. Adding BlazeDS to the Server

2.1 Assuming you have obtained the BlazeDS WAR file, unzip it using some ZIP program

image

2.2 Open the BlazeDS web.xml file and copy the mappings into the web.xml in your Google test project.

The web.xml file is located in MyGoogleServer/war/WEB-INF

image

The resulting web.xml file should look like the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "
http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>MyGoogleServer.html</welcome-file>
  </welcome-file-list>
  <!-- Servlets -->
  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>com.example.myproject.server.GreetingServiceImpl</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/mygoogleserver/greet</url-pattern>
  </servlet-mapping>
  <!-- Begin BlazeDS Stuff -->
  <!-- Http Flex Session attribute and binding listener support -->
    <listener>
        <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>

    <!-- MessageBroker Servlet -->
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <display-name>MessageBrokerServlet</display-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>

</web-app>

2.3 Copy the BlazeDS flex directory into MyGoogleServer/war/WEB-INF

image

2.4 Open the services-config.xml file and change the three instances of {server.name}:{server.port}/{context.root} to your server name, which in the case of this example is localhost:9000. When you deploy this thing to Google you will of course need to change it.

image 

2.5 Open the appengine-web.xml file and add the node <sessions-enabled>true</sessions-enabled>

image

2.6 Copy all of the JAR file in the BlaseDS lib directory to your lib directory, which in this example is MyGoogleServer/war/WEB-INF/lib

Before copy:

image

After copy:

image

2.7 If your server will be at a different location than your client, then you need to create a crossdomain.xml file to grant the client access.

image

In this case I like to share, so I am granting the entire internet access to the web services here. Normally you wouldn’t want to do this for reasons that should be obvious:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "
http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
    <allow-access-from domain="*" />
</cross-domain-policy>

3. Creating server-side logic

3.1 Create a class that will be used as a remote object that will just return “Hello”

package com.example.myproject.hello;

public class Hello {
    public String sayHello() {
        return "Hello World";
    }
}

3.2 Open the remoting-config.xml file and add the following node to map the Hello remote object:

<destination id="Hello">
        <properties>
            <source>com.example.myproject.hello.Hello</source>
        </properties>
</destination>

3.3 Open the messaging-config.xml file and add the following node to be able to use the chat variable for messaging:

<destination id="chat"/>

4. Creating the Flex client

4.1 Run the Google Server project

image

4.2 From the File menu create a new Flex Project

image

4.3 Specify the project name and set it to be a J2EE application server and don’t use WTP.

image

4.4 Uncheck the box to use the default LiceCycle Data services server and specify the location of the war directory of the Google Server project, and change the URL to reflect the the location of the server. Since I am planning on running the client on a different machine than the server, specify the Output folder to be bin-debug.

image

4.5 Specify the output folder URL to match the location on your local test server.

image

4.6 Run two instance of the Flex application, and notice how the sayHello button tests the Hello.java class, and you can use the Send B button to send message back and forth the the “chat” variable.

image

4.7 The Flex Code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="
http://www.adobe.com/2006/mxml" creationComplete="consumer.subscribe()">
    <mx:RemoteObject id="blazeService" fault="faultHandler(event)" source="hello.Hello" destination="Hello">
        <mx:method name="sayHello" result="resultHandler(event)" />
    </mx:RemoteObject>
     <mx:Consumer id="consumer" destination="chat" message="messageHandler(event.message)"/>
    <mx:Producer id="producer" destination="chat"/>

    <mx:Script>
        <![CDATA[
            import mx.rpc.events.ResultEvent;
            import mx.rpc.events.FaultEvent;
            import mx.messaging.messages.AsyncMessage;
            import mx.messaging.messages.IMessage;
            private function send():void{
                var message:IMessage = new AsyncMessage();
                message.body.chatMessage = msg.text;
                producer.send(message);
                msg.text = "";
            }
            private function messageHandler(message:IMessage):void{
                log.text += message.body.chatMessage + "\n";
            }

            private function faultHandler(fault:FaultEvent):void {
                result_text.text = "code:\n" + fault.fault.faultCode + "\n\nMessage:\n" + fault.fault.faultString + "\n\nDetail:\n" + fault.fault.faultDetail;
            }
            private function resultHandler(evt:ResultEvent):void {
                result_text.text = evt.message.body.toString();
            }
        ]]>
    </mx:Script>
    <mx:Button x="10" y="132" label="sayHello" width="79" click="blazeService.getOperation('sayHello').send();"/>
    <mx:TextArea x="10" y="27" width="319" height="100" id="result_text"/>
    <mx:Panel title="Chat" width="100%" height="100%">
        <mx:TextArea id="log" width="100%" height="100%"/>
        <mx:ControlBar>
            <mx:TextInput id="msg" width="100%" enter="send()"/>
            <mx:Button label="Send B" click="send()"/>
        </mx:ControlBar>
    </mx:Panel>

</mx:Application>

5. Last Thoughts

I haven’t tried it, but apparently if you want to get remoting-only BlazeDS to work you have to do the following hack: http://martinzoldano.blogspot.com/2009/04/appengine-adobe-blazeds-fix.html

Friday, June 12, 2009

Flex Memory Issue #4: Modules and Application Domains

“ApplicationDomains are pretty much a neverending source of confusion and suffering…”

- Roger Gonzales, helped in the design of Application Domains in Flex

If you are using modules in Flex whether you are aware of it or not, you are using Application Domains. In particular I have had several issues with them and their effects on memory. An Application Domain is a container for discrete groups of classes, which are used to partition classes that are in the same security domain.

What? Yes, I know. That definition is from Adobe’s asdoc for the ApplicationDomain class, and it really doesn't tell you what it has to do with modules. For a start Roger Gonzales did a great job of explaining the history and purpose of the Application Domain on his blog, located at http://blogs.adobe.com/rgonzalez/2006/06/applicationdomain.html.

To attempt to summarize the Application Domain it is essentially a way to contain AS3 definitions, which effects how modules load and unload. With modules Application Domains let you do one of two things, which I refer to as the “Current Domain Method” and the “Copied Domain Method”

Current Domain Method

Example Usages:

var info:IModuleInfo = ModuleManager.getModule("com/foo/FooModule.swf");
info.load(ApplicationDomain.currentDomain);

This loads a module into the current Application Domain, so that when it unloads the class definition stays in memory but the rest of the module related instances are supposed to go. The next time the module is loaded the class definitions are already there so it loads faster.

For example if you have an Application that has 3 modules and you load one module at a time using the Current Domain Method, unload them, and load the next module in the sequence you get the following memory profile assuming you force garbage collection (http://jvalentino.blogspot.com/2009/05/flex-memory-issue-3-garbage-collection.html) after each unload:

 image

* Assumes modules are all the same size, garbage collection is forced after unload, memory snapshot is taken using System.totalMemory, and memory snapshot is taken between unload of previous and load of new

Notice that the memory peaks after all of the modules are loaded, levels off when those three modules are again loaded, and then only drops down slightly after all the modules are unloaded. The remaining memory isn’t just Flash Player stuff, it is also the definitions for the modules classes. This means that when using the Current Domain Method, modules will never truly unload.

For another example if you look at the time it takes those module to load for the 3 module scenario using the Current Domain Method, you get the following:

image

The initial load times for each module are much greater than the amount of time for next time the same module is loaded. This is because the definitions for those modules are stored in the current Application Domain for the application when using the Current Domain Method.

Theoretically, when would one use the Current Domain Method?

You would want to use the current domain method if you were not worried about the user loading enough modules to cause the application to go out of memory. When you are using this method you are essentially caching the module definitions when you load them, allowing for the module to be loaded much faster the next time it is needed. To summarize it requires more memory in order to achieve faster load times.

Copied Domain Method

This is what happens when you use the load function of the ModuleManager and specify the parameter as a new Application Domain using the current Application Domain. This is also the default behavior when not using a parameter at all.

Example Usage:

var info:IModuleInfo = ModuleManager.getModule("com/foo/FooModule.swf");
info.load();

or…

var info:IModuleInfo = ModuleManager.getModule("com/foo/FooModule.swf");
info.load(new ApplicationDomain(ApplicationDomain.currentDomain));

This loads the module into a new Application Domain so that they are no references to keep module definitions from unloading. The result is that when you unload a module all of the related classes are supposed to entirely unload. The amount of time required to load a module never changes, because it has to be completely loaded each time.

For example if you have an Application that has 3 modules and you load one module at a time using the Copied Domain Method, unload them, and load the next module in the sequence you get the following memory profile assuming you force garbage collection (http://jvalentino.blogspot.com/2009/05/flex-memory-issue-3-garbage-collection.html) after each unload:

image 

* Assumes modules are all the same size, garbage collection is forced after unload, memory snapshot is taken using System.totalMemory, and memory snapshot is taken between unload of previous and load of new

Notice that when the all modules are unloaded the memory is supposed to go back down to where it was before the first module was loaded.

For another example if you look at the time it takes those module to load for the 3 module scenario using the Copied Domain Method, you get the following:

 image

The amount of time that it takes to load each module never changes, because each time the module has to be completely loaded. It is my understanding that the reason for this is because when using the Copied Domain Method you are not adding AS3 definitions to the current domain, so those definitions can fully unload when the module is unloaded.

Theoretically, when would one use the Copied Domain Method?

You want want to use this method if your application was under some kind of memory constraint, or if there were so many modules that it could cause the Flash VM to run out of memory. The down side to this is that you have to fully load each module every time you need it, whether it has already been loaded or not.

Current Domain Method versus Copied Domain Method in the real world

In large and stylistically complex applications in the real world, the copied domain method has provided me with a world of trouble when it comes to memory. The memory behavior is erratic to say the least in the following ways:

  • Even when forcing garbage collection the memory jumps around erratically like nothing is being done at all.
  • If you run the same exact scenario over and over again you get different results. One time memory averages 30 MB, another 50 MB, and another time it just runs out of memory

I know what you are thinking, “there are other memory leaks in the application!” That would be incorrect, as the profiler shows no leaking and if you change to the Current Domain Method the problem is gone in the particular scenario in which this happens. It is that point which I became completely confused and I was even able to talk to Adobe about it through a third party, but I was never able to get an answer.

The scenario in particular was the loading and unloading of two different modules over and over again about 100 times. Load Module 1, unload Module 1 and load Module 2, unload Module 2 and load Module 1, etc.

When using the Copied Domain Method each time I ran this scenario I got different results:

image

For example the first attempt at this scenario was fine, the second attempt ran out of memory, and the third attempt went up in memory and never came back down.

If you take that same exact scenario and make it use the Current Domain Method you get the following:

image

Using the Current Domain Method seemed to have fixed the memory issue in this scenario, but by using this method I was stuck with modules with definitions that never get unloaded.

Contributors