Part III – Back-end development: Spring Roo and WebSockets

Remember the previous posts? (Building an information radiator: Part II – Front end development (HTML5 & CSS3)) We started off by explaining Walle’s architecture and the front-end development, but Walle has a lot of more cool technology under the hood: Spring Roo and WebSockets.

Components

In the previous posts we mentioned that there are several components, each with a client-side part (view) and a server-side part (controller and model):

Client and server side of the Walle components

The following sections will focus on the server-side (Spring Roo) and communicating from the client-side with the server-side (WebSockets).

Spring Roo

We needed a high productivity framework that provides us the following features:

  • layered architecture
  • a nice way of differing and blending custom and generated code
  • based on standards when using integration and data connectivity
  • extensible with plugins
  • and last but not least: easy to get rid off
After using Roo for some different projects we tried it for Walle and it worked out nicely because it appeared to be a match for the required features:
  • based on Spring MVC, with several other options for the Views like JSF and GWT
  • using Spring AOP, Roo does a nice job handling generated .aj files and custom .java files
  • the stack is based in the Spring Framework with full JPA-support and Spring MVC with REST support.
  • it’s easy to write Roo add-ons
  • it’s possible to un-Roo your application, after a so called push-in a runnable Java Web application is left.
We started by defining a small data model for Walle containing concepts for BuildInfo (Jenkins), ServerStatus (Nagios), CalendarItem (Google Calendar), ReleaseInfo (JIRA), Tweet (do I need to explain this?) and HeartBeat (we like to know if our data sources are still up and running):

Datamodel Walle

This leads us to using the shell to create the project, appropriate entities, controllers and JSON-support:
// create project and setup database
project --topLevelPackage nl.avisi.wallboard --projectName wallboard --java 6
persistence setup --provider HIBERNATE --database MYSQL

// create simple entity
entity --class ~.domain.DevelopmentProject
field string name

// create an enum
enum type --class ~.domain.info.Environment
enum constant --name ONTWIKKELING
enum constant --name TEST
enum constant --name ACCEPTATIE
enum constant --name PRODUCTIE
enum constant --name INTEGRATIE
// create a less simple entity using an enum
entity --class ~.domain.info.ServerStatus
field string service
field string hostname
field string hostaddress
field string status
field number --type java.lang.Long --fieldName tstamp
field enum --fieldName environment --type ~.domain.info
Environment
// create a reference between the created entities
field reference --fieldName project --type ~.domain.config.DevelopmentProject --notNull false

// generate controllers
controller all --package ~.controller
// create a custom finder to use in the controller
finder add --finderName findServerStatusesByProjectAndEnvironment
// add JSON support to the controllers
json add

When the smoke is gone, Spring Roo generated some great code:

  • for every entity a .java file and one or more .aj files
  • for every controller a .java file and one or more .aj files
  • for every controller a scaffolded view using Spring MVC

As a developer you have full control of the .java files so we did some small modifications to the entity like a unique constraint and a custom query:

@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"service", "hostname"}))
public class ServerStatus {
  private String service;
  private String hostname;
  private String hostaddress;
  @Enumerated
  private ServerStatusKey status;
  private Long tstamp;
  @Pattern(regexp = "[0-9]{1,2}.[0-9]{1,2}.[0-9]{1,2}")
  private String runningversion;
  @ManyToOne
  private DevelopmentProject project;

  @ManyToOne
  private Environment environment;

  public static TypedQuery<ServerStatus> findAllAssembledServerStatuses()
  {
    EntityManager em = ServerStatus.entityManager();
    TypedQuery<ServerStatus> q =
      em.createQuery("SELECT ServerStatus FROM ServerStatus as serverstatus WHERE serverstatus.status = " + "(SELECT MIN(ss.status) FROM ServerStatus ss WHERE ss.project = serverstatus.project AND ss.environment = serverstatus.environment) ORDER BY serverstatus.project", ServerStatus.class);
    return q;
  }
}

The ServerStatusController generated by Roo has an almost empty .java file. Most code is kept away in .aj files but you can move some generated methods to the .java file to take control:

@RequestMapping("admin/info/serverstatuses")
@Controller
public class ServerStatusController {
  @Autowired
  private WebSocketManager webSocketManager;

  @RequestMapping(method = RequestMethod.POST, headers = "Accept=application/json")
  public ResponseEntity<String> createFromJson(@RequestBody String json) {
    ServerStatus incomingServerStatus = ServerStatus.fromJsonToServerStatus(json);
    ServerStatus foundServerStatusForId = findServerStatusesByServiceEqualsAndHostnameEquals(incomingServerStatus);
    ServerStatus combinedServerStatus = assembleServerStatusForCurrentProject(foundServerStatusForId);

    WebSocketMessage webSocketMessage = new WebSocketMessage(Action.UPDATE);
    webSocketMessage.setComponent("nagios");
    webSocketMessage.addWebsocketData(DataAction.UPDATE, combinedServerStatus);
    ResponseEntity<String> response = new ResponseEntity<String>("ServerStatus updated", HttpStatus.OK);
    webSocketManager.sendWebsocketMessage(webSocketMessage);
    return response;
  }
}

In the generated controller the method createFromJSON only deserializes the JSON to the entity and calls the persist method. But how can we notify the front-end that some data has changed? This is where WebSockets come in handy:

  • first do some deserialization and database-magic
  • create a WebSocketMessage (helper class that creates the full JSON payload) and define the receiver (in this case the component “nagios”)
  • add data to the message, send the message and let the originator of this REST-call know that everything went well.

For sending the message the WebSocketManager is used, which delegated the actual work to one or more WallboardWebSockets that send the data to the client that originally started the websocket-connection.

How and when to create a WebSocket? Just by adding a simple Servlet as a FrontController all requests for WebSocket-connections are delegated to the WallboardWebSocketServlet. From there every component (nagios, calendar, tweet, etc.) handles its own notifications and corresponding messages:

public class WallboardWebSocketServlet extends WebSocketServlet {
  @Autowired
  private WebSocketManager webSocketManager;
  @Autowired
  private ComponentManager componentManager;

  @Override
  public void init() throws ServletException {
    // register the Walle components (like twitter,
    // calendar, nagios, etc.) to the componentManager
    // ...
  }

  protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException,
    IOException {
      getServletContext().getNamedDispatcher("default").forward(request, response);
  }

  public WebSocket doWebSocketConnect(HttpServletRequest request,
    String protocol) {
       return new WallboardWebSocket();
  }

  public class WallboardWebSocket implements WebSocket {
    private Connection connection;
    @Override
    public void onOpen(Connection connection) {
      this.connection = connection;
      // send init-data to the right component
      // ..
    }
    @Override
    public void onClose(int closeCode, String message) {
    }
    public void sendMessage(String message) {
      connection.sendMessage(message);
    }
  }
}

All preparations on the server-side are done for using websockets, but how about the client-side and communicating with this frontcontroller servlet?

WebSockets

After trying AJAX with jQuery we noticed lots of data-traffic and lots of slow data-traffic. By switching from Tomcat to Jetty we enabled the custom servlet from the previous section and built the WebSocketManager and WebSocketMessage classes. We also had to change all AJAX-calls to websocket calls so we wrote a wallboard “class” in JavaScript that can be used by any client-side component:

var wallboard = (function($) {
  var webSocketUrl = "ws://" + document.location.host + document.location.pathname + "resource";
  var components = {};

  function addComponent(componentName, object) {
    components[componentName] = object;
  };
  function initialize(options) {
    $.extend(this, options);
    initializeWebsocketConnection();
  };
  function initializeWebsocketConnection() {
    var socket = new WebSocket(webSocketUrl);
    socket.onopen = function(event) {...};
    socket.onclose = function(event) {...};
    socket.onmessage = function(event) {
      if (event.data.length > 0) {
        var json = $.parseJSON(event.data);
        var component = components[json.component];
        if (component) {
          if (!initialized && json.action === "INITIALIZE") {
            component.initialize(json.data);
          }
          else if (json.action === "UPDATE") {
            component.update(json.data);
          }
          else {
           console.log("Received unknown action: " + json.action);
          }
        }
        else {
          console.log("Received unknown component: " + json.component);
        }
      }
    };
  };
  return {
    version : version,
    initialize : initialize,
    addComponent : addComponent
  };
})(this.jQuery);
  • create a wallboard object that creates a websocket object
  • wait for the onopen and onmessage
  • when onmessage find out which component is the receiver for the JSON data

Using a partly generated back-end and some custom JavaScript we were able to build a faster and more scalable wallboard. The next part of this serie will focus on how datasources like Jenkins, Nagios and JIRA connect with Walle, later on we’ll cover some Arduino stuff (the Coffee component) and some plans for more modularity using the Atlassian Plugin Framework.

  • Facebook
  • LinkedIn

2 thoughts on “Part III – Back-end development: Spring Roo and WebSockets

  1. Would love some insight into how you got you “json push” from the datasources Jenkins, Nagios and JIRA to connect with Walle. Did you build some sort of custom plugin? Thanks!! This example is the core guide for the wallboard we are building.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>