Thursday, June 29, 2006

Another ColdFusion team members blog

On of the newer members of the ColdFusion team, Ashwin Mathew, has started up a blog about ColdFusion among other things. Ashwin has posted a great article on caching and plans to follow it up with some interesting posts about how CF does its internal caching of things like template.

Ashwin is also here at CFUnited this week. Check it out.

Thursday, June 22, 2006

CFUnited next week

Since I am on a blogging roll this week, I wanted to note that I and many other members of the ColdFusion team will be attending CFUnited next week.

Even though I am really looking forward to Adobe MAX in Las Vegas this fall (probably because I love BlackJack and Vegas), CFUnited is where "my peeps" are at since it is focused almost 100% on ColdFusion. Through some quirk of fate, I am not giving a session this year (I guess I am not famous enough :-). But I will be hanging at the Adobe booth for large chunks of time and attending as many sessions as I can. I really find it fascinating to hear people explain features of CF that I have had a hand in designing. It is almost like a game of telephone - where someone whispers a message to another person on down the line, then you compare what the last person in the line says to the original message. The result is always surprising. Sometimes the message gets through, sometimes it gets really garbled. :-) But in every case it allows me to get a better view of the problems our customers are solving (or not solving) with CF so that next time I can get it closer to right.

This is your chance to harass me about how CFCs should be "just like" Java objects, why CFML should be strongly typed, why we should rewrite the file browser in the CF administrator to not use Java, why you can't possibly use CF anymore because we don't have {cfimap,cfimage,etc, etc}. I will have my laptop and access to source control, so maybe you can convince me to add/fix/remove something that you have always wanted in Scoprio right on the spot! :-)

See you there!





Wednesday, June 21, 2006

A Better Way to Make a String Array

I was jumping through some hoops to generate a true Java String array in CFML yesterday to pass in to a web service API. Today, while talking to some of the guys on the CF team it came up that there is a really simple way to get an array of Strings in CFML: Use the Java String split() API!

Code before:
string = CreateObject("java", "java.lang.String");
array = CreateObject("java", "java.lang.reflect.Array");
cookies = array.newInstance(string.getClass(), 3);
array.set(cookies, 0, "x=1");
array.set(cookies, 1, "x=2");
array.set(cookies, 2, "x=3");

Code After:
s = "x=1,x=2,x=3";
cookies = s.split(",");
I would say the second way is much easier. It is using a regular expression, so it isn't going to go as fast as the first code sample, but for something like this I would not worry about it at all.

How to get web service response cookies

So yesterday I wrote about how to get the Axis engine that underlies the ColdFusion web services to send cookies in the request. The follow on question asked shortly after I posted that was how do I get cookies sent to me from (say) an initial login request to the web service.

Here is how to do that:

ws = CreateObject("webservice", ...);
ws.setMaintainSession(true); // required so axis will do cookies
ret = ws.log_in_or_something();
...
// Get cookies returned from server (if any)
call = ws._getCall();
ctx = call.getMessageContext();
server_cookies = ctx.getProperty("Cookie");
Getting the cookies out of the Axis MessageContext will return either a single string if there is one cookie, or an array of Strings if there are more than one. One thing to watch out for is if there are not any cookies returned from the server, the return value will be (Java) null, so you will need to used isDefined() on that variable before using it. The other thing to watch out for is that the MessageContext property 'Cookie' will keep the values that you have set in to it if you set cookies before makeing a call. They will only get overwritten if the server send something back.

Tuesday, June 20, 2006

How to set Cookies in ColdFusion SOAP requests

I spent some time today working with a customer who is using a web service that requires its users to set HTTP cookies in their SOAP requests. First off, I think this is just wrong as this is what SOAP headers are used for and ColdFusion MX 7 (6.1 too) can easily set SOAP headers in both requests and responses.

In any case, Apache Axis can include HTTP cookies in the requests it sends out and since ColdFusion uses Axis as its implementation engine, you can do it in CFML also.

First, how would you set a cookie (or cookies) in Java? Here is some sample Java code:

    mystub._setProperty(
HTTPConstants.HEADER_COOKIE,
new String[]{"myCookie1=hello", "myCookie2=goodbye"});

This calls the "_setProperty" function on the Stub object, passing it a string ("Cookie") and an array of strings, which are the cookies to set. It turns out that the Axis code will look at the second argument and if it is a single String, it will set that as a cookie. If it is a String array, then it will treat each string in the array as a different cookie header.

So, how do we duplicate this in CFML?

First, realize that when you create a "webservice" object in CFML, this can be treated as the org.apache.axis.client.Stub class using the CF/Java interop. So in the case of setting a single cookie, we have no problems:


ws = CreateObject("webservice", "path-to-wsdl");
ws._setProperty("Cookie", "myCookie1=hello");

But this doesn't work! Why not? Well for those of you following along in the Apache Axis source tree, you will notice that the org.apache.axis.transport.http.HTTPSender.java class has this code in it (around line 260):

// don't forget the cookies!
// mmm... cookies
if (msgContext.getMaintainSession()) {
fillHeaders(msgContext, HTTPConstants.HEADER_COOKIE, otherHeaders);
fillHeaders(msgContext, HTTPConstants.HEADER_COOKIE2, otherHeaders);
}

So we need one more thing, we have to turn on the "maintain session" switch in the message context. This turns out to be simple to do as there is an API on the Stub to do just that:

ws.setMaintainSession(true); // required so axis will do cookies

Great, we add that and we can set one cookie for any web service operation we invoke. But what if we want to set more than one (this to-be-nameless service requires three!)?

Well, we need to create an array of Java Strings to pass in to _setProperty(). What happens if we try and use a CFML array? Well, usually CF is really good about taking its (mostly) typeless things and converting them to particular java types. But in this case the API signature for _setProperty is this:

public void _setProperty(String name, Object value);

So CF happily passes the CFML Array in to this API as an object. But deep down in the Axis code (HTTPSender.java, line 534 or so), it tries to treat this object either as a string array (String[]) or a String. Neither of which works in this case. You next thought might be to use the JavaCast() CFML function, as this is one of the cases that it is designed for. But unfortunately it only handles scalar (simple) types (yes, we need to enhance that). What next?

Well, in CFML you can create an Java object that you want using CreateObject. It just so happens that the java.lang.reflect.Array class has functions that will create arrays. So we can use the newInstance() function to allocate an array. This function takes in a Class object and the number of elements in the array.

string = CreateObject("java", "java.lang.String");
array = CreateObject("java", "java.lang.reflect.Array");
cookies = array.newInstance(string.getClass(), 3);

This creates a 3 element String array. One bug that this uncovers is that you can not treat this array the same as a CFML array. For instance you can not assign cookies[1] = "x=1" because ColdFusion will report a problem with casting "coldfusion.runtime.Cast$1", which is a really bad error message that makes sense if you could read the source code like I did, but doesn't help at all. Suffice to say that we are doing the wrong thing with a Java array when trying to set a value in that array. So what can we do? The Java Array class comes to the rescue again as we see that it has a set() API that takes an array, the index and the value you want to set. So we can call it like this:

array.set(cookies, 0, "x=1");
array.set(cookies, 1, "x=2");
array.set(cookies, 2, "x=3");

Watch out for the zero (0) indexed Java array, which differs from the one (1) indexed arrays in CFML.

We are then all set, we can use our cookies array as an argument to _setProperty and since we know it is a String[] (and CF wont change that) it will get down to the Axis HTTP class and be sent out in the HTTP request.

Here is the complete cfscript code:

// Create the array for cookies
string = CreateObject("java", "java.lang.String");
array = CreateObject("java", "java.lang.reflect.Array");
cookies = array.newInstance(string.getClass(), 3);
// set the cookie values
array.set(cookies, 0, "cookie1=one");
array.set(cookies, 1, "cookie2=two");
array.set(cookies, 2, "cookie3=three");

ws = CreateObject("webservice", "http://localhost:8500/test.cfc?WSDL");
ws.setMaintainSession(true); // required so axis will do cookies
ws._setProperty("Cookie", cookies);

// invoke an operation
ret = ws.echo("hi");