Mosaic and LiveCycle Data Services

Adobe’s two RIA products – LiveCycle Mosaic and LiveCycle Data Services (LCDS) – go together like peanut butter and jelly.  One provides a framework for intuitive, user focused applications, the other provides efficient access to real time back end data.  Between the two of them they can provide up-to-date data in an engaging user experience.

There are a couple of different ways to combine the two products, however.  What one you will choose will depend on your particular setup and how your back end data is organized.  So, do you prefer your jelly on top, or on the bottom?

In this post I’ll present both methods of Mosaic/LCDS integration using an overly simple example.  Hopefully this will give you some ideas for your projects.

In the end I built a simple Mosaic application that demonstrated both methods.

image

A Simple Service

For this example, I’m going to create a very simple LCDS RPC service that accesses a Java object.  This is based on the Adobe documentation that can be found at: http://livedocs.adobe.com/livecycle/8.2/programLC/programmer/lcds/help.html?content=lcoverview_4.html

First I created a trivially simple POJO that echoes back a single String:

package remoting;
public class EchoService
{
    public String echo(String text) {
        return "Server says: I received '" + text + "' from you";
    }
}

Once that was compiled I moved the class file to the  WEB-INF/classes/remoting directory.  Then I added the following entry into the WEB-INF/flex/remoting-config.xml file:

<destination id="echoServiceDestination" channels="my-amf">
    <properties>
        <source>remoting.EchoService</source>
    </properties>
</destination>

The source entry points to my Java class and it uses the existing my-amf channel.

The last thing that I needed to do was to add a crossdomain.xml file to my LCDS server.  This is because my Mosaic server is running on a different box.

Jelly on top; Calling LCDS directly from a tile

The first method I’ll show is also the most direct.  This involves making a direct call from the Mosaic tile to the LCDS service. This is a useful method when there is a direct relationship between the tile’s visual elements and the data. What it has in simplicity, it lacks in power.  This method does not lend itself to decoupling the data from the tile.

<?xml version=”1.0″ encoding=”utf-8″?>
<mc:Tile xmlns:fx=”
http://ns.adobe.com/mxml/2009″

xmlns:s=”library://ns.adobe.com/flex/spark”
xmlns:mx=”library://ns.adobe.com/flex/mx”
xmlns:mc=”com.adobe.mosaic.core.*”
layout=”absolute” width=”100%” height=”100%”
creationComplete=”init()”>

<fx:Declarations>
<mx:RemoteObject id=”remoteObject”
destination=”echoServiceDestination”
result=”resultHandler(event);”
fault=”faultHandler(event);”/>
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.collections.ArrayList;
import mx.messaging.Channel;
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;
import mx.messaging.config.ConfigMap;
import mx.messaging.events.*;
import mx.messaging.messages.*;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ObjectProxy;


private function init():void{
register();
var cs:ChannelSet = new ChannelSet();
var customChannel:Channel = new AMFChannel("my-amf","
http://myserver/lcds/messagebroker/amf");
cs.addChannel(customChannel);
remoteObject.channelSet = cs;
}

private function register():void{
registerClassAlias("flex.messaging.messages.CommandMessage",CommandMessage);
registerClassAlias("flex.messaging.messages.RemotingMessage",RemotingMessage);
registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
registerClassAlias("flex.messaging.messages.ErrorMessage",ErrorMessage);
registerClassAlias("DSC", CommandMessageExt);
registerClassAlias("DSK", AcknowledgeMessageExt);
registerClassAlias("flex.messaging.io.ArrayList", ArrayList);
registerClassAlias("flex.messaging.config.ConfigMap",ConfigMap);
registerClassAlias("flex.messaging.io.ArrayCollection",ArrayCollection);
registerClassAlias("flex.messaging.io.ObjectProxy",ObjectProxy);
registerClassAlias("flex.messaging.messages.HTTPMessage",HTTPRequestMessage);
registerClassAlias("flex.messaging.messages.SOAPMessage",SOAPMessage);
registerClassAlias("flex.messaging.messages.AsyncMessage",AsyncMessage);
registerClassAlias("DSA", AsyncMessageExt);
registerClassAlias("flex.messaging.messages.MessagePerformanceInfo", MessagePerformanceInfo);
}

// Send the message in response to a Button click.
private function echo():void {
var text:String = ti.text;
remoteObject.echo(text);
}

// Handle the recevied message.
private function resultHandler(event:ResultEvent):void {
ta.text += "Server responded: "+ event.result + "\n";
}

// Handle a message fault.
private function faultHandler(event:FaultEvent):void {
ta.text += "Received fault: " + event.fault + "\n";
}
]]>
</fx:Script>

<s:Label x=”11″ y=”5″ text=”Direct LCDS Call”/>
<s:TextInput x=”11″ y=”24″ id=”ti” text=”Hello World!”/>
<s:Button x=”147″ y=”25″ label=”Send” click=”echo();”/>
<s:TextArea id=”ta” width=”100%” height=”628″ y=”68″/>
</mc:Tile>

Again this is based on the LCDS documentation.  There are some concessions to the Mosaic framework however.  First, an AMF channel needs to be setup, also several classes need to be registered.  I know I’ve registered more than I need, but I will probably expand this tile later on.

As you can see, when the user clicks on the Send button, a function is called that makes use of the remote object.  The remote object then uses the AMF channel to make a LCDS call to the POJO.  The results are then shown on the screen.

Jelly on bottom; Using a Mosaic service to make the LCDS call

Another way to make an LCDS call from a Mosaic tile would be to use a Mosaic service as an intermediary.  In this case the tile wouldn’t be aware of the LCDS service at all, any interactions would be made through the Mosaic service.  This is very useful if multiple tiles need to access the same LCDS service as it only needs to be accessed once.  It also allows easy decoupling of the tiles and the data as the Mosaic service can be changed without affecting the inner workings of the tiles.  The draw back is that it does require a bit more coding and more for thought when planning out the Mosaic service.  For more info on creating Mosaic services, see my earlier blog entry

The interface

Let’s look at the code to do something similar to the simple example.  First the Interface object:

package com.adobe.etech{

public interface IHelloWorld    {
function setEcho(msg:String):void;
function getEcho():String;
function echoReady():Boolean;
function setLoaderURL(url:String):void;
}
}

Since the LCDS call is asynchronous, I needed a getter and setter function to make the request and get the response. I also need a method to determine when the asynchronous call is complete (so I can go get the data). The echoReady function is a bit of a cheat to allow this.  As I mentioned in an earlier post, there is no easy way to send a message from the Mosaic service to the tile because the service is not context aware.  You can push the context into the service, but for this example it would be overkill.  So I built a cheap and cheesy Boolean function that I can ping from the tile.

The final function setLoaderURL is very important to the application and requires a bit of explanation.  When I was working on this project, I set everything up as I did with the direct LCDS call.    I kept getting an error that the URL was null.  I knew I had set the URI for the amf channel, so what was the URL it needed?  After discussing it with several of my colleagues I was able to narrow it down to  the fact that the LoaderConfig.mx_internal::_url  object needs to have a URL to a MXML element.  Passing the tile’s loaderInfo.url value to the Mosaic service seems to do the trick.

The Mosaic Service

The service implementation is below:

package com.adobe.etech.impl
{
import com.adobe.etech.IHelloWorld;

import flash.display.Sprite;
import flash.net.registerClassAlias;

import mx.collections.ArrayCollection;
import mx.collections.ArrayList;
import mx.core.mx_internal;
import mx.logging.targets.TraceTarget;
import mx.messaging.Channel;
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;
import mx.messaging.config.ConfigMap;
import mx.messaging.config.LoaderConfig;
import mx.messaging.events.*;
import mx.messaging.messages.*;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
import mx.utils.ObjectProxy;


public class HelloWorldLCDSService implements IHelloWorld
{

private var remoteObject:RemoteObject;
private var _echoMsg:String = “”;
private var _echoComplete:Boolean = false;

public function HelloWorldLCDSService()    {
register();

var amfChannel:AMFChannel  = new AMFChannel(“my-amf”,http://server/lcds/messagebroker/amf);
amfChannel.requestTimeout = 3;
amfChannel.connectTimeout = 3;
var channelSet:ChannelSet = new ChannelSet();
channelSet.addChannel( amfChannel );

amfChannel.addEventListener(ChannelFaultEvent.FAULT, handleChannelFault);
amfChannel.addEventListener(ChannelEvent.CONNECT, handleChannelConnect);
amfChannel.addEventListener(ChannelEvent.DISCONNECT, handleChannelDisconnect);

remoteObject = new RemoteObject();
remoteObject.destination =”echoServiceDestination”;
remoteObject.channelSet = channelSet;

remoteObject.addEventListener(ResultEvent.RESULT,resultHandler);
remoteObject.addEventListener(FaultEvent.FAULT,faultHandler);
}

private function handleChannelFault(e:ChannelFaultEvent):void {
_echoComplete = true;
_echoMsg +=”Channel Fault” + “\n”
trace(“Channel Fault”);
trace(e);
}

private function handleChannelConnect(e:ChannelEvent):void {
_echoComplete = true;
_echoMsg +=”Channel Connect” + “\n”
trace(“Channel Connect”);
trace(e);
}

private function handleChannelDisconnect(e:ChannelEvent):void {
_echoComplete = true;
_echoMsg +=”Channel Disconnect” + “\n”
trace(“Channel Disconnect”);
trace(e);
}


public function setEcho(msg:String):void    {
_echoComplete = false;
remoteObject.echo(msg);
}

public function getEcho():String{
return _echoMsg;
}

public function echoReady():Boolean{
return _echoComplete;
}

private function register():void{
registerClassAlias(“flex.messaging.messages.CommandMessage”,CommandMessage);
registerClassAlias(“flex.messaging.messages.RemotingMessage”,RemotingMessage);
registerClassAlias(“flex.messaging.messages.AcknowledgeMessage”, AcknowledgeMessage);
registerClassAlias(“flex.messaging.messages.ErrorMessage”,ErrorMessage);
registerClassAlias(“DSC”, CommandMessageExt);
registerClassAlias(“DSK”, AcknowledgeMessageExt);
registerClassAlias(“flex.messaging.io.ArrayList”, ArrayList);
registerClassAlias(“flex.messaging.config.ConfigMap”,ConfigMap);
registerClassAlias(“flex.messaging.io.ArrayCollection”,ArrayCollection);
registerClassAlias(“flex.messaging.io.ObjectProxy”,ObjectProxy);
registerClassAlias(“flex.messaging.messages.HTTPMessage”,HTTPRequestMessage);
registerClassAlias(“flex.messaging.messages.SOAPMessage”,SOAPMessage);
registerClassAlias(“flex.messaging.messages.AsyncMessage”,AsyncMessage);
registerClassAlias(“DSA”, AsyncMessageExt);
registerClassAlias(“flex.messaging.messages.MessagePerformanceInfo”, MessagePerformanceInfo);

}

public function setLoaderURL(url:String):void{
LoaderConfig.mx_internal::_url = url;

}

// Handle the recevied message.
private function resultHandler(event:ResultEvent):void {
_echoComplete = true;
_echoMsg += “Server responded: “+ event.result + “\n”;
}

// Handle a message fault.
private function faultHandler(event:FaultEvent):void {
_echoComplete = true;
_echoMsg += “Received fault: ” + event.fault + “\n”;
}
}
}

This is very similar to the direct LCDS call, except it has getters/setters so the calling tile can go and get the resulting data.   The amf channel listeners are there as a result of me trying to debug the setLoaderURL issue mentioned above.

The Tile

The tile that uses the service is quite simple. It uses the IHelloWorld interface to make the call and relies on Mosaic to inject the service instance at runtime (see my previous entry for more on how this works):

<?xml version=”1.0″ encoding=”utf-8″?>
<mc:Tile xmlns:fx=”
http://ns.adobe.com/mxml/2009″

xmlns:s=”library://ns.adobe.com/flex/spark”
xmlns:mx=”library://ns.adobe.com/flex/mx”
xmlns:mc=”com.adobe.mosaic.core.*”
layout=”absolute” width=”100%” height=”100%”>
<fx:Declarations>
<!– Place non-visual elements (e.g., services, value objects) here –>
</fx:Declarations>

<fx:Script>
<![CDATA[
import com.adobe.etech.IHelloWorld;

[Bindable] public var helloService:IHelloWorld;
private var myTimer:Timer = new Timer(1000,1);  //1 second
private var timeoutID:uint;

// Send the message in response to a Button click.
private function echo():void {
var text:String = ti.text;
//remoteObject.echo(text);
helloService.setLoaderURL(this.loaderInfo.url);
helloService.setEcho(text);
if (helloService.echoReady()){
this.getEcho();
}else{
myTimer.addEventListener(TimerEvent.TIMER,checkEcho);
myTimer.start();
}
}

private function checkEcho(event:TimerEvent):void{
if (helloService.echoReady()){
myTimer.stop();
myTimer.removeEventListener(TimerEvent.TIMER,checkEcho);
this.getEcho();
} else{
//try again
myTimer.reset();
myTimer.start();
}
}

private function getEcho():void{
ta.text = helloService.getEcho();
}

]]>
</fx:Script>

<s:Label x=”11″ y=”5″ text=”LCDS Using Service” width=”195″/>
<s:TextInput x=”11″ y=”24″ id=”ti” text=”Hello World!”/>
<s:Button x=”147″ y=”25″ label=”Send” click=”echo();”/>
<s:TextArea id=”ta” width=”100%” height=”628″ y=”68″/>
</mc:Tile>

You may notice that I have a timer that checks to see if the data is returned.  This is because the service has no built in way of sending a message to the tile. The timer was a bit of a cheat and I wouldn’t recommend using this method in production code. Instead I would recommend passing the tile context to the service so it can send a proper Mosaic message (see this post for more info)

Conclusion

As you can see from this simple example, there are a couple of different options for combining LCDS and Mosaic.  Each has its advantages and weaknesses and it will depend on your situation as to which one to use:

Direct LCDS Call:

Pros:

  • Simple to code
  • Direct tie from data to visual elements

Cons:

  • visual elements not decoupled from tile
  • each tile makes its own connection – no connection sharing

Mosaic Service LCDS Call:

Pros:

  • LCDS call abstracted from tile
  • multiple tiles use same call

Cons:

  • more complex code
  • need to implement your own checker for returned data, or pass the tile context to the service

Special thanks to Leo Schuman and Marcel Boucher for their assistance and patience with LCDS. If you are interested, the source code can be found here.