Update: I have created a follow up tutorial to this one in which I strip the JSONP callback handling out of the @Controller and handle it in a Servlet Filter using Spring’s DelegatingFilterProxy.
Introduction
Spring Roo speeds up your development process by taking care of all the “boilerplate” and maintaining that code automatically as you make changes. When I first saw Spring Roo appear on the scene, I had never heard of the terms “scaffolding” or “automatic code generation” when it came to Java. When I first tried it, it did not seem like I could make much use of it because it seemed to force me to work the way it wanted me to work, rather than it working the way I wanted to work. I quickly lost interest. More recently however, I watched the 2010 Google I/O keynote where Google announced a partnership with VMware and more specifically, Spring. A Google engineer, teamed up with Spring Roo founder Ben Alex performed a live on stage demo of creating a web application for tracking expense reports, all from scratch using Spring Roo. At that moment in time, I then realized the power of Spring Roo and immediately returned to it for another try. If they could make it look so easy to use and take advantage of the speed of development, then surely I needed to give it a second chance.
This tutorial is based on a production project for a 3PL company that needed a web application that would allow visitors to enter multiple locations either by city or zip code. The application would then calculate the mileage between those cities and determine the transit time in days that it would take a transportation carrier to legally deliver that load. In this tutorial, we will create a Spring backend using Spring Roo, Hibernate EntityManager, a DB2/400 datasource and jQuery. I’ll be using Spring version 3.0.2.RELEASE and Hibernate EntityManager version 3.5.1-Final, so if the Roo generated project you create uses an older version of Spring, it’s a good idea to use at least the latest one, which was 3.0.2.RELEASE when I wrote my application. When using the latest version of Hibernate, I also needed to remove any other Hibernate dependency added by Roo as well as the JPA 1.0 dependency.
Contents
- Spring Roo essentials
- Creating our entity class to read from the datasource
- Creating the web controller for AJAX calls
- The HTML form and JavaScript
- Conclusion
1. Spring Roo essentials
Before you can start, the first thing you’ll need is either the SpringSource Toot Suite (STS) or the Eclipse STS plugins. The latest milestone release of the SpringSource Tool Suite uses also the latest milestone of Spring Roo which comes with GWT support. Already having Eclipse installed and up to date, I opted for the plugins instead. The plugins provided are the latest stable release, so (at the time of writing this tutorial) they do not include the milestone release of Spring Roo with GWT support.
With the Spring Roo IDE support, you can create a Spring Roo project and set the top level package, all using the “New Roo Project” window, which is basically just a frontend for the project command.

Using this window essentially accomplishes the same thing as the following command.
project --topLevelPackage org.mypackage --projectName tools --java 6
You’ve just created the base of your Spring Roo project. The next thing to setup is the persistence layer. If you’re already familiar with Spring Roo, feel free to skip to the next sections as these steps will have already been covered in really any good Spring Roo fundamentals tutorial. The following command initializes a Hypersonic in memory datasource. Don’t fret just yet if you read above that I’m using a DB2/400 datasource. Spring Roo doesn’t support it out of the box, so we’ll start with the Hypersonic in memory datasource and modify the generated code after to support our DB2/400 datasource. Enter the following command for persistence setup.
persistence setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE
With that done, our project has been setup to use Hibernate’s JPA implementation, Hibernate EntityManager. Before you continue, you’ll want to update the Maven dependencies in your pom file. For me, Roo added dependencies for older versions of Hibernate EntityManager and JPA spec. You’ll want to remove those and simply add a dependency for Hibernate EntityManager 3.5.1-Final which will also include JPA 2.0 jar files. This is the dependency from my project pom file. I added exclusions for commons-collections and slf4j-api since those were already included by Roo.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.5.1-Final</version>
<exclusions>
<exclusion>
<artifactId>commons-collections</artifactId>
<groupId>commons-collections</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
Now to customize it for DB2/400. First thing we’ll do is update the persistence.xml file located in src/main/resources/META-INF. Here is the updated file.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.DB2400Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.connection.pool_size" value="5"/>
</properties>
</persistence-unit>
</persistence>
You’ll also need to add a dependency to your project on JT400.
dependency add --groupId net.sf.jt400 --artifactId jt400 --version 6.7
The next thing you’ll need to modify is the applicationContext.xml file located in src/main/resources/META-INF/spring. Here is the updated file which is slightly different from the generated one in terms of the entityManagerFactory bean.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
<context:spring-configured/>
<context:component-scan base-package="org.mypackage.tools">
<context:exclude-filter expression=".*_Roo_.*" type="regex"/>
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<bean class="javax.persistence.Persistence" factory-method="createEntityManagerFactory" id="entityManagerFactory">
<constructor-arg value="persistenceUnit"/>
<constructor-arg>
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="javax.persistence.jdbc.driver" value="${database.driverClassName}"/>
<entry key="javax.persistence.jdbc.url" value="${database.url}"/>
<entry key="javax.persistence.jdbc.user" value="${database.username}"/>
<entry key="javax.persistence.jdbc.password" value="${database.password}"/>
</map>
</constructor-arg>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
</beans>
We’ve just finished setting up the base of our Spring Roo project and updated the persistence setup to support DB2/400. Next we will setup a persistent entity class.
2. Creating our entity class which for this tutorial we’ll just use to read from the datasource
The following Roo command will create our persistent entity class and domain object. I should note that Spring Roo at current release focuses more on a rich domain object model rather than separating code into a domain object, service and DAO layer. This threw me off at first and after going back and forth for a while, I decided the benefits of using Spring Roo outweigh my personal preference of separating my classes into domain object, service and DAO layers. There’s actually several articles and debates out there you can read on which method to use. Both rich domain object and layered methods have pros and cons, so it’s up to the developer to choose what they want to use. In any case, on with the tutorial. The following command creates our entity class.
entity --class ~.mileage.domain.ZipSystem
Ok, so I’ve put my entity class into a sub-package called domain, couldn’t help falling back slightly to the layered approach. The ~ in the command basically means use the top level package we defined for the project as the root of where to place this class and any sub-package. Here is the final code for our entity class.
package org.mypackage.tools.mileage.domain;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.Transient;
import javax.persistence.Version;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
@Entity
@RooJavaBean
@RooToString
@RooEntity
public class ZipSystem {
/*
* Need to make version transient so that Hibernate EntityManager doesn't query for it
*/
@Version
@Transient
private int version;
@Column(name = "ZSCITYNM")
private String cityName;
@Column(name = "ZSSTCODE")
private String stateCode;
@Id
@Column(name = "ZSZIP")
private String zipCode;
public ZipSystem() {};
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public String getZipCode() {
return zipCode.trim();
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getCityName() {
return cityName.trim();
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public String getStateCode() {
return stateCode.trim();
}
public void setStateCode(String stateCode) {
this.stateCode = stateCode;
}
@SuppressWarnings("unchecked")
public static List<ZipSystem> findZipSystemsByCityName(String cityName, boolean partialMatch, int maxResults) {
return entityManager().createQuery("select o from ZipSystem o where cityName like ?1")
.setParameter(1, partialMatch ? cityName.toUpperCase() + "%" : cityName.toUpperCase())
.setMaxResults(maxResults)
.getResultList();
}
}
In the above code, the 3 fields or private members of this class were created using the following Roo commands. The one thing you’ll notice is that I’ve also created the getters/setters myself. This isn’t necessary since Spring Roo uses AspectJ weaving to insert code such as getters/setters at compile time. Really the only getters/setters I needed to define myself were the @Version and @Id fields so that I make Hibernate happy. The ones for cityName and stateCode could have easily been maintained by Roo as just described. Here are the commands I ran to create those fields.
field string --fieldName cityName --column ZPCITYNM field string --fieldName stateCode --column ZSSTCODE field string --fieldName zipCode --column ZSZIP
The one important method I added was the “findZipSystemsByCityName” method which will perform a partial lookup in the datasource by the city name the user enters in the form on the page. What’s important to note about the code is the return statement. You’ll notice it starts with entityManager() which is a method call, not a variable. Spring Roo maintains the insertion of an EntityManager object in the entity class automatically using AspectJ weaving and makes the EntityManager available through the entityManager() method. If you look at your file system as you’re creating your project, you’ll find several *.aj files in your source directories. Those AspectJ files contain the java code that Roo maintains automatically and will compile into your class at compile time.
That’s it for our entity class. In our datasource, this table essentially has records of cities in North America with state codes, zip codes and actually a lot of other data, but the 3 fields I created above are all that was important to the project. You might ask why I’m using my own datasource instead of using a public service like geonames.org to accomplish this. Well in fact in my production application, I have switched to geonames.org for the extra performance I get, but this still shows a good example of an entity class to use with Hibernate EntityManager.
3. Creating the web controller for AJAX calls
The following command creates the base of our controller which we will then modify to suit the project.
controller scaffold --class ~.mileage.MileageController --path /mileage
This command results in a controller class named MileageController with a request mapping starting at /mileage. The following code is significantly modified compared to the Roo generated controller. For the purpose of this project, I did not require everything that was generated, such as mappings for writing/updating/deleting entities. Here is the updated controller class.
package org.mypackage.tools.mileage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RequestParam;
import org.mypackage.tools.mileage.domain.Mileage;
import org.mypackage.tools.mileage.domain.ZipSystem;
import org.mypackage.tools.util.JSONUtil;
@RequestMapping("/mileage/**")
@Controller
public class MileageController {
private final Log log = LogFactory.getLog(MileageController.class);
@RequestMapping(value = "/mileage/locations", method = RequestMethod.GET)
public ResponseEntity<String> getLocations(@RequestParam(value = "q", required = true) String cityName, @RequestParam(value = "limit", required = false) String limit, @RequestParam(value = "callback", required = false) String callback) {
int maxResults = 0;
try {
maxResults = Integer.parseInt(limit);
} catch(NumberFormatException e) {
if(log.isWarnEnabled())
log.warn("A limit was passed that could not be parsed as an int. Setting maxResults to default." );
maxResults = 20;
}
List<ZipSystem> list = ZipSystem.findZipSystemsByCityName(cityName, true, maxResults);
Set<String> set = new HashSet<String>();
for(ZipSystem zip : list) {
if(set.size() < maxResults)
set.add(zip.getCityName() + ", " + zip.getStateCode());
}
String json = JSONUtil.convertToJSONString(set);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("Access-Control-Allow-Origin", "http://www.domain.com");
responseHeaders.set("Access-Control-Allow-Methods", "GET");
responseHeaders.set("Access-Control-Allow-Headers", "");
responseHeaders.set("Access-Control-Max-Age", "86400");
if(callback.trim().length() > 0) {
json = callback + "(" + json + ")";
}
return new ResponseEntity<String>(json, responseHeaders, HttpStatus.OK);
}
@RequestMapping(value = "/mileage/calculator", method = RequestMethod.GET)
public ResponseEntity<String> calculateMileage(@RequestParam(value = "origin", required = true) String origin, @RequestParam(value = "destination", required = true) String destination) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("Access-Control-Allow-Origin", "http://www.domain.com");
responseHeaders.set("Access-Control-Allow-Methods", "GET");
responseHeaders.set("Access-Control-Allow-Headers", "");
responseHeaders.set("Access-Control-Max-Age", "86400");
return new ResponseEntity<String>(Mileage.calculateMileage(origin, destination), responseHeaders, HttpStatus.OK);
}
}
Let’s explain this code since there’s a lot going on. First off, I removed the Roo generated annotations at the class level since I didn’t need them in this case. The first method is called “getLocations” and it takes 3 parameters. The first parameter is the city name to lookup. The @RequestParam value is actually “q” in this case. I originally started using the jQuery autocomplete plugin on my form, hence the “q” parameter, but more recently I changed to the jQuery UI autocomplete. Doing this allows me to assist my users in selecting the correct spelling of cities before passing them off to the stored procedure that passes back the mileage.
The next thing I do is get a list of partially matching cities by calling the method I added to my ZipSystem class. I then pass the list through a loop which adds the results to a Set in order to eliminate any duplicates. Finally I convert that Set to a String representation of a JSON object using a utility class. Originally when I was creating this project, I was trying to use a return type annotated with @ResponseBody in an attempt to have Spring automatically convert the returned List of ZipSystem objects into JSON using Jackson JSON Mapper. Long story short, I spent almost 2 days trying to get it to work and it just didn’t and I couldn’t find very many working examples. In the end, I created a simple utility class that accomplishes this and returns a String representation of the JSON object. Part of the code above actually wraps the JSON object with a callback method to be used when making the JSONP request. That should actually go in the JSON utility class as well for cleanliness, but for the purpose of this tutorial, it’s fine. Here is the JSON utility class.
package org.mypackage.tools.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
public abstract class JSONUtil {
private final static ObjectMapper mapper = new ObjectMapper();
private final static Log log = LogFactory.getLog(JSONUtil.class);
public static final String convertToJSONString(Object obj) {
String json = "";
try {
json = mapper.writeValueAsString(obj);
if(log.isDebugEnabled())
log.debug(json);
} catch (Exception e) {
if(log.isErrorEnabled())
log.error("Error converting object to JSON.", e);
}
return json;
}
}
When I started getting to the AJAX portion of this application, I ran into the cross-domain XHR policy which I hadn’t come across before. It took about a day to figure it out, but I learned that since my public website was located at something like http://www.domain.com and my Tomcat server was located at http://apps.domain.com, I couldn’t make the normal AJAX call to to apps.domain.com because of the cross-domain XHR policy. That’s where a return type of ResponseEntity came into play. It allowed me to set the proper response headers, for the browser to permit cross-domain XHR. This worked fine in Firefox, Chrome and Safari, but guess what, it didn’t work in IE. Essentially, although I’m doing a simple GET, IE was still attempting a preflighted request. Finally I came across JSONP which is an extension to JSON. When performing my jQuery ajax call, I could specify a return type of JSONP which would then append a paramater to the request called “callback” which you’ll see is also an input parameter of the “getLocations” method. With jQuery’s JSONP support, I can either let jQuery generate an automatic callback method name, or specify a name myself. There’s lots of resources explaining JSONP if you’re not familiar with it, so I won’t go into much explanation of it. The jQuery UI autocomplete widget actually has a working example of how to do this remote JSONP call.
The second method in our controller is called “calculateMileage” and it takes 3 parameters also, origin, destination and callback. The origin and destination parameters get passed to a static method call which is responsible for calling the stored procedure that will return the mileage between the two locations. Here is the class with static method.
package org.mypackage.tools.mileage.domain;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Types;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
public class Mileage {
private final static Log log = LogFactory.getLog(Mileage.class);
private static String miles;
public static String calculateMileage(String origin, String destination) {
Session session = (Session) entityManager().unwrap(Session.class);
try {
Connection con = session.connection();
CallableStatement proc = con.prepareCall("{ call milookup(?,?,?) }");
proc.registerOutParameter(1, Types.CHAR);
proc.setString(2, origin);
proc.setString(3, destination);
proc.execute();
miles = proc.getString(1);
if(log.isInfoEnabled())
log.info("Miles calculated from " + origin + " to " + destination + "\n" + Integer.parseInt(miles));
con.close();
session.close();
} catch(Exception e) {
if(log.isErrorEnabled())
log.error("An error occurred while getting the milage.", e);
}
return String.valueOf(Integer.parseInt(miles));
}
}
I spent a couple days trying to accomplish this using JPA and Hibernate to no avail. With this project needing to get done, I reverted to CallableStatement for calling the stored procedure. This means having to take care of the the connection and session myself, but it got the job done.
With that out of the way, all that’s left to do is our frontend and JavaScript.
4. The HTML form and JavaScript
The following HTML snippet is our basic form which allows you start with 2 locations, but add up to 8 additional locations for a total of 10 locations.
<form name="calculator" id="calculator" action="" onsubmit="return false;" autocomplete="off"> <table id="locations"> <tr> <th>Locations</th> <th class="miles">Miles</th> </tr> <tr><td><label for="location_1">1.</label><input type="text" id="location_1" class="location"/></td><td></td></tr> <tr><td><label for="location_2">2.</label><input type="text" id="location_2" class="location"/></td><td class="miles"></td></tr> </table> <div id="totalMiles">Total Miles: <span>0</span></div> <button id="addLocation">Add Location</button> <button id="removeLocation">Remove Location</button> </form>
The important part of this form is the class I’ve added to the text input fields. The class “location” is what we will use to identify the text input fields that the jQuery UI Autocomplete widget needs to provide suggestions to when the user types in city names.
Here is the JavaScript code that makes all of this work. I’m using Google’s AJAX API loader, so to do this yourself, you’ll need to get your free API key from Google.
google.load("jquery", "1.4.2");
google.load("jqueryui", "1.8.2");
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
};
var locations = 2;
var minLocations = 2;
var maxLocations = 10;
var errorIcon = '<img src="http://www.domain.com/error.png" alt="error" width="16" height="16"/>';
function initCalculator() {
$("input.location:first").focus();
$("input.location").autocomplete({
source: function(request, response) {
$.ajax({
url: "http://apps.domain.com/tools/mileage/locations",
dataType: "jsonp",
data: {
q: request.term,
limit: 15
},
success: function(data) {
response($.map(data, function(item) {
return {
label: item,
value: item
}
}))
}
})
},
minLength: 2,
});
}
function initAddLocationHandler() {
$('#addLocation').click(function() {
if(($('#locations tr').length - 1) < maxLocations) {
locations += 1;
$('#locations').append('<tr><td><label for="location_' + locations + '">' + locations + '.</label><input type="text" id="location_' + locations + '" class="location"/></td><td class="miles"></td></tr>');
initCalculator();
$('#location_' + locations).focus();
}
});
}
function initRemoveLocationHandler() {
$('#removeLocation').click(function() {
if(($('#locations tr').length - 1) > minLocations) {
locations -= 1;
$('#locations tr').last().remove();
$('#totalMiles span').html(calculateTotalMiles());
}
});
}
function initCalculateMileageHandler() {
$('#locations tr td input').live('blur', function() {
destination = $(this).val();
origin = $(this).parent().parent().prev().find('td:first input').val();
resultElement = $(this).parent().next();
if(origin != null && origin.trim() != '' && destination.trim() != '') {
calculateMileage(origin, destination, resultElement);
}
});
}
function calculateMileage(origin, destination, element) {
$(element).html('<img src="http://www.domain.com/ajax-loader.gif" alt="loading" width="16" height="16"/>');
$.ajax({
url: 'http://apps.domain.com/tools/mileage/calculator',
dataType: 'jsonp',
data: {
origin : origin,
destination : destination
},
success: function(data) {
if(data > 0) {
$(element).html(data);
$('#totalMiles span').html(calculateTotalMiles());
} else {
$(element).html(errorIcon);
}
},
error: function(xhr, status, error) {
$(element).html(errorIcon);
}
});
return false;
}
function calculateTotalMiles() {
totalMiles = 0;
$('td.miles').each(function() {
totalMiles += parseInt($(this).html());
});
return (totalMiles != 'NaN' ? totalMiles : errorIcon);
}
google.setOnLoadCallback(function() {
initCalculator();
initAddLocationHandler();
initRemoveLocationHandler();
initCalculateMileageHandler();
});
The first thing we do is load the APIs from Google and setup a String.trim() function. The global variables are self explanatory I think. The first method “initCalculator” is what will bind the jQuery UI Autocomplete widget to our text input fields. The second method “initAddLocationHandler” will be called when the user clicks the button to add an additional location up to the maximum set by the global variable maxLocations. An important part of this method is calling the initCalculator method after adding the new HTML. The reason for that is on the initial call to initCalculator, the autocomplete widget is bound to the text inputs that exist only. In order to bind the new field as well, we need to reinitialize the autocomplete widget so it knows about the new field. The next method “initRemoveLocationHandler” is used to remove the last location field that was added up to the minimum needed for the application which I’ve defined as 2 using the minLocations global variable. The next method “initCalculateMilageHandler” will be called as soon as the user exits a location field. That method will then retrieve the location of the field they just exited and the preceding one and pass those locations to method “calculateMileage” along with a DOM element where the miles returned from the stored procedure are to be recorded for the user to view. That same method will also call the “calculateTotalMiles” method which will look at all the individual miles and calculate the total to display at the bottom of the form. This method is also called by the “initRemoveLocationHandler” so that the total miles are recalculated when a location is removed.
5. Conclusion
To conclude, this tutorial has touched a little upon several technologies like scaffolding provided by Spring Roo, JPA provided by Hibernate EntityManager, cross-domain AJAX using JSONP requests and some jQuery flare to round it all up. Even if you don’t use all of what I’ve shown, there’s certainly small parts that might just point you in the right direction if you’re struggling on a certain part of your own project.
I always appreciate feedback from anybody especially if it’s to point out something I’ve done wrong or could do better.
Btw, don’t forget to read my follow up tutorial which demonstrates how to remove the JSONP callback handling from the @Controller and puts it in a Servlet Filter using Spring’s DelegatingFilterProxy.
What was the problem you were having with @ResponseBody and Jackson? I use this feature all the time and haven’t had problems. Interested to hear more about that.
Keith
You know I practically had to go back in time to remember what was happening. Sure enough, now that I’m trying to reproduce the error, I’m not getting it anymore. What I do remember was seeing some sort of exception being thrown about it not finding the ObjectMapper class, which was on my classpath. This is a link to the forum post I submitted to the Spring forums, http://forum.springsource.org/showthread.php?t=90345 in hopes to find an answer at the time. Today after rewriting a copy of my controller to use @ResponseBody, it does indeed convert to JSON. My logs even have an entry something like “Written … as application/json;charset=UTF-8 using org.springframework.http.converter.json.MappingJacksonHttpMessageConverter” so I know it’s working now.
Anyways, since then I had discovered the cross-site ajax policy which lead me to use ResponseEntity, but that didn’t work in IE, so then I used JSONP which got it all working in all browsers.
I guess a question I would have for you is, how can I make use of @ResponseBody while making a JSONP request to my controller in a cross-site/cross-domain manner?
I would recommend getting the JSONP callback handling out of the controller altogether and writing either in a Servlet filter or MVC HandlerInterceptor that:
1. Checks whether the request is asking for an appropriate media type where the JSONP callback would be allowed (you will likely need to use the file extension)
2. Checks for a callback parameter in the request
3. Wraps the servlet-generated response with the appropriate callback
This has the advantage that it will work with *any* of the @Controller method styles.
Also, if you’re looking for more information on various approaches to handling cross-domain requests, as well as the security issues around them, I highly recommend this Sitepen article:
http://www.sitepen.com/blog/2008/09/25/security-in-ajax/
Cheers,
Jeremy
Hey Jeremy, thanks for the info. I’ll look into implementing what you’ve suggested. Thanks also for the article. Haven’t had a chance to read it yet, but I have been looking for a good article on cross-domain ajax security.
I just got confirmation today that I’ll be attending SpringOne 2GX in Chicago. I’m looking forward to meeting you, Keith Donald, Ben Alex and the rest of the team.
Great tutorial. Sounds like you are working on some interesting stuff. Send me an email, as I’d love to see it in action.
Correct me if I’m wrong, but I think you don’t really need the Google AJAX Search API, right? Specially since you’re already using jQuery.
That’s right, you don’t really need to use Google AJAX API. You can provide jQuery libraries within your app and reference them directly. The main reason I used the Google AJAX API is so that it performs better if accessed from various parts of the world instead of relying solely on my server to provide those libraries.
Or simply use the path http://code.google.com/apis/libraries/devguide.html#jquery
Yup, you could do that too. The other reason for using google.load is that it allows you to wait for the jQuery APIs to be completely loaded before using them. That’s where the google.setOnLoadCallback call comes into play. It will only execute my other methods once jQuery is fully loaded.
I noticed you are not using the .aj files for your entities. Instead your entity class contains all this code. Did you use push-in refactoring?
Actually all I did was have Eclipse generate my getters/setters for me. I had to create them for the version and id fields, so just generated the others at the same time. Really doesn’t matter. Using Roo to maintain the getters/setters is as good and I would use that if my entity had a lot of members.
Considering all the HTML, JS, and Spring wiring that was done to support AJAX, what benefits do you feel Spring Roo gave you? Roo did generate a basic framework for you, but outside of that, what else?
In all honesty, I probably didn’t use Roo to it’s full advantage on this project. I did not have too much experience with it prior to this, although now I use it almost exclusively for any project I create. I am happy with the base it did provide, like taking care of the JPA configuration, that was really nice. This was all done back when Roo didn’t have the new features that it does today, like database reverse engineering or complete round tripping of .jspx files.
Pingback: Happy New Year! « Java Tutorials for Developers