Monday, July 02, 2007

Flash for Java Programmers: Lesson 5 - Calling an xfire webservice from a flash client

Steps on learning to develop Flash, with a Java developer focus...

In this post, I will show how to call a webservice from a flash client. The webservice will be exposed using xfire and the flash client will connect to the wsdl and call methods on the web service endpoint.

This is lesson 5 in my series of posts on what I learn about developing filthy rich flash apps using flex2. The other lessons are:

An xfire based webservice
Here is how to construct a simple web service, exposed through xfire using the JSR-181 annotations. It is dead simple. My service is called "ClientLogService" and can be used to send a log message from the flash client to the server for logging. Here is the interface and the implementation:
public interface ClientLog {
void acceptLog(String message);
}
and
@WebService(serviceName = "ClientLogService")
public class ClientLogImpl implements ClientLog {
public void acceptLog(@WebParam(name = "message") String message) {
System.out.println(String.format("[LOG-MSG-FROM-FLASH] %s", message));
}
}
Notice the annotations. These are what xfire needs to expose it as a web service. I also need to package a META-INF/xfire/services.xml with this content:
<beans xmlns="http://xfire.codehaus.org/config/1.0">
<service>
<name>ClientLogService</name>
<namespace>http://techpolesen.blogspot.com/ClientLogService</namespace>
<serviceClass>com.blogspot.techpolesen.ws.ClientLog</serviceClass>
<implementationClass>com.blogspot.techpolesen.ws.ClientLogImpl</implementationClass>
</service>
</beans>
And lastly, add the xfire servlet definition to the web.xml:
<servlet>
<servlet-name>XFire</servlet-name>
<servlet-class>org.codehaus.xfire.transport.http.XFireConfigurableServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>XFire</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
That was the server side of it. Next comes the flex implementation accessing the web service.

Flex calling web service using ActionScript
Flex comes with RPC components for doing HTTP and WebService calls. You can actually use them by using only "mx:" xml tags, but constructing and controlling them completely from ActionScript gives you the full power.

Here I show a simple mxml flex2 file, where the UI consists of two components: An input field where simple text can be entered and a button, which triggers the web service call with the text input as parameter.

There is a Script element which contains a variable declaration clientLogService which is of type WebService, and two methods:
  • loadWebService() : Initializes the clientLogService variable by pointing it to the WSDL document published by xfire. Then, when calling clientLogService.loadWSDL(), flex will load and parse the wsdl document, and add dynamic methods to the clientLogService variable, corresponding to the methods published by the web service.
  • callWebService() : Simply takes the value of the input field and uses it as parameter when sending a method call to the web service.
The loadWebService() method is set to be called on the creationComplete event, which will be when the flash application starts up.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="loadWebService()">
<mx:Script>
<![CDATA[
import mx.rpc.soap.WebService;
import mx.rpc.events.FaultEvent;
import mx.controls.Alert;

private var clientLogService : WebService = new WebService();

private function loadWebService() : void {
clientLogService = new WebService();
clientLogService.wsdl = "/services/ClientLogService?wsdl";
clientLogService.loadWSDL();

clientLogService.addEventListener("fault", function(event:FaultEvent) : void {
Alert.show(event.fault.faultString);
});
}

private function callWebService() : void {
clientLogService.acceptLog(message.text);
}
]]>
</mx:Script>

<mx:TextInput id="message" text="Text entered here will be logged on server..."/>
<mx:Button label="Send message" click="callWebService()"/>
</mx:Application>
Notice one important thing here: RPC calls are asynchronous in flash! This is why we register for a "fault" event on the web service. If something goes wrong, we will be called back on this method, asynchronously. Actually, we wont know after the call to clientLogService.acceptLog() method, if the call has succeeded. We will need to register for the "result" event to be sure, which is also how to get at any return values from web service calls.

Downloading the source
As usual, I have zipped up the sources for you. It can be downloaded from here. Ready to be build with maven.

This is a multi-module maven build. There are two directories:
  • client : Contains the flex source and a pom to build it
  • server : Contains the web service and a pom to build it
The war artifact output from the server module have in it the flash output from the client module and a index.html which loads it.

To start the server after a build, you simply jump into the server directory and do a "mvn jetty:run-exploded".

8 comments:

Kocka said...

Nice work!

Anonymous said...

Hi

I have added xfire libs to my classpath in Eclipse but I keep getting this error when I start my local JBoss server:

Cannot load servlet class: org.codehaus.xfire.transport.http.XfireConfigurableServlet

Any idea why?

Per Olesen said...

Hi anon,

Sheez, I'd like to help but I'm really no eclipse expert. If I am to help you, I would need to see how the webapp layout is, WEB-INF/lib content is and web.xml content.

One thing that strikes me. Have you copy-pasted the error you are getting? Cause the servlet classname is with a small "f", and it needs to be "XFire..." and not "Xfire...".

George said...

Thanks. I figured it out. Problem was with mis-spelling of "f" in "XFireConfigurableServlet" class name inside web.xml file. I had put it as
"XfireConfigurableServlet".

Thanks for help anyway :-D

SRIRAMAN said...

Hi,

All your tutorials were great. However, I ran into a small problem when trying out lesson 5. It is as follows:

In the action script for lesson 5, where we specify the wsdl url, you had suggested to use "/services/ClientLogService?WSDL".

However, it threw an error message saying "Http Error" in the loadWebService() method.

And subsequently, when I clicked on the button, it threw an error saying "wsdl did not load properly".

I suspected it to be an xfire issue. I tried to see if the wsdl is getting generated properly or not by directly giving the full url what is like shown below:

http://localhost:8080/<contextroot>/services/ClientLogService?WSDL

This properly displayed the wsdl contents in the browser.

To get the example to work, I had to prefix the wsdl url with the following:

http://localhost:8080/<contextroot>

I just want to know if I can get this automatically from actionscript?

regards
sriraman

Per Olesen said...

@sriraman:

It should work fine. Did you start the server-part with the jetty:run-exploded through maven or did you deploy the war somewhere else? Cause I have in the pom.xml, this config for jetty:

<contextPath>/</contextPath>

which makes jetty deploy it with "/" as context-root, hence no need to specify context-root in the wsdl url.

With respect to context-root in actionscript. You can serve the flash application from a jsp, and give context-root into the flash as a parameter, like this:

<object width="100%" height="100%">
<param name="movie" value="client.swf">
<param NAME="PLAY" value="true">
<param NAME="LOOP" value="false">
<param NAME="QUALITY" value="high">
<param NAME="SCALE" value="showall">
<param NAME="flashVars" value='<%= "contextRoot=" + request.getContextPath() %>'>
<embed src="client.swf" flashVars='<%= "contextRoot=" + request.getContextPath() %>' width="100%" height="100%" play="true" loop="false" quality="high" scale="showall">
</embed>
</object>

And the access it from actionscript code like this:

_contextRoot = Application.application.parameters.contextRoot;

There is also something about a context-root parameter to the compiler or something, but I haven't gotten it to work properly for me.

SRIRAMAN said...

Hi,

That worked great. However, I would like to apologise for not giving the full picture of how I was trying out your sample codes.

First, I use JBoss and hence, I had commented out the jetty stuff from pom. Hence, after the creation of the war file, currently, I manually copy them the JBoss deploy folder.

Second, I use the Maven eclipse plugin from codehaus for running maven commands from within my eclipse IDE.[http://m2eclipse.codehaus.org/update-dev/]. This should not be an issue I guess.

Thanks for your quick and timely suggestions.

If I am not troubling you too much, can you please let me know as to how to incorporate FDS [Flex Data Services] into the maven manifold?

I am little new to maven and flex. Kindly bear with me if I am asking too silly questions.

I searched the extensively. But, I was not able to get any clear directions as to how to setup a project for maven and FDS.

best regards

Per Olesen said...

@SRIRAMAN:

No problem! Glad I could help, and I really like to hear from people who read my posts.

I cannot help you with FDS, as I am not using it myself. Hence, I do not know exactly how it is deployed and what it requires from the build.

We choose not to use FDS, as a) it is payware with some server-based license model, and b) we could easily provide the part of FDS that our application required using web-services.

In short: We choose flex to be used purely as a client-side technology, which is where flash (and Adobe) has proved itself. The FDS part of flex makes intrusion on the server-side, where we already are using other good and proven technologies.

Sorry I am of no help here :-)