Dec/090
Java Builds Using ANT and Enunciate
In a previous entry I gushed over the features that Enunciate provided. Today, I’m going to walk through how to integrate Enunciate with your ANT script and provide a true Service Oriented Architecture which makes your web services universally accessible. I’ve chosen to write on the integration of Enunciate with ANT as opposed to Maven, because I generally use ANT to create my build scripts.
For the purposes of this example I want to create a simple SOAP service and allow the service to be utilized via AMF so that it is optimized for Flash integration. Let’s see how easy it is to do this.
The first task for using Enunciate to generate your web services, is to make sure that all of your service interfaces and implementations have been given the correct Annotations. For the purposes of this example I’ve created a simple interface, which I’ve included below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.softwarejesus.enunciate.service; import java.util.List; import javax.jws.WebService; import com.softwarejesus.enunciate.vo.SampleVO; @WebService ( targetNamespace = "http://softwarejesus.com/EnunciateSample", serviceName = "sampleService" ) public interface ISampleService { List<SampleVO> getSamples(); SampleVO updateSample(SampleVO sample); } |
The only annotation I had to include was the @WebService annotation. You can read up on the WebService Annotation here. By using the WebService Annotation you have now indicated to Enunciate that this will be a SOAP endpoint.
Here is my service implementation, notice that it too has the WebService Annotation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package com.softwarejesus.enunciate.service; import java.util.List; import javax.jws.WebService; import com.softwarejesus.enunciate.vo.SampleVO; @WebService( endpointInterface="com.softwarejesus.enunciate.service.ISampleService" )public class SampleService implements ISampleService { @Override public List<SampleVO> getSamples() { // TODO Auto-generated method stub return null; } @Override public SampleVO updateSample(SampleVO sample) { // TODO Auto-generated method stub return null; } } |
Once we’ve written our web services, and provided the correct annotations, we’re ready to create our ANT build script. For the purposes of this example, I’m going to add all of the jar files in the {ENUNCIATE_HOME}/lib directory as well as all the jar files in the spring and amf module directories to the ANT build path. Next, we’re going to create the ‘enunciate’ task, which is located in {ENUNCIATE_HOME}/lib/enunciate-core-1.xx.jar.
Now that we’ve configured ANT a bit, let’s write our ANT script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?xml version="1.0" encoding="UTF-8"?> <!--Ant build file for building the sample API.--> <project default="war"> <!-- - - - - - - - - - - - - - - - - - target: enunciate - - - - - - - - - - - - - - - - - --> <target name="enunciate"> <!--define the task...--> <mkdir dir="${basedir}/target/EnunciateSample/"/> <enunciate dir="${basedir}/../src" configFile="${basedir}/enunciate.xml" > <export artifactId="app.dir" destination="target/EnunciateSample/"/> </enunciate> </target> <target name="war" depends="enunciate"> <copy todir="target/EnunciateSample/WEB-INF/lib/"> <fileset dir="../buildLibs/" /> </copy> <war destfile="target/EnunciateSample.war" webxml="target/EnunciateSample/WEB-INF/web.xml"> <fileset dir="target/EnunciateSample" /> </war> </target> </project> |
Let’s look at the ‘enunciate’ task. First, need to point enunciate in the direction of our source code, which is done via the “dir” attribute. Second, you have to create a configuration xml that enunciate will use to determine what will be built.
The export node is important, because it tells enunciate what artifacts we want it to produce. Here’s a full list of the artifacts that you can specify. For this example, I wanted enunciate to give me the “exploded” war file. I don’t want enunciate to generate my war file, because I’ve found that enunciate does not include some jars that are necessary for the deployment to Tomcat 6.
Now let’s look at the configuration file that enunciate uses:
1 2 3 4 5 6 | <enunciate xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.16.xsd"> <modules> <amf disabled="false" swcDownloadable="false" asSourcesDownloadable="true"/> </modules> </enunciate> |
Enunciate has a whole slew of modules that you can incorporate into your build, but for this sample I wanted to keep it as simple as possible. I need to indicate that I want to include AMF for my services, so I add the ‘amf’ node and set the disabled attribute to false. Now, as I mentioned in my earlier post, I haven’t been able to get the swc that enunciate produces to work exactly as I want it to, so I’m going to tell it not to forget the swc, but generate the Action Script source.
I really have enjoyed working with Enunciate thus far, I think it’s a wonderful tool for those who truly believe in a Service Oriented Architecture, because it allows you to provide numerous gateways to the exact same web services. Along with generating all of the endpoints for you, it will, with some work on the developer’s part, generate all of the documentation that any client would need to connect to and utilize the resulting services.
Sample project here.
Nov/090
Quick-Hit: Enunciate
In my most recent work project I was asked to develop a set of middle tier services to allow users to connect to commonly used pieces of data in the organization. As I gathered requirements from different developers that were hoping to utilize these services I found that my standard technology stack would not suffice for a number of reasons. First, the organization I was working with had very strict regulations on technologies which could be used. But most importantly, the vast majority of middle-tier web services I’ve done recently have provided data solely to Flex applications, but in this new project I had a number of different clients wanting to pull data.
I knew immediately that I wanted to write one set of web services but allow access to it via a couple of different gateways: AMF and SOAP. I started by asking around to numerous guys I know to see if they recommended anything that would make the SOAP gateway as easy as possible to write. One article that my coworker Andrew sent me pointed me in the direction of Enunciate.
As I sifted through all of the information in the site, I thought, there’s no way they can provide this much functionality for free, but I have to say, it’s pretty awesome. So what does enunciate do? Well enunciate allows you the ability to write the interfaces and implementations of your web services, add a few choice annotations, and Encunciate will configure all of your SOAP, REST, as well as AMF endpoints. Enunciate does all this via an xml based build script which fits in nicely with both ANT and Maven.
Along with all of the service configurations, Enunciate will also export libraries that different client apps can use to connect to your middle tiers. Enunciate will export a Flash library, Objective C (for you iPhone developers), as well as numerous others. I have not been able to use the swc in my eclipse workspace, but I’ve started exporting the source and modifying it as needed.
Another feature that’s really nice about it enunciate, is that it automatically generates all of the documentation for your web services. It gives detailed information about what is available via REST and SOAP. The AMF documentation is not as detailed as SOAP or REST, which is a little disappointing.
All-in-all this tool has blown my expectations away. My proof of concept was completed in no time at all. I plan to do a full how-to blog entry on Enunciate before the end of the year.
Nov/090
Box 2D Tutorial
If you were out at Adobe Max this year, you might have seen the huge Max Companion walls. It’s a little bit difficult to see in the video, but tweets relating to Adobe Max would come in, they would be loaded and displayed for a few seconds, then they would fall as if gravity was applied to them. The introduction of physics into this application was done via a framework called Box-2D.
What I plan to show you is how to set up a very basic application that has physics applied to it. Setting up your “physical world” is definitely the most difficult part of using Box2D. Here’s the methods I use to create the Box2D world.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public function createWorld(event:Event = null):void { var ab:b2AABB = new b2AABB(); //the bounds of the physics world ab.lowerBound.Set(-200, -200); ab.upperBound.Set(200, 200); var gravity:b2Vec2 = new b2Vec2(0, 4.0); //the constant force of gravity world = new b2World(ab, gravity, false); //create the world createBorderObject(0, this.height, this.width, 100); //floor createBorderObject(0, -this.height, this.width, 100); //ceiling createBorderObject(-100, -this.height, 100, 3*this.height); //left createBorderObject(this.width + 2, -this.height, 100, 3*this.height); //right this.addEventListener( Event.ENTER_FRAME, caluclateWorld ); } private function createBorderObject(xp:Number, yp:Number, w:Number, h:Number):void { //these bodies will prevent objects from getting out of our physics world and being unusable var the_box:b2PolygonDef = new b2PolygonDef(); the_box.SetAsBox(w/physicsScale/2, h/physicsScale/2); the_box.density = 0; the_box.friction = .4; the_box.restitution = 0; var gbd:b2BodyDef = new b2BodyDef(); gbd.position.Set((xp+w/2)/physicsScale, (yp+h/2)/physicsScale); var ground:b2Body = world.CreateBody(gbd); ground.CreateShape(the_box); ground.SetMassFromShapes(); var rect:Canvas = new Canvas(); rect.graphics.beginFill(0xFF00FF, 0); rect.graphics.drawRect(xp, yp, w, h); addChild(rect); } |
In order to create the world you need to set up a few things. The first thing you have to set up is the boundaries of the world, which need to extend beyond what you are actually going to use. The reason it needs to be larger than your play area, is because if any objects exit this region, they’ll become unusable to the physics world. Next you’ll need to create gravity, which doesn’t need to go to the bottom of your screen. You could easily set up gravity to go to the top of the screen or to the side to one side. The last part of the constructor is whether or not you want objects at rest to go to sleep. I’ve always set it to false, but I’m by no means an expert on all of the options.
Within my createWorld method I’ve also created four border objects. These are invisible barriers that prevent objects from reaching my physics border. Lastly, I add an ENTER_FRAME event listener so that every time flash begins to redraw this component it will calculate any changes. What follows is my calculateWorld function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | private function updateWorld(e:Event):void { world.Step((1/30),10); var bodyPos:b2Vec2; var bodyRot:Number; var renderers:ArrayCollection = getActors(); //updates our visual components to coincide with their physics for( var i:Number = 0; i < renderers.length; i++ ) { var renderer:Actor = renderers.getItemAt(i) as Actor; var body:b2Body = renderer.physicsBody; if( body ) { bodyPos = body.GetPosition(); renderer.rotation=0; var m:Matrix=renderer.transform.matrix; m.tx =- renderer.width/2; m.ty =- renderer.height/2; m.rotate( body.GetAngle() ); //m.scale(1,1); // Now set the position to the world position m.tx+=bodyPos.x*physicsScale; m.ty+=bodyPos.y*physicsScale; // ...and set the whole thing at once via the matrix. // ie. Update the sprite. renderer.transform.matrix=m; renderer.scaleX = 1; renderer.scaleY = 1; } } } |
My updateWorld function has a number of steps. First, I tell Box2D to calculate the changes, by passing in the number of refresh frequency (1/30) as well as the number of iterations to calculate. After Box2D has calculated the changes I have to loop through all of the objects in the “world” and update their renderer. The renderers are visual components that are tied to a physics body, which is necessary because the objects in the Box2D are not visible.
For the purposes of my sample, I’ve created a simple renderer which you can see here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.softwarejesus.physics { import Box2D.Dynamics.b2Body; import mx.containers.Canvas; public class Actor extends Canvas { public var physicsBody:b2Body; public var shape:String; public function Actor() { } public function drawShape():void { if (shape == Physics.SQUARE_SHAPE) { this.setStyle("backgroundAlpha", 1); this.setStyle("backgroundColor", 0x000000); } else { this.setStyle("backgroundAlpha", 0); graphics.beginFill(0x000000); graphics.drawCircle(this.width/2, this.height/2, this.width/2); } } } } |
Now that you’ve seen the definition for my renderers, let’s see how objects get added to the world.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | public function addShape():void { if(this.selectedShape == CIRCLE_SHAPE) addBall(); else addBox() } public function addBox():void { var newCanvas:Actor = new Actor(); newCanvas.width = this.diameter; newCanvas.height = this.diameter; newCanvas.x = Math.random() * ( this.width - this.diameter); newCanvas.y = -newCanvas.height; newCanvas.shape = this.selectedShape; newCanvas.drawShape(); var b1:b2PolygonDef = new b2PolygonDef(); b1.SetAsBox((diameter/2)/physicsScale , (diameter/2) / physicsScale); b1.density = density; b1.friction = friction; b1.restitution = restitution; var bd:b2BodyDef = new b2BodyDef(); bd.position.Set( (newCanvas.x+(newCanvas.width/2))/physicsScale, (newCanvas.y+(newCanvas.height/2))/physicsScale ); bd.angle = 0; var physicsBody:b2Body = world.CreateBody(bd); physicsBody.CreateShape(b1); physicsBody.SetMassFromShapes(); physicsBody.ApplyImpulse( b2Vec2.Make(Math.floor(Math.random() * (10 - -10)) + -10,0), b2Vec2.Make(10,5) ); newCanvas.physicsBody = physicsBody; newCanvas.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown); this.addChild(newCanvas) } public function addBall():void { var newCanvas:Actor = new Actor(); newCanvas.width = diameter; newCanvas.height = diameter; newCanvas.x = Math.random() * ( this.width - diameter); newCanvas.y = -newCanvas.height; newCanvas.shape = selectedShape; newCanvas.drawShape(); var b1:b2CircleDef = new b2CircleDef(); b1.radius = (diameter/2)/ physicsScale; b1.density = density; b1.friction = friction; b1.restitution = restitution; var bd:b2BodyDef = new b2BodyDef(); bd.position.Set( (newCanvas.x+(newCanvas.width/2))/physicsScale, (newCanvas.y+(newCanvas.height/2))/physicsScale ); bd.angle = 0; var physicsBody:b2Body = world.CreateBody(bd); physicsBody.CreateShape(b1); physicsBody.SetMassFromShapes(); physicsBody.ApplyImpulse( b2Vec2.Make(Math.floor(Math.random() * (10 + 10)) + -10,0), b2Vec2.Make(10,5) ); newCanvas.physicsBody = physicsBody; newCanvas.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown); this.addChild(newCanvas) } |
I start by creating my renderer. Next, I create the shape that I want to add to the world, for this example I’ve limited the shapes to circles and squares for the sake of simplicity. I set a few options on the shape that determine how the shape interacts with it’s surroundings in our world. Those options are density, friction, restitution (bounciness), and size. You can see the effects of each of these options in my example below.
The last element of my functions for adding my shapes is the addition of a MOUSE_DOWN event listener. The purpose of this is to allow the user to actually interact with the objects in the world. Here’s my mouse event handlers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public function handleMouseDown(evt:MouseEvent):void { var newX:int = (evt.stageX+15)/physicsScale; var newY:int = (evt.stageY+15)/physicsScale; var renderer:Actor; if (evt.target is Actor) { renderer = evt.target as Actor; } else { } if( renderer ) { var body:b2Body = renderer.physicsBody; var md:b2MouseJointDef = new b2MouseJointDef(); md.body1 = world.GetGroundBody(); md.body2 = body; md.target.Set(newX, newY); md.maxForce = 5000.0; md.timeStep = (1/30); var newMouseJoint:b2MouseJoint = world.CreateJoint(md) as b2MouseJoint; mouseJoint = newMouseJoint; body.WakeUp(); this.addEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove); this.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp); } } public function handleMouseMove(event:MouseEvent):void { var newX:int = (event.stageX+10)/physicsScale; var newY:int = (event.stageY+10)/physicsScale; var p2:b2Vec2 = new b2Vec2(newX, newY); if(mouseJoint) mouseJoint.SetTarget(p2); } public function handleMouseUp(event:MouseEvent):void { this.removeEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove); this.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp); if (mouseJoint) { world.DestroyJoint(mouseJoint); mouseJoint = null; } } |
When the user mouses down on one of the renderers a mouse joint is created and we listen for a mouse move event. A mouse joint is a means for us to apply an external force to our objects.
When you put all this together, you have the goofy little sample below.
Aug/091
Java Middle-Tier Using Ibatis
This is going to be my first entry in a series on how to quickly and efficiently develop the middle-tier portion of an enterprise level flex application that will allow you to communicate with your application via the most efficient and simple means available, that being Remote Procedure Calls. By the time I finish the series, my hope is that you will be able to knock out the middle-tier portion of your application in no time at all, giving you a maximum amount of time to develop your front end Flex app.
The focus of this particular entry is ibatis, which is a framework which allows you to configure the population of java objects from the database using xml files. Ibatis has a ton of features which you can use, and detailing the full litany of them is a more laborious task than I’m up for. For the purposes of this entry, I’m going to show you how to map database fields to java fields, select, update, and insert data.
For the sake of these examples, I will have an embarassingly simple object model, reflected by the following VO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package com.softwarejesus.ibatis.domain; public class FirstObject { private String fieldOne; private String fieldTwo; private int fieldThree; public String getFieldOne() { return fieldOne; } public void setFieldOne(String fieldOne) { this.fieldOne = fieldOne; } public String getFieldTwo() { return fieldTwo; } public void setFieldTwo(String fieldTwo) { this.fieldTwo = fieldTwo; } public int getFieldThree() { return fieldThree; } public void setFieldThree(int fieldThree) { this.fieldThree = fieldThree; } } |
The first step in using ibatis, is to add the necessary jar files to your application’s buildpath. The jar files are available at the ibatis site here
Step two, is to determine what type of interactions you’ll need with your database. Here’s my Data Access Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package com.softwarejesus.ibatis.dao; import java.io.IOException; import java.io.Reader; import java.sql.SQLException; import java.util.List; import com.ibatis.common.resources.Resources; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.client.SqlMapClientBuilder; import com.softwarejesus.ibatis.domain.FirstObject; public class FirstDao { private static SqlMapClient client; public FirstDao() { try { Reader reader = Resources.getResourceAsReader("sql-map-config.xml"); client = SqlMapClientBuilder.buildSqlMapClient(reader); reader.close(); } catch (IOException e) { System.out.println("There are better ways to do this, and I'll show you in a later post"); } } public List<FirstObject> getAllFirstObjects() { List<FirstObject> returnList = null; try { returnList = client.queryForList("getAllFirstObjects"); } catch (SQLException sqlE) { sqlE.printStackTrace(); } return returnList; } public List<FirstObject> getFirstObjects(String param) { List<FirstObject> returnList = null; try { returnList = client.queryForList("getFirstObjects"); } catch (SQLException sqlE) { sqlE.printStackTrace(); } return returnList; } public void updateFirstObject(FirstObject object) { try { client.update("updateFirstObject", object); } catch (SQLException sqlE) { sqlE.printStackTrace(); } } public void insertFirstObject(FirstObject newObject) { try { client.insert("insertFirstObject", newObject); } catch (SQLException sqlE) { sqlE.printStackTrace(); } } } |
As you can see, I have two ways I can retrieve data, one will return all the data, the other will select based on some input criteria. I also have an update and insert method. The constructor creates a SqlMapClient object, the SqlMapClient object is basically where all the magic happens with ibatis. I won’t focus on the constructor, because my next entry will show you how to use Spring to populate this, and many other necessary objects.
Now that we know what interactions we want to have with the database, we need to set up our SQL-Map XML. My SQL Map includes only 6 different node types: typeAlias, resultMap, result, select, update, and insert.
The typeAlias node creates a shorthand Alias for a specific java class.
The resultMap node create another Alias, indicating how a specific set of fields will populate a java object.
The result node matches a specific result set field to a java field.
The select, update, and insert nodes should all be relatively self-explanatory.
Here’s my SQL-Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd"> <sqlMap namespace="Model"> <typeAlias alias="myFirstObject" type="com.softwarejesus.ibatis.domain.FirstObject" /> <resultMap id="firstObjectResult" class="myFirstObject"> <result property="fieldOne" column="oneField"/> <result property="fieldTwo" column="twoField"/> <result property="fieldThree" column="threeField"/> <resultMap> <select id="getAllFirstObjects" resultMap="firstObjectResult"> Select * from firstTable </select> <select id="getFirstObjects" resultMap="firstObjectResult" parameterClass="java.lang.String"> Select * from firstTable where oneField = #value# </select> <update id="updateFirstObject" parameterClass="myFirstObject"> UPDATE firstTable SET oneField = #fieldOne# ,twoField = #fieldTwo# ,threeField = #fieldThree# WHERE oneField = #fieldOne# </update> <insert id="insertFirstObject" parameterClass="myFirstObject"> INSERT INTO firstTable VALUES (#fieldOne#, #fieldTwo#, #fieldThree#) </insert> </sqlMap> |
Obviously this was a very simple example of what can be done using ibatis. I like how flexible ibatis is though, notice how the database calls (select, update, and insert nodes) have an attribute called parameterClass. What’s nice is that you can set the parameter class to either a typeAlias or the fully qualified Java class name. I almost always use the typeAlias, for the sake of readability, but, it’s not necessary to do so. The same goes for the result type, you can either use a resultMap defined in your SQL Map or set the resultClass attribute to a fully qualified Java class.
You may have noticed that sometimes in the select/insert/update portions of my SQLMap I use #value# to inject values into the SQL and sometime I use $value$ to inject values. The difference between the two is that using the #value# formats the value to ensure that it is SQL, for example handling apostrophes in Strings. Whereas, $value$ indicates that the value is already SQL formatted.
In the DAO all I had to provide ibatis with was the ID of the SQL statement I wanted to run, and any parameters necessary for the statement, that’s it! I love using ibatis because it makes my java piece so tiny and essentially maintenance free.
One thing to be careful of when using ibatis, is that it struggles with null values returned from the database, usually you’ll have to indicate in the resultMap what the java type is if there’s a null value returned.
Ibatis has tons of really great features, and I’ve only scratched the surface on them here. But one of the things I like the most is if the result set fields match the java fields, you don’t have to define a resultMap, because ibatis will handle it for you.
Aug/090
Quick-Hit: JOTM on Tomcat for LiveCycle
If you’ve tried to set up a Tomcat Server to run LiveCycle Data Services you may know that it can be quite a bit more difficult than you anticipated, especially if you’re familiar with BlazeDS.
I thought things were going well until I tried to update the data that I had received from LiveCycle. At that point I received the infamous “Unable to access UserTransaction in DataService” error.
Adobe has some great information on fixing it, available here.
I followed their advice, but it still wasn’t working. Finally, one of my friends suggested that instead of putting the jar files in the {CATALINA_HOME}/common/lib folder that we put them in our WEB-INF/lib folder. We restarted our Tomcat Server, and thankfully it worked like a charm.
Aug/093
Flex Configurations made easy
If you have worked on a project of any size using Flex and Cairngorm one of the issues that you might have encountered is how to allow the application to connect to the necessary services in different environments. For example with your local development you might connect to a Tomcat server running on your machine, but in the test environment it would connect to a different server. To solve this I used to have code that looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.softwarejesus.configuration.model { import com.adobe.cairngorm.model.ModelLocator; public class OldModelLocator implements ModelLocator { private static var modelLocator : OldModelLocator; public var SERVICE_URL:String = "http://localhost:8080/sampleServices/messagebroker/amf"; // public var SERVICE_URL:String = "http://devServer:8080/sampleServices/messagebroker/amf"; public static function getInstance() : OldModelLocator { if ( modelLocator == null ) { modelLocator = new OldModelLocator; } return modelLocator; } public function OldModelLocator() { if ( modelLocator != null ) throw new Error( "Only one ModelLocator instance should be instantiated" ); } } } |
You’ll notice that I have different endpoints commented out in my ModelLocator. In order to create a Test build, I would un-comment the second line and rebuild the application, but I was never entirely happy with that solution.
What some coworkers and I came up with, was creating a simple configuration XML that would reside in the same directory as the generated SWF. At startup, the application would load any environment specific constants before any real work began.
Now, how did this impact my code? The only major deviation from the standard Cairngorm pattern is that we had to wait to add the Services to our application until after the application had received confirmation that the configuration has been loaded.
As you’ll see here my Services.mxml code looks exceptionally ordinary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" encoding="utf-8"?> <ServiceLocator xmlns="com.adobe.cairngorm.business.*" xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import com.softwarejesus.configuration.model.ConfigModelLocator; [Bindable] private var serviceUrl : String = ConfigModelLocator.getInstance().SERVICE_URL; ]]> </mx:Script> <mx:RemoteObject id="sampleService" destination="sampleService" showBusyCursor="true" endpoint="{serviceUrl}"/> </ServiceLocator> |
Here is how I’ve written my ModelLocator, notice that when I instantiate the model, I retrieve the configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com.softwarejesus.configuration.model { import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cairngorm.control.CairngormEventDispatcher; import com.adobe.cairngorm.model.ModelLocator; import flash.events.Event; import flash.events.IOErrorEvent; import flash.net.URLLoader; import flash.net.URLRequest; import mx.controls.Alert; public class ConfigModelLocator implements ModelLocator { private static var modelLocator : ConfigModelLocator; private static const CONFIG_FILE_NAME:String = "config.xml"; public var SERVICE_URL:String; public static const CONFIG_LOAD_COMPLETE:String = "ConfigLoaded"; public static function getInstance() : ConfigModelLocator { if ( modelLocator == null ) { modelLocator = new ConfigModelLocator; modelLocator.loadXML(); } return modelLocator; } public function ConfigModelLocator() { if ( modelLocator != null ) throw new Error( "Only one ModelLocator instance should be instantiated" ); } public function loadXML():void { var configPath:String = CONFIG_FILE_NAME; var loader:URLLoader = new URLLoader(new URLRequest(configPath)); loader.addEventListener(IOErrorEvent.IO_ERROR, handleIOError); loader.addEventListener(Event.COMPLETE, handleXML); } private function handleXML(event:Event):void { var xml:XML = new XML((event.currentTarget as URLLoader).data); SERVICE_URL = xml.system.serviceUrl; CairngormEventDispatcher.getInstance().dispatchEvent(new CairngormEvent(CONFIG_LOAD_COMPLETE)); } private function handleIOError(event:Event):void { Alert.show("Unable to open config.xml. Please contact the your IT representative"); } } } |
Tying it all together, in my Application file, we I initialize the app, I retrieve the model, and add a listener for when it has completed the configuration process. Once the configuration complete event has been dispatched, I know I can now add the services and get to work making Service Calls
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="doInit()"> <mx:Script> <![CDATA[ import com.adobe.cairngorm.control.CairngormEventDispatcher; import com.softwarejesus.configuration.services.Services; import com.adobe.cairngorm.control.CairngormEvent; import com.softwarejesus.configuration.model.ConfigModelLocator; private function doInit():void { ConfigModelLocator.getInstance(); CairngormEventDispatcher.getInstance().addEventListener(ConfigModelLocator.CONFIG_LOAD_COMPLETE, addServices); } private function addServices(event:CairngormEvent):void { var serv:Services = new Services(); addChild(serv); } ]]> </mx:Script> </mx:Application> |
I found this solution to be perfectly suited to address an issue I ran into on another project. In that project, the client was a manufacturer of goods, and had distributors that sold his products. Initially it was set up so that when someone logged in they could browse through the entire catalog of products. A couple of months in, he told me that each of his distributors had a finite set of products they could sell, and that none of them could sell all of the products. As a result, the distributors could not show their customer’s the app, for fear that the customer would decide to purchase something they were not authorized to sell.
The solution I came up with was to host the web services, and all the assets (and believe me, the number of assets was disgusting) centrally, and develop a swf that could be given to the distributors to host on their respective websites. Along with the swf, the customer was also given a config.xml that would determine which products were available through the application. This solution allowed me to introduce brand new functionality and all I had to do was implement one new service call.
Jul/090
Cairngorm Super Classes
I’m a big fan of the Cairngorm micro-architecture for Flex, I am unapologetic and vocal about my support of it. I’m also incredibly lazy, and hate to rewrite the same two or three lines of code over and over, but I found that because of some of the design choices that were made on two of my recent projects, I was having to do just that. The biggest example of my rewriting the same code was in my commands. If you use Cairngorm, and you’re anything like me, you probably have commands that look roughly like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.softwarejesus.cairngorm.command { import com.adobe.cairngorm.commands.Command; import com.adobe.cairngorm.control.CairngormEvent; import mx.controls.Alert; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; public class SimpleCommand implements Command, IResponder { public function execute(event:CairngormEvent):void { /*Do something - call a web service*/ } public function result(data:Object):void { /*Handle the response*/ } public function fault(info:Object):void { var event:FaultEvent = info as FaultEvent; Alert.show(event.fault.faultString); /*trace(event.fault.faultString);*/ } } } |
So I decided I was going to run with this lazy thing and create a simple class called MyResponder which would at least remove the need to re-implement the fault method again and again. But then I noticed another pattern, but before I can show you I need to explain a little bit about how applications were structured. These applications allowed the user to open up 1 to n datagrids or charts. So I created a simple multiton model which contained data that controlled how the data was retrieved, how the data was rendered, as well as the actual result data. Here’s what models basically looked like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.softwarejesus.cairngorm.model { import mx.collections.ArrayCollection; [Bindable] public class GenericModel { /*Criteria variables*/ public var string1:String; public var date1:Date; /*Display variables*/ public var string2:String; public var number1:Number; /*List of some type of objects*/ public var resultData:ArrayCollection; } } |
When somebody launched a new chart or datagrid a new GenericModel was created. It was then populated with the data my app needed to render it properly, search criteria, and then a new Cairngorm Event was created to retrieve the applicable data. Depending on how the data was structured, I would have a number of events, like this one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com.softwarejesus.cairngorm.event { import com.adobe.cairngorm.control.CairngormEvent; import com.softwarejesus.cairngorm.model.GenericModel; public class SpecificEvent extends CairngormEvent { public var obj:GenericModel; public static const EVENT_TYPE:String = "SpecificEventType"; public function SpecificEvent(obj:GenericModel) { super(EVENT_TYPE); this.obj = obj; } } } |
I would also have a number of commands that were structured like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package com.softwarejesus.cairngorm.command { import com.adobe.cairngorm.commands.Command; import com.adobe.cairngorm.control.CairngormEvent; import com.softwarejesus.cairngorm.event.SpecificEvent; import com.softwarejesus.cairngorm.model.GerenicModel; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; public class SpecificCommand implements Command, IResponder { var obj:GerenicModel; public function execute(event:CairngormEvent):void { var newEvent:SpecificEvent = event as SpecificEvent; this.obj = newEvent.obj; //get my delegate //make some service call } public function result(data:Object):void { var event:ResultEvent = data as ResultEvent; obj.resultData = event.result as ArrayCollection; } public function fault(info:Object):void { var event:FaultEvent = info as FaultEvent; Alert.show(event.fault.faultString); } } } |
A repeated pattern is an opportunity to be lazy, which I’m a huge fan of. What I came up with were 2 super classes that I would use to cut a great deal of superfluous, repetitive code out of my project. The first super class I’ve created is my SuperEvent, you’ll notice that it implements EventConstants. EventConstants contains all the event type strings that I use in my project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package com.softwarejesus.cairngorm.event { import com.adobe.cairngorm.control.CairngormEvent; import com.softwarejesus.cairngorm.model.GerenicModel; public class SuperEvent extends CairngormEvent implements EventConstants { public var object:GerenicModel; public function SuperEvent(object:GerenicModel, type:String) { this.object = object; super(type); } } } |
Next came my SuperCommand,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package com.softwarejesus.cairngorm.command { import com.adobe.cairngorm.commands.Command; import com.adobe.cairngorm.control.CairngormEvent; import com.softwarejesus.cairngorm.event.SuperEvent; import com.softwarejesus.cairngorm.model.GenericModel; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; public class SuperCommand implements Command, IResponder { private var object:GerenicModel; public function execute(event:CairngormEvent):void { var newEvent:SuperEvent = event as SuperEvent; this.object = newEvent.object; } public function result(data:Object):void { var event:ResultEvent = data as ResultEvent; object.resultData = event.result as ArrayCollection; } public function fault(info:Object):void { var event:FaultEvent = info as FaultEvent; Alert.show(event.fault.faultString); } } } |
With good middle-tier design, you can eliminate the need to implement 80-90% of your web service result handlers. Thereby saving you valuable time, which you can turn around and dedicate to World of Warcraft or whatever it is you choose to do with your spare time.
Jun/092
AnyChart Review
Recently, our client wanted to include a funnel chart in the next phase of the project I’m currently working on, but Flash Charting does not include this capability. As we looked around for solutions, we were pointed in the direction of AnyCharts. In this blog post I’m going to talk about the AnyChart plugin tool that my team used recently, specifically the good, the bad, and what I would like to see as a Flex user. For this specific post, I will not be providing any code samples, just two-bit opinions. If anyone has any requests for future AnyChart posts, please let me know.
As you look at the AnyChart website what you’ll notice is that the charts look really polished right out of the box. I am in no way a graphic designer so components that looks good out of the box, usually gets my attention first, because look and feel is the most difficult aspect of Flex for me.
AnyChart allows you to download their product in a few different formats, but I’m just going to focus on the SWC available for Flex, which is available here. The AnyChart SWC contains precisely one visual component that you can incorporate into your application, that being: AnyChartFlex. Within the AnyChartFlex Component, there are two fields that you can set to control all of the charting options: anychartXML and anychartXMLFile.
As I alluded to earlier, the AnyChart presentation is very crisp and polished without any styling, whereas Flex is not. To give you an idea of the difference in the polish, I’ve included a simple chart with no styling applied to it using the Flex charting packages.
This is the same type of chart rendered using AnyChart without any styles.
As I said, right out of the box, it looks really nice… if it has enough space. But this is where Anycharts started to give us trouble. When you start trying to embed the chart within an application where space is a premium, AnyCharts needs a tremendous amount of massaging to look good. Here’s an example of what I’m talking about, our application was set up similar to this, 2 rows of charts with 2 charts in each row.
Whereas, Flex Charting has no problem scaling down to a smaller size.
As you can see, all of the items that looked really slick right out of the box, are now all jumbled up. It takes a while to do, but you can change numerous settings on the XML you pass into the component and get something that fits into the confined space and still looks nice.
One of the things I think the creators of AnyChart could do to help with the problem is to make AnyChartFlex a utility class instead of a visual component and create at least two different visual components.
A) AnyChartCanvas – this would essentially be exactly what they have today, it would have the legend, title, etc
B) AnyChart – this would be just the chart, no legend, no title, no extra padding at all
One other thing I would request is
C) AnyChartLegend – self explanatory
Overall, the tool is really nice for Flex developers who aren’t great with look and feel, but I see tremendous growth opportunities for it.
Jun/090
