Wednesday, July 19, 2006

Flex: Sending Messages from Server-Side Java Code

I've been trying to figure out how to send messages from the depths of my Java application out to Flex clients. I'm not talking about messages that originate on a client, pass through the Java server, and then head back to the clients - that stuff is documented in the manual. I mean there is a process going on in the server that needs to send messages to the clients.

Of course, JMS and the JMS adapter would work, but I've been really leery of the complexity of JMS since it came out years ago. It has the advantage that it would allow my application to be separated from the Flex server, which would work well in terms of scaling. I may yet try JMS, but for the moment, I'm looking to avoid JMS.

So, I was thinking that writing a message service adapter would be the key. Adobe has a brief example of creating a custom Message Service adapter. It seems to be a complete example, but it left me with lots of questions. For example Message is an interface, which concrete class do we use? How do we construct it? What all can we do with the MessageService? What value are we returning from invoke?

After some digging around in the sample code, I came across a couple of Java classes associated with the dashboard sample application. So, I've taken the code from that example, paired it down a bit, and wrote a very simple, Soviet-style (butt-ass ugly but functional) Flex interface for it.

Below is the server-side Java code.

import java.util.*;
import flex.messaging.MessageBroker;
import flex.messaging.messages.AsyncMessage;
import flex.messaging.util.UUIDUtils;

public class FmsDateFeed
{
private static FeedThread thread;
public FmsDateFeed()
{
System.out.println("FmsDateFeed.ctor: " + this);
}

public static void main(String[] args)
{
FmsDateFeed feed = new FmsDateFeed();
feed.toggle();
}

public void toggle()
{
if (thread == null)
{
System.out.println("FmsDateFeed.toggle: starting...");
thread = new FeedThread();
thread.start();
}
else
{
System.out.println("FmsDateFeed.toggle: stopping...");
thread.running = false;
thread = null;
}
}
public static class FeedThread extends Thread
{
public boolean running = true;
public void run()
{
MessageBroker msgBroker = MessageBroker.getMessageBroker(null);
String clientID = UUIDUtils.createUUID(false);
System.out.println("FmsDateFeed.run: msgBroker=" + msgBroker + ", clientID=" + clientID);

Random random = new Random();
while (running)
{
Date now = new Date();

AsyncMessage msg = new AsyncMessage();
msg.setDestination("time_msgs");
msg.setClientId(clientID);
msg.setMessageId(UUIDUtils.createUUID(false));
msg.setTimestamp(System.currentTimeMillis());
HashMap body = new HashMap();
body.put("userId", "daemon");
body.put("msg", now.toString());
msg.setBody(body);
System.out.println("sending message: " + body);
msgBroker.routeMessageToService(msg, null);
try
{
long wait = random.nextInt(8000) + 3000;
System.out.println("sleeping: " + wait);
Thread.sleep(wait);
}
catch (InterruptedException e) { }
}
}
}
}


The toggle method is called from the client to turn the message stream on or off. When messages are turned on, a thread is created and the run method is called. It just loops creating Java Date objects and putting the String representation of the date into Flex messages - instances of AsynchMessage. The key for me was the routeMessageToService method on the MessageBroker class. When you have the class compiled, install the Java class file in samples/WEB_INF/classes.

The client is pretty simple, by comparison. It has two features: a button for turning the message stream on and off (via a RemoteObject call) and an event handler to process the incoming messages.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#FFFFFF"
creationComplete="initComp()">
<mx:Script>
public function initComp():void {
consumer.subscribe();
}
public function toggle():void {
feeder.toggle();
}
import mx.rpc.events.ResultEvent;
import mx.messaging.events.MessageEvent;
public function messageHandler(event:MessageEvent):void {
var body:Object = event.message.body;
output.text += body.userId + ": " + body.msg + "\n";
}
</mx:Script>

<mx:RemoteObject id="feeder" destination="time_msgs">
<mx:method name="toggle"/>
</mx:RemoteObject>
<mx:Consumer id="consumer" destination="time_msgs" message="messageHandler(event)"/>

<mx:Form>
<mx:FormItem>
<mx:Button label="Toggle Date Feed" click="toggle()"/>
</mx:FormItem>
<mx:FormItem label="Output Message">
<mx:TextArea id="output" width="250" height="150" />
</mx:FormItem>
</mx:Form>
</mx:Application>

The interesting features are the configuration information for the RemoteObject and the message Consumer. These correspond to destinations configured on the server (see below). When the toggle button is pressed, we just call the toggle ActionScript method, which make a call on our RemoteObject, feeder; we don't care about a return value, which simplifies things.

We configure the Consumer to call our messageHandler method when new messages arrive. It just gets the message body and appends it to our text box. Install this file in somewhere in the samples directory - I created my own subdirectory for the dumb, little programs like this.

To configure the server, edit messaging-config.xml in samples/WEB-INF/flex and clone the destination for "dashboard_chat" and call the new version "time_msgs". You could change the Java and MXML to use the existing dashboard_chat destination (which is what I did initially), but a) you could end up with a conflict with the Flex demos, and b) writing to messaging-config.xml (or any of those other config files) is a convenient way to get the server to restart and see your new Java class.

Oh yeah, you also need to edit remoting-config.xml to contain a RemoteObject destination also called "time_msgs". (You could use separate names for the two destinations, and it would probably be less confusing, but I was too unimaginative.)

If all goes well, you should get messages with the current time showing up randomly in the Flash client.


Enjoy,
Charles.

6 comments:

song said...
This comment has been removed by a blog administrator.
song said...

Nice example,
I’m new at flex and i have a new question about this example.
Hot to send messages from Server-Side Java Code with Subtopic?
Thank you for reply.

UpperLimit said...

Well done.

I have been unsuccessfully troubling myself with the issue until I discovered your example.

I was missing actually the UUID generation thing.

Also, the messagebroker didn't work in my case but the custom message adapter's invoke method worked perfectly when I passed it the custom message.

You can use it as an alternative.

contact info: eliasbalasis@gmail.com

Justin said...

I have a similar implementation running in my application however the thread typically freezes after about 5-6 days of service. I'm trying to hunt down the source of the problem... if something is freezing my thread or if my connection to the messagebroker expires after 5-6 days

forzasec said...

I can't put running this example:

Tomcat running and I can invoke methods (remoting-config.xml configured)

For receiving messages, I add the destination to messaging-config.xml

I can subscribe to the destination, I have an alert for that.

Ok, now
I startup the tomcat.
I put running the flex project.
And at the time i run the java project, it gives me an error:

Exception in thread "Thread-0" java.lang.NullPointerException
at test.FmsDateFeed$FeedThread.run(FmsDateFeed.java:90)

That refers to this code Line:

msgBroker.routeMessageToService(msg, null);

any idea?

thanks in advance

Cornelius said...

im also getting this error - most curious