Friday, September 16, 2011

Spring MVC: Integrating MySQL, MongoDB, RabbitMQ, and AJAX - Part 2

Review

In the previous section, we created the core Event management system and utilized the Spring Data JPA project to simplify data access. We've also added AJAX functionality to make the application responsive. In this section, we will integrate RabbitMQ and messaging in general.

Where am I?

Table of Contents

  1. Event Management
  2. Messaging support
  3. Error persistence
  4. Build and deploy

Messaging Support

Our application is capable of broadcasting events as simple text messages. These messages are CRUD actions and exceptions that arise during the lifetime of the application. This means if a user performs an add, edit, delete, or get these actions will be published to a message broker. If there are any exceptions, we will also publish them.

Why do we need to publish these events? First, it helps us study and explore RabbitMQ messaging. Second, we can have a third-party application whose sole purpose is to handle these messages for intensive statistical analysis. Third, it allows us to monitor in real-time and check the status of our application instantaneously.

If you're unfamiliar with messaging and its benefits, I suggest visiting the following links:

We'll divide this page into four sections:
  1. B. Messaging Support
    • B1. Aspect
    • B2. Configuration
    • B3. Controller
    • B4. View

B1. Aspect

We will publish CRUD operations as simple messages. It's like logging except we send the logs to a message broker. Usually we would add this feature across existing classes. Unfortunately, this leads to "scattering" and " tangling" of code. No doubt--it's a crosscutting concern. We have the same scenario when added the logging feature earlier (See A5. Crosscutting Concerns section earlier).

We have created an Aspect, EventRabbitAspect.java, to solve this concern. To send messages, we use the AmqpTemplate where we pass the exchange, routing key, and the message. Since we want to publish messages as they are called and also errors as they happened, we use @Around to capture these events. The proceeding code is a variation of the original tutorial Chatting in the Cloud: Part 1 by Mark Fisher (http://blog.springsource.com/2011/08/16/chatting-in-the-cloud-part-1/)

EventRabbitAspect.java
package org.krams.tutorial.aop;
import java.util.Date;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* Interceptor for publishing messages to RabbitMQ
*
* @author krams at {@link http://krams915@blogspot.com}
*/
@Aspect
@Order(1)
@Component
public class EventRabbitAspect {
protected Logger logger = Logger.getLogger("aop");
@Autowired
private volatile AmqpTemplate amqpTemplate;
public static final String RABBIT_EXCHANGE = "eventExchange";
public static final String GENERAL_EVENT_ROUTE_KEY = "event.general.*";
public static final String ERROR_EVENT_ROUTE_KEY = "event.error.*";
@Around("execution(* org.krams.tutorial.service.EventService.*(..))")
public Object interceptService(ProceedingJoinPoint pjp) throws Throwable {
try {
logger.debug("Publishing event to RabbitMQ");
this.amqpTemplate.convertAndSend(RABBIT_EXCHANGE, GENERAL_EVENT_ROUTE_KEY, new Date() + ": " + pjp.toShortString());
return pjp.proceed();
} catch (Exception e) {
logger.debug("Publishing event to RabbitMQ");
this.amqpTemplate.convertAndSend(RABBIT_EXCHANGE, ERROR_EVENT_ROUTE_KEY, new Date() + ": " + pjp.getSignature().toLongString() + " - " + e.toString());
return pjp.proceed();
}
}
}


In order for Spring to recognize this aspect, make sure to declare the following element in your XML configuration (we've done this in the trace-context.xml):

<!-- For parsing classes with @Aspect annotation -->
<aop:aspectj-autoproxy/>


B2. Configuration

After declaring the Aspect, we now declare and configure the RabbitMQ and Spring AMQP specific settings. In fact, you will soon discover that most of the work with Spring AMQP is configuration-related: declaration of queues, bindings, exchanges, and connection settings.

We've declared two queues: eventQueue for normal events, and errorQueue for errors. We have a single exchange, eventExchange where we declare the bindings for the two queues.

In order to send messages to the eventQueue, we set the routing key to event.general.*. Likewise, to send messages to the errorQueue, we set the routing key to event.error.*. For a tutorial on these concepts, please visit the official examples at http://www.rabbitmq.com/getstarted.html

spring-rabbit.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:cloud="http://schema.cloudfoundry.org/spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
http://schema.cloudfoundry.org/spring
http://schema.cloudfoundry.org/spring/cloudfoundry-spring-0.8.xsd">
<context:property-placeholder location="/WEB-INF/spring.properties" />
<rabbit:queue id="eventQueue"/>
<rabbit:queue id="errorQueue"/>
<rabbit:topic-exchange name="eventExchange">
<rabbit:bindings>
<rabbit:binding queue="eventQueue" pattern="event.general.*"/>
<rabbit:binding queue="errorQueue" pattern="event.error.*"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<rabbit:template connection-factory="rabbitConnectionFactory" exchange="eventExchange"/>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<rabbit:listener-container>
<rabbit:listener queues="eventQueue" ref="monitorController" method="handleEvent"/>
<rabbit:listener queues="errorQueue" ref="monitorController" method="handleError"/>
</rabbit:listener-container>
<bean id="rabbitConnectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory"
p:username="guest" p:password="guest" p:port="5672">
<constructor-arg value="localhost" />
</bean>
<!-- Cloud-based
<cloud:rabbit-connection-factory id="rabbitConnectionFactory"/> -->
</beans>


B3. Controller

After configuring RabbitMQ and Spring AMQP, we declare a controller, aptly named MonitorController. It's main purpose is to handle monitoring requests. This controller is based on Chatting in the Cloud: Part 1 blog by Mark Fisher (http://blog.springsource.com/2011/08/16/chatting-in-the-cloud-part-1/).

MonitorController.java
package org.krams.tutorial.controller;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/monitor")
public class MonitorController {
@Autowired
private volatile AmqpTemplate amqpTemplate;
private final Queue<String> eventMessages = new LinkedBlockingQueue<String>();
private final Queue<String> errorMessages = new LinkedBlockingQueue<String>();
@RequestMapping(value = "/event")
public String eventPage() {
return "event-monitor-page";
}
@RequestMapping(value = "/error")
public String errorPage() {
return "error-monitor-page";
}
@RequestMapping(value = "/event/log")
@ResponseBody
public String eventLog() {
return StringUtils.arrayToDelimitedString(this.eventMessages.toArray(), "<br/>");
}
@RequestMapping(value = "/error/log")
@ResponseBody
public String errorLog() {
return StringUtils.arrayToDelimitedString(this.errorMessages.toArray(), "<br/>");
}
/**
* Handles normal event messages.
* This method is invoked when a RabbitMQ message is received.
*/
public void handleEvent(String message) {
if (eventMessages.size() > 100) {
eventMessages.remove();
}
eventMessages.add(message);
}
/**
* Handles error messages.
* This method is invoked when a RabbitMQ message is received.
*/
public void handleError(String message) {
if (errorMessages.size() > 100) {
errorMessages.remove();
}
errorMessages.add(message);
}
}


B4. View

We have two JSP pages event-monitor-page.jsp and error-monitor-page.jsp for event and error views respectively. We've utilized AJAX to pull information from the application. Again, this is based on Chatting in the Cloud: Part 1 blog by Mark Fisher (http://blog.springsource.com/2011/08/16/chatting-in-the-cloud-part-1/).

event-monitor-page.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ page session="false" %>
<c:url value="/" var="rootUrl"/>
<c:url value="/resources" var="resourcesUrl"/>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- CSS Imports-->
<link rel="stylesheet" type="text/css" media="screen" href="${resourcesUrl}/css/jquery/dark-hive/jquery-ui-1.8.6.custom.css"/>
<link rel="stylesheet" type="text/css" media="screen" href="${resourcesUrl}/css/main/main.css"/>
<!-- JS Imports -->
<script type="text/javascript" src="${resourcesUrl}/js/jquery/jquery-1.5.2.min.js"></script>
<script type="text/javascript" src="${resourcesUrl}/js/jquery/jquery-ui-1.8.12.custom.min.js"></script>
<title>Event Monitor</title>
</head>
<body class="ui-widget-content">
<div id="menu">
<ul>
<li><a href="${rootUrl}event">Events (DataTables)</a></li>
<li><a href="${rootUrl}jqgrid/event">Events (jQgrid)</a></li>
<li><a href="${rootUrl}error">Errors</a></li>
<li><a href="${rootUrl}monitor/event">Monitor Events</a></li>
<li><a href="${rootUrl}monitor/error">Monitor Errors</a></li>
</ul>
<br style="clear:left"/>
</div>
<script type="text/javascript">
var running = false;
var timer;
function load() {
if (running) {
$.ajax({
url : '${rootUrl}monitor/event/log',
success : function(message) {
if (message && message.length) {
var messagesDiv = $('#log');
messagesDiv.html(message);
messagesDiv.animate({ scrollTop: messagesDiv.attr("scrollHeight") - messagesDiv.height() }, 150);
}
timer = poll();
},
error : function() {
timer = poll();
},
cache : false
});
}
}
function start() {
if (!running) {
running = true;
if (timer != null) {
clearTimeout(timer);
}
timer = poll();
}
}
function poll() {
if (timer != null) {
clearTimeout(timer);
}
return setTimeout(load, 1000);
}
$(function() {
start();
});
</script>
<h3 class="title">Event Monitor</h3>
<div id="log" class="monitor"> </div>
</body>
</html>


error-monitor-page.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ page session="false" %>
<c:url value="/" var="rootUrl"/>
<c:url value="/resources" var="resourcesUrl"/>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- CSS Imports-->
<link rel="stylesheet" type="text/css" media="screen" href="${resourcesUrl}/css/jquery/dark-hive/jquery-ui-1.8.6.custom.css"/>
<link rel="stylesheet" type="text/css" media="screen" href="${resourcesUrl}/css/main/main.css"/>
<!-- JS Imports -->
<script type="text/javascript" src="${resourcesUrl}/js/jquery/jquery-1.5.2.min.js"></script>
<script type="text/javascript" src="${resourcesUrl}/js/jquery/jquery-ui-1.8.12.custom.min.js"></script>
<title>Error Monitor</title>
</head>
<body class="ui-widget-content">
<div id="menu">
<ul>
<li><a href="${rootUrl}event">Events (DataTables)</a></li>
<li><a href="${rootUrl}jqgrid/event">Events (jQgrid)</a></li>
<li><a href="${rootUrl}error">Errors</a></li>
<li><a href="${rootUrl}monitor/event">Monitor Events</a></li>
<li><a href="${rootUrl}monitor/error">Monitor Errors</a></li>
</ul>
<br style="clear:left"/>
</div>
<script type="text/javascript">
var running = false;
var timer;
function load() {
if (running) {
$.ajax({
url : '${rootUrl}monitor/error/log',
success : function(message) {
if (message && message.length) {
var messagesDiv = $('#log');
messagesDiv.html(message);
messagesDiv.animate({ scrollTop: messagesDiv.attr("scrollHeight") - messagesDiv.height() }, 150);
}
timer = poll();
},
error : function() {
timer = poll();
},
cache : false
});
}
}
function start() {
if (!running) {
running = true;
if (timer != null) {
clearTimeout(timer);
}
timer = poll();
}
}
function poll() {
if (timer != null) {
clearTimeout(timer);
}
return setTimeout(load, 1000);
}
$(function() {
start();
});
</script>
<h3 class="title">Error Monitor</h3>
<div id="log" class="monitor"> </div>
</body>
</html>


Result

You can preview the final output by visiting our live app at http://spring-mysql-mongo-rabbit.cloudfoundry.com/monitor/event


Next Section

In the next section, we will integrate MongoDB and add error persistence to the current application. To proceed to the next section, click here.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC: Integrating MySQL, MongoDB, RabbitMQ, and AJAX - Part 2 ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

2 comments:

  1. Hi there! Do you have the new URLs for the demo in the pivotal world? :-)

    ReplyDelete
  2. I have read your blog its very attractive and impressive. I like it your blog.

    Spring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training

    spring training in chennai spring hibernate training in chennai

    ReplyDelete