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):
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
- 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.
// 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.


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.
For Jira you can check the Atlassian developer documentation, specifically on event listeners: https://developer.atlassian.com/display/JIRADEV/Writing+JIRA+event+listeners+with+the+atlassian-event+library. For nagios there is various sources, for example: http://www.kernel-panic.it/openbsd/nagios/nagios6.html. Jenkins plugins: https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins.