Saturday, January 2, 2010

JBoss ESB on the Google's Clouds: Spreeadsheet Listener

Recently my brother started a new kind of service in his very small consultancy company, he is offering a kind of Consultancy over the Google Apps focused in small business customers, although I believe that Google Apps is not suitable just for small ones, but for bigger companies/clients as well.




Google Apps offers lots of good functionalities and services, such as: a) E-Mail, b) Site, c) Agenda, d) Documents (An online Office suite). I believe that this is a good strategy, so you might face in a near feature people replacing the older Excel files by new Google Spreadsheets, and than I decide work on it a little bit, creating a new Gateway to interact with this Google Service.

Getting Started with Google APIs

Google offers a good documentation over their APIs[1], in fact, everything that Google does, you are able to interact with just using Java, Python or even JavaScript[2].

You might use Google Spreadsheets for many purposes, since a basic data collecting, or as my friend JP Viragine told me: "Maybe we could use as a "Decision Table on the Clouds", or just a excel replacement. The big advantage, beyond the fact you don't need to install anything in your computer, is the "Collaboration", because you might share the online file with another people that will be able for editing any time and anywhere.

Another big difference when we are handling such kind of technology is the simple fact that we are totally stateless, once the Http allows us connect, and after the processing the http will give an answer, and that's it, is not that easy we use "Observers/Observables" when we are talking about HTTP (by default Stateless protocol), so my strategy was use "Timers" for polling the http service. I had not found a way to receive a notification from the service using Java, maybe it would make my job easier.

New JBoss ESB Listeners


If you create your first JBoss ESB gateway, you will be able to create as much as you need! The only thing that I have to recall when I have to create a new Gateway/Listeners is the wiki section that Tom Fennelly wrote [3]. So everything you will see here in this entry is very well documented there, you must say thanks to Tom, he did a really good job documenting about this subject.


JBoss ESB in Action with GoogleDocs

The first step is declare our listener into Jboss-esb.xml, there we will describe everything we need, such as properties and its values as well as the Listener class itself:



The most important configuration from this listener is the property: gatewayClass, which we are using: org.jboss.soa.jbossesb.gateways.google.GoogleDocsGateway , which is not an ou-of-the-box class from JBoss ESB, it is totally new, and you will see it working pretty soon. Moreover, we defined other properties that will be used by the Listener classes further, this properties are: documentName, feedURL, username, password and so on.

I created a very ugly class GoogleDocsGateway, that for this entry is handling just the spreadsheet, this is a class that extends AbstractThreadedManagedLifecycle, after this the rest is quite easy. When you are extending this class, you must keep in mind that there are certain tasks to think about:

1 -Define what you will do in the doInitialise() method, this is the method called when JBoss ESB invokes this service by the first time.

2- The doRun() is the most important one, this is a method that or will be waiting for an event notification, or you must try get a way to poll the protocol that you are interacting with, in order to don't consume unnecessary resources.

package org.jboss.soa.jbossesb.gateways.google;

import java.util.Timer;
import java.util.TimerTask;

import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.Service;
import org.jboss.soa.esb.client.ServiceInvoker;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.listeners.lifecycle.AbstractThreadedManagedLifecycle;
import org.jboss.soa.esb.listeners.lifecycle.ManagedLifecycleException;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.format.MessageFactory;

/**
 * Google Gateway Class
 * 
 * @author esilva
 */
public class GoogleDocsGateway extends AbstractThreadedManagedLifecycle {

 private ConfigTree listenerConfig;
 private Service service;
 private ServiceInvoker serviceInvoker;

 private GoogleSpreadSheet sheet = new GoogleSpreadSheet();

 protected boolean firstExecution = true;

 public GoogleDocsGateway(ConfigTree config) throws ConfigurationException {
  super(config);

  this.listenerConfig = config;

  String serviceCategory = listenerConfig
    .getRequiredAttribute(ListenerTagNames.TARGET_SERVICE_CATEGORY_TAG);

  String serviceName = listenerConfig
    .getRequiredAttribute(ListenerTagNames.TARGET_SERVICE_NAME_TAG);

  service = new Service(serviceCategory, serviceName);

  sheet.setService(config.getAttribute("servicename"));

  sheet.setUsername(config.getAttribute("username"));

  sheet.setPassword(config.getAttribute("password"));

  sheet.setFeedurl(config.getAttribute("feedurl"));

  sheet.setDocumentName(config.getAttribute("documentname"));

 }

 @Override
 protected void doRun() {

  publishMessageToESB();

  int delay = 10000; 
  int period = 5000; 
  Timer timer = new Timer();

  timer.scheduleAtFixedRate(new TimerTask() {
   public void run() {

    try {
     if (sheet.hasChanges()) {

      System.out
        .println("Changes found, publishing the message again");

      publishMessageToESB();

     }

     else {

      System.out.println("No updates found");

     }

    } catch (Exception e) {

     e.printStackTrace();
    }

   }

  }, delay, period);

 }

 private void publishMessageToESB() {
  Message esbMessage = MessageFactory.getInstance().getMessage();

  try {

   esbMessage.getBody().add(sheet.load());

  } catch (Exception e) {

   e.printStackTrace();
  }
  try {
   serviceInvoker.deliverAsync(esbMessage);

  } catch (MessageDeliverException e) {

   e.printStackTrace();
  }
 }

 @Override
 protected void doInitialise() throws ManagedLifecycleException {

  try {
   serviceInvoker = new ServiceInvoker(service);

   sheet.initialize();

  } catch (MessageDeliverException e) {

   throw new ManagedLifecycleException(
     "Failed to create ServiceInvoker for Service listening Google Service: "
       + service + "'.");
  }

  catch (Exception e) {

   throw new ManagedLifecycleException(e);
  }

 }

}



I created a new class called: GoogleSpreadSheet, that is an abstraction over the tasks we must to do to interact with the Google Spreadsheets, and through the doRun() method I am using a kind of scheduler to poll the url of my spreadsheet according the property configuration made by the "user" of this gateway.

package org.jboss.soa.jbossesb.gateways.google;

import java.net.URL;
import java.util.List;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;

public class GoogleSpreadSheet implements Job {

 protected static SpreadsheetService myService;

 protected URL metafeedUrl;

 protected SpreadsheetFeed feed;

 protected List spreadsheets;

 protected SpreadsheetEntry entry = null;

 private String service;
 private String username;
 private String password;
 private String feedurl;
 private String documentName;
 private String lastUpdate;

 protected boolean on = false;

 public String getService() {
  return service;
 }

 public void setService(String service) {
  this.service = service;
 }

 public String getUsername() {
  return username;
 }

 public void setUsername(String username) {
  this.username = username;
 }

 public String getPassword() {
  return password;
 }

 public void setPassword(String password) {
  this.password = password;
 }

 public String getFeedurl() {
  return feedurl;
 }

 public void setFeedurl(String feedurl) {
  this.feedurl = feedurl;
 }

 public String getDocumentName() {
  return documentName;
 }

 public void setDocumentName(String documentName) {
  this.documentName = documentName;
 }

 public String getLastUpdate() {
  return lastUpdate;
 }

 public void setLastUpdate(String lastUpdate) {
  this.lastUpdate = lastUpdate;
 }

 public GoogleSpreadSheet() {

 }

 public void initialize() throws Exception {

  myService = new SpreadsheetService(service);

  System.out
    .println("Initializing Connection between JBoss ESB and Google Docs...");

  myService.setUserCredentials(username, password);

  metafeedUrl = new URL(feedurl);

  feed = myService.getFeed(metafeedUrl, SpreadsheetFeed.class);

  spreadsheets = feed.getEntries();

  entry = null;

  for (int i = 0; i < spreadsheets.size(); i++) {

   entry = spreadsheets.get(i);

   if (entry.getTitle().getPlainText().equalsIgnoreCase(documentName)) {

    setLastUpdate(entry.getUpdated().toString());

   }
  }

 }

 public String load() throws Exception {

  StringBuilder builderOut = new StringBuilder();

  entry = null;

  for (int i = 0; i < spreadsheets.size(); i++) {

   entry = spreadsheets.get(i);

   if (entry.getTitle().getPlainText().equalsIgnoreCase(documentName)) {

    List worksheets = entry.getWorksheets();

    for (int j = 0; j < worksheets.size(); j++) {

     WorksheetEntry worksheet = worksheets.get(j);

     URL listFeedUrl = worksheet.getListFeedUrl();

     ListFeed listFeed = myService.getFeed(listFeedUrl,
       ListFeed.class);

     if (listFeed.getEntries().size() > 0) {

      StringBuilder header = new StringBuilder();

      for (String tag : listFeed.getEntries().get(0)
        .getCustomElements().getTags()) {

       header.append(tag + ",");

      }

      builderOut.append(header.toString().substring(0,
        header.lastIndexOf(",")));

     }

     for (ListEntry entrada : listFeed.getEntries()) {

      StringBuilder row = new StringBuilder();

      for (String tag : entrada.getCustomElements().getTags()) {

       row.append(entrada.getCustomElements()
         .getValue(tag)
         + ",");

      }

      builderOut.append(row.toString().substring(0,
        row.lastIndexOf(",")));

     }
    }

   }

  }

  return builderOut.toString();

 }

 public boolean hasChanges() throws Exception {

  SpreadsheetFeed feed = myService.getFeed(metafeedUrl,
    SpreadsheetFeed.class);

  List spreadsheets = feed.getEntries();

  entry = null;

  for (int i = 0; i < spreadsheets.size(); i++) {

   entry = spreadsheets.get(i);

   if (entry.getTitle().getPlainText().equalsIgnoreCase(documentName)) {

    if (getLastUpdate().equalsIgnoreCase(
      entry.getUpdated().toString())) {

     System.out.println(getLastUpdate() + "==========="
       + entry.getUpdated().toString());

     return false;

    } else {

     this.setLastUpdate(entry.getUpdated().toString());
     System.out.println("Changes arriving: " + getLastUpdate()
       + "===========" + entry.getUpdated().toString());
     return true;

    }

   }

  }
  return false;

 }

 public GoogleSpreadSheet(String service, String username, String password,
   String feedurl, String documentName, String lastUpdate) {

 }

 public String load(String service, String username, String password,
   String feedurl, String documentName, String lastUpdate)
   throws Exception {

  SpreadsheetService myService = new SpreadsheetService(service);

  myService.setUserCredentials(username, password);

  URL metafeedUrl = new URL(feedurl);

  SpreadsheetFeed feed = myService.getFeed(metafeedUrl,
    SpreadsheetFeed.class);

  List spreadsheets = feed.getEntries();

  SpreadsheetEntry entry = null;

  StringBuilder builderOut = new StringBuilder();

  for (int i = 0; i < spreadsheets.size(); i++) {

   entry = spreadsheets.get(i);

   if (entry.getTitle().getPlainText().equalsIgnoreCase(documentName)) {

    System.out.println("Last Update: " + entry.getUpdated());

    List worksheets = entry.getWorksheets();

    for (int j = 0; j < worksheets.size(); j++) {

     WorksheetEntry worksheet = worksheets.get(j);

     URL listFeedUrl = worksheet.getListFeedUrl();

     ListFeed listFeed = myService.getFeed(listFeedUrl,
       ListFeed.class);

     if (listFeed.getEntries().size() > 0) {

      StringBuilder header = new StringBuilder();

      for (String tag : listFeed.getEntries().get(0)
        .getCustomElements().getTags()) {

       header.append(tag + ",");

      }

      System.out.println(header.toString().substring(0,
        header.lastIndexOf(",")));
      builderOut.append(header.toString().substring(0,
        header.lastIndexOf(",")));

     }

     for (ListEntry entrada : listFeed.getEntries()) {

      StringBuilder row = new StringBuilder();

      for (String tag : entrada.getCustomElements().getTags()) {

       row.append(entrada.getCustomElements()
         .getValue(tag)
         + ",");

      }

      System.out.println(row.toString().substring(0,
        row.lastIndexOf(",")));
      builderOut.append(row.toString().substring(0,
        row.lastIndexOf(",")));

     }
    }

   }

  }
  return builderOut.toString();

 }

 @Override
 public void execute(JobExecutionContext ctx) throws JobExecutionException {

  try {
   System.out.println("Has Changes: " + this.hasChanges());
  } catch (Exception e) {

   e.printStackTrace();
  }

 }

}



Instead to use lots of confusing images, I will use a screencast:



I hope you liked that idea, and you can feel yourself more comfortable for creating new Listeners/Gateways for JBoss ESB.


Rerefences

[1] - http://code.google.com/intl/pt-BR/apis/spreadsheets/
[2] - http://code.google.com/intl/pt-BR/apis/spreadsheets/data/3.0/developers_guide_java.html
[3] - http://community.jboss.org/docs/DOC-13193

No comments: