Monday, May 18, 2009

Cloud to RIA: Accessing Google App Engine (Java) Data Through Flex AMF - Part II

[Part I] [Part II] [Part III]

Installation

OK, you should now have a GAE account and GAE (Java) access (if not, get those) and be ready to install the necessary software.
  1. Install Eclipse (3.4 "Ganymede" version). Downloads are available athttp://www.eclipse.org/downloads/, and I'd recommend getting Eclipse IDE for Java EE Developers to make sure you have everything you'd need. Installation is pretty straightforward.
  2. Add the Flex Builder plugin. Adobe offers a 60 day trial, if you're ready to buy.
  3. Once you have those going, get the Google Plugin. You install plugins in Eclipse by going to Help->Software Updates and specifying the install URL through "Add Site." The link for Google Plugin is http://dl.google.com/eclipse/plugin/3.4.
  4. Next, create a Google Project. Open File->New->Other (or Ctrl+N) and select Google->Web Application Project. Enter a Project name (e.g., "Data") and a Package name (e.g., "com.acme.data"). Uncheck "Use Google Web Toolkit" under "Google SDKs" because we're not going to use GWT.
  5. Since we're going to also build a Flex app in this project we need to give it a Flex Project Nature. Select the project name in the Eclipse left panel and Right Click->Flex Project Nature->Add Flex Project Nature. This will automatically create a Data.mxml file (which we will edit later) in the src/ folder.
  6. You will immediately see an error message "Cannot create HTML wrapper. Right-click here to recreate folder html-template." on the Problems tab in the bottom-right panel. Do that, and Flex Builder will create a template for generating Data.html.
  7. Install the GraniteDS library. Get the latest rev from the download site, unzip to graniteds/, and copy granite.jar (we don't need anything else for this project) from graniteds/build/ to your project's war/WEB-INF/lib/.
  8. Finally, get the Xalan-J version xalan-j_2_7_1 from the download site, unzip, and copy serializer.jar and xalan.jar from the root to your project's war/WEB-INF/lib/.
You now have all the software you'll need; next step is to configure it.

Configuration

For configuring, you need to mess with four files.

web.xml
This file is in war/WEB-INF/ and contains info about the servlets. Your GraniteDS installation has servlets that will run on GAE, and you need to add their info into web.xml. Copy/paste the below XML inside the <web-app> tag.

<!-- GraniteDS -->
<!-- Read services-config.xml file at web application startup -->
<listener>
<listener-class>org.granite.config.GraniteConfigListener</listener-class>
</listener>

<!-- Handle AMF requests ([de]serialization) -->
<filter>
<filter-name>AMFMessageFilter</filter-name>
<filter-class>org.granite.messaging.webapp.AMFMessageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AMFMessageFilter</filter-name>
<url-pattern>/graniteamf/*</url-pattern>
</filter-mapping>

<!-- Handle AMF requests (execution) -->
<servlet>
<servlet-name>AMFMessageServlet</servlet-name>
<servlet-class>org.granite.messaging.webapp.AMFMessageServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AMFMessageServlet</servlet-name>
<url-pattern>/graniteamf/*</url-pattern>
</servlet-mapping>

services-config.xml
Create a flex/ directory under war/WEB-INF/ and make a Flex-specific file "services-config.xml" with the below contents. This file tells Flex how to direct the RemoteObject calls. We're naming the service Data with the Java class Data (which we will create later) under the package com.acme (change this as necessary).

By default, the Flex compiler will not know of this file, so you'll need to add "-services ../war/WEB-INF/flex/services-config.xml" to the compiler flags. To access the flags, select the project name on the left panel and Right Click->Properties->Flex Compiler.

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service
id="granite-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">
<destination id="Data">
<channels>
<channel ref="my-graniteamf"/>
</channels>
<properties>
<scope>application</scope>
<source>com.acme.data.Data</source>
</properties>
</destination>
</service>
</services>

<channels>
<channel-definition id="my-graniteamf" class="mx.messaging.channels.AMFChannel">
<endpoint
uri="/graniteamf/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
</services-config>

granite-config.xml
Next, create a granite/ directory in your war/WEB-INF/, and copy the GraniteDS file "granite-config.xml" from graniteds/examples/graniteds_pojo/resources/WEB-INF/granite/ to it.

persistence.xml
JPA loads its configuration info (like persistence unit "transactions-optional" that the EMF class in Part III uses) from a "persistence.xml" file, but GAE currently doesn't automatically create it. So, make a persistence.xml file in src/META-INF/ with the following contents.

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="transactions-optional">
<provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
<properties>
<property name="datanucleus.NontransactionalRead" value="true"/>
<property name="datanucleus.NontransactionalWrite" value="true"/>
<property name="datanucleus.ConnectionURL" value="appengine"/>
</properties>
</persistence-unit>
</persistence>

Flex Application

We're now set to create the Flex app. To illustrate the basic aspects of data access, I'm going to build a small app that does simple CRUD: create, read, update, and delete. Copy the below MXML to the Data.mxml file in your src/ folder. I've made this as simple as possible, and it should be self-explanatory. Note that Flex refers to the services-config.xml (see above) to figure out how to contact the service.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:RemoteObject id="data" destination="Data" fault="status.text=event.fault.toString();">
<mx:method name="createData" result="status.text='Created.';"/>
<mx:method name="readValue" result="value.text=event.result.toString();status.text='Read.'"/>
<mx:method name="updateValue" result="status.text='Updated.';"/>
<mx:method name="deleteData" result="status.text='Deleted.';"/>
</mx:RemoteObject>
<mx:Panel width="100%" height="100%">
<mx:Form>
<mx:FormItem label="Key"><mx:TextInput id="key"/></mx:FormItem>
<mx:FormItem label="Value"><mx:TextInput id="value"/></mx:FormItem>
</mx:Form>
<mx:HBox>
<mx:Button label="Create" click="data.createData(key.text,value.text);status.text=null;"/>
<mx:Button label="Read" click="data.readValue(key.text);status.text=null;"/>
<mx:Button label="Update" click="data.updateValue(key.text,value.text);status.text=null;"/>
<mx:Button label="Delete" click="data.deleteData(key.text);status.text=null;"/>
</mx:HBox>
<mx:TextArea id="status" width="100%" height="100%"/>
</mx:Panel>
</mx:Application>

This is all we need to do on the client side. Complete these, and we'll create the server side code and wrap up in Part III.

16 comments:

CemKoc said...

I could not understand "Flex Project Nature" part. It seems that I have no such a option in my eclipse 3.4. (not flex builder)

Thanks

Sekhar Ravinutala said...

Are you using the Eclipse plugin version of Flex Builder? You will see "Flex Project Nature" if you installed the plugin.

rolance said...

So I need to download and install the flex builder plugin for eclipse? Perhaps include this in your documentation above.

Sekhar Ravinutala said...

Yes, you do need to add the Flex Builder plugin. Added this as an explicit step.

rolance said...

I wonder if you could share the whole web.xml file for those of us who are new to eclipse/gae/flex. I have been able to get your example to deploy and serve the swf, but when I try to create a key/value pair i get:

[RPC Fault faultString="Send failed" faultCode="Client.Error.MessageSend" faultDetail="Channel.Connect.Failed error NetConnection.Call.Failed: HTTP: Failed: url: 'http://ndolc-001.appspot.com/graniteamf/amf'"]

Nehul said...

rolance,
Make sure you have granite.jar in war/WEB-INF/lib. Also run from http://localhost:8080/Data.html or whatever name you have provided.

Cheers,
Nehul

David said...

Hello, great post!
Just a question about: "...the Flex compiler will not know of this file ... services-config.xml ... to access the flags, select the project name on the left panel and Right Click->Properties->Flex Compiler."
I do not retrieve such flags in Flex compiler options?
Sure, I miss a little detail, but I'll appreciate some help!
Thanks

David said...

Sorry, it was a bad moment for me, I was lloking for some GUI, but "flag" is a precise word!

Peter said...

This is a great tutorial! I got it to work without a hitch. I really appreciate that you took the time to post the details.

Thanks

smusa said...
This comment has been removed by the author.
CC said...

Works nicely! Thx. I used Eclipse and Flex Builder (stand-alone). Note: although the services-config.xml is included at compilation time in the swf, graniteds only started to work after including it also in the war (as described in the blog post)

Dr.Drane said...

Excellent article, works like a charm!

Ric said...

Add Flex Project Nature - this doesn't exist with the Flash Builder beta 2 plugin.

You have to right click on the project do:
Add/Change project type > Add Flex Project Type

Also Data.mxml isn't created but it creates an mxml file with the same name as the project in src/

Hari Kolasani said...

Great article. Just curious if you had tried the datapush feature of GraniteDS on Google App Engine.

Paul Georges said...

I'm getting endless class not found errors... the latest that I don't quite understand is this one when trying to run locally...


Caused by: java.lang.ClassNotFoundException: org.granite.messaging.amf.io.convert.impl.GAEKeyConverter

and this one on GAE...

[RPC Fault faultString="Send failed" faultCode="Client.Error.MessageSend" faultDetail="Channel.Connect.Failed error NetConnection.Call.Failed: HTTP: Status 500: url: 'http://granitedsgae.appspot.com/graniteamf/amf'"]

any suggestions?

Paul Georges said...

I'm having errors thrown related to SpringSecurity when I try run locally or on GAE... any ideas?