Real time notification using Data Services in ADEP

With the release of ADEP and the introduction of the Experience Server (RIA services running on the CRX stack), developers now have access to data services within the ADEP Core (previously known as CRX) runtime. This means that data services components are now deployed as OSGi bundles in ADEP Core. This opens now interesting doors for developers building content-oriented applications (like the app we will build during my BYOD lab at MAX), In this post, I will talk about how you can leverage the data services MessageBroker to push notifications to Flex clients that a node in the ADEP content repository has been added or changed. There are many use cases for this functionality, but the one I find most exciting is in the context of mobile application development.

Imagine you have a mobile application that displays information stored on a server – of course we would use the ADEP content repository for this! The challenge for these types of applications is how to keep that information in sync. Make the user request a refresh? Build some sort of polling mechanism? Why? ADEP data services already has this problem solved with real time messaging. The trick is how to send a message when a node is updated or added (or even removed) in the repository. Before we tackle this challenge, lets look at how data services has been adapted to run within the ADEP Experience Server.

If we are going to use data services messaging, we have to add a destination to the messaging-config.xml file right? You bet, but where can we find this file? Not on the file system that’s for sure. We have a content repository now. The best way to access the content repository as a developer is either to use CRXDE (an Eclipse-based IDE for ADEP Core) or CRXDE Lite (a web-based IDE for ADEP Core). For the purposes of this post, I am going to use CRXDE Lite.

Step 1: Creating a destination

To access CRXDE Lite on your machine (running ADEP Experience Server or the ADEP Solution Quickstart) navigate to http://localhost:4502/crx/de/ in your favorite browser. In this image, you see that the JCR repository path is /etc/aep/config/dataservices/messaging-config.xml.  You can open the file by double-clicking it. You will notice something different about the include statement in the file. Instead of pointing to a specific file to include, we are pointing to a repository folder location.

<destination-include directory-path=”destinations/messaging”/>

What this means is that the MessageBroker will iterate through each file located in the destinations/messaging folder and read in the configurations it finds. Which means that if we want to add a new destination, we need to add a new file in this location. In my sample, I created a file called com.adobe.tm.flex.broker.messaging-config.xml the contents of this file is typical destination config information:

Step 2: Loading configuration changes

This defines a new destination called “flexmobile“. The only thing left to activate this new destination is reload the MessageBroker class. In the J2EE version, this means bouncing the app server or some other weird incantation. Remember that in ADEP, Data Services is now a set of OSGi bundles.To reload MessageBroker to pick up the new configuration changes, we need to access the OSGi (Felix) console. On your machine, navigate to http://localhost:4502/system/console/configMgr. Click on the Components button in the toolbar and locate the following class: com.adobe.dataservices.impl.ApplicationManagerImpl

In the actions column, click on the stop icon. Once the component has stopped, the icon will change to a play button. Click it again to start the component. This will trigger the MessageBroker class to reload the configuration xml files.

Step 3: Creating a bundle to send a message via MessageBroker

Back to our original scenario. We want to send a message via message broker when a node in the content repository changes. A very simple approach to setting this up is to use the ADEP WEM workflow engine. It has built-in launchers that can be triggered by content repository node events like update, add and delete. What I did in this case, was build an ADEP OSGi bundle that implements the workflow execute() interface so it can be called from a workflow process. Since this post is not meant to be a complete tutorial on how to develop on ADEP Core, please refer to Defining a Process Step: with a Java Class for detailed steps and examples. Here is the code from the bundle class:

/*
 * @author: Marcel Boucher (mboucher@adobe.com)
 * MessageBrokerProxy class
 * This class is an implementation of a customer workflow step that can be used
 * to publish messages to the dataservices message broker running on this server.
 * 
 */
package com.adobe.tm.wf.dataservices.messaging;

import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Constants;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;

import flex.messaging.MessageBroker;
import flex.messaging.messages.AsyncMessage;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;



@SuppressWarnings("unused")
@Component
@Service
@Properties({
        @Property(name = Constants.SERVICE_DESCRIPTION, value = "Send Message to Data Services MessageBroker."),
        @Property(name = Constants.SERVICE_VENDOR, value = "Adobe"),
        @Property(name = "process.label", value = "Send to MessageBroker")})


public class MessageBrokerProxy implements WorkflowProcess
{
        private static final Logger log = LoggerFactory.getLogger(MessageBrokerProxy.class);
        private static final String TYPE_JCR_PATH = "JCR_PATH";

        public void execute(WorkItem item, WorkflowSession session, MetaDataMap args) throws WorkflowException 
        {
            //Collect Message information from the dialog.
            String destination = args.get("destination","not set");
            // Default Messagge.
            String messageBody = args.get("message", "not set");
            
            //Set the message to contain the path of the node that has changed.
            WorkflowData workflowData = item.getWorkflowData();
            if (workflowData.getPayloadType().equals(TYPE_JCR_PATH)) {
                String path = workflowData.getPayload().toString() + "/par";
                messageBody = path;
            }
            
            //Create a new message.
            AsyncMessage msg = new AsyncMessage();

            msg.setClientId("ADEP-Experience-Server");
            msg.setTimestamp(new Date().getTime());
            //you can create a unique id
            msg.setMessageId("ADEP-Experience-Server-Message");
            //destination to which the message is to be sent
            msg.setDestination(destination);        
            //set message body
            //msg.setBody(messageBody != null?messageBody:"");
            msg.setBody(messageBody);
            //set message header
            msg.setHeader("sender", "From the server");

            //send message to destination
            MessageBroker.getMessageBroker("__default__").routeMessageToService(msg, null);
          
    }
}

Notice that the getMessageBroker() uses the __default__ argument which is to indicate the instance currently running in the same container.

Step 4: Create and configure the workflow

The next step is defining a workflow that will invoke our custom class when something in a specified node tree changes. You can access the workflow console on you machine using this url: http://localhost:4502/libs/cq/workflow/content/console.html

In the Models tab within the Workflow Console, click the New button and give your new model a meaningful name. For example “Post to Message Broker”. Double click on the newly created model to edit it. Delete the default user task from the model and add the custom step you created in the previous step from the Workflow group in Sidekick. You can double click the step to access it’s property dialog and configure additional settings. When you are done editing, click the Save button to return to the Workflow Console.

That’s it for the workflow model. All we want it to do is call our custom class to post a message to the MessageBroker.

The next thing we need to define is how this new workflow model will be launched. From the Workflow Console, click on the Launcher tab. You will see a significant list of launchers that are pre-configured in the system. We are going to add a new launcher, so click on the Add… button. Complete the launcher configuration settings and click OK.

ADEP will now monitor the node located in the path provided and launch the specified workflow when that node is modified.

The next step is to build a Flex application that subscribed to the MessageBroker destination and handles the messages it receives.

Step 4: The message consumer

Create a typical Flex application, you will need the FDS.swc library of course to use messaging. Simply define a consumer like you would with regular data services


<span><<span>mx</span>:Consumer id="consumer"</span>
<span> destination="<span>flexmobile</span>"</span>
 message="messageHandler(event);"
<span> fault="<span>faultHandler</span>(event);"/></span>

All you have to do now is build your handler functions and you now have real time notification of ADEP content repository changes. Go on and start building your content-driven applications.

Comments Closed