3. Data Push (Gravity)

Introduction

Granite Data Services provides a Data Push feature, code name Gravity, implemented as a Comet-like service with AMF3 data polling over HTTP (producer/consumer based architecture). This implementation is freely based on the Bayeux protocol specification (1.0draft1 at this time).

For a basic sample of GDS/Gravity, download graniteds-2.0.0.GA.zip and import the examples/graniteds_chat as a new project in Eclipse.

Flex Classes & Sample Usage

GDS Data Push relies on two main AS3 classes on the Flex side: org.granite.gravity.Consumer and org.granite.gravity.Producer. These classes reproduce the original Adobe Flex Consumer and Producer almost exactly. The only differences are that you must use topic instead of subtopic due to a change introduced in Flex 3.

Here is a quick example of GDS Consumer/Producer usage:

...
import org.granite.gravity.Consumer;
import org.granite.gravity.Producer;
...
private var consumer:Consumer = null;
private var producer:Producer = null;

private function connect():void {
    consumer = new Consumer();
    consumer.destination = "gravity";
    consumer.topic = "discussion";
    consumer.subscribe();
    consumer.addEventListener(MessageEvent.MESSAGE, messageHandler);

    producer = new Producer();
    producer.destination = "gravity";
    producer.topic = "discussion";
}

private function disconnect():void {
    consumer.disconnect();
    consumer = null;

    producer.disconnect();
    producer = null;
}

private function messageHandler(event:MessageEvent):void {
    var msg:AsyncMessage = event.message as AsyncMessage;    
    trace("Received message: " + (msg.body as String));
}

private function send(message:String):void {
    var msg:AsyncMessage = new AsyncMessage();
    msg.body = message;
    producer.send(msg);
}
...

In this sample code, the producer sends String messages, which could of course be of any type, and the producer receives String messages as well. These Strings are sent in AsyncMessage envelopes, which is the only envelope type allowed in GDS.

Common Configuration

You need to configure a specific channel and destination as follows:

services-config.xml
<services-config>
    <services>
        <service id="messaging-service"
            class="flex.messaging.services.MessagingService"
            messageTypes="flex.messaging.messages.AsyncMessage">
            <adapters>
                <adapter-definition
                    id="default"
                    class="org.granite.gravity.adapters.SimpleServiceAdapter"
                    default="true"/>
            </adapters>

            <destination id="gravity">
                <channels>
                    <channel ref="my-gravityamf"/>
                </channels>
            </destination>
        </service>
    </services>

    <channels>
        <channel-definition
            id="my-gravityamf"
            class="org.granite.gravity.channels.GravityChannel">
            <endpoint
                uri="http://{server.name}:{server.port}/{context.root}/gravity/amf"
                class="flex.messaging.endpoints.AMFEndpoint"/>
        </channel-definition>
    </channels>
</services-config>

Here, we define a GravityChannel (my-gravityamf) and we use it in the gravity destination. See above destination usage in Consumer/Producer usage.

Three servlet parameters are shared by all Gravity servlet implementations (in particular Tomcat and Jetty) and are related to channel cleanup and client reconnection attempts:

The servlet listener definition is important to ensure proper startup/shutdown of the Gravity services.
web.xml
<web-app version="2.4" ...>
    ...
    <listener>
        <listener-class>org.granite.config.GraniteConfigListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>GravityServlet</servlet-name>
        <servlet-class>org.granite.gravity.tomcat.GravityTomcatServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>GravityServlet</servlet-name>
        <url-pattern>/gravity/*</url-pattern>
    </servlet-mapping>
    ...
</web-app>

Of course, if you are not using Tomcat, you should fill the servlet-class definition with the relevant servlet implementation.

Supported application server deployments

As of now, there is no standard defined for asynchronous servlets and almost each application server has its own support for Comet/asynchronous events.
GraniteDS 2.0 provides implementations of asynchronous servlets for the main open source application servers and also provides a generic non asynchronous servlet implementation suitable for any servlet container.

Application server Servlet name Specific notes
Tomcat 6.0.18+ org.granite.gravity.tomcat.GravityTomcatServlet Only with APR/NIO enabled
JBoss 4.2.2+ org.granite.gravity.tomcat.GravityTomcatServlet APR/NIO, disable CommonHeadersFilter
Jetty 6.1.5+ org.granite.gravity.jetty.GravityJettyServlet  
JBoss 5 org.granite.gravity.jbossweb.GravityJBossWebServlet  
GlassFish V3 org.granite.gravity.async.GravityAsyncServlet Experimental support for Servlet 3.0
Any other org.granite.gravity.generic.GravityGenericServlet No asynchronous support

Unified Configuration

Whatever is the Gravity servlet implementation used in your application, the configuration goes always in granite-config.xml. Here is a sample Gravity configuration with all default options:

granite-config.xml
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE granite-config PUBLIC "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/2.0.0/granite-config.dtd">

<granite-config>

    <gravity
        factory="org.granite.gravity.DefaultGravityFactory"
        channel-idle-timeout-millis="1800000"
        long-polling-timeout-millis="20000"
        reconnect-interval-millis="30000"
        reconnect-max-attempts="60">
    	
        <thread-pool
            core-pool-size="5"
            maximum-pool-size="20"
            keep-alive-time-millis="10000"
            queue-capacity="2147483647" />
    	
    </gravity>

</granite-config>

This <gravity> section is purely optionnal and you may omit it if you accept default values.

Some explanations about these options:

  • channel-idle-timeout-millis: the elapsed time after which an idle channel (pure producer or dead client) may be silently unsubcribed and removed by Gravity. Default is 30 minutes.
  • long-polling-timeout-millis: the elapsed time after which an idle connect request is closed, asking the client to reconnect. Default is 20 seconds. Note that setting this value isn't supported in Tomcat/APR configurations.
  • thread-pool attributes: all options are standard parameters for the Gravity ThreadPoolExecutor instance.

All other configuration options are for advanced use only and you should keep default values.

Tomcat and JBoss/Tomcat Specific Configuration Tips

GDS Data Push for Tomcat relies on the org.apache.catalina.CometProcessor interface. In order to enable Comet support in Tomcat, you must configure an APR or NIO connector.

At least for now, APR is the simplest to configure and the most reliable. To configure APR, see documentation here. In Windows®, it's simply a matter of downloading a native dll and putting it in your WINDOWS/system32 directory – while other and better configurations are possible.

For JBoss 4.2.*, you must comment out a specific filter in the default global web.xml (<JBOSS_HOME>/server/default/deploy/jboss-web.deployer/conf/web.xml):

web.xml
...
<!-- Comment this out!
<filter>
  <filter-name>CommonHeadersFilter</filter-name>
  <filter-class>org.jboss.web.tomcat.filters.ReplyHeaderFilter</filter-class>
  <init-param>
    <param-name>X-Powered-By</param-name>
    <param-value>...</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>CommonHeadersFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
-->
...

See above for Tomcat configuration.

For JBoss 5+ servers, you must use a specific servlet. JBoss 5 implements its own version of Tomcat, named JBossWeb:

web.xml
<web-app version="2.4" ...>
    ...
    <servlet>
        <servlet-name>GravityServlet</servlet-name>
        <servlet-class>org.granite.gravity.jbossweb.GravityJBossWebServlet</servlet-class>
        ... (see Tomcat configuration above for options)
    </servlet>
    ...
</web-app>

Note that you do not need to comment out the CommonHeadersFilter with JBoss 5, but you still need to enable APR.

JMS Configuration & Usage

The JMS adapter configuration follows as closely as possible the standard Flex configuration for the JMS adapter. See here.

Here is a sample configuration for a default JBoss installation with a brief description of the different options:

services-config.xml
<adapters>
  <adapter-definition id="jms" class="org.granite.gravity.adapters.JMSServiceAdapter"/>
</adapters>

<destination id="chat-jms">
  <properties>
    <jms>
      <destination-type>Topic</destination-type>
      <!-- Optional: forces usage of simple text messages
      <message-type>javax.jms.TextMessage</message-type>
      -->
      <connection-factory>ConnectionFactory</connection-factory>
      <destination-jndi-name>topic/testTopic</destination-jndi-name>
      <destination-name>TestTopic</destination-name>
      <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
      <transacted-sessions>false</transacted-sessions>
      <!-- Optional JNDI environment. Specify the external JNDI configuration to access 
        a remote JMS provider. Sample for a remote JBoss server.
      -->
      <initial-context-environment>
        <property>
          <name>Context.SECURITY_PRINCIPAL</name>
          <value>guest</value>
        </property>
        <property>
          <name>Context.SECURITY_CREDENTIALS</name>
          <value>guest</value>
        </property>
        <property>
          <name>Context.PROVIDER_URL</name>
          <value>http://my.host.com:1099</value>
        </property>
        <property>
          <name>Context.INITIAL_CONTEXT_FACTORY</name>
          <value>org.jnp.interfaces.NamingContextFactory</value>
        </property>
        <property>
          <name>Context.URL_PKG_PREFIXES</name>
          <value>org.jboss.naming:org.jnp.interfaces</value>
        </property>
      </initial-context-environment>
    </jms>
    ...
  </properties>
  ...
  <adapter ref="jms"/>
</destination>

Comments on configuration options:

  • destination-type must be Topic for the moment. Queues may be supported later.
  • message-type may be forced to simple text messages by specifying javax.jms.TextMessage.
  • connection-factory and destination-jndi-name are the JNDI names respectively of the JMS ConnectionFactory and of the JMS topic.
  • destination-name is just a label but still required.
  • acknowledge-mode can have the standard values accepted by any JMS provider: AUTO_ACKNOWLEDGE, CLIENT_ACKNOWLEDGE, and DUPS_OK_ACKNOWLEDGE.
  • transacted-sessions allows the use of transactions in sessions when set to 'true'.
  • initial-context-environment: The initial-context parameters allow to access a remote JMS server by setting the JNDI context options.
     

Embedded Apache ActiveMQ Configuration

In the case of a simple Tomcat/Jetty installation, or to allow Flex-to-Flex interactions with advanced capabilities (durable messages), Gravity can be integrated with an embedded ActiveMQ instance.

To enable ActiveMQ, just put the activemq-xx.jar in your WEB-INF/lib directory. The necessary topic will be lazily created on first use, except if the property create-broker is set to false. The uri of the created ActiveMQ broker will be vm://adapterId.

Here is a sample configuration:

services-config.xml
<adapters>
  <adapter-definition
    id="activemq"
    class="org.granite.gravity.adapters.ActiveMQServiceAdapter"/>
</adapters>

<destination id="chat-activemq">
  <properties>
    <jms>
      <destination-type>Topic</destination-type>
      <!-- Optional: forces usage of simple text messages
      <message-type>javax.jms.TextMessage</message-type>
      -->
      <connection-factory>ConnectionFactory</connection-factory>
      <destination-jndi-name>topic/testTopic</destination-jndi-name>
      <destination-name>TestTopic</destination-name>
      <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
      <transacted-sessions>false</transacted-sessions>
    </jms>
    
    <server>
      <durable>true</durable>
      <file-store-root>/var/activemq/data</file-store-root>
      <create-broker>true</create-broker>
      <wait-for-start>false</wait-for-start>
    </server>
  </properties>
  ...
  <adapter ref="activemq"/>
</destination>

Comments on configuration options:

  • The main parameters (<jms>...</jms>) are identical to those used in JMS configuration. See above.
  • durable, if set to true, allows for durable messages, stored in the filesystem. The data store directory of ActiveMQ can be specified by the file-store-root parameter.
  • create-broker is optional, as well as the dependant wait-for-start attribute. When create-broker is false, creation of the broker is not automatic and has to be done by the application itself. In this case, wait-for-start set to true tells the ActiveMQConnectionFactory to wait for the effective creation of the broker. Please refer to the ActiveMQ documentation for more details on these options.
     

Server to Client Messaging Sample with JMS

Sending messages from the server to Flex clients simply consists of sending JMS messages to the corresponding JMS topic. Text messages are received as simple text on the Flex side, object messages are serialized in AMF3 and deserialized and received as ActionScript3 objects. The Gravity messaging channel supports lazily loaded collections and objects, exactly as the Granite remoting channel.

TestBean.java (sending a message from an EJB3 session)
@Stateless
@Local(Test.class)
public class TestBean implements Test {

    @Resource
    SessionContext ctx;

    @Resource(mappedName="java:/ConnectionFactory")
    ConnectionFactory jmsConnectionFactory;

    @Resource(mappedName="topic/testTopic")
    Topic jmsTopic;


    public TestBean() {
       super();
    }

    public void notifyClient(Object object) {
        try {
            Connection connection = jmsConnectionFactory.createConnection();
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            javax.jms.Message jmsMessage = session.createObjectMessage(person);
            MessageProducer producer = session.createProducer(jmsTopic);
            producer.send(jmsMessage);
            session.close();
            connection.close();
        }
        catch (Exception e) {
            log.error("Could not publish notification", e);
        }
    }
}

 

TestBean.java (sending a message from a Seam component)
@Stateless
@Local(Test.class)
@Name("test")
public class TestBean implements Test {

    private static Logger log = Logger.getLogger(TestBean.class.getName());

    @In
    private TopicPublisher testTopicPublisher;   
    @In 
    private TopicSession topicSession;
  
    public void notifyClient(Serializable object) {
        try {
            testTopicPublisher.publish(topicSession.createObjectMessage(object));
        } 
        catch (Exception e) {
            log.error("Could not publish notification", e);
        }
    }
}

 

Server to Client Messaging Sample with Embedded ActiveMQ

The only difference with standard JMS is that you can get a ConnectionFactory more easily. Also ActiveMQ supports subtopics. The name of the topic is built with the following rule:

  • Without subtopic, the name of the ActiveMQ destination should be the same as defined in the jms/destination-name configuration parameter.
  • With subtopic, the name is the concatenation of the destination-name parameter with the subtopic. Wildcards are supported in the subtopic following Flex convention and are converted to the ActiveMQ format (see here), meaning that toto.** is converted to toto.>.
public class Test throws JMSException {
    // adapterId should be the id of the JMS adapter as defined in services-config.xml
    ConnectionFactory f = new ActiveMQConnectionFactory("vm://adapterId");
    Connection connection = jmsConnectionFactory.createConnection();
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

    ActiveMQTopic activeMQTopic= new ActiveMQTopic("destination");
    javax.jms.Message jmsMessage = session.createObjectMessage(person);
    MessageProducer producer = session.createProducer(activeMQTopic);
    producer.send(jmsMessage);

    session.close();
    connection.close();
}

 

Server to Client Messaging Sample with the low-level Gravity API

If you use the SimpleAdapter, the message send will have to be done at a lower level and you will have a hard dependency on Gravity. It's also possible but not recommended to use this API with the JMS and ActiveMQ adapters.

It requires to get the Gravity object from the ServletContext. It is set as an attribute named org.granite.gravity.Gravity.

Then you can send messages of type flex.messaging.messages.Message by calling the method gravity.publish(message);

Gravity gravity = GravityManager.getGravity(servletContext);
AsyncMessage message = new AsyncMessage();
message.setDestination("my-gravity-destination");
message.setHeader(AsyncMessage.SUBTOPIC_HEADER, "my-topic");
message.setBody("Message content");
gravity.publishMessage(message);

It you need to simulate a publish from the client subscribed in the current session, you can get the clientId in the session attribute named org.granite.gravity.channel.clientId.{destination} and set it in the message.


Browse Space

- Pages
- Blog
- Labels
- Attachments
- Bookmarks
- Mail
- Advanced

Explore Confluence

- Popular Labels
- Notation Guide

Your Account

Log In

Other Features

Add Content

- Add Comment


SourceForge.net Logo
Copyright © 2007-2008 Adequate Systems. All Rights Reserved.