Friday, September 16, 2011

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

Review

In the previous two sections, we've managed to create the core Event management system and integrated RabbitMQ messaging using Spring AMQP. For performing CRUD operations, we've utilized the Spring Data JPA project. In this section, we will integrate MongoDB and add error persistence.

Where am I?

Table of Contents

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

Error Persistence

It might be odd why we need to record errors in the application. Isn't the default logging feature enough? That depends. But for the purpose of this tutorial, we are doing it to explore MongoDB integration. There are also advantages with this approach. We can easily query and analyze our application's errors and provide a statistical view.

It's interesting that logging is the first use case listed in the MongoDB site and statistical analysis as the last well-suited use case (see MongoDB Use Cases).

We'll divide this page into five sections:
  1. C. Error Persistence
    • C1. Domain
    • C2. Service
    • C3. Aspect
    • C4. Controller
    • C5. View

C1. Domain

The domain contains a simple ErrorLog class. At the class head we've added @Document, a Spring Data MongoDB annotation, for efficiency reasons, and @Id to mark the field id as the class's identifier. If you scrutinize the application's domain classes, notice the @Id annotation in the Event.java (earlier) is from the javax.persistence package; whereas the @Id in ErrorLog.java is from the org.springframework.data.annotation package.

ErrorLog.java
package org.krams.tutorial.domain;
import java.io.Serializable;
import java.util.Date;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* Represents an error log entity
*
* @author krams at {@link http://krams915@blogspot.com}
*/
@Document
public class ErrorLog implements Serializable {
private static final long serialVersionUID = 1326887243102331826L;
@Id
private String id;
private String type;
private String message;
private String signature;
private String arguments;
private int count;
private Date dateEncountered;
public ErrorLog() {
super();
}
public ErrorLog(String type, String message, Date dateEncountered) {
super();
this.type = type;
this.message = message;
this.count = 1;
this.dateEncountered = dateEncountered;
}
public ErrorLog(String type, String message, Date dateEncountered, String signature, String arguments) {
super();
this.type = type;
this.message = message;
this.count = 1;
this.dateEncountered = dateEncountered;
this.signature = signature;
this.arguments = arguments;
}
public ErrorLog(String type, String message, int count,
Date dateEncountered) {
super();
this.type = type;
this.message = message;
this.count = count;
this.dateEncountered = dateEncountered;
}
public ErrorLog(String type, String message, int count,
Date dateEncountered, String signature, String arguments) {
super();
this.type = type;
this.message = message;
this.count = count;
this.dateEncountered = dateEncountered;
this.signature = signature;
this.arguments = arguments;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getArguments() {
return arguments;
}
public void setArguments(String arguments) {
this.arguments = arguments;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Date getDateEncountered() {
return dateEncountered;
}
public void setDateEncountered(Date dateEncountered) {
this.dateEncountered = dateEncountered;
}
@Override
public String toString() {
return "Error [id=" + id + ", type=" + type + ", message=" + message
+ ", signature=" + signature + ", arguments=" + arguments
+ ", count=" + count + ", dateEncountered=" + dateEncountered
+ "]";
}
}
view raw ErrorLog.java hosted with ❤ by GitHub


C2. Service

Normally before we start developing the service layer we have to create first the data access objects (DAOs) manually. It becomes tedious as you keep repeating the same implementation. Gladly, Spring is here to rescue us from this repetitive task with Spring Data project.

Spring Data's main purpose is to provide us ready-made data access objects for various database sources. This means all we need to do is write our service to contain the business logic. And since we're using a MongoDB, we will use the corresponding Spring Data project: Spring Data MongoDB. (To see a list of other Spring Data projects, visit http://www.springsource.org/spring-data).

IErrorLogRepository.java
package org.krams.tutorial.repository.mongo;
import org.krams.tutorial.domain.ErrorLog;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author krams at {@link http://krams915@blogspot.com}
*/
public interface IErrorLogRepository extends MongoRepository<ErrorLog, String> {
}


That's it. We're done with the service. Wait, where's the service? We don't create one since we don't have custom business logic that needs to be wrapped in a service. All we're doing is retrieval of records.

C3. Aspect

Error persistence, at its core, is no different with logging. It's a crosscutting concern. And similar with how we implemented AMQP messaging, we will use an Aspect to add error persistence.

We have declared EventMongoAspect with two pointcut expressions: one for the service and another for the controller. The interceptService() method handles and persists service errors, while the interceptController() method handles controller errors. We don't persist controller errors; instead we provide a default value so that when returning JSP pages we're not propagating stack traces to the browser.

EventMongoAspect.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.krams.tutorial.domain.ErrorLog;
import org.krams.tutorial.domain.Event;
import org.krams.tutorial.dto.ResponseDto;
import org.krams.tutorial.repository.mongo.IErrorLogRepository;
import org.krams.tutorial.util.ErrorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* Interceptor for persisting {@link Event} errors to MongoDB and
* handling controller failures by providing default response value
*
* @author krams at {@link http://krams915@blogspot.com}
*/
@Aspect
@Order(2)
@Component
public class EventMongoAspect {
protected Logger logger = Logger.getLogger("aop");
@Autowired
private IErrorLogRepository errorLogRepository;
@Around("execution(* org.krams.tutorial.service.EventService.*(..))")
public Object interceptService(ProceedingJoinPoint pjp) throws Throwable {
logger.debug(String.format("Processing %s", pjp.getSignature()));
try {
return pjp.proceed();
} catch (Exception e) {
logger.error("Unable to process method", e);
logger.debug("Persisting error to MongoDB");
StringBuilder arguments = new StringBuilder();
for (Object arg: pjp.getArgs()) {
arguments.append(arg);
}
errorLogRepository.save(new ErrorLog(ErrorUtil.getErrorType(e),
e.getMessage(),
new Date(),
pjp.getSignature().toLongString(),
arguments.toString()));
return pjp.proceed();
}
}
/**
* When a controller method encounters an exception by default the exception is
* propagated to the browser. It's an ugly sight, specially when dealing with AJAX.
* To avoid this side-effect, we catch all controller exception and return an Event
* containing a false value
*/
@Around("execution(* org.krams.tutorial.controller.EventController.*(..))")
public Object interceptController(ProceedingJoinPoint pjp) throws Throwable {
try {
return pjp.proceed();
} catch (Exception e) {
return new ResponseDto<Event>(false);
}
}
}


C4. Controller

The error controller is a simple controller with the single purpose of serving the error page.

ErrorController.java
package org.krams.tutorial.controller;
import java.util.List;
import org.krams.tutorial.domain.ErrorLog;
import org.krams.tutorial.dto.ResponseDto;
import org.krams.tutorial.repository.mongo.IErrorLogRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/error")
public class ErrorController {
@Autowired
private IErrorLogRepository errorLogRepository;
@RequestMapping
public String getErrorPage() {
return "error-page";
}
@RequestMapping(value = "/getall", method = RequestMethod.POST)
public @ResponseBody ResponseDto<ErrorLog> getall() {
List<ErrorLog> errors = errorLogRepository.findAll();
if (errors != null) {
return new ResponseDto<ErrorLog>(true, errors);
}
return new ResponseDto<ErrorLog>(false);
}
}


C5. View

Our view is a single JSP page containing a table and an AJAX request for retrieving records. The setup is similar with the event-page.jsp (see A4. View), except we don't have dialogs for adding, editing, or deleting records.

The getRecords() function is the AJAX request responsible for retrieval and filling-in of records to the table. Then it will convert the table to a DataTables. The addition of DataTables is mainly for aesthetic purposes (and some nifty features like sorting and searching).

error-page.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ 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/datatables/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>
<script type="text/javascript" src="${resourcesUrl}/js/datejs/date.js"></script>
<script type="text/javascript" src="${resourcesUrl}/js/datatables/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="${resourcesUrl}/js/util/util.js"></script>
<title>Errors</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>
<h3 class="title">Errors</h3>
<table id="errorTable">
<thead>
<tr>
<th></th>
<th>Type</th>
<th>Arguments</th>
<th>Count</th>
<th>Date</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script type="text/javascript">
// Retrieves all records
$(function() {
$.getRecords('#errorTable', '${rootUrl}error/getall',
['type', 'arguments', 'count', 'dateEncountered'],
function() {
$('#errorTable').dataTable( {
"bJQueryUI": true,
"sPaginationType": "full_numbers"
});
});
});
</script>
</body>
</html>
view raw error-page.jsp hosted with ❤ by GitHub


Result

You can preview the final output by visiting a live deployment at http://spring-mysql-mongo-rabbit.cloudfoundry.com/error


Next Section

In the next section, we will build and deploy our project using Tomcat and Jetty. To proceed, 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 3 ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

2 comments:

  1. 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
  2. Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.

    core java training in Electronic City

    Hibernate Training in electronic city

    spring training in electronic city

    java j2ee training in electronic city

    ReplyDelete