The MetaSieve Blog

August 25, 2010

Using Cometd 2.x with Grails

Filed under: Uncategorized — Tags: , , , — Björn Wilmsmann @ 8:16 pm

I’ve been struggling quite a bit to get CometD (see this for more information on the Comet Pattern) working with Grails and the corresponding Grails CometD plugin, the documentation being sparse and the new version of the CometD API being somewhat confusing with lots of new interfaces and abstractions.

Anyway, I’ve got it working so I thought, I might share the experience (see code sections at the end of the article for copying source code):

Assuming that you would like a Grails service to communicate via CometD, this is how a basic implementation of a simple service that finds Account entities for search parameters would look like:

AccountService.groovy

AccountService.groovy

There are a few things to note here. The service implements the interface InitializingBean provided by Spring. This interface supplies an afterPropertiesSet() method that allows you to initialize additional properties after Spring has wrought its magic. This way you can initialize and handshake with the Bayeux server using the bayeux bean that’s injected by the CometD plugin.

The runAsync closure in findAccounts(def query, def params = [:]) is provided by the Grails Executor plugin, which allows you to run background threads in your application without losing the Hibernate session. This is exactly why runAsync is needed here. As findAccounts(def query, def params = [:]) will be called asynchronously we have to make sure everything that occurs inside this method is thread-safe.

Via the subscribe() method you can have a so-called SubscriberListener subscribe to a messaging channel:

MySubscriberListener.groovy

MySubscriberListener.groovy

Such a SubscriberListener has to extend the abstract class SessionChannel.SubscriberListener as provided by org.cometd.bayeux.client.SessionChannel. This class demands that its sub-classes provide an onMessage(SessionChannel channel, Message message) method, which will act as a callback method upon new messages on the channels the listener is subscribed to.

Apart from that, there’s some nice Groovy magic happening here that allows you to instantiate a MySubscriberListener which calls a service method without actually hard-coding that service method in the MySubscriberListener class. In this case, the listener will call the service’s findAccounts(def query, def params = [:]) method.

The client side of this little example would look something like this (assuming you’re using the jQuery version of the CometD client), with cometd-subscriptions.js being loaded first and init.js afterwards:

cometd-subscriptions.js

cometd-subscriptions.js

init.js

init.js

Once this is all set up, you can do something like this to nudge AccountService from your JavaScript client:

$.cometd.publish('/requests/search', { 'payload': { q: 'some query', params: { offset: 10 } } });

I hope this primer helps you to overcome initial problems with CometD and Grails. If you’ve got any questions or additional contributions, please feel free to leave a comment.

Source code for copy & paste:

AccountService.groovy:

package myapp

import grails.converters.JSON

import org.springframework.beans.factory.InitializingBean

class AccountService implements InitializingBean {

def bayeux
def bayeuxSession

static transactional = true

void afterPropertiesSet() {
bayeuxSession = bayeux.newLocalSession()
bayeuxSession.handshake()
bayeuxSession.getChannel('/requests/search').subscribe(new MySubscriberListener
(this, 'findAccountsToFollow', ['q', 'params']))
}

def findAccounts(def query, def params = [:]) {
runAsync {
def accounts = Account.findAllByName(query, params)

// publish to search results channel
bayeuxSession.getChannel('/results/search').publish(['payload':['accounts':accounts]] as JSON)
}
}
}

MySubscriberListener.groovy:

package myapp

import org.cometd.bayeux.Message
import org.cometd.bayeux.client.SessionChannel

class MySubscriberListener extends SessionChannel.SubscriberListener {
def callbackService
def callbackMethod
def callbackParams

public MySubscriberListener(def service, def methodName, def params = []) {
callbackService = service
callbackMethod = methodName
callbackParams = params
}

public void onMessage(SessionChannel channel, Message message) {
// callback
def callbackParamValues = []
callbackParams.each { param ->
callbackParamValues.add(message.data.payload."${param}")
}

callbackService."${callbackMethod}"(*callbackParamValues)
}
}

cometd-subscriptions.js:

var subscriptions = {};

function refreshCometSubscriptions(channels, callbackFunctions) {
for (var i in channels) {
if (typeof channels[i] == 'string') {
unsubscribeFromCometChannel(channels[i]);
subscribeToCometChannel(channels[i], callbackFunctions[channels[i]]);
}
}
}

function unsubscribeFromCometChannel(channel) {
if (subscriptions[channel]) {
$.cometd.unsubscribe(subscriptions[channel]);
}
subscriptions[channel] = null;
}

function subscribeToCometChannel(channel, callbackFunction) {
subscriptions[channel] = $.cometd.subscribe(channel, callbackFunction);
}

init.js:

// initialize cometd
var channels = ['/test', '/results', '/results/search'];

var testCallback = function() { };
var resultsCallback = function() { };
var searchResultsCallback = function(message) {
renderFollowerSearchResults(JSON.parse(message.data).payload.accounts, '#resultsContainer', 'resultList', '#box2');
};

var callbackFunctions = { '/test' : testCallback, '/results' : resultsCallback, '/results/search' : searchResultsCallback };

$.cometd.init('../../cometd');
$.cometd.addListener('/meta/connect', function(message) {
if ($.cometd.isDisconnected()){
return;
}
if (message.successful) {
$.cometd.publish('/test', { 'data': { 'message':'Connection with CometD server has been established.' } });
}
});
refreshCometSubscriptions(channels, callbackFunctions);

Advertisements

39 Comments »

  1. Thx for that! Very handy that I started the cometd part of my project today and not last week 😉

    Comment by Jim — August 26, 2010 @ 3:34 pm

  2. One additional note: I’ve refactored MySubscriberListener a little. Instead of extending SessionChannel it now implements ClientSessionChannel and also takes ClientSessionChannel instead of SessionChannel as an argument for onMessage.

    Sorry for this but some old beta Bayeux JavaDoc still mentioned SessionChannel as a valid API end point whereas a more recent beta JavaDoc deprecates SessionChannel. As I said the Bayeux documentation currently still is a bit of a mess …

    Comment by Björn Wilmsmann — August 27, 2010 @ 2:08 am

  3. Hi,
    I have created a grails application, installed the cometd plugin, the executor plugin and the jquery plugin. And also create the service and the listener.
    I have the following p1.gsp but does post requests to http://localhost:8080/cometd/handshake with error 400 bad request.
    Any idea?

    Sample title

    $(document).ready(function(){
    });

    $.cometd.publish(‘/requests/search’, { ‘payload’: { q: ‘some query’, params: { offset: 10 } } });

    Sample line

    Comment by Franki — September 1, 2010 @ 11:58 am

    • There some typo errors:
      -in AccountService.groovy the MySubscriberListener constructor use findAccountsToFollow instead of findAccount.
      -also use ‘/requests/search’ instead of ‘/results/search’

      I also have change $.cometd.init(‘../../cometd’); on init.js to use an absolute url,in my case ‘http://localhost:8080/poc-cometd/cometd’

      Comment by Franki — September 1, 2010 @ 2:13 pm

  4. Thanks for the corrections.

    Comment by Björn Wilmsmann — September 3, 2010 @ 11:25 am

  5. Hi,

    Could you tell me what version of Grails this app was created with? I’m trying to use the plugin with Grails 1.3.4 but it doesn’t seem to want to play ball.

    Regards.

    J.

    Comment by J. Morgan — September 13, 2010 @ 11:41 pm

  6. I did this using Grails 1.3.4, too.

    Comment by Björn Wilmsmann — September 14, 2010 @ 8:11 am

    • Hi,

      I’m new to Grails but would love to get the Cometd plugin working for a project at work. I’m still having trouble and I have a few questions:

      1. The MySubscriberListener can’t find the org.cometd.bayeux.Message or the import org.cometd.bayeux.client.ClientSessionChannel classes. Do you include the bayeux-api-2.0.0.jar as a libarary?

      2. If I do include the bayauex-api library, I get the following error:

      MySubscriberListener.groovy: 4: Can’t have an abstract method in a non-abstract class. The class ‘MySubscriberListener’ must be declared abstract or the method ‘boolean isService()’ must be implemented.

      Comment by J. Morgan — September 14, 2010 @ 4:43 pm

  7. Oh ok, thanks. I shall persevere. 🙂

    J.

    Comment by J. Morgan — September 14, 2010 @ 10:54 am

  8. No, you don’t have to include that JAR yourself. It’s included in the plugin.

    Comment by Björn Wilmsmann — September 14, 2010 @ 4:56 pm

    • We need to add the bayeux-api to implement the same. But I am getting the same issue Can’t have an abstract method in a non-abstract class.

      Comment by Dibya — October 11, 2010 @ 4:20 am

  9. I am new to cometd implementation. Can anyone help me how to integrate the same with GRAIL application.

    Comment by DIbya — September 28, 2010 @ 6:27 pm

  10. I can’t run the application… STS says unable to resolve class org.cometd.bayeux.Message etc.
    Did someone meet this problem? How to fix it?
    Or maybe somebody has a working Eclipse (or some other IDE) project?
    Thanks in advance!

    Comment by greg — October 15, 2010 @ 9:29 am

  11. Anyone?

    Comment by greg — October 19, 2010 @ 12:38 pm

  12. Thanks for this very helpful tutorial !

    Here is an update for MySubscriberListener needed to make the code work with grails 1.3.5 and the 0.2.2 version of the cometd plugin

    package cavi

    import org.cometd.bayeux.Message
    import org.cometd.bayeux.client.ClientSessionChannel;

    class MySubscriberListener implements org.cometd.bayeux.client.ClientSessionChannel.MessageListener{
    def callbackService
    def callbackMethod
    def callbackParams

    public MySubscriberListener(def service, def methodName, def params = []) {
    callbackService = service
    callbackMethod = methodName
    callbackParams = params
    }

    public void onMessage(ClientSessionChannel channel, Message message) {
    // callback
    def callbackParamValues = []
    callbackParams.each { param ->
    callbackParamValues.add(message.data.payload.”${param}”)
    }

    callbackService.”${callbackMethod}”(*callbackParamValues)
    }
    }

    also in movieService the method should be called findAccountsToFollow instead of findAccounts (in order to match the argument in the afterPropertiesSet method

    Comment by Lucas T — November 29, 2010 @ 7:21 am

    • about the client, I’m using JQuery and the $.cometd.init(‘http://localhost:8080/myapp/cometd’) wont work. I read in here http://cometd.org/documentation/cometd-javascript that I need to have the files in a specific path: org/cometd.js, and jquery/jquery.cometd.js etc etc. But I have them in js/org/cometd.js and js/jquery.cometd.js, does this matter or what am I doing wrong?. Server works fine.

      Comment by Humberto Guerrero — December 8, 2010 @ 8:44 pm

      • This should work fine but you’ll of course have to set your paths accordingly where the JS files are included. Your browser’s error console should tell further details as for why $.cometd.init(‘http://localhost:8080/myapp/cometd’) doesn’t work.

        Comment by Björn Wilmsmann — December 9, 2010 @ 1:27 am

      • Wow lucky me, quick answer. Well my js file paths seem correct, and I got this from my browser console says in cometd.js: “_transport is null” and in jquery.cometd.js says: “object doesn’t accept this property or method”,

        PS: brower messages are translated from Spanish so I don’t know how the browsers in english show up these errors. Thanks in advance. 🙂

        Comment by Humberto Guerrero — December 9, 2010 @ 1:50 am

      • Hey. I made it WORK!, i had the imports in the wrong order. Thanks! 🙂

        Comment by Humberto Guerrero — December 11, 2010 @ 2:20 am

  13. Glad you made it 🙂

    Comment by Björn Wilmsmann — December 11, 2010 @ 6:03 am

  14. Anyone having issues with cometd and google chrome? My app works perfectly in Firefox and even IE, but when using Chrome I get a “Wrong url scheme for WebSocket” and then none of my JS works from that point forward. Any ideas?

    Comment by Jeremy — December 13, 2010 @ 1:25 am

    • That’s strange because WebSockets are different from Comet. See http://en.wikipedia.org/wiki/WebSockets for more information.

      Comment by Björn Wilmsmann — December 13, 2010 @ 1:41 am

    • Actually it seems CometD 2 JavaScript has some built-in WebSocket compatibility, although I could not find any documentation about it. In fact, there is hardly any documentation about Cometd 2. Was the project abandoned ?

      In order to handle WebSockets properly I guess some server-side coding is needed. Actually I think only Jetty 8 supports Websocket. To disable weboscket handling in Chrome I had to change these lines in cometd.js , around line 75

      // By default, support WebSocket
      var _supportsWebSocket = true;

      Just set _supportswebSocket to false and it will fall back to conventional cometd on Chrome.

      Comment by Agence Drupal — December 13, 2010 @ 5:11 am

  15. Hey guys its me again. I think I got the hang of the basics just fine but now I would like some advice, so here is the situation:

    I’m handling users, each one with his own channel (sort of like an IM but with valuable data). So I handle the message sending through the server using an ajax request (for security and saving data to the database) and the recieving by using js (unsecure).

    I was thinking, maybe in the listener instead of reading the contents of the message directly in js, I do an ajax request to read the contents from the database, and use the js callback just to fire the request.

    Let me know what you guys think, or if there is a better solution I would be grateful.

    Comment by Humberto Guerrero — January 11, 2011 @ 8:44 pm

    • came up with solution, If you wanna know how let me know 🙂

      Comment by Humberto Guerrero — January 14, 2011 @ 8:01 pm

  16. Hi, I’m trying to get cometd work with grails, but with no success. Can someone post some updated code, with the correct imports, implements…
    Thanks.

    Comment by Roje — February 4, 2011 @ 12:02 am

    • Here is my code. I made a service specifically for Cometd on the server Side.
      If you could be more specific I could help you better. 🙂

      import java.text.SimpleDateFormat;
      import org.cometd.bayeux.Bayeux;
      import org.cometd.bayeux.Channel;
      import org.cometd.bayeux.Message;
      import org.cometd.bayeux.Bayeux.BayeuxListener;
      import org.cometd.bayeux.client.ClientSession;
      import org.cometd.bayeux.client.ClientSessionChannel;
      import org.cometd.bayeux.server.BayeuxServer;
      import org.cometd.bayeux.server.ServerMessage;
      import org.cometd.bayeux.server.ServerSession;
      import org.cometd.bayeux.server.ServerSession.MessageListener;
      import org.springframework.beans.factory.InitializingBean;
      import mx.com.cel.cometd.SuscripcionListener;

      class CometdService implements InitializingBean, Runnable{

      def bayeux
      def bayeuxSession
      def bitacoraService
      def authService
      def timer

      static transactional = true

      static timerChannelName = ‘/system/timer’

      void afterPropertiesSet(){

      def timer = new Thread(this)

      bayeuxSession = bayeux.newLocalSession()
      bayeuxSession.handshake()
      bayeuxSession.getChannel(Channel.META_HANDSHAKE).addListener(new ClientSessionChannel.MessageListener(){

      void onMessage(ClientSessionChannel clientSessionChannel, Message message){
      println “HANDSHAKE SUCCESSFUL”
      }

      })

      //Starts the system timer thread
      timer.start()
      }

      void run(){
      //Publishes the System current Time
      def timerChannel = bayeuxSession.getChannel(timerChannelName)
      while(true){

      timerChannel.publish(System.currentTimeMillis())
      sleep(1000)
      }
      }
      }

      Comment by Humberto Guerrero — February 4, 2011 @ 1:58 am

      • Ignore the bitacoraService and the authService lines

        Comment by Humberto Guerrero — February 4, 2011 @ 3:55 am

      • Thank you Humberto for your quick answer. It was a bit late yesterday evening, and I wasn’t very explicit, sorry.
        My problem was that I had compilation errors with ClientSessionChannel and MessageListener. I added the bayeux jars in the project lib directory and updated the classpath. I know that the cometd plugin should/would integrate the jars, but there was probably a problem.
        The compilation problems are gone and I can run the project, but now I have a javascript error, something like “$.cometd is not an object”. As it was late I stopped trying and searching. But I’ll try again this evening at home. Is it mandatory to use the jquery plugin ? I’m using the dojo framework…

        Thanks,
        Roje.

        Comment by Roje — February 4, 2011 @ 2:30 pm

      • Hey Roje

        jquery is not mandatory and I guess you could use dojo just fine (I’ve never tried it myself), only the syntax changes a little bit. You should check this site to see what I mean.

        http://cometd.org/documentation/cometd-javascript.

        Greetings. Humberto

        Comment by Humberto Guerrero — February 4, 2011 @ 8:02 pm

      • Thank you Humberto,
        and sorry for the response time…
        but I got it working 🙂

        Comment by Roje — February 10, 2011 @ 9:11 pm

      • Awesome 🙂

        Comment by Humberto Guerrero — February 11, 2011 @ 1:15 am

  17. I have develop a graph showing cpu usage(like task manager shows and showing those on graph in graphical representation(but I am using the static values)) using rgrahs.
    And I am generating the graphs on grails 1.3.5 by taking random values genrated by the random of rgraph or we can those values are static.
    can anyone help to know how to generate random values using comted plugin and implementing interfaces from server side and utilizing that values to generate graph.
    Please provide help if anyone can!!

    Comment by Swati — February 8, 2011 @ 1:53 pm

  18. Hi,
    you could make available to the entire sample project?
    I can not run your sample application.

    Thanks

    Comment by Andrea — April 7, 2011 @ 2:26 pm

    • Sorry, there’s no sample app. The code’s been extracted from an actual app.

      Comment by Björn Wilmsmann — April 7, 2011 @ 3:06 pm

      • Ok, thank you. Where can I find more detailed documentation about the plugin cometd grails? Thanks

        Comment by Andrea — April 7, 2011 @ 3:12 pm

  19. Ther’s none that I know of apart from the one linked in the blog post.

    Comment by Björn Wilmsmann — April 7, 2011 @ 3:33 pm

  20. Hi,
    Thanks for posting that , I was struggling with threading in a service and your runAscyn explanation solved it for me…. Full CometD next…

    Comment by Alan — February 17, 2012 @ 5:28 pm

  21. i am new to both grails and cometd. However I need to set up a demo example of how cometd and grails run together. Will it be possible for someone to help by providing a starting steps of how to take this ex forward.

    I mean

    1) I would be required to create a grails app

    grails create-app

    2) Install the cometd plugin

    grails install-plugin cometd

    What i would request is where does the remaining part of the code go. Like where shall i put it. In the controller? etc. can someone please help?

    Comment by avi — August 2, 2012 @ 2:42 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: