Monday, 6 May 2013

Spring MVC, Ajax and JSON Part 2 - The Server Side Code

In my last blog I said that I was going to talk about Spring, Ajax and JSON, but didn't. The reason for this is that I wanted to set the scene using a (barely) credible shopping web site scenario. In this scenario when the user clicks on the eCommerce page link, the server app loads some the items from a catalogue and displays them on the page. The user then checks a number of items and presses 'Confirm Purchase'. Now, this is where Ajax and JSON come in, on pressing 'Confirm Purchase' the browser makes an Ajax request to the server sending it the item ids. The server then retrieves the items from the database returns them as JSON to the browser. The browser then processes the JSON, displaying the items on he screen.

My last blog got as far as creating and displaying a form that presented a list of items from the imaginary catalogue to the user. This blog takes a look at the next step in the project: creating some JSON.

The Guys at Spring have been busy working on Ajax and JSON over the last couple of years and, as you’d expect, they do a lot of the work for you in the background. This means that all you need to do is to define a simple bean class that Spring can turn into JSON and write some controller code. In this case that class that Spring will convert to JSON is the OrderForm class:

public class OrderForm {

 
private final List<Item> items;

 
private final String purchaseId;

 
public OrderForm(List<Item> items, String purchaseId) {
   
super();
   
this.items = items;
   
this.purchaseId = purchaseId;
 
}

 
public List<Item> getItems() {
   
return items;
 
}

 
public String getPurchaseId() {
   
return purchaseId;
 
}
}

The OrderForm class contains a list of Item objects and a unique order ID used to define an order.

Having created the OrderForm, the next thing to do is to sort out the Spring controller code:

  public @ResponseBody
 
OrderForm confirmPurchases(@ModelAttribute("userSelections") UserSelections userSelections) {

   
logger.debug("Confirming purchases...");
    OrderForm orderForm = createOrderForm
(userSelections.getSelection());
   
return orderForm;
 
}

 
private OrderForm createOrderForm(List<String> selections) {

   
List<Item> items = findItemsInCatalogue(selections);
    String purchaseId = getPurchaseId
();

    OrderForm orderForm =
new OrderForm(items, purchaseId);
   
return orderForm;
 
}

 
private List<Item> findItemsInCatalogue(List<String> selections) {

   
List<Item> items = new ArrayList<Item>();
   
for (String selection : selections) {
     
Item item = catalogue.findItem(Integer.valueOf(selection));
      items.add
(item);
   
}
   
return items;
 
}

 
private String getPurchaseId() {
   
return UUID.randomUUID().toString();
 
}

The code above is all that's required to return some JSON to the browser and you can see that there’s not that much to it. Firstly, the method’s @RequestMapping annotation, using the confirm and RequestMethod.POST values, maps the form attributes from my previous blog to this method.

          <form:form modelAttribute="userSelections" action="confirm" method="post">

The modelAttribute annotation tells Spring to create and map a userSelections object from the forms posted data and inject it into the confirmPurchases(...) method's userSelections argument. The UserSelections class is a convenience class that wraps a list of Strings. Although an example of the Lazy Class anti-pattern, this class is used to effortlessly integrate with Spring’s <form:checkbox> tag and in a real world application would contain more attributes.

public class UserSelections {

 
private List<String> selection = Collections.emptyList();

 
public List<String> getSelection() {
   
return selection;
 
}

 
public void setSelection(List<String> selection) {
   
this.selection = selection;
 
}

 
@Override
 
public String toString() {

   
StringBuilder sb = new StringBuilder("Selections are: ");

   
for (String str : selection) {
     
sb.append(str);
      sb.append
(",  ");
   
}

   
return sb.toString();
 
}
}

The confirmPurchases(...) method converts a UserSelections input object into an OrderForm output object that's passed back to the browser as JSON. The OrderForm object is created by looping through the list of Item ids held in the UserSelection object and looking up the corresponding Items using the fake catalogue service. Once it has the list of Items it then creates a unique purchase id using Java's UUID class. It then passes the Items list and the purchase ID to the OrderForm's constructor and the order form is then passed back to Spring. Don't forget the @ResposeBody annotation, which tells Spring to bind the OrderForm to the HTTP response body using a suitable HttpMessageConverter. This is where the magic comes in. As you may guess, a HTTP response body needs to include data that is of the correct media type to send over the internet and OrderForm definitely doesn't fit that bill. To fix the problem it seems that Spring takes a look at the project config for suitable ways of converting the OrderForm object where it finds the jackson-core and jackson-databind libraries that were added to the project in the last blog.

  <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <version>2.0.4</version>
  </dependency>
  <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.0.4</version>
  </dependency>

In the absence of any other suitable candidates, uses these libraries to convert the OrderForm object to JSON. All of which means that you and I don't actually have to do any real coding to produce our JSON output. Pretty clever huh!

Obviously, all this magical jiggery-pokery that's going on in the background hides away the actual JSON output so, I find it useful to create a simple unit test similar to the one shown below:

  @Test
 
public void testDemonstrateJSON() throws JsonGenerationException, JsonMappingException, IOException {

   
UserSelections userSelection = new UserSelections();
    String
[] selections = { "1", "2" };
    userSelection.setSelection
(Arrays.asList(selections));

    Item item1 = Item.getInstance
(1, "name", "description", new BigDecimal("1.00"));
    when
(catalogue.findItem(1)).thenReturn(item1);
    Item item2 = Item.getInstance
(2, "name2", "description2", new BigDecimal("2.00"));
    when
(catalogue.findItem(2)).thenReturn(item2);

    OrderForm orderForm = instance.confirmPurchases
(userSelection);

    ObjectMapper mapper =
new ObjectMapper();
    String result = mapper.writeValueAsString
(orderForm);

    System.out.println
(result);
 
}

You may argue that this isn't a real test as it doesn't assert anything. The value of this test is to give a visual representation of the JSON output and to ensure that the object you're attaching to the HTTP response body can be converted into JSON by the Jackson parser. If it can't then when you run this test you'll get an exception.

So, that’s the server side code covered. The next, and hopefully last, blog in this short series takes a look at the client side code.


For the full source code to this blog, see GitHub - https://github.com/roghughe/captaindebug/tree/master/ajax-json


No comments: