Friday, January 18, 2008

flash bitmapdata crossdomain

This is more note to myself- I needed to load some images from another domain for use with flash bitmapdata.draw() . All went fine until the site was actually online- all of a sudden there's no images but no error messages or anything.. As it turns out, there is security sandbox limitations in flash for accessing bitmapdata across domains- but luckily you can overcome this by using crossdomain.xml AND pointing flash to get it with System.security.loadPolicyFile("http://other.domain/crossdomain.xml") for as2 and loaderContext.checkPolicyFile = true on as3 . Of course this works only if you have access to the crossdomain.xml on the other domain..

...and this seems to only work on flash players > 8.0.24

Tuesday, December 18, 2007

Flex, BlazeDS and Seam

Intro

I have seen this question asked few times but so far there hasn't been any real solution for this simple problem; is it possible to integrate Flex with Seam. And I'm not talking about some ajax-bridge-html-js-hack, or using seam to create webservices and consuming them on the flex , even thought this is possible as well and for some situations good option too, but a direct access to java methods without re-writing (at least that much) of the java code. So basically using remoting.

I needed to provide a bit more "nicer" view for few pages on a seam app we have already built, nothing fancy or complicated- just a page to display the data but without any extras the site normally has (like navigation...) and with a bit of extra functionality on the interface (plus maybe in the future even create AIR app for this data) .. The release of Adobes BlazeDS got me thinking this a bit more (eventhough this feature won't be needed anytime soon..), and finally got me working to find the solution..

Well, after learning about and fighting with EJB's, sessions, seam transactions, entitymanagers, Jndi and hibernate co-operation and factories and lazy-loading problems for few days (and finally bothering to actually RTFM:) I'm finally happy to tell you it is possible, and also the integration works perfectly using the new BlazeDS remoting components. Since BlazeDS is based on LiveCycle ES, this should work exactly the same way with LiveCycle ES as well, just the jars are different (however, this you have to find out yourself, since I haven't tried LiveCycle ES).

This article assumes you have Seam & Jboss already installed and running and you have some kind of understanding how Seam, EJB3 and Hibernate works if you happen to run into problems. And since the frontend is Flex, a knowledge of flex & AS3.0 of course helps.. Note also that I'm not using any authentications/ restricted access to any beans- there is a way to manage this as well (I haven't tested though) but this is out of the scope of this article.

This article isn't about creating apps with seam or installing jboss either, there are plenty of tutorials for doing those elsewhere on the net..

The setup I'm using is Seam 2.0 running on Jboss 4.2.1.GA and the latest BlazeDS (blazeds_b1_121307) all running on localhost (on OS X with jvm1.5 [I would like java6 on leopard as well btw...]). I'm using the standalone Flex builder 3 beta 2 for flex and eclipse for java. I had the java project already on eclipse workspace - so I'm assuming you have already somekind of project to test this on (if not, this would be the perfect time to create one..). For flex there is a good example app to test on in the samples that comes with the BlazeDS- more about this later.

The files, besides jboss and seam, you need are:

BlazeDS and Flex from labs.adobe.com

EJBFactory
by Ryan J. Norris

NOTE! this version has the EJB3-support, while the other one in adobe exchange hasn't (meaning for EJB3 it doesn't use the create() method for ejbhome and some other stuff- see the src/)

After dowloading & unzipping all of the above, we're ready to start.

Seam + BlazeDS

1. First follow the instructions how to install BlazeDS on Jboss at adobe labs, http://labs.adobe.com/wiki/index.php/BlazeDS:Release_Notes . For now the tag is not needed, so just ignore that and the part about context.xml (at least if youre running Jboss alone, I don't know about the tomcat embed-thing, maybe you need this then).

2. After installing the sample apps (ie. deploying the .wars on Jboss and 2 installing the jars) and making sure they work, continue to the next step... For BlazeDS to work, you don't need the .wars on the server, the jars are all that's needed, so you can safely remove them after this if you like.

3. Copy the jars for Flex FROM blazeds_b1_121307/blazeds-samples/WEB-INF/lib TO [path_to_your_seam_app]/lib, ie. include them on your project. The jars to copy:

backport-util-concurrent.jar
concurrent.jar
flex-messaging-common.jar
flex-messaging-opt.jar
flex-messaging-remoting.jar
flex-messaging-core.jar
flex-messaging-proxy.jar

the commons* jars you should already have in your projects lib, if not, copy them too.

4. Copy the FlexEJBFactory/flex-ejb-factory.jar TO [path_to_your_seam_app]/lib

5. Make sure the new jars are included on your project when deploying- check build.xml and add them there (around line 133 ${war.dir}/WEB-INF/lib).

NOTE! Depending how you have setup your project and jboss, you might have different locations for the main lib and jars- on our project we try to keep all the jars on the projects own /lib/ and nothing on jboss, but if your projects are setup differently, use your paths- the main thing is to get all the necessary libs included.. (Jboss will give the classnotfound-errors on start if something is missing so don't worry)

6. Create a new dir: [path_to_your_seam_app]/resources/WEB-INF/flex

7. copy the *-config.xml files for BlazeDS FROM blazeds_b1_121307/resources/config/ TO [path_to_your_seam_app]/resources/WEB-INF/flex . I recommend these since they contain some useful instructions and are just the basic templates. You can check out more examples about the config-files in blazeds-sample and blazeds -dirs (under /WEB-INF/flex).

8. copying of files done! moving on...

Configuring the xml-files

1. in resources/WEB-INF/web.xml add the flex-listener and messagebroker servlet:

<!-- FLEX -->

<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>

<!-- context-param>
<param-name>flex.class.path</param-name>
<param-value>/WEB-INF/flex</param-value>
</context-param-->

<!-- 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>
<init-param>
<param-name>flex.write.path</param-name>
<param-value>/WEB-INF/flex</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>



I added this last on my config-file. Note the commented-out flex.class.path context-param; everything seems to work fine without that but I kept it there just in case.

NOTE! Read the docs about configuring the BlazeDS-config files on adobes site, what follows is just the needed changes..

2. WEB-INF/flex/services-config.xml add line:

<factories>
<factory id="ejb3" class="com.adobe.ac.ejb.EJB3Factory">
</factories>


This refers to the EJB3Factory that is needed for flex to get own instance of the EJB3. Without the factory you can access just the methods in java classes without any kind of connection to entitymanager (and using dot syntax - see the next step), so basically nothing..

on the same file change the <security>login-command to
<login-command class="flex.messaging.security.TomcatLoginCommand" server="JBoss">


3. WEB-INF/flex/remoting-config.xml

Here is a bit I spent quite long figuring out. The format of the channels source (the java object we're calling) depends on the jndi pattern cofigured on the server. For many it seems to be with dots, like com.whatever.ClassName. However, for Seam the default jndi-pattern, as defined in component.properties.xml is in the form of "jndiPattern \#{ejbName}/local", so instead of dots, seam uses the form ejbName/local. Also notice the /local - this can be set either to remote or local depending of the java object (if the interface isn't annotated as @Remote, it's local) . And If you don't include this seemingly trivial /local on the path, nothing works (trust me, I got stuck with this for a while..).

Another important thing - for the destination ids to work correctly, Seam needs them to be real class/ejb names- otherwise it gives "value of context variable is not an instance of the component bound to the context variable"-errors when accessing the pages. But then on the other hand, why would you use something else than the class name you're accessing? (Yeah ok, I'll admit- Of course I was stuck with this for some time as well when using just short names like "user" instead of UserEditorAction which was the actual name of the bean...).

So an example destination looks like this (the scope is optional) - also notice the factory-tag which tells flex to use the EJB3Factory defined earlier. The "appname" is the name of your application/project.

<destination id="UserEditorAction">
<adapter ref="java-object">
<channels>
<channel ref="my-amf">
</channels>
<properties>
<factory>ejb3</factory>
<source>appname/UserEditorAction/local</source>
<scope>session</scope>
</properties>
</destination>


For this first test I recommend using a bean which has some kind of list/collection to return for flex app- this way it's easiest to see if everything works ok..

4. All done for the configuration!

Get hibernate, EJB's and Seam finally talking to flex

So now you should have the configuration done and files on the right places. The final step is to get the data to flex app. These are more like general instructions, since there is (at least for now) no example application to use..

First let's look at the java:

You should have one entity and a bean using it- for example entity User with some properties with stateful/statelss bean for accessing User, for example to list all the users (if you have collections on the entity, even better- since then you'll see lazy loading working as well!). I had a simple method like this:

public List<user> getUserList() {
userList = entityManager.createQuery("select U from User u").getResultList();
return userList;
}


Speaking about lazy loading- if you're using EJB3 entitymanager (@Persistencecontext annotation), you will run into problems with collections using lazy loading- the entitymanager gets just the data needed for the main entity but closes the connection before fetching of any data from the lazy loaded collections is completed. See more info about this on the resources-list on the bottom of the article.

The solution for this problem is simple- use the Seam's own entitymanager for handling the connection, and all your lazy loading problems are solved! So make sure you have in your bean the lines

@In
private EntityManager entityManager;


and that's all. This one took me quite a while to realize as well since in our project both entitymanagers were used depending on the bean, and I haven't really never before thought why...

If you need to use EJB3 entitymanager, I read something about using Jboss interceptors to strip the proxy objects from the sent object so they don't get triggered by flex but unfortunately lost the link...

On the flex side:

Open the blazeds-samples/testdrive-remoteobject project in flex builder (or eclipse, whichever you prefer) from the blazeds source files. Feel free to use any other project as well, or create your own, but this is the fastest way to get testing.

The project includes a datagrid and a button that loads the data from the remote object. Replace the "destination" in mx:remoteObject with your own destination from the remoting-config.xml. Then on the button and datagrid code change the methods to your own:

<mx:button label="Get Data" click="srv.getMyListOfStuff()">

<mx:datagrid dataprovider="{srv.getMyListOfStuff.lastResult}" width="100%" height="100%">


Now comes the important part: for remoting, flex needs to get access to the services-config.xml, since it will compile the info inside of the .swf. To set this, open the project properties and select compiler. Type the path to your services-config.xm file to the additional arguments and set the context-root as well. For services-ath you can use the path to your project in your workplace, this is only for the compiling of .swf. The whole arguments string looks something like this on OS X, assuming you have your workspace under Documents:

-locale en_US --services=/Users/[user]/Documents/workspace/[myProject]/resources/WEB-INF/flex/services-config.xml --context-root=/myProject

You're done!

Run the app and click the "get data" button (or however you're accessing the data on your own project). If everything went ok, the datagrid should be populated with the data from Seam/Hibernate!

Some useful links:

BlazeDS developers guide
http://livedocs.adobe.com/labs/blazeds/html/index.html

Adobe livedocs:
Using the factory mechanism
http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=ent_services_config_097_26.html
Accessing Java objects in the source path
http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=dataservices_099_25.html

Seam:
http://docs.jboss.com/seam/latest/reference/en/html/configuration.html
http://docs.jboss.com/seam/latest/reference/en/html/persistence.html

Great posts about flex and java integration:
http://weblogs.macromedia.com/pmartin/archives/2006/08/ejb_and_flex_in.cfm

Lazy loading
http://www.hibernate.org/162.html
http://forum.hibernate.org/viewtopic.php?t=947299&postdays=0&postorder=asc&start=0&sid=5b259f71c96ed0925c407ab926de3850