Server woes

Bah. I stupidly put a simple password on a temporary user I was setting up for someone to ftp to my server and it happened to be just the right timing for some script kiddie to be running brute force dictionary attacks across my ip range. I’ve had to rebuild the whole darn thing – lesson learned I suppose. Thank god with CentOS and yum it was very very easy to get back up and running! I didn’t lose any data, so that’s a plus.

More bloggage coming soon!

Post to Twitter Tweet This Post

Frustration

I just spent ages searching for this for someone, and thought I might as well post it here. This is how I often feel reading the threads on cfaussie…

Post to Twitter Tweet This Post

CF Startup and shutdown on OSX

There’s a simple and easy way to control CF instances on OSX (or flex / jrun for that matter). Get a little program called Lingon.

  1. Once installed, run it and click the “New” button (top left).
  2. Choose “Users Daemons”
  3. On the resulting screen, set the “Label” field as something like “CFMX Service”
  4. Use the plus icon to the right of the “Program Arguments” box to add the following arguments. These arguments should be in order, and each should be added as a seperate entry in the list (by clicking the little plus button again):
    1. /System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home/bin/java
    2. -server
    3. -Djava.awt.headless=true
    4. -jar
    5. /Applications/JRun4/lib/jrun.jar
    6. -start
    7. cfusion
  5. Cick “Just save” or “Save and Load” and you’re done.

As you might imagine, you can change the last argument from “cfusion” to “flex” or whatever you named the instance of your FDS setup to create a service for starting your flex instance.

Post to Twitter Tweet This Post

Flex goes open source!

Wow. look at Ted Patrick’s blog – Flex is being made open source. What a brilliant decision! The guys on the team must have done some very hard work to get this to happen, and I am absolutely over the moon – not only does it open flex development to more people it means the potential contributions and leaps and bounds to come from the community could be staggering. It’s a good time to be in development :)

Post to Twitter Tweet This Post

Bender Alpha Release

Finally! Ok you can now download an alpha release of Bender:

Bender Alpha 01 (Bender_alpha01.zip)

Please bear in mind this is a first release, and while it works for me your mileage may vary wildly. I would really appreciate hearing from anyone who had time to try this out, and has bugs, suggestions, corrections or criticisms.

Download the zip file, and expand it into your webroot, or somewhere that a cf mapping points to as “com”. You shouldn’t need to setup anything specific for flex, it will just use the default flash remoting adapters.

The following steps should get you up and running with Bender:

  1. Make sure Transfer release 0.6.3 or higher is installed and working
  2. Make sure /com points to the com directory from the bender zip file
  3. Browse to http://yourserver/com/ls/bender/tools/BenderClassGenerator.cfm
  4. Change the paths to match your settings and submit the form – this will create an actionscript class file for each definition in your Transfer.xml
  5. I haven’t made the auto compiled swc bit yet, so for the moment, make sure you drop these class files into your flex application, along with com/ls/bender/as/Bender.as and import them in your code
  6. In your flex code you can now run operations like: bender.read(oUser, “User”, userPK);

Operations:

bender.read(Class class, String TransferClassName, String primaryKey);

The first parameter for a read is an already instantiated object – one of the classes bender generated for you. You pass this in, and when the read is complete the object will contain the relevant data.
The second parameter is the name of the object definition – the “class” attribute in your Transfer.xml
The third is the unique identifier you’d normally use to retrieve that object via transfer.

make whatever changes you like to the data in the As objects then use:

bender.save(Class class);

just pass in the actionscript instance and this will automatically save back to the db.

MAPPINGS
You can map functions to a custom CFC if you choose. For example, if you wanted a UserManager.cfc to do something with the user objects before they went to Transfer for saving, you could create a Bender.xml like so:

<?xml version="1.0" encoding="UTF-8"?>
<bender>
	<mappings>
		  <mapping action="save" mapobject="UserManager" mapmethod="save" />
	</mappings>
</bender>

This will mean that when you use bender.save(myObj) in flex, the data will go to Bender on the coldfusion side and get translated into an appropriate TransferObject which will then be passed on to UserManager.save(TO). If you don’t create a mapping for a particular method and class, it defaults to sending straight to Transfer.

CAVEATS:
I had some issues in actionscript using methods named get and delete, so in Bender you call bender.read() and bender.del() instead. This may be something I did wrong and if so I’ll fix it – but for the moment that’s how it works.

Delete works the same way as save.

EVENTS
When you have instantiated one of the bender-generated actionscript classes, you can set oMyObj.OLNDATA() and oMyObj.ONFAULT() to provide functions to which Bender will map upon data population or fault. For example, if you want a message shown to the user when you save something, you could do this:

public function showResults(obj : *) : void {
result_text.text = "Save Successful";
}
public function showFailure(obj : *) : void {
result_text.text = "Save Failed";
}
oObj.ONDATA(showResults);
oObj.ONFAULT(showFailure);
bender.save(oObj);

Please download the code and take a look – and I’d love to hear from anyone on ways to improve it. Let me know what works and what doesn’t and contact me on this blog or on skype / icq / email etc etc.

P.S. I haven’t added the SWC generation yet, but am working on it now and hopefully that will make integration of auto-generated as classes and the bender code into your flex app much easier. If anyone has any preferences or suggestions for this bit, please let me know.

Post to Twitter Tweet This Post

Mind-Bender

A quick update – the Bender alpha release *is* imminent, but I got held up on some recursive code that was being obstreperous and I’m having to rethink the way I was handling cfc mappings.

I wanted to set it up so you could instantiate Bender and pass it in all the cfc’s you want to map to, or be able to setup a ColdSpring definition with the relevant auto-wiring, but I realised that the flex component is talking directly to the bender component so this wouldn’t work.

What I’m doing now is creating BenderService.cfc – a facade that manages the instantiation of Bender. You change the instance code at the top of this cfc to however you want to instantiate bender (EG a CS getBean() call) and the flex component talks to BenderService.cfc, which just passes traffic back and forth to Bender.cfc.

At the moment I’m wrestling with the best way to make it all happen, trying to pass in instantiated components and have them stored – it’s getting there but a few finicky details with unnamed arguments etc are causing me grief. More as soon as it comes.

PS. btw any comments on this would be greatly appreciated – any suggestions on how best to set it up, especially from anyone who knows coldspring would be fantastic.

Post to Twitter Tweet This Post

Muppet on Wheels

Wahey! My little boy just took his first solo bike ride, and did a brilliant job of it :) He had an absolute ball, and it was a real thrill to watch.

I knew it was a special sort of moment but it was very heart in mouth when he first did it, even a short distance. As soon as he did it once he was insatiable – it was “right get off me, I’m riding”.

I’m so proud!

Post to Twitter Tweet This Post

Bender has deep composition

Ok just a quick update for those who care – Bender now has working deep recursions on composite objects in both directions. This means if you have for example a manytomany in your object definition, and within the objects that get put into that array you *also* have a manytomany definition (etc etc ad nauseum) all will be retrieved and translated back and forth between AS and coldfusion.

If this makes no sense, feel free to ignore – but composite objects work properly now :) (both struct and array collections).

I’m now moving on to the mapping of custom CFCs – more updates to come.

Post to Twitter Tweet This Post

Bender Code Preview

Well we’re still a ways from havinga working prototype but I thought I’d post some code to give people an idea of where I’m heading (and to get Mark off my back ;p). Much thanks go Justin McLean and Robin Hilliard for answering stupid questions.

here is the current Bender.as:

package com.ls.bender {
 
	public class Bender {
		import mx.rpc.events.ResultEvent;
		import mx.rpc.events.FaultEvent;
		import mx.rpc.remoting.RemoteObject;
 
		private var Remote : RemoteObject = new RemoteObject();
		private var RequestQueue : Object = new Object();
 
		public function Bender() {
			Remote.destination = "ColdFusion";
			Remote.source = "com.ls.bender.Bender";
		}
 
		public function read(obj : *, classname : String, key : String) : * {
			if (obj.getONDATA()) {
				Remote.getOperation("read").addEventListener(ResultEvent.RESULT, readResultHandler);
			}
			if (obj.getONFAULT()) {
				Remote.getOperation("read").addEventListener(FaultEvent.FAULT, readFaultHandler);
			}
			RequestQueue[Remote.read(classname, key)] = obj;
		}
 
		private function readResultHandler(event : ResultEvent) : void {
			RequestQueue[event.token].fill(event);
			RequestQueue[event.token].getONDATA()(RequestQueue[event.token]);
		}
 
		private function readFaultHandler(event : FaultEvent) : void {
			RequestQueue[event.token].getONFAULT()(RequestQueue[event.token]);
		}
 
		public function save(obj : Object) : void {
			if (obj.getONDATA()) {
				Remote.getOperation("save").addEventListener(ResultEvent.RESULT, saveResultHandler);
			}
			if (obj.getONFAULT()) {
				Remote.getOperation("save").addEventListener(FaultEvent.FAULT, saveFaultHandler);
			}
			var args : Object = new Object();
			args.OBJECT = obj.data();
			RequestQueue[Remote.save(args)] = obj;
		}
 
		private function saveResultHandler(event : ResultEvent) : void {
			RequestQueue[event.token].getONDATA()(RequestQueue[event.token]);
		}
 
		private function saveFaultHandler(event : FaultEvent) : void {
			RequestQueue[event.token].getONFAULT()(RequestQueue[event.token]);
		}
 
		public function del(obj : Object) : void {
			if (obj.getONDATA()) {
				Remote.getOperation("del").addEventListener(ResultEvent.RESULT, delResultHandler);
			}
			if (obj.getONFAULT()) {
				Remote.getOperation("del").addEventListener(FaultEvent.FAULT, delFaultHandler);
			}
			var args : Object = new Object();
			args.OBJECT = obj.data();
			RequestQueue[Remote.del(args)] = obj;
		}
 
		private function delResultHandler(event : ResultEvent) : void {
			RequestQueue[event.token].getONDATA()(RequestQueue[event.token]);
		}
 
		private function delFaultHandler(event : FaultEvent) : void {
			RequestQueue[event.token].getONFAULT()(RequestQueue[event.token]);
		}
	}
}

Here is Bender.cfc:

<!--- $ID $
 
	author : 		Toby Tremayne (toby@lyricist.com.au)
	written: 		2007-04-08"
	description: 	Creates a bridge between standard Transfer CRUD methods and actionscript objects
 
	Copyright 2007 Lyricist Software
 
	Unless required by applicable law or agreed to in writing, this software
	distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
	KIND, either express or implied.
 
	All rights to this code belong to Lyricist Software
 
	$Header $
 --->
<cfcomponent name="Bender" displayName="Bender : Transfer-ActionScript Bridge" hint="serves as a bridging service between ActionScript and Transfer">
 
	<cfset variables.instance = structNew() />
	<cfset variables.instance.Transfer = createObject("component", "transfer.TransferFactory").init("Datasource.xml", "Transfer.xml", "/Transfer/resources/definitions").getTransfer() />
	<cfif not isDefined("application")><cfapplication name="Bender" sessionManagement="true" /></cfif>
 
	<cffunction name="init" access="public" returntype="com.ls.bender.Bender" hint="default constructor">
		<cfreturn this />
	</cffunction>
 
	<cffunction name="get" access="remote" returntype="any" hint="">
		<cfargument name="class" type="string" required="true" />
		<cfargument name="key" type="any" required="true" />
 
		<cfscript>
			var oObj = instance.Transfer.get(arguments.class, arguments.key);
			stTemp = oObj.getMemento();
			stTemp.ASCLASSNAME = "com.ls.bender.transferobjects.#listLast(replace(oObj.getClassName(), """", "", "all"), ".")#";
			return stTemp;
		</cfscript>
	</cffunction>
 
	<cffunction name="save" access="remote" returntype="void" hint="">
		<cfargument name="object" type="any" required="true" />
 
		<cfscript>
			var key = getObjectPrimaryKeyValue(arguments.object);
			var oObj = instance.Transfer.get(arguments.object.className, key);
			var omd = instance.Transfer.getTransferMetaData(arguments.object.className);
			var iterator = omd.getPropertyIterator();
 
			writeoutput(key);
			// loop over the properties
			while (iterator.hasNext()) {
				name = iterator.next().getName();
				writeoutput("oObj.set#Name#(""#arguments.object["#name#"]#"")<br>");
				// copy the data from the struct we received into the Transfer Object
				evaluate("oObj.set#name#(""#arguments.object["#name#"]#"")");
			}
			name = omd.getPrimaryKey().getName();
			// set the primary key
			evaluate("oObj.set#name#(""#arguments.object["#name#"]#"")");
			// save the changes
			instance.Transfer.save(oObj);
		</cfscript>
	</cffunction>
 
	<cffunction name="del" access="remote" returntype="void" hint="">
		<cfargument name="object" type="any" required="true" />
		<cfscript>
			var key = getObjectPrimaryKeyValue(arguments.object);
			var oObj = instance.Transfer.get(arguments.object.className, key);
			instance.Transfer.delete(oObj);
		</cfscript>
	</cffunction>
 
	<cffunction name="getObjectPrimaryKeyValue" access="private" returntype="any" hint="">
		<cfargument name="object" type="any" required="true" />
		<cfscript>
			// get the TO metadata
			var omd = instance.Transfer.getTransferMetaData(arguments.object.className);
			if (isStruct(arguments.object)) {
				return arguments.object[omd.getPrimaryKey().getName()];
			} else {
				// set the primary key
				return evaluate("oObj.get#name#(""#arguments.object["#name#"]#"")");
			}
		</cfscript>
	</cffunction>
 
</cfcomponent>

and here is a sample autogenerated class for a “User” object (User.as):

package com.ls.bender.transferobjects {
	public class User {
 
		import mx.rpc.events.ResultEvent;
 
		private var userUUID : String;
		private var firstName : String;
		private var lastName : String;
		private var emailAddress : String;
		private var password : String;
		private var userToRole : Array;
		private var TransferClassName : String = "mglogin.User";
		private var ASClassName : String = "com.ls.bender.transferobjects.User";
		private var onData : Function;
		private var onFault : Function;
 
		public function User() {
			setUSERUUID("");
			setFIRSTNAME("");
			setLASTNAME("");
			setEMAILADDRESS("blah@blah.com");
			setPASSWORD("");
			setUSERTOROLE(new Array);
		}
 
		public function setUSERUUID(userUUID : String) : void {
			this.userUUID = userUUID;
		}
 
		public function getUSERUUID() : String {
			return this.userUUID;
		}
 
		public function setFIRSTNAME(firstName : String) : void {
			this.firstName = firstName;
		}
 
		public function getFIRSTNAME() : String {
			return this.firstName;
		}
 
		public function setLASTNAME(lastName : String) : void {
			this.lastName = lastName;
		}
 
		public function getLASTNAME() : String {
			return this.lastName;
		}
 
		public function setEMAILADDRESS(emailAddress : String) : void {
			this.emailAddress = emailAddress;
		}
 
		public function getEMAILADDRESS() : String {
			return this.emailAddress;
		}
 
		public function setPASSWORD(password : String) : void {
			this.password = password;
		}
 
		public function getPASSWORD() : String {
			return this.password;
		}
 
		public function setUSERTOROLE(userToRole : Array) : void {
			this.userToRole = userToRole;
		}
 
		public function getUSERTOROLE() : Array {
			return this.userToRole;
		}
 
		public function getCLASSNAME() : String {
			return this.TransferClassName;
		}
 
		public function getASCLASSNAME() : String {
			return this.ASClassName;
		}
 
		public function setCLASSNAME(className : String) : void {
			this.TransferClassName = className;
		}
 
		public function setASCLASSNAME(asclassname : String) : void {
			this.ASClassName = asclassname;
		}
 
		public function setONDATA(func : Function) : void {
			this.onData = func;
		}
 
		public function getONDATA() : Function {
			return this.onData;
		}
 
		public function setONFAULT(func : Function) : void {
			this.onFault = func;
		}
 
		public function getONFAULT() : Function {
			return this.onFault;
		}
 
		public function fill(event : ResultEvent) : void {
			this.setUSERUUID(event.result.USERUUID);
			this.setFIRSTNAME(event.result.FIRSTNAME);
			this.setLASTNAME(event.result.LASTNAME);
			this.setEMAILADDRESS(event.result.EMAILADDRESS);
			this.setPASSWORD(event.result.PASSWORD);
			this.setUSERTOROLE(event.result.USERTOROLE);
			this.setCLASSNAME(event.result.CLASSNAME);
			this.setASCLASSNAME(event.result.ASCLASSNAME);
		}
 
		public function data() : Object {
			var Obj : Object = new Object();
			Obj.userUUID = getUSERUUID();
			Obj.firstName = getFIRSTNAME();
			Obj.lastName = getLASTNAME();
			Obj.emailAddress = getEMAILADDRESS();
			Obj.password = getPASSWORD();
			Obj.userToRole = getUSERTOROLE();
			Obj.ClassName = getCLASSNAME();
			Obj.ASClassName = getASCLASSNAME();
			return Obj;
		}
	}
}

Finally, here’s an example of the use of Bender in mxml. I’m not doing anything clever here, just a simple get and a delete, but hopefully it gives you an idea.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:Script>
		<![CDATA[
            import com.ls.bender.transferobjects.User;
            import com.ls.bender.Bender;
 
			private var oUser : User = new User();
			private var bender : Bender = new Bender();
 
			public function saveIt(e : Event) : void {
				oUser.setONDATA(showResults);
				bender.read(oUser, "mglogin.User", 'CF47B443-FD21-C25E-910A87B22004637A');
				oUser.setONDATA(showDeleted);
				oUser.setONFAULT(showDeleteFault);
				bender.del(oUser);
			}
 
			public function showDeleted(obj : *) : void {
				result_text.text = "Deleted";
			}
 
			public function showDeleteFault(obj : *) : void {
				result_text.text = "Delete Failed";
			}
 
			public function showResults(obj : *) : void {
				result_text.text = obj.getEMAILADDRESS();
			}
		]]>
	</mx:Script>
 
	 <mx:Panel title="RPC Test" paddingTop="20" paddingLeft="20" paddingBottom="20" paddingRight="20">
        <mx:TextArea x="10" y="36" width="319" height="113" id="result_text"/>
        <mx:Button x="250" y="157" label="saveObject" width="79" click="saveIt(event);"/>
    </mx:Panel>
</mx:Application>

All comments are welcome – it works with what you see here, but various things haven’t been done yet. No list functions are in there, complex properties aren’t being dealt with yet, mapping to custom cfcs is not done yet. Now that I have the basic concept working, I am going to expand the cfc next to handle validation returns and custom cfcs.

Feedback would be appreciated!

Post to Twitter Tweet This Post

Bender Explained

Ok Bender seems to be generating some interest and I’ve found that a lot of people aren’t really understanding my explanation of what it’s supposed to achieve, so here’s an attempt at a clearer definition.

If you’re writing a flex app with database connectivity, your workflow should look like this:

  1. Install Transfer on server
  2. Write Transfer.xml
  3. Install Bender on Server
  4. Run Bender AutoGenerating Tool
  5. Include the resulting SWC library in your Flex App
  6. Instantiate the generated classes in your flex app, fill ‘em with data, and make calls to Bender as you would to Transfer – for example Bender.save(myProductClass)

That’s it – nothing more complicated. The primary idea is to give flex developers a seamless access to the benefits of database persistence via Transfer. In theory, if you can setup Transfer, you can add db persistence to your flex app with Bender, without any CF knowledge.

Bender itself is comprised of 3 components – Bender.cfc, Bender.as and BenderAutoGenTool.cfm. Bender.as gets compiled into a swc along with classes for all your objects defined in the Transfer.xml. Bender.cfc sits on the server.
Looks like this:

Here are a couple of examples of how something might happen:

Creating and Saving an object:

The steps in this workflow are as follows (assuming Product.class is one of the autogenerated actionscript classes):

  1. Instantiate Product.class and use setters to fill with data
  2. Call Bender.save(myProduct);
  3. Bender makes a RemoteObject call to the Bender.cfc save() method, passing the actionscript class, which Flash Remoting will convert into arrays and structures
  4. Bender.cfc looks at the received data, determines which TransferObject it needs and instantiates the relevent TransferObject
  5. Bender loops through the data received, calling matching setters on the new TransferObject
  6. We check to see if we have a specific hander (CFC and method name) for save() on Product objects
  7. If we don’t have a specific handler registered then we pass directly to Transfer itself, using Transfer.save(ProductTransferObject) – END
  8. If we DO have a specific handler registered, we pass the TransferObject to that cfc and method instead, for example ProductManager.save(ProductTransferObject)
  9. ProductManager.cfc does it’s thing, making any required manipulations to the ProductTransferObject
  10. ProductManager.cfc then passes to Transfer using Transfer.save(ProductTransferObject)

Before anyone asks, in the above example I haven’t bothered setting out the steps for the case in which ProductManager.cfc does validation and has to pass it back with messages etc, but the ability would be there (I haven’t decided yet on any specific way to handle this, but that will come).

Here’s an example of retrieving an object from the flex app – imagine you have a form in your flex app allowing the user to enter the id number of a product, and you want the app to then retrieve that product.

  1. User enters an ID and requests the product
  2. Call Bender.get(“product”, productID);
  3. Bender makes a RemoteObject call to the Bender.cfc get() method, passing the productID as a parameter, also passing the classname of Product and the action GET
  4. Bender.cfc looks at the received data to determine the Transfer classname we’re looking for
  5. Bender checks to see if there is a specific handler registered for this class and action
  6. If we don’t have a specific handler registered then we pass directly to Transfer itself, using Transfer.get(classname, arguments.productID) – SKIP TO 10
  7. If we DO have a specific handler registered, we pass the cfc and method to that handler instead, for example ProductManager.get(arguments.productID)
  8. ProductManager.cfc does it’s thing, making any required manipulations to the ProductTransferObject
  9. ProductManager.cfc then passes to Transfer using Transfer.get(classname, arguments.productID)
  10. Transfer returns the requested TransferObject back to either Bender.cfc or ProductManager.cfc accordingly (10.5 – PageManager.cfc then passes the Product TransferObject back to Bender.cfc)
  11. Bender.cfc pulls the data out of the TransferObject and puts it into structs and arrays
  12. Bender.cfc passes the resulting struct back to Bender.as as the result of the RemoteObject call
  13. Bender.as takes the received data (which Flash Remoting has converted into a simpe AS Object) and instantiates the required AS Class (IE Product), using the setters on the the newly instatiated Product to pass in the received data then passes the ProductClass back to the Flex app
  14. Whatever handlers are in the flex app for this event are fired and the flex app does it’s thing

Hopefully this all makes a bit more sense. Please bear in mind that it’s not 100% finished yet, and when it is, I’ll be asking for feedback and may change the way some of this works if somebody sees a better way to do it. I hope that for now this will serve as a better explanation of what I’m trying to achieve.

Post to Twitter Tweet This Post