I worked on this feature for LCDS 2.6 (which by the way just got released last week). The SQLite DB built in to AIR is really nice to work with and the Actionscript APIs for using it were nicely designed by Jason, one of my coworkers here at Adobe.

I think the offline feature is pretty neat. There is still lots of room for improvement, but the basics that we do have are pretty powerful. We are in the planning stages for the next LCDS release and I expect that improving our offline story, particularly for AIR, will be on the list.

Friday, July 11, 2008

Paul Hastings sent me a questions yesterday about the ColdFusion SMS Event Gateway. He wanted to know how users could set their own optional parameters in the text messages sent from the gateway. He blogged my answer, which means I can just link to it without writing it up myself.

A few notes: If the gateway receives optional parameters in a message, it will be included in the data struct that is returned to the onIncomingMessage CFC function under the "optionalParameters" key. CFDump to a file or console is your friend in these cases.

If you do not format the key (has to be able to be parsed by the Java Short.decode(String) function) or the value (must be a byte[], which in CFML is a "binary object") correctly the setting is silently ignored.

This functionality has been in the SMS Gateway since 7.0.2. It is not in the 7 or 8 documentation, which is bad, but we will correct this moving forward.

Wednesday, July 02, 2008

A mere hours after I posted about how to figure out why CF does not like the arguments you are passing to invoke an operation in a web service, Sean Corfield pinged me with a problem exactly like that. Feeling pretty good, I pointed him to the post I made hours before. He of course had already tried the wsdl2java trick and still could not get CF to do the deed.

He sent me the WSDL and his test and everything looked OK. It was a pretty complex input to an operation (names changed to protect privacy): public com.example.RegisterResponse register(com.example.RegisterRequest parameters)

Fun stuff. Sean had done all the right things creating CFML structs that matched each of the JavaBean types (ThingyType, and ThingsILike). ThingsILike was interesting because it contained a single item that was an array. His code was right on the money here, notice that the ThingsILike object had a single member named "things" that was the array. Here is what he did and (rightfully) expected to work:thing1 = { name = "bobby", location = "Portland" };thing2 = { name = "sally", location = "Boston" };iLike = { things = ["Bunnies", "Kittens", "Koalas", "ColdFusion"] };args = { sessionID = 0, importantThing = thing1, otherThingy = thing2, likeList = iLike, duration = 60 };

Bonus points to Sean for using the new ColdFusion 8 syntax to create arrays and structures. So the array wasn't the problem. What was? Well you may have noticed that duration is listed in the Java function as being of type org.apache.axis.types.UnsignedInt. this is because the XMl Schema type in the WSDL says that the number is an Unsigned Integer:

<xsd:element name="Duration" type="xsd:unsignedInt" />

As an aside here, XML Schema has a lot of different types that elements can be. Things like NonNegativeInteger, nonPositiveInteger and other slightly wacky things. Java on the other hand doesn't have any of these. So the Axis folks (which included me) came up with a set of classes that would enforce the limitations of the Schema types and allow you to 'round trip' a service that you generated from a WSDL, then deployed and allowed Axis to create the WSDL from the Java. This is good, and when you are writing Java directly against the WSDL2Java generated code no big deal because you can pretty quickly notice that you need one of these types and make one.

But back to ColdFusion. In order for the "60" given in the code to turn in to the class UnsignedInt, ColdFusion has to be smart about how to construct this object. It's not. However there is a simple work around - create the object yourself. Here is how that would look:

Notice that I call init(60), which invokes the constructor of the class. Setting this object as the value of duration in our argument structure works like a charm. So problem solved, and I hope that we can make CF smarter about these types in a future release, but encountering these XML Schema types in the wild is pretty rare so I don't expect you will need this workaround much if at all.

For some reason I haven't actually even written up the procedure I use to figure out how to invoke a web service from ColdFusion when people ask me what CFML structures they need to pass in so CF can match the arguments to a function in the Apache Axis stub it generated from the WSDL.

First, if you can't figure out why CF gives you an error about not finding the right function to invoke, you probably aren't passing in the right arguments. How do you figure these out? You run the WSDL2Java command in Apache Axis, and take a look at the Java code CF is trying to invoke. Here's a batch script for Windows and ColdFusion 8:set CFLIB=c:\ColdFusion8\libset CLASSPATH=%CFLIB%\axis.jar;%CFLIB%\wsdl4j-1.5.1.jar;%CFLIB%\log4j-1.2.12.jar;%CFLIB%\commons-logging.1.0.4.jar;%CFLIB%\commons-discovery-0.2.jar;%CFLIB%\xercesImpl.jar;%CFLIB%\xml-apis.jar;%CFLIB%\saaj.jar;%CFLIB%\jaxrpc.jar;%CFLIB\..\runtime\lib\jrun.jar

java org.apache.axis.wsdl.WSDL2Java %1 %2 %3 %4 %5 %6 %7 %8 %9

Save this as "wsdl2java.bat". This will also work with ColdFusion MX 7. You will need to change the log4j-1.2.12.jar file to be just log4j.jar.

Change to an empty directory. Wsdl2Java is going to spew directories and files all over the current directory, so I find its best to create something like c:\temp\x and run the command from there. Otherwise you can give the -o option and specify the output directory.

wsdl2java.bat -v -o c:\temp\x http://example.com/path/to/wsdl

The -v is the verbose argument. I like to see all the files that are being created.

Now you can start browsing through the Java files and see what we have to work with. Look for a file named the same as the "portType" name in your WSDL. For instance, if your WSDL contains a portType that looks like this:<wsdl:portType name="CodexWS">

Then you would look for CodexWS.java. This will be the interface of the service, and you should be able to see each of the operations in your service, and the Java objects that ColdFusion will be trying to create to call them.

How do you create the CFML to pass to the operation? Simple. Most likely there will be JavaBeans created from the XML Schema in the WSDL. These aren't that big of a deal, basically they are structures with name/value pairs, which is why you will need to make a CFML structure with the same names in it.

There would be a getManifest() and setManifest() function in the bean also, along with a get and set function for each one of the other bean properties. These only matter because this is what CF will look for when it finds a "manifest" entry in your CFML structure. So what would we pass in to an operation that takes an "AddThingRequest" object as an argument? Something like this:atr = StructNew();atr.manifestfile = "foo";atr.uri = "uri:gimme";atr.certlevel = "high";atr.statusname = "Alert";art.ldapcredentials = mycreds;

But wait, you say, what is "mycreds"? Well, the process just repeats here - we would go find the com.adobe.Ldapcredentials Java object, look at the properties it contains and make a CFML structure (mycreds in this example above) that matches it.

So that's all (ha!) there is to it. You now have the magic to invoke almost any web service out there. This isn't exactly "making the hard stuff easy" (that would be that WSDL2CFML tool I haven't written yet), but once you understand how the process works, you should very rarely have to resort to grunging through the Java code generated from a WSDL to construct the right CFML inputs. My opinion is that Web Services should be designed to present an easy to use interface, simple inputs with only reasonably complex outputs. I know that isn't the case for many services out there that you are forced to used. But with this technique you should be able to easily figure out how to use the more complex ones.