Thursday, December 13, 2007

DataServiceTransaction & LCDS

Just a quick blog note on something that I've now wasted a good day on twice, only because I never blogged it last time. The challenge is say you need to make changes to data in a database, maybe across multiple tables, and using DataServices just doesn't make sense. For example, cloning x number of records. It's easy enough to create your Java remote objects and call them from Flex, but then in an FDS/LCDS implementation how do you push those changes out to Flex clients?

There's some documentation on this, scattered vaguely across the web, and there's a good chance that perhaps this has already been blogged - but I just couldn't find it. The trick is that there are 2 sides to this story, one, you must perform your operations on the database directly, and then two, you must INFORM FDS that changes have occurred. So say I want to create a new Author record, this is what that might look like:

public void createAuthor(){  
DataServiceTransaction dtx = DataServiceTransaction.begin(false);
//create record and save:
Author auth = new Author();
auth.setFirstName("Ernest");
auth.setLastName("Hemingway");
AuthorDAO dao = new AuthorDAO();
dao.create(auth);
//let LCDS know:
dtx.createItem("author", auth);
dtx.commit();
}
So I just do my regular db insert, then I use a DataServiceTransaction to let FDS know that I've created an Item. Alternatively, I could use this approach:
public void createFDSItem(){
DataServiceTransaction dtx = DataServiceTransaction.begin(false);
Author auth = new Author();
auth.setFirstName("victor");
auth.setLastName("javarubba");
AuthorDAO dao = new AuthorDAO();
dao.create(auth);
dtx.refreshFill("author", null);
dtx.commit();
}
This approach might be a bit of overkill and might be better suited when doing mass updates across many records. I did a little bit of testing and it seemed that for the most part calling CreateItem() was faster that the refreshFill() in pushing the new record to the Flex Client.

What cost me so much grief is that I was falsely led down the garden path to believe that calling dtx.createItem("author", auth) would not only inform FDS but also actually create the item. This is not the case. There! Officially blogged!

Tuesday, November 13, 2007

Losing the RollOverColor in TileList

In one of our projects we have a custom effect that plays over each tile as a user mouses over the items in a tilelist. Not getting too much into details, I basically overlay a canvas that appears and disappears as the mouse moves over the tile, giving the illusion that each tile sort of explodes. So the one thing I don't need is the rollOverColor effect, which defaults to a nasty baby blue. Usually I just set the rollOverColor to the same color as the background but that's not working so well on this project, since the background can be an assortment of wallpapers.

Unfortunately there doesn't seem to be any way to disable the rollOverColor, and maybe someone can shed some light on an easier solution. I ended up extending TileList with my own mxml component and overriding the drawitem function, as follows:

<?xml version="1.0" encoding="utf-8"?>
<mx:TileList xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.controls.listClasses.IListItemRenderer;

protected override function drawItem(item:IListItemRenderer,
selected:Boolean = false,
highlighted:Boolean = false,
caret:Boolean = false,
transition:Boolean = false):void{

super.drawItem(item,false,false,caret,transition)
}
]]>
</mx:Script>
</mx:TileList>

As you can see, I override the selected and highlighted parameters with false, thereby eliminating the selected and roll over effect. Seems to work quite well. I now use this mxml component instead of TileList throughout my applications.

Friday, November 09, 2007

Where or where has my intellisense gone?

I'm working on a flex 3 app using the latest flexbuilder 3 beta, both the plugin and standalone versions. For some reason, in some mxml files my intellisense just seems to not work at all. While in other mxml files the intellisense works fine. Turns out that in this latest beta release, Flexbuilder gets cranky if you use double quotations in Metadata Event tags:

[Event("forward", type="mx.events.Event")] will kill your intellisense and your control clicks. You will feel very lonely.

[Event('forward', type='mx.events.Event')] will make everything right again, blue coding skies ahead.

On a side note, the single quote is actually only necessary on the type attribute, intellisense seems to work fine if you use double quotes on the Event name itself.

Hopefully this will be fixed in the next release.

Thursday, November 01, 2007

Using floating IFrames in Flex 3 Beta: wmode buh-bye?

Just a quick one today... we're porting some work to Flex 3 where we do the floating Iframe thing, and noticed some strange behaviour changes. In Firefox, the floating Iframe would briefly flash into view and then disappear, while in IE the Iframe would appear, however if we clicked into the IFrame embedded html page and then clicked outside it somewhere on the flex app, the IFrame would disappear from view.

Took a little bit of sniffing around but in the end the culprit was the html-template\index.template.html file. For some reason, and it might well be a very good reason, the property wmode is no longer included in the flash player tags ("wmode", "opaque" in AC_FL_RunContent() && wmode="opaque" in <EMBED>) in FlexBuilder 3. Once we added those back in, the floating IFrame started working again.

Thursday, October 25, 2007

Not another Flex, AmfPHP, Cairngorm CodeGen! MAKE IT STOP!

Haven't blogged in quite some time and this is a project that's been half done for far too long. Like everyone else we decided to build our own code generator, just something fairly straightforward that would generate all the DAO code, the Cairngorm code, and a nice little interface to test all the main CRUD and DAD functions we have come to know and love. Lately we've been busy souping up the generated UI, not so much in terms of style... beauty does not live here... functionality however does.

You can get all the gory details by clicking on this link:

http://www.crazedcoders.com/php/codegen.php

We built this code to use AMFPHP, Cairngorm, PHP and Flex 2.01. We've put in place validators, datepickers, checkboxes and textinputs on our forms and generated full Command/Event/Model/Controller classes. We've implemented binding on the generated view form fields. Our code generator will detect relationships between tables and render comboboxes accordingly, for example, if there's a foreign key on a customer table linked to a country table, the customer view will have a combobox linked to the model.countries collection. But I must stop, I'm getting too technical...

Basically, if the generator runs successfully, you're a few clicks away from being able to manage the data in your database from Flex. The generated code is zipped up for you to download. Inside the zip you will find a complete and ready to import Flexbuilder Project and the php packages ready to copy into your amfphp services folder. Further details can be found by clicking on the link.

Wednesday, May 30, 2007

Flash.media.Sound & Memory Issues in Flex

Taking a short break from my module woes, I've been working with audio in Flex. In my app I was building a preview intro scan of my listed mp3's. What I noticed is that as the list of mp3's got longer, the memory that my browser was eating up got larger. I haven't really found any articles addressing best practices regarding handling audio in Flex, however I would venture a guess that this has been addressed many times in the past by Flash Developers.

I'm pretty tired so I'll keep this simple. Best way I have found to ensure that an audio stream is destroyed and the memory returned when you stop it:


private var mySound:Sound = new Sound();
private var audio:SoundChannel = new SoundChannel();

private function startPlaying():void{
mySound = new Sound(new URLRequest("music/sample.mp3"));
mySound.addEventListener(Event.COMPLETE, completeSound);
audio = mySound.play();
}

private function stopPlaying():void{
audio.stop();
mySound.removeEventListener(Event.COMPLETE,completeSound);
mySound = null;
}
Trick is to declare the variables outside the function, ensure you configure a sound channel (audio) for your sound, and make sure you that you can reference both from the stop function. Then you need to stop the audio, remove any event listeners, and set sound to null. I found it's also a good idea to do your URLRequest inline, as it seems to get destroyed at the same time as mySound.

Short, simple.

Saturday, May 12, 2007

Can Cairngorm and Modules play nice? Sample App

First I'd like to say thanks to those of you that commented on my previous blog entry. I was inspired to go back and take another look at ApplicationDomain. To be honest, I can't get it to work for me. So I'm just going to put up my prototype application with source code and let you play with it. There is a shell application and 3 very simple modules. The challenge is to have a controller in the shell and still have the modules all work independently of each other. The sample app as it sits on the server works because it's essentially Cairngorm free. Good luck!

Source can be found here. Demo can be viewed here.

Can Cairngorm and Modules play nice?

Let me say, first off, that my wife gave birth to a beautiful baby girl on Wednesday morning, and they are doing just fine. I've been relocated to the spare bedroom so that I can actually function during the day, however I'm missing about 30 hours of sleep this week that I'll never get back. Having said that, I am blogging today because even though I've sort of fixed my latest issues, I'm not entirely sure I've used the best approach.

This is really a sequel to my previous blog... I am still fighting with getting modules to load properly and independently of each other and the shell flex application. Here's the deal...

I have one complex flex module that is built on the Cairngorm 2.2 framework. In itself, the module works fine, and after the progress from the other day, loading multiple instances of the module into a simple shell works fine too. However, when I tried loading multiple instances of the module into the actual application, things died. Why? Because the shell is built on the Cairngorm framework as well. The minute I add in the <services:AppController id="appController"> line to my shell app, subsequent instances of the module don't load properly. I determined that the shell needed to use a different controller than the module, but that wasn't enough (though still necessary). There were funky things happening again when events were triggered - weird casting errors, events firing across multiple module instances, shared models, etc.

A couple of observations:
CairngormEventDispatcher basically returns a singleton. If the shell app gets an instance of this class, then any Modules that load will use the same instance. So, adding the appcontroller to the application will in fact get an instance of the dispatcher. Running through the debugger, I noticed that as I loaded each new instance of the same module, event listeners were added to the singleton's eventDispatcher. So, say your module was listening for the ContactEvent.LOAD_CONTACT event. If you loaded 3 instances of that module, your controller would execute 3 commands, once for each module.

When an event is received by a command, and you try to cast that event back to its origin, i.e. ContactEvent(event), it will only work in the module that actually originated the event. The other modules will throw the following exception:
" cannot convert com.crazedCoders.moduleTest.event::ContactEvent@4b23281 to com.crazedCoders.moduleTest.event.ContactEvent." Trying to test for it, i.e. if(event is ContactEvent) doesn't work since the event is in fact a ContactEvent. Very frustrating.

If you do not use any Caingorm in your shell application, you will not run into these issues, so for some that might be the quick solution. Sadly I was not afforded that luxury, so I had to find another way.

Since I was trying to treat each Module as an autonomous mini-application, I decided that I needed a CairngormEventDispatcher specifically for each Module and the Application. I created a Factory Class that would do just that. Here's the code:

package com.adobe.cairngorm.control{
import mx.modules.ModuleManager;
public class CairngormEventDispatcherFactory
{
private static var instances : Array = new Array;
public static function getDispatcher(obj:Object) : CairngormEventDispatcher
{
// in order for each module developed using cairngorm to be able
// to work independently of the shell and other modules,
// we need to create a hash map of cairngormeventdispatchers
// that are keyed on module instance or application.
// This prevents cross module listening for events,
// which is beneficial especially when loading multiple instances
// of the same module
var ed:Object = ModuleManager.getAssociatedFactory(obj);
var parent:String = "application";
if(ed != null){
trace(ed.name);
parent = ed.name;
}
if(instances[parent] == null){
var cgDispatcher:CairngormEventDispatcher = new CairngormEventDispatcher();
instances[parent] = cgDispatcher;
return cgDispatcher;
}
else{
return instances[parent];
}
}
}
}
It took me a while, but eventually I stumbled upon ModuleManager.getAssociatedFactory(object). This function *seems* to be able to figure out within the context of which Module (if any) the object was instantiated. If an object belongs to the shell, it returns null, if not, then it returns a ton of information, including a name property that contains a unique name i.e. instance77 for the loaded module. I create a new instance of the CairngormEventDispatcher for each instance and stuff it in the static associative array, then I replace the usual CairngormEventDispatcher.getInstance().dispatchEvent(...) with
CairngormEventDispatcherFactory.getDispatcher(this).dispatchEvent(...));
Now the application shell and each module gets it's own singleton, and the overlap seems to be taken care of.

Again, I welcome any feedback. I'm not particularily fond of this solution, and I hope someone out there can offer a better one.

Monday, May 07, 2007

Loading Multiple Independent Instances of a Module

So, it's Monday night... my wife's contractions are 13 minutes apart, and it seems I have nothing better to do than blog. Of course, this is more akin to the calm before the storm that will be the next 24 hours and probably the next six months. Eventually I will once again emerge from the dark precipice that lies ominously before me. But on to modules.

I love Cairngorm... I just upgraded all my projects 2.2 and it was painless, nothing like child labor, I'm told. I am working on a project where we have to build all these pretty much independent modules, and in these modules we stick to Cairngorm as well.

The first problem we ran into occurred when attempting to load 2 instances of the same module. ModuleManager, as it turns out, keys modules by url. So if you try to load the same module twice, you're just getting a reference to the same one. There is no way around this (or so I thought) This has a number of repercussions:

  • Only one ServiceLocator instance can be instantiated - this wonderful little Error message pops up the minute you load a second instance of the module. Quick workaround is to declare the system locator code much like you do model:
    private var dataServices:ServiceLocator = ServiceLocator.getInstance();
    and abstain from using the mxml format:
    <business:Services id="dataServices" />
  • All your module instances will share the same model... so if you say do a search, get a searchresult back and stick that in your model, all your modules will display the same search results, which defeats the purpose of having separate file/image search browsers (for example).

Here's a couple other funky things I tried:

  • Created 2 modules pretty much identical, Mod1.swf and Mod 2.swf. I loaded them up and they both worked fine and independently
  • Created a copy of Mod1.swf and called it Mod1a.swf. I loaded them up and they both worked fine.
  • Tried loading two Mod1.swf and suddenly no independence, plus the ServiceLocator error.

I tried wiring up my own ModuleManager, but after a good couple hours I gave up... I was getting all sorts of weird behaviour. I figured out that everything is based around the url that you pass to ModuleManager or ModuleLoader. I was pretty much ready to give up when I had this really silly notion, I did this:

private var count:int;
private var spaces:String = "";
private function LoadModule():void{
for (var x:int; x < count; x++) spaces += " ";
var info:IModuleInfo = ModuleManager.getModule("mod1.swf" + spaces);
info.addEventListener(ModuleEvent.READY, done);
info.load();
count ++;
}
private function done(event:ModuleEvent):void{
var visual:DisplayObject = event.module.factory.create() as DisplayObject;
tabber.addChild(visual);
}
Much to my surprise, this hack worked. The modules loaded successfully and ran independently. Of course, I probably need to test this some more, but I figured I'd blog it quick before the trauma of childbirth hopefully graces me with some convenient and kind short term memory loss.

Friday, April 27, 2007

Mapping VO's from Flex to PHP using AMFPHP

I sometimes wonder why it is that I never seem to blog till shortly after the midnight hour... oh well... life just gets too crazy when the sun is up. So as it happens, one of my minions spent the better part of today putting together php classes, using a nifty code generator found called DAO Generator by the guys over at Titantic Linux. It seems to do a pretty decent job of generating the sql necessary to create the db table, and then builds three additional files, the VO, a datasource file, and then finally a DAO file that has a ton of functions in it. (and I mean a ton!)

Going to take a moment here to give credit where credit is due... Michael Ramirez's tutorial on Using Amfphp 1.9 with the Adobe Flex 2 SDK is what really got us started in the right direction, it's pretty detailed and yet easy to follow. We quickly were able to get our AMFPHP up and running with the tutorial files as outlined by Michael. Tickled pink by the code generator we figured we could easily start building our own php gateway. But enough background... on to the thick of things.

Passing VO's from PHP seems to be very well covered in numerous articles on the web, and it involves mainly adding a var $_explicitType="tutorials.Person" to the php VO, with a value that corresponds to the [RemoteClass(alias="tutorials.Person")] in the Flex VO. Pretty straightforward and foolproof. But no one seems to spend any time talking about going back the other way.

Generally when a VO is passed from Flex to AMFPHP, it is treated as an Associative Array. In Michael's examples, he treats the ValueObject that is passed back to Save function as just that $valueObject["lastName"]. But we were pretty keen on using the generated code, which actually uses the syntax $valueObject->getLastName(). And this of course, failed, because the $valueObject being passed to PHP from Flex was not in fact being mapped back to the right VO, or any php VO for that matter.

Turns out, after much googling, that only under certain conditions will AMFPHP successfully map an incoming Flex VO to the corresponding PHP VO. In the globals.php, found at the root of the AMFPHP folder structure, you'll find the following line:

$voPath = "services/vo/";

Meanwhile we had been placing our VO in the same folder as our Datasource and DAO files, something like com/crazedCoders/testapp, and this was off the services/ folder, not the services/vo folder. In fact we didn't even have a /vo folder. I found this little tidbit of information: "As for incoming class mapping, remember that if amfphp doesn't find the class to be mapped in the services/vo folder, it will simply deserialize the object as though it were untyped, that is, as an associative array. " in this blog.

It took a little bit of playing around but ultimately, without making any changes to the core AMFPHP files, I was able to get the reverse mapping working successfully. This is what that looked like:

//file: src/tutorials/Person.as
package tutorials{
[RemoteClass(alias="tutorials.Person")]
[Bindable]
public class Person {
public var firstName:String;
public var lastName:String;
public var phone:String;
public var email:String;
}}
and then in php:
<?php
//file: services/vo/tutorials/Person.php
class Person {
var $firstName;
var $lastName;
var $phone;
var $email;
// explicit actionscript package
var $_explicitType = "tutorials.Person";}
function formatLastName(){
$lastName = strtolower($lastName); }
?>
To test the mapping I added the following function to a PersonService.php file:
//file: services/tutorials/PersonService.php
function modify($person){
//this will call a function on person and return it
$person->formatLastName();
return $person;
}
Only if the valueobject was mapped correctly would it be able to call the function on the PHP VO. In Flex I just created a Person object, passed it in to the remoteobject call, and observed the formatting of the last name on the Person object that was returned.

Again, I hope this will save others the time that it took me to figure this out. Enjoy!

Wednesday, April 25, 2007

SizeableTitleWindow & Custom Skins

Most of you are probably familiar with the SizeableTitleWindow.as that the CF code generator for Flexbuilder spits out, along with a plethora of other useful tidbits of code. In fact that was the only reason I initially installed Coldfusion and the tools - I saw a modal resizable window in Flex and absolutely had to have it!

That was like 8 months ago. Recently I started working a lot more closely with skinning and runtime loading of css. I created some really funky UI's using run time style sheets that I found at http://www.scalenine.com, in particular I like using the Obsidian Theme Skin. However, this skin, like many others, replaces the skins for the Panel control - which, though very cool, breaks the resize function of the SizeableTitleWindow.

In simple terms, an event listener attached to the MouseOver event checks the coordinates of the mouse and if the coordinates fall within the right range, a cursor image is assigned. When the user clicks the mouse, the mouse down event checks which cursor is active and acts accordingly. The only problem is that all the coordinate calculations are based on event.localX and event.localY and those relate to event.target. However when using skins, the event.target isn't the SizeableTitleWindow but rather the skin, so it throws everything off. The result? The east and south cursors don't appear and even when they do, the drag functionality is buggy at best.

I fixed the problem by replacing all localX and localY with event.currentTarget.mouseX and event.currentTarget.mouseY. Also I no longer check to see if event.target is SizeableTitleWindow, and that seems to have fixed it.

You can find this code in action here and the source here. Enjoy.

Friday, April 20, 2007

Flex Modules, Compile and Run Time CSS

I've recently become involved in building a very module based Flex app, where the question of css and css inheritance came up, particularly with respect to how Flex handles run time loading of style sheets when they are loaded within not only the shell application but also the therein loaded modules.

Essentially, there are 3 rules that prevail:

  1. The most recent style to be loaded always wins.
  2. Run time overrides compile time styles
  3. StyleManager is a global Manager
  4. Style inheritance is granular to the attribute level
  5. Compile time css will only compile styles found in Application
Let me explain further, and to do so I'm just going to use our trusty <mx:Button>

The most recent style loaded always wins
Say you load a runtime style sheet with the following:

Button {
fillColors: #ff0000, #ff0000, #ffffff, #eeeeee; /*red */
letterSpacing:3;
}

Then in a (rather large) module you load another runtime style sheet with this:
Button {
fillColors: #006600, #009900, #ffffff, #eeeeee; /*green*/
letterSpacing:1;
}

You'll notice that the first color style is applied right away, to any buttons in the shell application. However, when the second module finishes loading, all Buttons will change from Green to Red, and spacing will be reduced to 1. To make it even more interesting, you could add an additional module of roughly the same size as the first and have it load its own style sheet. Then the end result is pretty much random, whichever module takes the longest to load wins, the shell run time style never to be heard from again.


Run time overrides compile time styles
In your shell application you add the following:
<style>
Button{
fillColors: #9900ff, #9900ff, #ffffff, #eeeeee;
letterSpacing:1;
}
</style>

Once your app loads the run time css, this will be overwritten. Now if you have a huge run time css swf with massive fonts or images embedded in, there's a chance you might even see your original color first and then have it overwritten later. This can cause for some funky effects in your app, in which case you might want to attach an eventListener to StyleEvent.COMPLETE, and hide your interface until that event has triggered using a ViewState or the like.

StyleManager is a global Manager
It that does not distinguish between <Applications> and <Modules>. Say you create a custom style class called .myDogAteMyHomeworkButton for Buttons in your module. You then place a corresponding style entry in your compile time or run time styles for this module. Now someone else happens, just happens to use the same exact style class in their module. Or the shell application developers happens to do so. Well again, the last one loaded wins. Your custom style class is never safe. But then again neither are their's. The best thing you can do is be very very unique in your style class naming conventions, and hope for the best.

Style inheritance is granular to the attribute level
Getting back to our original example, you'll notice the style attribute letterSpacing was defined explicitly in both style sheets. Because inheritance is on the attribute level, if you do not specify an attribute in say your run time style sheet for your application, then that attribute can be set in compile time style tags and it will override the default values. What's the lesson to be learned here? If you want to make sure that your runtime css has the final word, then get detailed and explicitly list every style attribute and its value, even if the default will suffice.

Compile time css will only compile styles found in Application
Ever seen this warning "The type selector 'HDividedBox' was not processed, because the type was not used in the application."? Until the dawn of Modules, this was a message I generally ignored. Made sense to me, if I didn't have an HDividedBox anywhere in my application, why include the style? But what would happen to modules that perhaps do use the HDividedBox control but rely solely on the shell application styles for their look and feel? The application doesn't know at runtime that one or more of its modules will need this style defined, so therefore it just ignores it. End result, your HDividedBox in your module will not be styled properly. The solution? Use run time stylesheets. (and that's how this all began!)

Tuesday, April 10, 2007

A closer Look at IFrames and ExternalInterface in Flex

Well I've been super busy as usual, working on some pretty cool stuff, still managing to only put food on the table but also sticking to my guns and doing only Flex projects. Of course, my blogging has suffered dearly.

In one of my current projects, I am faced with the challenge of somehow embedding IFrames inside my flex app, however without having any control over the html wrapper that my Flex app will sit in. Sure I could 'advise' users on adding the appropriate javascript functions and the hidden div element at the bottom of the body, as we've seen in other Flex Iframe examples out there. But I figured there had to be a way to do this without needing to rely on this external dependency.

As it turns out I can do inline Javascript function calls within my ExternalInterface.call, an example of this would be:

ExternalInterface.call("function(){return window.location.href;}");
which is a simple little function to quickly get the address of the current page. Another handy example is this one:

ExternalInterface.call("function(){document.location.href=document.location;}");
This is a quick way for a user to 'logout' of the flex application, it simply reloads the web page. So as you can see, we can do a great deal without ever needing to touch the html wrapper. So why not do the same thing with Iframe support? Here's what I came up with:
ExternalInterface.call("function(){" +
"var tempIFrame=document.createElement('div');" +
"tempIFrame.setAttribute('id','vyFrame');" +
"tempIFrame.style.position='absolute';" +
"document.body.appendChild(tempIFrame);" +
"tempIFrame.style.backgroundColor='transparent';" +
"tempIFrame.style.border=0;" +
"tempIFrame.style.visibility='hidden';}");
I am going to try and create a div element in memory and append it to the DOM. It's best to run this code during the application preinitialize() event. However, before I move on to explain the rest I just want to mention that I did run into some issues here with IE6. As you'll notice I appendChild and then I set the styles. Turns out that if I manipulate a new element too much prior to adding it to the DOM, it just gets all flaky and will not work. Believe me, this was not an easy bug to track down!

Now, we need to change the remaining externalInterface calls as well... things like move, hide, show and source. Building on the example at deitte.com that first got me started ages ago, I changed all the calls as they are found in his IFrame.mxml component:

ExternalInterface.call("moveIFrame",globalPt.x ,
globalPt.y, this.width, this.height);
Becomes:
ExternalInterface.call("function(){" +
"var frameRef=document.getElementById('vyFrame');" +
"frameRef.style.left=" + globalPt.x + ";"+
"frameRef.style.top=" + globalPt.y + ";" +
"var iFrameRef=document.getElementById('vyIFrame');" +
"iFrameRef.width=" + this.width + ";" +
"iFrameRef.height=" + this.height + ";}");
ExternalInterface.call("loadIFrame", source);
Becomes:
ExternalInterface.call("function(){vyFrame.innerHTML='" +
"<iframe id=\"vyIFrame\" src=\"" + source +
"\" frameborder=\"0\"></iframe>';}");
ExternalInterface.call("showIFrame");
Becomes:
ExternalInterface.call("function(){" +
"document.getElementById('vyFrame').style.visibility='visible';}");
ExternalInterface.call("hideIFrame");
Becomes:
ExternalInterface.call("function(){" +
"document.getElementById('vyFrame').style.visibility='hidden';}");

After a bit of testing I did manage to get this to work, sort of. I ended up spending some time playing with the compiler options in Flexbuilder. I found that if I turned off history management, my Iframes wouldn't appear. However removing the script reference to history.js seemed to have no real effect. For those of you that are not Flash-savvy :) it turned out that the only things to really watch out for, and again I am at the mercy of the users who embed my flex app into their html pages, is that the AC_FL_RunContent() has a final parameter setting of "wmode", "opaque" and the object embed tag also contains wmode="opaque". If this setting is not there or set incorrectly the Iframe will appear behind your flash player.

Thursday, February 15, 2007

Export Data from Flex App using Coldfusion

It's been a while since I blogged, just so amazingly busy these days that I barely have time to breathe, let alone blog. I'm currently up to my neck in building a flex app that uses Coldfusion 7.02 and DB2 v8/9 on the back end. I've never used Coldfusion before so it's been one hell of a time. I managed to figure something out today that I think is definitely worth sharing. In the app I was required to find a way for users to 'export' or 'save' their work to their drive, in a format they could easily open directly. That eliminated shared objects and I wasn't allowed to use a third party tool like swfStudio to build a helper app and use Localconnection to get the job done (can't wait for Apollo!)

I don't have much time so I'm going to just paste code and explain a bit.

Step 1. Enable session management in the CF app. Create an Application.cfm file in the root of your CF application, put this in it:

<CFAPPLICATION NAME="Name" SESSIONMANAGEMENT="Yes">
Step 2. Create a CFC that takes whatever data you wish to save and sticks it in a session variable, return a boolean to let Flex now this worked(Export.cfc):
<cfcomponent>
<cffunction name="toXml" access="remote" returntype="boolean" output="true">
<cfargument name="data" type="string" required="yes" default="" />
<cfset session.exportData = #arguments.data#>
<cflog text="data to be exported: #session.exportData#"/>
<cfreturn true>
</cffunction>
</cfcomponent>
Step 3. Create a CFM file in the application root that will grab the session variable and output it to http, while flagging it as an attachment (this causes nice save as popup to appear). In my example I'm creating an xml file and I ran into issues with whitespace, hence the cfsetting.. this fixed the problem for me (getFile.cfm)
<cfsetting enablecfoutputonly="true" showdebugoutput="No">
<cfheader name="Content-Disposition"
value="attachment; filename=export.xml"/>
<cfcontent type="xml/text" reset="true"/>
<cfscript>
WriteOutput(session.exportData);
</cfscript>
<cfexit />
Step 4. In flex declare the remote object that will call the cfc:
 <mx:RemoteObject 
id="exportManager"
destination="ColdFusion"
source="com.sample.utils.Export"
showBusyCursor="true">
<mx:method name="toXml" result="login_result(event)" fault="serverFault(event)" />
</mx:RemoteObject>
Step 5. Add your code to call it and handle the result. The result, on success, will make an httpservice call to the CFM you created:
private function test():void{
exportManager.toXml("" +
"" +
"The application has successfully exported this file" +
"Eventually the actual data will be passed into this function" +
"
");
}
private function login_result(event:ResultEvent):void{
if(event.result == "true"){
var request:URLRequest = new URLRequest("getFile.cfm");
flash.net.navigateToURL(request, "_self");
}
}
Conclusion. Basically that's it. I had issues with the session not persisting from setting the variable to getting it, but adding the application.cfm fixed that. I can now push any xml data to the browser for saving. There's lots of other possibilities here as well, such as exporting to Excel or Word, but there's other articles out there that you can google which explain how to do those specific tasks in Coldfusion. Hope this helps!

Sunday, December 31, 2006

Setting up Db2 v9 as a Coldfusion Datasource

In the last few weeks I've had the opportunity to get to know Coldfusion just a little bit better. One of the first challenges I faced is getting CF talking to Db2, so I could take advantage of the CF Extensions for Flexbuilder and that funky Wizard. To be precise I used ColdFusion MX 7.02 and DB2 Community Express Edition v9.1

This doesn't just apply to Flex, but maybe others might find this useful. Keep in mind I have only tested this in Windows:

  1. Install the db2 client or get access to a machine that does. I installed db2 in the default folder on my dev machine to get this working.
  2. Copy C:\Program Files\IBM\SQLLIB\java\db2java.zip to C:\CFusionMX7\lib. Important: Make sure db2jdbc.dll is in the path somewhere or copy it to same folder!
  3. Restart Coldfusion MX. Login to CF Admin. Go to Datasources.
  4. Add new Datasource:
  • driver type Other
  • CF DataSource Name: [sourcename]
  • JDBC url: jdbc:db2:[dbname]
  • Driver Class: COM.ibm.db2.jdbc.app.DB2Driver
  • Driver Name: DB2Driver
  • username: [name]
  • password: [password]
  • [Submit]
Couple things to note. Out of the blue the other day suddenly I started getting this db2jdbc not found in java path error... I figure something I installed hooped my system path. Instead of messing around with why, I figured out that just copying the dll into my CF lib folder fixed the problem. The ...app.DB2Driver is specific to Windows OS I believe. The username and password are usually a Windows account... probably best to use the account that has admin rights to db2 at least until you get it working.

I also did spend a good chunk of time trying to get the built in driver to talk to db2... I personally had no luck whatsoever, and I have read in other places that the drivers generally are only backwards compatible... the driver that ships with CF is definitely not v9, I think it might be v7... not sure though. I did test the backward compatibility claim by using the v9.1 jdbc driver against a db2 v8.x datasource and it worked fine.

DoubleClickEnabled & other little Flex tips

Want to code against double clicks in Flex? Don't forget to set this property to true!

Adding child objects to a canvas or some other container and need to keep the scroll moving down so users can see the new objects? Put this in a function:
canvas.verticalScrollPosition = canvas.maxVerticalScrollPosition + 45;
And remember to CallLater, this gives Flex a chance to lay the new object out and get you the right maxVerticalScrollPosition!

Are those child objects some kind of data entry control? Attach an event listener to the event
FocusEvent.FOCUS_IN for the object and point it to a function that does this:
private function test(event:FocusEvent):void{
canvas.verticalScrollPosition = event.currentTarget.y - canvas.height + 45;
}


Having issues with the custom Item Renderer in your TileList not updating when the underlying data changes? Make sure you're not using an ArrayCollection, it doesn't quite work every time.

Pulling data from a .Net webservice and having to parse out the date from the xml with your own Date Utility? Just remember that in AS3 months, hours, minutes and seconds are 0 based... in other words, October is month 09, not 10. That took me a while to clue in to.

Want to display hierarchical data relationships in a tree view? Stick the child objects in an arraycollection and assign it to a property called children in the parent object. This can be repeated down the chain as often as you like but remember it can get a bit slow quickly. Then point the Tree dataProvider property at the topmost object of the hierarchy.

Sometimes building a Flex front end on a .Net webservice back end can get complicated, especially when the developer (me) doesn't have access to the webservice. If the .Net webservice is designed right, simply taking the resulting xml of the webservice calls and using HttpService calls to load the xml works. When it comes time to use the webservice directly, simply replace the HttpService calls with WebService calls. Works like a charm!

Here's something silly. I stuck aTabNavigator in my view, and tried to capture the click event on the tabs. I tried pretty much every event I could find but not a single one would trigger when clicking on the tab. I replaced the tabNavigator with a TabBar and a ViewStack, and then was able to trap the tabIndexChange event to get the desired result.

Not my usual blog style but things are pretty busy these days. I hope to blog more again in the new year.

Thursday, December 07, 2006

Autosizing Datagrid with Horizontal Scrolling

Today I was tasked with creating a spreadsheet like datagrid, one with more columns than what the available real estate would be able to show. The datagrid was to always use the maximum screen space available so that for those users with huge budgets and subsequently huge monitors would not need to scroll. For all other, more mortal users, the datagrid should simply scroll off to the right, with a nice horizontal scroll bar.

I figured this would be easy. Turns out it's a bit tricky. If I set the maxWidth of my datagrid to something smaller than the sum of my column widths, the desired behaviour would occur. I'd get a nice horizontal scroll bar and the columns would span out to the right. The problem with maxWidth is that it does not accept percentage values. So if I want the datagrid to use up 100% of the width of the parent container, I can't use maxWidth.

I tried sticking the datagrid in a canvas, thinking i'd be able to use the scrolling of the canvas to quasi hover/move over the datagrid rendered in its entirety underneath, but the vertical scrolling didn't really work out well. Perhaps someone else might have more luck with this approach. I kinda wanted to get back to using the scrollbars in the datagrid, as it seemed that this HAD to be possible!

I figured the solution had to lie in maxWidth. I put the datagrid inside a canvas and made the canvas size percentage based. I then, in a resize() function triggered by creationComplete of the app, assigned the value of the width of the canvas to the maxWidth property of my datagrid. This seemed to work. I also tried to have the resize event of the app call this function instead but I'd get null errors. Turned out I needed to assign the event listener on creationComplete.

Everything looked good, the datagrid was resizing with the canvas. At certain times however the datagrid seemed to lag behind and not fill the canvas properly. Using callLater fixed this. Here's the Demo. Right click on the demo to get the source.

Tuesday, December 05, 2006

Many-to-Many using FDS & Hibernate

Douglas McCarroll recently sent me an email asking if I'd had any luck getting Hibernate, Hibernate Tools for Eclipse and FDS to work with many to many relationships. I quickly threw together an example that I had built for a client a couple weeks ago, but in that particular case it wasn't a true many-to-many... I had a student, course and registration table but my registration table had it's own unique ID so really, it was just a combination of 2 one-to-many relationships. For most situations this would probably work however, as he pointed out, some clients might already have a db in place and would be unwilling to make a change just to accommodate the poor starving flex programmer.

Now back in the summer I did attempt to get a true many-to-many relationship to work using Hibernate Tools. What I didn't realize at the time is that the tools didn't yet support this feature. Then i saw this link and decided to give it another go.

I will be using mysql for this example. It is based loosely off the crm db that ships with FDS. Here's the scripts for the 3 tables in play:

CREATE TABLE  `crm`.`employee` (
`id` int(10) unsigned NOT NULL auto_increment,
`First_Name` varchar(45) NOT NULL default '',
`Last_Name` varchar(45) NOT NULL default '',
`Email` varchar(45) NOT NULL default '',
`Phone` varchar(45) NOT NULL default '',
`Title` varchar(45) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `crm`.`event` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(200) NOT NULL default '',
`eventDate` datetime NOT NULL default '0000-00-00 00:00:00',
`eventTime` varchar(45) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `crm`.`eventemployee` (
`eventID` int(10) unsigned NOT NULL default '0',
`employeeID` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`eventID`,`employeeID`),
KEY `FK_eventemployee_1` (`employeeID`),
CONSTRAINT `FK_eventemployee_1` FOREIGN KEY (`employeeID`) REFERENCES `employee` (`id`),
CONSTRAINT `FK_eventemployee_2` FOREIGN KEY (`eventID`) REFERENCES `event` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
As you can see I have 3 tables, an Employee table, an Event table and an EventEmployee table. The EventEmployee table only has 2 fields, each a foreign key to the corresponding table.

The next step was to ensure I had the right version of the Hibernate Tools installed in Eclipse (3.2.0.beta7 or greater). I'm not going to spend any time on explaining how to configure the Hibernate Tools.. needless to say I got the Tool configured to reverse-engineer my db and it created these .hbm.xml files:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 5, 2006 1:55:21 PM by Hibernate Tools 3.2.0.beta8 -->
<hibernate-mapping>
<class name="samples.crm.Employee" table="employee" catalog="crm">
<comment></comment>
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native" />
</id>
<property name="firstName" type="string">
<column name="First_Name" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="lastName" type="string">
<column name="Last_Name" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="email" type="string">
<column name="Email" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="phone" type="string">
<column name="Phone" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="title" type="string">
<column name="Title" length="45" not-null="true">
<comment></comment>
</column>
</property>
<set name="events" inverse="true" table="eventemployee">
<key>
<column name="employeeID" not-null="true">
<comment></comment>
</column>
</key>
<many-to-many entity-name="samples.crm.Event">
<column name="eventID" not-null="true">
<comment></comment>
</column>
</many-to-many>
</set>
</class>
</hibernate-mapping>

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 5, 2006 1:55:21 PM by Hibernate Tools 3.2.0.beta8 -->
<hibernate-mapping>
<class name="samples.crm.Event" table="event" catalog="crm">
<comment></comment>
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native" />
</id>
<property name="name" type="string">
<column name="name" length="200" not-null="true">
<comment></comment>
</column>
</property>
<property name="eventDate" type="timestamp">
<column name="eventDate" length="19" not-null="true">
<comment></comment>
</column>
</property>
<property name="eventTime" type="string">
<column name="eventTime" length="45" not-null="true">
<comment></comment>
</column>
</property>
<set name="employees" inverse="true" table="eventemployee">
<key>
<column name="eventID" not-null="true">
<comment></comment>
</column>
</key>
<many-to-many entity-name="samples.crm.Employee">
<column name="employeeID" not-null="true">
<comment></comment>
</column>
</many-to-many>
</set>
</class>
</hibernate-mapping>
I also configured the Tools to create my domain code. What I found encouraging is that it only created the Employee.java and the Event.java classes, no EventEmployee.java class. Things were looking up! Ok, now it was time to configure the corresponding destinations in my data-management-config.xml file. I took a bit of an educated guess here, since the fds documentation surrounding the metadata tags is pretty weak... lucky for me I got it first try - I guess I spend way too much time here :)
<destination id="crm.employee.hibernate">
<adapter ref="java-dao" />
<properties>
<use-transactions>true</use-transactions>
<source>flex.data.assemblers.HibernateAssembler</source>
<scope>application</scope>
<metadata>
<identity property="id"/>
<many-to-many property="events"
destination="crm.event.hibernate" lazy="false" />
</metadata>
<network>
<session-timeout>20</session-timeout>
<paging enabled="false" pageSize="10" />
<throttle-inbound policy="ERROR" max-frequency="500"/>
<throttle-outbound policy="REPLACE" max-frequency="500"/>
</network>
<server>
<hibernate-entity>samples.crm.Employee</hibernate-entity>
<fill-configuration>
<use-query-cache>false</use-query-cache>
<allow-hql-queries>true</allow-hql-queries>
</fill-configuration>
</server>
</properties>
</destination>

<destination id="crm.event.hibernate">
<adapter ref="java-dao" />
<properties>
<use-transactions>true</use-transactions>
<source>flex.data.assemblers.HibernateAssembler</source>
<scope>application</scope>
<metadata>
<identity property="id"/>
<many-to-many property="employees"
destination="crm.employee.hibernate" lazy="false" />
</metadata>
<network>
<session-timeout>20</session-timeout>
<paging enabled="false" pageSize="10" />
<throttle-inbound policy="ERROR" max-frequency="500"/>
<throttle-outbound policy="REPLACE" max-frequency="500"/>
</network>
<server>
<hibernate-entity>samples.crm.Event</hibernate-entity>
<fill-configuration>
<use-query-cache>false</use-query-cache>
<allow-hql-queries>true</allow-hql-queries>
</fill-configuration>
</server>
</properties>
</destination>
Finally in my flex application I built my correponding .as classes:
package samples.crm
{
import mx.collections.ArrayCollection;
[Managed]
[RemoteClass(alias="samples.crm.Event")]
public class Event{
public function Event() {}
public var id:int;
public var name:String;
public var eventDate:Date;
public var eventTime:String;
public var employees:ArrayCollection;
}
}

package samples.crm
{
import mx.collections.ArrayCollection;
[Managed]
[RemoteClass(alias="samples.crm.Employee")]
public class Employee {
public function Employee() {}
public var id:int;
public var firstName:String;
public var lastName:String;
public var title:String;
public var phone:String;
public var email:String;
public var events:ArrayCollection;
}
}
Then I just created a quick application with 2 datagrids: one id="dg" with a dataProvider="{employees}" and the other id="childDg" with a dataProvider="{dg.selectedItem.events}" - by setting lazy to false on my many-to-many relationship in the destination, it's guaranteed that the child records will load automatically. You could also turn this around... create a datagrid that points to {events} and have the other point to {dg.selectedItem.employees}. By the time you get to this point you can do pretty much anything anyway. The real trick was getting here!

Tuesday, November 28, 2006

How to keep db connections alive in FDS/Hibernate

Struggled with a good title on that one. Anyway... here's the deal. I noticed a while back that if i left my fds server running over night, and in the morning tried to open the sample CRM application, well, it would fail. Fail so badly in fact I'd have to restart or redeploy to get it working again. I'd get an error in the fds log that looked something like this
SEVERE: Communications link failure due to underlying exception:
** BEGIN NESTED EXCEPTION **
java.net.SocketException
MESSAGE: Software caused connection abort: socket write error
Communications link failure due to underlying exception:
...

So yesterday I decided to see if I could fix this. It turned out to be a very very long day. Here are the lessons I learned.

Lesson 1: By default, mySQL terminates db connections after 8 hours of inactivity
Here are the default mySQL settings:

#The number of seconds the server waits for activity on a connection before closing it
wait_timeout=28800
#The number of seconds the server waits for activity on an interactive connection before closing it.
interactive_timeout=28800
For the remainder of my testing I changed these values to 300 in the my.ini file so that my connections would close after only 5 minutes of activity. A much shorter timeframe for testing! So the initial problem is that once mySql closes down the connection, Hibernate does not natively manage this connection and reopen it. If you connect directly with a jdbc connection you may run into the same issue. The trick is to use some kind of connection pool manager to keep the connections alive... the question remains, how can we achieve this?

Lesson 2: Using App Server JNDI datasources gives you connection pooling
In jrun or jboss, it is possible to create jndi datasources. These will then be managed by the application server. I created a jndi datasource for the crm database that comes with FDS which I recreated a while back in mySQL. Creating this datasource involved adding the following configuration to my jrun-resources.xml in the \fds2\JRun4\servers\default\SERVER-INF folder:
<data-source>
<dbname>crm</dbname>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://127.0.0.1:3306/crm</url>
<username>username</username>
<password>password</password>
<encrypted>false</encrypted>
<encryption-class>jrun.security.JRunCrypterForTwofish</encryption-class>
<native-results>true</native-results>
<remove-on-exceptions>true</remove-on-exceptions>
<pool-statements>true</pool-statements>
<initial-connections>1</initial-connections>
<connection-timeout>1200</connection-timeout>
<pool-retry>30</pool-retry>
<transaction-timeout>20</transaction-timeout>
<cache-enabled>false</cache-enabled>
<cache-size>5</cache-size>
<cache-refresh-interval>30</cache-refresh-interval>
<jndi-name>crm</jndi-name>
<poolname>Pool</poolname>
<minimum-size>0</minimum-size>
<maximum-size>2147483647</maximum-size>
<user-timeout>20</user-timeout>
<skimmer-frequency>420</skimmer-frequency>
<shrink-by>5</shrink-by>
<maximum-soft>true</maximum-soft>
<debugging>false</debugging>
<disable-pooling>false</disable-pooling>
<description />
<isolation-level>READ_COMMITTED</isolation-level>
</data-source>
The key thing to note here is the jndi-name I've given this datasource. In Jboss I added a file called mysql-ds.xml to my deploy folder which looks like this:
<datasources> 
<local-tx-datasource>
<jndi-name>crm</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/crm</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>user</user-name>
<password>password</password>
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<idle-timeout-minutes>4</idle-timeout-minutes>
</local-tx-datasource>
</datasources>
I've kept the jndi names the same in both jrun and jboss so that it's easy for me to deploy my apps from one to the other. I ran some tests after I did this. Hibernate - still using it's own connection - would still fail permanently after the connection timed out. Yet the crm app that was configured to use jdbc/jndi directly (ConnectionHelper.java):
javax.naming.Context context = new javax.naming.InitialContext();
return ((DataSource) context.lookup("java:crm")).getConnection();
would fail once and then reconnect successfully. It looked like I was on the right track, now it was time to reconfigure Hibernate.

Lesson 3: Hibernate is not recommended for connection pooling
I had to configure Hibernate to take advantage of my jndi datasources. Here's what the hibernate.cfg.xml looks like for both jrun and jboss:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:crm</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
[...]
</session-factory>
</hibernate-configuration>
I saved my changes, restarted the server, ran my app, waited for the timeout, ran the app again, and even though it hiccupped on the first try, the connection was reestablished on the second try. But why this initial hiccup? Shouldn't there be some way to configure it so that even this initial error can be avoided? I still had much work to do!

Lesson 4: Make sure to have the latest mysql jdbc drivers installed
It turns out that Jboss jndi datasources can be configured to actively ping db connections and keep them alive. Yet I kept getting errors after configuring my datasource to do this 'pinging', and it turned out I was running older jdbc drivers. I upgraded to the latest ones and the errors went away. Here's the jboss datasource file with the ping features enabled:
<datasources> 
<local-tx-datasource>
<jndi-name>crm</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/crm</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>user</user-name>
<password>password</password>
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<idle-timeout-minutes>4</idle-timeout-minutes>
<exception-sorter-class-name>
com.mysql.jdbc.integration.jboss.ExtendedMysqlExceptionSorter
</exception-sorter-class-name>
<valid-connection-checker-class-name>
com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker
</valid-connection-checker-class-name>

</local-tx-datasource>
</datasources>
Since my production environment is Jboss, and it had already been a long day, I decided to not worry about figuring out how to do the same in JRun. If anyone out there knows this, please leave a comment!

Lesson 5: Hibernate must be told that it's connection is now being managed
At this point i was able to retrieve data without error, despite connection time outs. When I went to update a record, however, I got a cool new error:
java.sql.SQLException: You cannot commit during a managed transaction!
So, I did some digging and changed my hibernate.cfg.xml to this:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:crm</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="transaction.manager_lookup_class">
org.hibernate.transaction.JBossTransactionManagerLookup
</property>

[...]
</session-factory>
</hibernate-configuration>
As you can see this is specific to Jboss. There are several other TransactionManagerLookups available that you can read about here

Summary
I spent alot of time on this yesterday, so this blog might seem a bit scarce and a bit rushed. Basically all the configuration files are here for you to use and customize. If you have questions, please ask. Again, this is blogged here for my benefit as well as yours. I would not want to have to redo this from scratch 6 months from now!

Thursday, November 23, 2006

Installing FDS with JBoss and IIS

Here's the initial steps.


  1. Download Jboss and run locally on my dev box. Install Flex Data Services. Unzip the FDS Admin, Flex and Samples files into folders of the same name with a .war extension, and drop these into the C:\Java\Jboss4\server\default\deploy folder - I find unzipping them first let's me work on the files more easily.

  2. Configure mySql support. Drop a copy of the mysql-connector.jar in the C:\Java\Jboss4\server\default\lib folder. Create a datasource xml file called mysql-ds.xml in the C:\Java\Jboss4\server\default\deploy folder, looks like this:
    <datasources> 
    <local-tx-datasource>
    <jndi-name>crm</jndi-name>
    <connection-url>jdbc:mysql://localhost:3306/crm</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>user</user-name>
    <password>password</password>
    </local-tx-datasource>
    </datasources>

  3. Test the sample apps locally. This included Hibernate apps and all the sample apps. Also, a little flex app I wrote that uses remote calls and javamail to send email notifications.

Once I was confident that Jboss was running all the flex apps flawlessly, the next challenge was figuring out how to get jboss to run as a windows service. I downloaded the zip from this website, ensured that my JAVA_HOME and JBOSS_HOME system variables were set properly, namely without a trailing backslash, then ran "install default" from a command line. It successfully created a JBoss service, which worked great. I was now ready to move my stuff to my IIS Server.

I basically zipped up the jboss and javaservice folders and ftp'ed them up to the server. While it was uploading I installed the Java SDK, mySql, mySqltools and FDS on the server, in the same folders as on my local dev box. I setup my system variables JAVA_HOME and JBOSS_HOME, copied my data over to the mySQL data folder on the server. When the upload was done I unzipped my jboss and javaservice folders into my java folder, again keeping it exactly like my local install configuration. I ran jboss manually and tested all the sample apps. Then I installed it as a service, ran it and tested again.

The next test was hitting the sample applications from the web, outside my firewall. I could get the apps to launch however I was not getting any kind of connection... probably because my ISP blocks most ports by default (/sigh), including the default rtmp ports defined in the services-config.xml. So I went into the configuration files and added my-http to all the channels as the failover second channel. I restarted jboss just to be safe ( I find that hotdeployment on jboss seems to randomly end in out of memory errors!) and the apps did start working.

Now all the samples apps are setup by default to compile on the server - but my goal was to host the flex apps in IIS and not the app server. To test this, I had written another flex application that was set to compile locally in Flexbuilder. The advantage of this approach was that technically the swf files generated should be hostable on any webserver. On my local machine the app ran fine, but copying it up to the server gave me a channel connect error. The culprit here was the services-config.xml. By default the port used by any flex app is the same port the app is being served up on uri="http://{server.name}:{server.port}/{/{context.root}/messagebroker/http". On my machine IIS is running on 80, and jboss is configured to listen on 8800. I hardcoded the config by replacing {server.port} with 8800, recompiled my app and then uploaded it to my IIS folder. And that did the trick!