Showing posts with label 3. Show all posts
Showing posts with label 3. Show all posts

Monday, February 28, 2011

Spring 3: REST Web Service Provider and Client Tutorial (Part 2)

In this tutorial we'll create a Spring MVC REST client that uses Spring 3's RestTemplate. Our client is able to communicate via XML and JSON. It also has basic support for retrieving images. This tutorial is Part 2 of our Spring 3: REST Web Service Provider and Client Tutorial series. If you haven't read Part 1, please read it first (See here).

What is REST in Spring 3?
For a great introduction, please see the following links:
- REST in Spring 3: @MVC
- REST in Spring 3: RestTemplate

What is RestTemplate?
The central class for client-side HTTP access. It simplifies communication with HTTP servers, and enforces RESTful principles. It handles HTTP connections, leaving application code to provide URLs (with possible template variables) and extract results.

Source: Spring 3 Reference: RestTemplate

Development

Our client application is unique because it doesn't need a Service layer. We only need the Domain and the Controller layers. We also need a couple of JSP pages to display the records.

Domain Layer

We'll be declaring two domain objects: Person and PersonList. These are the same class declarations we had in the REST provider application.

Person.java
package org.krams.tutorial.domain;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="person")
public class Person {

 private Long id;
 private String firstName;
 private String lastName;
 private Double money;
 
 public Long getId() {
  return id;
 }
 public void setId(Long id) {
  this.id = id;
 }
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
 public Double getMoney() {
  return money;
 }
 public void setMoney(Double money) {
  this.money = money;
 }
}
Person is a simple POJO consisting of three properties. Notice we've annotated the class name with @XmlRootElement(name="person"). Its purpose is to help JAXB (the one responsible for marshalling/unmarshalling to XML) determine the root of our POJO.

PersonList.java
package org.krams.tutorial.domain;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="persons")
public class PersonList {

 @XmlElement(required = true)
 public List<person> data;

 @XmlElement(required = false)
 public List<person> getData() {
  return data;
 }

 public void setData(List<person> data) {
  this.data = data;
 }
}
PersonList is another simple POJO. It's purpose is to wrap a list of Person objects.

Ideally we should be able to return a List instead of a PersonList. However, JAXB has difficulties processing java.util.List. A simple Google search verifies this unwanted behavior, for example, http://stackoverflow.com/questions/298733/java-util-list-is-an-interface-and-jaxb-cant-handle-interfaces

To resolve this issue with JAXB, we wrap our List with another object. To learn more about JAXB please visit the following link: http://download.oracle.com/javaee/5/tutorial/doc/bnazf.html

Controller Layer

The controller layer is the most important section of the client application because here's where we communicate with the REST provider. It's also responsible for displaying the JSP pages.

ProviderController.java
package org.krams.tutorial.controller;

import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.krams.tutorial.domain.PersonList;
import org.krams.tutorial.util.Writer;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletResponse;

/**
 * REST service client
 */
@Controller
public class RestClientController {

 protected static Logger logger = Logger.getLogger("controller");
 
 private RestTemplate restTemplate = new RestTemplate();
 
 /**
  * Retrieves an image from the REST provider
  * and writes the response to an output stream.
  */
 @RequestMapping(value = "/getphoto", method = RequestMethod.GET)
 public void getPhoto(@RequestParam("id") Long id, HttpServletResponse response) {
  logger.debug("Retrieve photo with id: " + id);
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.IMAGE_JPEG);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<String> entity = new HttpEntity<String>(headers); 
  
  // Send the request as GET
  ResponseEntity<byte[]> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.GET, entity, byte[].class, id);
  
  // Display the image
  Writer.write(response, result.getBody());
 }
 
 /**
  * Retrieves all records from the REST provider
  * and displays the records in a JSP page
  */
 @RequestMapping(value = "/getall", method = RequestMethod.GET)
 public String getAll(Model model) {
  logger.debug("Retrieve all persons");
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<Person> entity = new HttpEntity<Person>(headers);

  // Send the request as GET
  try {
   ResponseEntity<PersonList> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/persons", HttpMethod.GET, entity, PersonList.class);
   // Add to model
   model.addAttribute("persons", result.getBody().getData());
   
  } catch (Exception e) {
   logger.error(e);
  }
  
  // This will resolve to /WEB-INF/jsp/personspage.jsp
  return "personspage";
 }
 
 /**
  * Retrieves a single record from the REST provider
  * and displays the result in a JSP page
  */
 @RequestMapping(value = "/get", method = RequestMethod.GET)
 public String getPerson(@RequestParam("id") Long id, Model model) {
  logger.debug("Retrieve person with id: " + id);
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<Person> entity = new HttpEntity<Person>(headers);

  // Send the request as GET
  try {
   ResponseEntity<Person> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.GET, entity, Person.class, id);
   // Add to model
   model.addAttribute("person", result.getBody());
   
  } catch (Exception e) {
   logger.error(e);
  }
  
  // This will resolve to /WEB-INF/jsp/getpage.jsp
  return "getpage";
 }
 
 /**
     * Retrieves the JSP add page
     */
 @RequestMapping(value = "/add", method = RequestMethod.GET)
 public String getAddPage(Model model) {
  logger.debug("Received request to show add page");

  // Create new Person and add to model
  // This is the formBackingOBject
  model.addAttribute("personAttribute", new Person());

  // This will resolve to /WEB-INF/jsp/addpage.jsp
  return "addpage";
 }
 
 /**
  * Sends a new record to the REST provider
  * based on the information submitted from the JSP add page.
  */
 @RequestMapping(value = "/add", method = RequestMethod.POST)
 public String addPerson(@ModelAttribute("personAttribute") Person person,
       Model model) {
  logger.debug("Add new person");
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  // Pass the new person and header
  HttpEntity<Person> entity = new HttpEntity<Person>(person, headers);

  // Send the request as POST
  try {
   ResponseEntity<Person> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person", HttpMethod.POST, entity, Person.class);
  } catch (Exception e) {
   logger.error(e);
  }
  
  // This will resolve to /WEB-INF/jsp/resultpage.jsp
  return "resultpage";
 }
 
 /**
     * Retrieves the JSP update page
     */
    @RequestMapping(value = "/update", method = RequestMethod.GET)
    public String getUpdatePage(@RequestParam(value="id", required=true) Integer id,  
              Model model) {
     logger.debug("Received request to show edit page");
    
     // Retrieve existing Person and add to model
     // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<Person> entity = new HttpEntity<Person>(headers);

  // Send the request as GET
  try {
   ResponseEntity<Person> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.GET, entity, Person.class, id);
   // Add to model
   model.addAttribute("personAttribute", result.getBody());
   
  } catch (Exception e) {
   logger.error(e);
  }
     
     // This will resolve to /WEB-INF/jsp/updatepage.jsp
     return "updatepage";
 }
 
 /**
  * Sends an update request to the REST provider
  * based on the information submitted from the JSP update page.
  */
    @RequestMapping(value = "/update", method = RequestMethod.POST)
 public String updatePerson(@ModelAttribute("personAttribute") Person person,
      @RequestParam(value="id",  required=true) Long id,
      Model model) {
  logger.debug("Update existing person");
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  // Pass the new person and header
  HttpEntity<Person> entity = new HttpEntity<Person>(person, headers);
  
  // Send the request as PUT
  ResponseEntity<String> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.PUT, entity, String.class, id);

  // This will resolve to /WEB-INF/jsp/resultpage.jsp
  return "resultpage";
    }
    
    /**
  * Sends a delete request to the REST provider
  */
    @RequestMapping(value = "/delete", method = RequestMethod.GET)
 public String deletePerson(@RequestParam("id") Long id,
          Model model) {
     logger.debug("Delete existing person");
     
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
     HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<String> entity = new HttpEntity<String>(headers); 
  
  // Send the request as DELETE
  ResponseEntity<String> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.DELETE, entity, String.class, id);
  
  // This will resolve to /WEB-INF/jsp/resultpage.jsp
  return "resultpage";
    }
}
Notice our controller is our typical Spring MVC 3 controller.

Let's dissect our controller and analyze each methods.

getPhoto() method

The getPhoto() method retrieves an image as a byte array from the REST provider. It displays the image by writing it to the output stream using a helper class: Writer
/**
  * Retrieves an image from the REST provider
  * and writes the response to an output stream.
  */
 @RequestMapping(value = "/getphoto", method = RequestMethod.GET)
 public void getPhoto(@RequestParam("id") Long id, HttpServletResponse response) {
  logger.debug("Retrieve photo with id: " + id);
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.IMAGE_JPEG);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<String> entity = new HttpEntity<String>(headers); 
  
  // Send the request as GET
  ResponseEntity<byte[]> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.GET, entity, byte[].class, id);
  
  // Display the image
  Writer.write(response, result.getBody());
 }

Writer.java
package org.krams.tutorial.util;

import java.io.ByteArrayOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;

/**
 * Writes the report to the output stream
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Writer {

 private static Logger logger = Logger.getLogger("service");
 /**
  * Writes the report to the output stream
  */
 public static void write(HttpServletResponse response, ByteArrayOutputStream bao) {
  
  logger.debug("Writing report to the stream");
  try {
   // Retrieve the output stream
   ServletOutputStream outputStream = response.getOutputStream();
   // Write to the output stream
   bao.writeTo(outputStream);
   // Flush the stream
   outputStream.flush();
   // Close the stream
   outputStream.close();

  } catch (Exception e) {
   logger.error("Unable to write report to the output stream");
  }
 }
 
 /**
  * Writes the report to the output stream
  */
 public static void write(HttpServletResponse response, byte[] byteArray) {
  
  logger.debug("Writing report to the stream");
  try {
   // Retrieve the output stream
   ServletOutputStream outputStream = response.getOutputStream();
   // Write to the output stream
   outputStream.write(byteArray);
   // Flush the stream
   outputStream.flush();
   // Close the stream
   outputStream.close();
   
  } catch (Exception e) {
   logger.error("Unable to write report to the output stream");
  }
 } 
}

To call this method, we have to use the following URL:
http://localhost:8081/spring-rest-client/krams/getphoto?id=1
Notice the port number is 8081. That's because I'm using two versions of Tomcat. For the REST provider, I use port 8080. For the REST client, I use port 8081. Depending on how you setup your server, please modify the URL accordingly.

Running the URL displays the following image:

I think you know who this person is (and it just so happen this is the photo I have in my desktop). Notice whatever id you use the application will show the same image. That's because the provider uses a fix image from the classpath. Anyway the point of this exercise is how to retrieve a byte array using RestTemplate.

getAll() method

The getAll() method sends a request to retrieve all records from the REST provider. Once the result has arrived, it is added to the model to be displayed in a JSP page.

/**
  * Retrieves all records from the REST provider
  * and displays the records in a JSP page
  */
 @RequestMapping(value = "/getall", method = RequestMethod.GET)
 public String getAll(Model model) {
  logger.debug("Retrieve all persons");
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<Person> entity = new HttpEntity<Person>(headers);

  // Send the request as GET
  try {
   ResponseEntity<PersonList> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/persons", HttpMethod.GET, entity, PersonList.class);
   // Add to model
   model.addAttribute("persons", result.getBody().getData());
   
  } catch (Exception e) {
   logger.error(e);
  }
  
  // This will resolve to /WEB-INF/jsp/personspage.jsp
  return "personspage";
 }

To call this method, we have to use the following URL:
http://localhost:8081/spring-rest-client/krams/getall

Running the URL shows the following page:

getPerson() method

The getPerson() method retrieves a single record based on the id from the REST provider. Once the result has arrived, it is added to the model to be displayed in a JSP page.

/**
  * Retrieves a single record from the REST provider
  * and displays the result in a JSP page
  */
 @RequestMapping(value = "/get", method = RequestMethod.GET)
 public String getPerson(@RequestParam("id") Long id, Model model) {
  logger.debug("Retrieve person with id: " + id);
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<Person> entity = new HttpEntity<Person>(headers);

  // Send the request as GET
  try {
   ResponseEntity<Person> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.GET, entity, Person.class, id);
   // Add to model
   model.addAttribute("person", result.getBody());
   
  } catch (Exception e) {
   logger.error(e);
  }
  
  // This will resolve to /WEB-INF/jsp/getpage.jsp
  return "getpage";
 }

To call this method, we have to use the following URL:
http://localhost:8081/spring-rest-client/krams/get?id=1
Alternatively we can click the Get link from the getAll page.

Running the URL shows the following page:

getAddPage() and addPerson() methods

The getAddPage() method returns a JSP form where the user can add a new record.

     /**
     * Retrieves the JSP add page
     */
 @RequestMapping(value = "/add", method = RequestMethod.GET)
 public String getAddPage(Model model) {
  logger.debug("Received request to show add page");

  // Create new Person and add to model
  // This is the formBackingOBject
  model.addAttribute("personAttribute", new Person());

  // This will resolve to /WEB-INF/jsp/addpage.jsp
  return "addpage";
 }

To call this method, we have to use the following URL:
http://localhost:8081/spring-rest-client/krams/add?id=1
Alternatively we can click the Add link from the getAll page

Running the URL shows the following page:

When the form is submitted, the addPerson() method takes control. Its purpose is to send a new record by sending a POST request to the REST provider.

/**
  * Sends a new record to the REST provider
  * based on the information submitted from the JSP add page.
  */
 @RequestMapping(value = "/add", method = RequestMethod.POST)
 public String addPerson(@ModelAttribute("personAttribute") Person person,
       Model model) {
  logger.debug("Add new person");
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  // Pass the new person and header
  HttpEntity<Person> entity = new HttpEntity<Person>(person, headers);

  // Send the request as POST
  try {
   ResponseEntity<Person> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person", HttpMethod.POST, entity, Person.class);
  } catch (Exception e) {
   logger.error(e);
  }
  
  // This will resolve to /WEB-INF/jsp/resultpage.jsp
  return "resultpage";
 }
To see the new record, please refresh the getAll page.

getUpdatePage() and updatePerson() methods

The getUpdatePage() method returns a JSP form where the user can edit an existing record.

 /**
     * Retrieves the JSP update page
     */
    @RequestMapping(value = "/update", method = RequestMethod.GET)
    public String getUpdatePage(@RequestParam(value="id", required=true) Integer id,  
              Model model) {
     logger.debug("Received request to show edit page");
    
     // Retrieve existing Person and add to model
     // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<Person> entity = new HttpEntity<Person>(headers);

  // Send the request as GET
  try {
   ResponseEntity<Person> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.GET, entity, Person.class, id);
   // Add to model
   model.addAttribute("personAttribute", result.getBody());
   
  } catch (Exception e) {
   logger.error(e);
  }
     
     // This will resolve to /WEB-INF/jsp/updatepage.jsp
     return "updatepage";
 }

To call this method, we have to use the following URL:
http://localhost:8081/spring-rest-client/krams/update?id=1
Alternatively we can click the Edit link from the getAll page

Running the URL shows the following page:

When the form is submitted, the updatePerson() method takes control. Its purpose is to send an updated record by sending a PUT request to the REST provider.

/**
  * Sends an update request to the REST provider
  * based on the information submitted from the JSP update page.
  */
    @RequestMapping(value = "/update", method = RequestMethod.POST)
 public String updatePerson(@ModelAttribute("personAttribute") Person person,
      @RequestParam(value="id",  required=true) Long id,
      Model model) {
  logger.debug("Update existing person");
  
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  // Pass the new person and header
  HttpEntity<Person> entity = new HttpEntity<Person>(person, headers);
  
  // Send the request as PUT
  ResponseEntity<String> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.PUT, entity, String.class, id);

  // This will resolve to /WEB-INF/jsp/resultpage.jsp
  return "resultpage";
    }
To see updated record, please refresh the getAll page.

deletePerson() method

The deletePerson() method sends a DELETE request to delete an existing record based on the submitted id.

    /**
  * Sends a delete request to the REST provider
  */
    @RequestMapping(value = "/delete", method = RequestMethod.GET)
 public String deletePerson(@RequestParam("id") Long id,
          Model model) {
     logger.debug("Delete existing person");
     
  // Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
     HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  HttpEntity<String> entity = new HttpEntity<String>(headers); 
  
  // Send the request as DELETE
  ResponseEntity<String> result = restTemplate.exchange("http://localhost:8080/spring-rest-provider/krams/person/{id}", HttpMethod.DELETE, entity, String.class, id);
  
  // This will resolve to /WEB-INF/jsp/resultpage.jsp
  return "resultpage";
    }

To call this method, we have to use the following URL:
http://localhost:8081/spring-rest-client/krams/delete?id=1
Alternatively we can click the Delete link from the getAll page

When you run the URL it will automatically send a request. To see the results, please refresh the getAll page.

Configuration

The application's configuration is quite trivial. It's just a basic Spring MVC configuration. To spare some space, I will not post the XML configurations here. Instead, you can look at them by checking the source code at the end of this tutorial.

Considerations

Notice in the header of each request we set the media type to APPLICATION_XML. We can equally change it to APPLICATION_JSON to accept JSON objects.

APPLICATION_XML
// Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_XML);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  // Pass the new person and header
  HttpEntity<Person> entity = new HttpEntity<Person>(person, headers);

APPLICATION_JSON
// Prepare acceptable media type
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
  
  // Prepare header
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(acceptableMediaTypes);
  // Pass the new person and header
  HttpEntity<Person> entity = new HttpEntity<Person>(person, headers);

For JSON to work, you need to have the Jackson library in your classpath. Make sure to check the pom.xml!

Conclusion

That's it. We've managed to create a REST client using Spring 3's RestTemplate. We've exchanged information with the provider via XML and displayed the result in a JSP page using Spring MVC.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-rest-guide/

You can download the project as a Maven build. Look for the spring-rest-client.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3: REST Web Service Provider and Client Tutorial (Part 2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Sunday, February 13, 2011

Spring Security 3 - OpenID with Javascript OpenID Selector

Introduction

In previous tutorials we've discussed how to add OpenID support to an existing Spring Security 3 application using various OpenID providers. We added each provider manually, including the images and specialized URLs. However that solution has introduced a couple of issues:

Problems

1. We're forced to manually add all OpenID providers in the JSP page. Developer A might implement it differently versus Developer B. This means users do not get a unified user experience.

2. Users are still required to know their full OpenID login. For example, to login using Blogspot, the user will type http://krams915.blogspot.com. Ideally the user should only type his username, i.e. krams915. Typing the full URL is error-prone, especially for non-technical users.

3. We could implement our own Javascript library that will parse and concatenate the default OpenID URLs. But that introduces extra work, and again, multiple developers may implement it differently. Also, are you expert enough to start your own Javascript framework?

Solution

Luckily for us there is a solution: the Javascript OpenID Selector

What is Javascript OpenID Selector?
This is a simple Javascript OpenID selector. It has been designed so that users do not even need to know what OpenID is to use it, they simply select their account by a recognisable logo.

Source: http://code.google.com/p/openid-selector/

You can find a live demo of this project at http://openid-selector.googlecode.com/svn/trunk/demo.html

Development

In this tutorial we'll add OpenID login support to an existing Spring Security 3 application. That means we'll be reusing existing configuration.

To fully appreciate this tutorial, it's required you know how to setup a simple Spring Security application using a simple user-service. If you need a review, please read my other guide Spring MVC 3 - Security - Using Simple User-Service.

You are also advised to read my other OpenID tutorials to understand the problem that we're trying to solve. You can check the following Spring Security 3 - OpenID Login with Google Provider and Spring Security 3 - OpenID Login with myOpenID Provider

We will based our application from the one we developed for Spring MVC 3 - Security - Using Simple User-Service tutorial. This is because the changes we need to implement are just a matter of configuration and placing Javascript snippets.

Preview

Before we add the Javascript OpenID Selector, our login page looks like the following:


After adding the Javascript OpenID Selector, our new login page should look like below:


Do you see the big improvement? We've just added multiple OpenID providers by just editing a JSP file which we'll show later.

Adding Javascript OpenID Selector

Since this is a third-party project, we must download it first and place the necessary files to the current Spring project. Here are the steps:

1. Visit the project's site at http://code.google.com/p/openid-selector/

2. Go to the Downloads section. Find the latest open-selector project:

3. Download and save the file.

4. Extract and open the extracted folder:


You should see a list of folders and files. We're only interested with the following:
js/jquery-1.2.6.min.js
js/openid-jquery.js
js/openid-en.js
images folder
css folder

5. Copy these files to the resources folder of the Spring project:

I suggest you create the following folders to logically contain these files.
css
images
js

Our next step is to edit the application's JSP login page to display the Javascript OpenID Selector.

6. Go back to the folder where you extracted the open-selector project.

7. Open the demo.html to see the following:


Right-click on this html file and view its source:

demo.html
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 <title>JQuery Simple OpenID Selector Demo</title>
 <!-- Simple OpenID Selector -->
 <link type="text/css" rel="stylesheet" href="css/openid.css" />
 <script type="text/javascript" src="js/jquery-1.2.6.min.js"></script>
 <script type="text/javascript" src="js/openid-jquery.js"></script>
 <script type="text/javascript" src="js/openid-en.js"></script>
 <script type="text/javascript">
  $(document).ready(function() {
   openid.init('openid_identifier');
   openid.setDemoMode(true); //Stops form submission for client javascript-only test purposes
  });
 </script>
 <!-- /Simple OpenID Selector -->
 <style type="text/css">
  /* Basic page formatting */
  body {
   font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  }
 </style>
</head>

<body>
 <h2>JQuery Simple OpenID Selector Demo</h2>
 <p>This is a simple example to show how you can include the Javascript into your page.</p>
 <br/>
 <!-- Simple OpenID Selector -->
 <form action="examples/consumer/try_auth.php" method="get" id="openid_form">
  <input type="hidden" name="action" value="verify" />
  <fieldset>
   <legend>Sign-in or Create New Account</legend>
   <div id="openid_choice">
    <p>Please click your account provider:</p>
    <div id="openid_btns"></div>
   </div>
   <div id="openid_input_area">
    <input id="openid_identifier" name="openid_identifier" type="text" value="http://" />
    <input id="openid_submit" type="submit" value="Sign-In"/>
   </div>
   <noscript>
    <p>OpenID is service that allows you to log-on to many different websites using a single indentity.
    Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
   </noscript>
  </fieldset>
 </form>
 <!-- /Simple OpenID Selector -->
</body>
</html>

8. Copy the code inside head and body section. Paste the code in the corresponding sections of the JSP login page. Here's the final JSP page:

loginpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 
 <c:url var="rootUrl" value="/resources/" />
 
 <title>JQuery Simple OpenID Selector Demo</title>
 <!-- Simple OpenID Selector -->
 <link type="text/css" rel="stylesheet" href="${rootUrl}css/openid.css" />
 <script type="text/javascript" src="${rootUrl}js/jquery-1.2.6.min.js"></script>
 <script type="text/javascript" src="${rootUrl}js/openid-jquery.js"></script>
 <script type="text/javascript" src="${rootUrl}js/openid-en.js"></script>
 <script type="text/javascript">
  $(document).ready(function() {
   openid.init('openid_identifier');
   //openid.setDemoMode(true); //Stops form submission for client javascript-only test purposes
   });
 </script>
 <!-- /Simple OpenID Selector -->
 <style type="text/css">
 /* Basic page formatting */
 body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 }
 </style>
</head>
<body>

<h1>Spring Security 3</h1>
<div id="login-error">${error}</div>

<c:url var="openIDLoginUrl" value="/j_spring_openid_security_check" />
<h2>JQuery Simple OpenID Selector Demo</h2> 
 <p>This is a simple example to show how you can include the Javascript into your page.</p> 
 <br/> 
 <!-- Simple OpenID Selector --> 
 <form action="${openIDLoginUrl}" method="post" id="openid_form"> 
  <input type="hidden" name="action" value="verify" /> 
  <fieldset> 
   <legend>Sign-in or Create New Account</legend> 
   <div id="openid_choice"> 
    <p>Please click your account provider:</p> 
    <div id="openid_btns"></div> 
   </div> 
   <div id="openid_input_area"> 
    <input id="openid_identifier" name="openid_identifier" type="text" value="http://" /> 
    <input id="openid_submit" type="submit" value="Sign-In"/> 
   </div> 
   <noscript> 
    <p>OpenID is service that allows you to log-on to many different websites using a single indentity.
    Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
   </noscript> 
  </fieldset> 
 </form> 
 <!-- /Simple OpenID Selector --> 

</body>
</html>

To make this code work, we have to correctly set the root URL by using a JSTL tag library:
<c:url var="rootUrl" value="/resources/" />
We've also commented out the following line which is responsible for activating the demo function:
//openid.setDemoMode(true); //Stops form submission for client 

Try running the application. You'll notice the images are missing. That's because we need to set the correct image path property inside the openid-jquery.js.

9. Go to the resources folder. Open the openid-jquery.js file.

10. Find the img_path property. Set the value as follows:
img_path : '../../resources/images/',

Run the Application

To run the application, use the following URL to display the login page:
http://localhost:8080/spring-security-openid-selector/krams/auth/login

Here's the final screenshot:

Try playing with all the various OpenID providers. Notice how user-friendly the interface is.

Reminder

Notice regardless whether you've entered the correct credentials the Spring application still throws out an error that the username or password is incorrect. That's because you need to add the correct OpenID identifier under the user-service tag. See the spring-security.xml

<security:user-service id="userDetailsService">
   <!-- user name is based on the returned open id identifier -->
   <!-- YOU MUST MANUALLY ADD THE RETURNED OPENID IDENTIFIER HERE -->
     <security:user name="http://krams915.blogspot.com/" password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>

Conclusion

That's it. We've managed to add the Javascript OpenID Selector. Of course to make the whole project work, you need to add the corresponding returned OpenID identifiers in the Spring Security's in-memory user-service. If you're going to test these with Google, Yahoo, or any of your favorite providers, please log out first so that you can test the login functionality!

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-openid/

You can download the project as a Maven build. Look for the spring-security-openid-selector.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security 3 - OpenID with Javascript OpenID Selector ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring Security 3 - OpenID Login with Google Provider

In this tutorial we'll add OpenID support for authenticating users in our existing Spring Security 3 application. It's required you understand how to setup a simple Spring Security application using a simple user-service. If you need a review, please read my other guide Spring MVC 3 - Security - Using Simple User-Service for an in-depth tutorial. We will use Google as our OpenID provider. You are therefore required to setup an account with Google.

We will based our application from the one we developed for Spring MVC 3 - Security - Using Simple User-Service tutorial. This is because everything is exactly the same. It's just a matter of configuration to enable OpenID support.

What is OpenID?
OpenID allows you to use an existing account to sign in to multiple websites, without needing to create new passwords.

You may choose to associate information with your OpenID that can be shared with the websites you visit, such as a name or email address. With OpenID, you control how much of that information is shared with the websites you visit.

With OpenID, your password is only given to your identity provider, and that provider then confirms your identity to the websites you visit. Other than your provider, no website ever sees your password, so you don’t need to worry about an unscrupulous or insecure website compromising your identity.

Source: http://openid.net/get-an-openid/what-is-openid/

Screenshot

Here's a screenshot of the application's OpenID login page:


Review

Enabling OpenID authentication is actually simple. Remember our application is based on the Spring MVC 3 - Security - Using Simple User-Service. Because of that all we need to do is modify the existing spring-security.xml config.

The Old Config

Here's our existing config file (for a thorough explanation please read the aforementioned guide):

spring-security.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:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/security 
   http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
 <!-- This is where we configure Spring-Security  -->
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <security:form-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
 </security:http>
 
 <!-- Declare an authentication-manager to use a custom userDetailsService -->
 <security:authentication-manager>
         <security:authentication-provider user-service-ref="userDetailsService">
           <security:password-encoder ref="passwordEncoder"/>
         </security:authentication-provider>
 </security:authentication-manager>
 
 <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
 <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

  <!-- An in-memory list of users. No need to access an external database layer.
      See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication -->
  <!-- john's password is admin, while jane;s password is user  -->
  <security:user-service id="userDetailsService">
     <security:user name="john" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_USER, ROLE_ADMIN" />
     <security:user name="jane" password="ee11cbb19052e40b07aac0ca060c23ee" authorities="ROLE_USER" />
   </security:user-service>
 
</beans>
This configuration uses Spring Security's form-based login where you enter a username and password. At the end of the file, we have declared a list of users with corresponding credentials.

To authenticate a user, he must first provide a username and password which the application will compare in the in-memory list as declared in the user-service tag. If a match is found, the user is authenticated and authorized.

The New Config

Here's our new config file which enables OpenID support. Notice almost everything is still exactly the same!

spring-security.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:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/security 
   http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
 <!-- This is where we configure Spring-Security  -->
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <!-- Adding the openid-login tag activates Spring Security's support for OpenID  -->
  <security:openid-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
 </security:http>
 
 <!-- Declare an authentication-manager to use a custom userDetailsService -->
 <security:authentication-manager>
         <security:authentication-provider user-service-ref="userDetailsService">
           <security:password-encoder ref="passwordEncoder"/>
         </security:authentication-provider>
 </security:authentication-manager>
 
 <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
 <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

  <!-- An in-memory list of users. No need to access an external database layer.
      See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication -->
  <security:user-service id="userDetailsService">
   <!-- user name is based on the returned OpenID identifier from Google -->
     <security:user name="https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E" 
          password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>
 
</beans>

Google: Enabling OpenID

With OpenID the same principle applies. To authenticate a user via Google's OpenID support, he must perform the following steps:

1. Click the Sign with Google button.


2. Login with the OpenID provider.


After a successful authentication, the provider will ask the user to continue and return back to the original application

4. Spring Security will then perform two additional steps:
- Check if the returned OpenID identifier is registered in the application's database.
- Check if the returned OpenID identifier has authorities assigned in the application's database.

Notice these are the same steps used in a form-based login. This is the reason why it's beneficial that you understand how to setup a simple Spring Security 3 application using a simple user-details service first.

Authorities/Roles

Take note the job of the OpenID provider is to authenticate the user and returned back a valid OpenID identifier. The appropriate authorities or roles is the responsibility of the application. We must assign the roles in other words!
<security:user-service id="userDetailsService">
   <!-- user name is based on the returned open id identifier -->
     <security:user name="https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E" 
          password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>
Notice for this username we assigned a ROLE_USER and ROLE_ADMIN.

Development

Let's enable OpenID authentication by modifying the config file. We just need to perform two steps:

1. Replace the form-login tag with openid-login tag
old config
<security:http auto-config="true" >
    <security:form-login >
    ...omitted declarations
</security:http>

new config
<security:http auto-config="true" >
    <security:openid-login >
    ...omitted declarations
</security:http>

2. Add a new user name inside the user-service tag
<security:user-service id="userDetailsService">
     <security:user name="https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E" 
          password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>
Notice the password attribute is empty. That's because the actual authentication is performed by the OpenID provider. The user name is based on the OpenID identifier returned by the provider. By default the OpenID login format is:
http://USERNAME.myopenid.com/
where USERNAME is a placeholder for the user's chosen username during registration at myOpenID website.

However Google doesn't follow this convention. Instead the returned OpenID identifier has the following format:
https://www.google.com/accounts/o8/id?id=XXXXXXXXXXXXXXXXXXXXX
For example:
https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E

The JSP Login Page

Since we'll be using OpenID, we need to modify our JSP login page to use the OpenID form identifiers.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Login</h1>
<div id="login-error">${error}</div>

<c:url var="logoUrl" value="/resources/openidlogosmall.png" />
<p><img src="${logoUrl}"></img>Login with OpenID:</p>
<c:url var="openIDLoginUrl" value="/j_spring_openid_security_check" />
<form action="${openIDLoginUrl}" method="post" >
 <label for="openid_identifier">OpenID Login</label>:
 <input id="openid_identifier" name="openid_identifier" type="text"/>
 <input  type="submit" value="Login"/>        
</form>

<hr/>

<c:url var="googleLogoUrl" value="/resources/google-logo.png" />
<img src="${googleLogoUrl}"></img>
<form action="${openIDLoginUrl}" method="post">
    For Google users:
   <input name="openid_identifier" type="hidden" value="https://www.google.com/accounts/o8/id"/>
   <input type="submit" value="Sign with Google"/>
</form>

</body>
</html>
Notice we have two form tags.

The first form tag is for OpenID providers that support direct id entry. For example, http://krams915.myopenid.com or http://krams915.blogspot.com. For a description of this form tag, see the tutorial Spring Security 3 - OpenID Login with myOpenID Provider

Google doesn't support direct id entry. Instead you need to use the following URL as the entry id:
https://www.google.com/accounts/o8/id
This works. However, the user must remember this exact URL which is bad because the majority of users will not remember that URL ever! Talk about bad user experience.

To resolve this issue, we added a second form tag that only works for Google. What we did here is hide the input text field and embed Google's default entry id (https://www.google.com/accounts/o8/id) so that when the user clicks the Sign with Google button, he doesn't need to type that unfriendly URL.

Determine the OpenID Identifier

The best way to determine the OpenID identifier is to set the application's logger to DEBUG level. Then run the application and check the logs. Of course, you need to have a working internet connection to be able to login with the provider. Our application uses log4j for logging.

Here's the log4j properties file:

log4j.properties
log4j.rootLogger=DEBUG,console

#Console Appender 
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%5p] [%t %d{hh:mm:ss}] (%F:%M:%L) %m%n

#Custom assignments
log4j.logger.controller=DEBUG,console
log4j.logger.service=DEBUG,console

#Disable additivity
log4j.additivity.controller=false
log4j.additivity.service=false

Run the application and login with OpenID. After a successful authentication, check the logs. Here's a sample output:

log output
[DEBUG] [http-8080-Processor23 02:33:21] (Association.java:sign:261) Computing signature for input data:
op_endpoint:https://www.google.com/accounts/o8/ud
claimed_id:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E
identity:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E
return_to:http://localhost:8080/spring-security-openid-google/j_spring_openid_security_check
response_nonce:2011-02-12T15:48:26Zdji30Q3btZj2Cw
assoc_handle:AOQobUebACOhT-Hh3ckKxe8Bxc_drPGqIIfzMcYDBfU0167YQZw6J4F6uvH18g2UlNRDPC7B

[DEBUG] [http-8080-Processor24 11:48:27] (Association.java:sign:267) Calculated signature: Mq8Iu1XQ87rKQePQxEnIcU7GttlT6E1usz3E+BgGvx4=
[DEBUG] [http-8080-Processor24 11:48:27] (ConsumerManager.java:verifySignature:1790) Local signature verification succeeded.
[ INFO] [http-8080-Processor24 11:48:27] (ConsumerManager.java:verifySignature:1850) Verification succeeded for: https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E
[DEBUG] [http-8080-Processor24 11:48:27] (ProviderManager.java:doAuthentication:127) Authentication attempt using org.springframework.security.openid.OpenIDAuthenticationProvider
Pay attention to these values:
claimed_id:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E
identity:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E
The claimed_id is what you need to add in the application's user-details table. In our case, it's an in-memory list.

If you really want to ensure that you're using the correct id, check the next line in the log file:
[ INFO] [http-8080-Processor24 11:48:27] (ConsumerManager.java:verifySignature:1850) Verification succeeded for: https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E
The value you see here is the id that must be placed in the user-details table. I'm adding emphasis here because if you're using other OpenID providers, you might get confused which id to used. This one is always correct.

Other OpenID Providers

Below is a sample table that shows the different identifiers returned by various OpenID providers (We'll try to expand this list as we explore OpenID further in future tutorials):

MyOpenID http://krams915.myopenid.com/
Yahoo https://me.yahoo.com/a/ooXDFSsdfsqDbYAGuDSFSIK.PIuBsfdKA--#ade71
Google https://www.google.com/accounts/o8/id?id=BVlajJOIDjsldfjszSfjsM5sdfs0E
Blogspot http://krams915.blogspot.com/

Notice some OpenID providers adhere to the standard format, but others do not. To see other availabe OpenID providers, please visit http://openid.net/get-an-openid/

Run the Application

To run the application, use the following URL:
http://localhost:8080/spring-security-openid-myopenid/krams/auth/login
Of course, you will need to modify the username in the user-details service to match your provider's returned OpenID identifier. You will need to run the application first, authenticate, after a failed authentication, you should be able to see the claimed_id in the logs.

Conclusion

That's it. We've managed to enable OpenID support on our existing Spring Security 3 application by just modifying two simple configurations and a single JSP file. We've also discussed how to retrieve the OpenID identifier and how Spring Security evaluates this property.

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-openid/

You can download the project as a Maven build. Look for the spring-security-openid-google.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security 3 - OpenID Login with Google Provider ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Saturday, February 12, 2011

Spring Security 3 - OpenID Login with myOpenID Provider

In this tutorial we'll add OpenID support for authenticating users in our existing Spring Security 3 application. It's required you understand how to setup a simple Spring Security application using a simple user-service. If you need a review, please read my other guide Spring MVC 3 - Security - Using Simple User-Service for an in-depth tutorial. We will use myOpenID as our OpenID provider. You are therefore required to setup an account with myOpenID.

We will based our application from the one we developed for Spring MVC 3 - Security - Using Simple User-Service tutorial. This is because everything is exactly the same. It's just a matter of configuration to enable OpenID support.

What is OpenID?
OpenID allows you to use an existing account to sign in to multiple websites, without needing to create new passwords.

You may choose to associate information with your OpenID that can be shared with the websites you visit, such as a name or email address. With OpenID, you control how much of that information is shared with the websites you visit.

With OpenID, your password is only given to your identity provider, and that provider then confirms your identity to the websites you visit. Other than your provider, no website ever sees your password, so you don’t need to worry about an unscrupulous or insecure website compromising your identity.

Source: http://openid.net/get-an-openid/what-is-openid/

What is myOpenID?
First and largest independent OpenID provider. Signing up with myOpenID gets you:
- Secure control of your digital identity
- Easy sign-in on enabled sites
- Account activity reports
- And a whole lot more!

Source: https://www.myopenid.com

Screenshot

Here's a screenshot of the application's OpenID login page:


Review

Enabling OpenID authentication is actually simple. Remember our application is based on the Spring MVC 3 - Security - Using Simple User-Service. Because of that all we need to do is modify the existing spring-security.xml config.

The Old Config

Here's our existing config file (for a thorough explanation please read the aforementioned guide):

spring-security.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:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/security 
   http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
 <!-- This is where we configure Spring-Security  -->
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <security:form-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
 </security:http>
 
 <!-- Declare an authentication-manager to use a custom userDetailsService -->
 <security:authentication-manager>
         <security:authentication-provider user-service-ref="userDetailsService">
           <security:password-encoder ref="passwordEncoder"/>
         </security:authentication-provider>
 </security:authentication-manager>
 
 <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
 <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

  <!-- An in-memory list of users. No need to access an external database layer.
      See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication -->
  <!-- john's password is admin, while jane;s password is user  -->
  <security:user-service id="userDetailsService">
     <security:user name="john" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_USER, ROLE_ADMIN" />
     <security:user name="jane" password="ee11cbb19052e40b07aac0ca060c23ee" authorities="ROLE_USER" />
   </security:user-service>
 
</beans>
This configuration uses Spring Security's form-based login where you enter a username and password. At the end of the file, we have declared a list of users with corresponding credentials.

To authenticate a user, he must first provide a username and password which the application will compare in the in-memory list as declared in the user-service tag. If a match is found, the user is authenticated and authorized.

The New Config

Here's our new config file which enables OpenID support. Notice almost everything is still exactly the same!

spring-security.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:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/security 
   http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
 <!-- This is where we configure Spring-Security  -->
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <!-- Adding the openid-login tag activates Spring Security's support for OpenID  -->
  <security:openid-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
 </security:http>
 
 <!-- Declare an authentication-manager to use a custom userDetailsService -->
 <security:authentication-manager>
         <security:authentication-provider user-service-ref="userDetailsService">
           <security:password-encoder ref="passwordEncoder"/>
         </security:authentication-provider>
 </security:authentication-manager>
 
 <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
 <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

  <!-- An in-memory list of users. No need to access an external database layer.
      See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication -->
  <security:user-service id="userDetailsService">
   <!-- user name is based on the returned open id identifier -->
     <security:user name="http://krams915.myopenid.com/" password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>
 
</beans>

myOpenID: Enabling OpenID

With OpenID the same principle applies. To authenticate a user via myOpenID, he must perform the following steps:

1. Enter his OpenID login.


If we're using myOpenID, our OpenID login has the following format:
http://USERNAME.myopenid.com/
USERNAME is a placeholder for the user's chosen username during registration at myOpenID website. If for example we chose krams915 as our username, our OpenID login is:
http://krams915.myopenid.com/

2. Click the Login button.

The user is then forwarded to the myOpenID provider's login page. The user will be required to enter his password:

3. Login with the OpenID provider.

After a successful authentication, the provider will ask the user to continue and return back to the original application:

4. Spring Security will then perform two additional steps:
- Check if the returned OpenID identifier is registered in the application's database.
- Check if the returned OpenID identifier has authorities assigned in the application's database.

Notice these are the same steps used in a form-based login. This is the reason why it's beneficial that you understand how to setup a simple Spring Security 3 application using a simple user-details service first.

Authorities/Roles

Take note the job of the OpenID provider is to authenticate the user and returned back a valid OpenID identifier. The appropriate authorities or roles is the responsibility of the application. We must assign the roles in other words!
<security:user-service id="userDetailsService">
   <!-- user name is based on the returned open id identifier -->
     <security:user name="http://krams915.myopenid.com/" password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>
Notice for this username we assigned a ROLE_USER and ROLE_ADMIN.

Development

Let's enable OpenID authentication by modifying the config file. We just need to perform two steps:

1. Replace the form-login tag with openid-login tag
old config
<security:http auto-config="true" >
    <security:form-login >
    ...omitted declarations
</security:http>

new config
<security:http auto-config="true" >
    <security:openid-login >
    ...omitted declarations
</security:http>

2. Add a new user name inside the user-service tag
<security:user-service id="userDetailsService">
     <security:user name="http://krams915.myopenid.com/" password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>
Notice the password attribute is empty. That's because the actual authentication is performed by the OpenID provider. The user name is based on the OpenID identifier returned by the provider. By default the OpenID login format is:
http://USERNAME.myopenid.com/
where USERNAME is a placeholder for the user's chosen username during registration at myOpenID website. However there are exceptions. Google and Yahoo do not follow this convention.

The JSP Login Page

Since we'll be using OpenID, we need to modify our JSP login page to use the OpenID form identifiers.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Login</h1>
<div id="login-error">${error}</div>

<c:url var="logoUrl" value="/resources/openidlogosmall.png" />
<p><img src="${logoUrl}"></img>Login with OpenID:</p>
<c:url var="openIDLoginUrl" value="/j_spring_openid_security_check" />
<form action="${openIDLoginUrl}" method="post" >
 <label for="openid_identifier">OpenID Login</label>:
 <input id="openid_identifier" name="openid_identifier" type="text"/>
 <input  type="submit" value="Login"/>        
</form>

</body>
</html>
Notice there are two important identifiers:
j_spring_openid_security_check the virtual URL used by Spring Security to verify the user
openid_identifier the input field for the user's OpenID login
These identifers are not arbirtrary! You must type them exactly as they are.

Determine the OpenID Identifier

The best way to determine the OpenID identifier is to set the application's logger to DEBUG level. Then run the application and check the logs. Of course, you need to have a working internet connection to be able to login with the provider. Our application uses log4j for logging.

Here's the log4j properties file:

log4j.properties
log4j.rootLogger=DEBUG,console

#Console Appender 
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%5p] [%t %d{hh:mm:ss}] (%F:%M:%L) %m%n

#Custom assignments
log4j.logger.controller=DEBUG,console
log4j.logger.service=DEBUG,console

#Disable additivity
log4j.additivity.controller=false
log4j.additivity.service=false

Run the application and login with OpenID. After a successful authentication, check the logs. Here's a sample output:

log output
[DEBUG] [http-8080-Processor23 02:33:21] (Association.java:sign:261) Computing signature for input data:
assoc_handle:{HMAC-SHA256}{4d5629ae}{3l86JQ==}
claimed_id:http://krams915.myopenid.com/
identity:http://krams915.myopenid.com/
mode:id_res
ns:http://specs.openid.net/auth/2.0
op_endpoint:http://www.myopenid.com/server
response_nonce:2011-02-12T06:33:19ZVytTyL
return_to:http://localhost:8080/spring-security-openid-myopenid/j_spring_openid_security_check
signed:assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed

[DEBUG] [http-8080-Processor23 02:33:21] (Association.java:sign:267) Calculated signature: dD3h9VCvAZLWwUPW/Av+sdfjlKSSs+6bSKTsfsFWrA=
[DEBUG] [http-8080-Processor23 02:33:21] (ConsumerManager.java:verifySignature:1790) Local signature verification succeeded.
[ INFO] [http-8080-Processor23 02:33:21] (ConsumerManager.java:verifySignature:1850) Verification succeeded for: http://krams915.myopenid.com/
[DEBUG] [http-8080-Processor23 02:33:21] (ProviderManager.java:doAuthentication:127) Authentication attempt using org.springframework.security.openid.OpenIDAuthenticationProvider
Pay attention to these values:
claimed_id:http://krams915.myopenid.com/
identity:http://krams915.myopenid.com/
The claimed_id is what you need to add in the application's user-details table. In our case, it's an in-memory list.

If you really want to ensure that you're using the correct id, check the next line in the log file:
[ INFO] [http-8080-Processor23 02:33:21] (ConsumerManager.java:verifySignature:1850) Verification succeeded for: http://krams915.myopenid.com/
The value you see here is the id that must be placed in the user-details table. I'm adding emphasis here because if you're using other OpenID providers, you might get confused which id to used. This one is always correct.

Other OpenID Providers

For other OpenID providers, we'll discuss them in future tutorials since some of them may need special treatment like Google and Yahoo.

Below is a sample table that shows the different identifiers returned by various OpenID providers (We'll try to expand this list as we explore OpenID further in future tutorials):

MyOpenID http://krams915.myopenid.com/
Yahoo https://me.yahoo.com/a/ooXDFSsdfsqDbYAGuDSFSIK.PIuBsfdKA--#ade71
Google https://www.google.com/accounts/o8/id?id=BVlajJOIDjsldfjszSfjsM5sdfs0E
Blogspot http://krams915.blogspot.com/

Notice some OpenID providers adhere to the standard format, but others do not. To see other availabe OpenID providers, please visit http://openid.net/get-an-openid/

Run the Application

To run the application, use the following URL:
http://localhost:8080/spring-security-openid-myopenid/krams/auth/login
Of course, you will need to modify the username in the user-details service to match your provider's returned OpenID identifier. You will need to run the application first, authenticate, after a failed authentication, you should be able to see the claimed_id in the logs.

Conclusion

That's it. We've managed to enable OpenID support on our existing Spring Security 3 application by just modifying two simple configurations and a single JSP file. We've also discussed how to retrieve the OpenID identifier and how Spring Security evaluates this property.

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-openid/

You can download the project as a Maven build. Look for the spring-security-openid-myopenid.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security 3 - OpenID Login with myOpenID Provider ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Tuesday, February 8, 2011

Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport

In this tutorial we will build a simple Spring MVC 3 application with reporting capabilities. We will use DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. We will use DynamicJasper's report concatenation to attach a subreport to a parent report. The data will be retrieved from a MySQL database.

This tutorial is part of the following reporting tutorial series that uses Jasper, DynamicJasper, and Apache POI:

Spring 3 - Apache POI - Hibernate: Creating an Excel Report Tutorial
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport
Spring 3 - DynamicJasper - Hibernate Tutorial: Using Plain List
Spring 3 - DynamicJasper - Hibernate Tutorial: Using JRDataSource
Spring 3 - DynamicJasper - Hibernate Tutorial: Using HQL Query

All of these tutorials produce the same document, and all of them demonstrate different ways of creating the same report.

What is DynamicJasper?
DynamicJasper (DJ) is an open source free library that hides the complexity of Jasper Reports, it helps developers to save time when designing simple/medium complexity reports generating the layout of the report elements automatically.

Source: http://dynamicjasper.com/

What is JasperReports?
JasperReports is the world's most popular open source reporting engine. It is entirely written in Java and it is able to use data coming from any kind of data source and produce pixel-perfect documents that can be viewed, printed or exported in a variety of document formats including HTML, PDF, Excel, OpenOffice and Word.

Source: http://jasperforge.org/projects/jasperreports

Background

Before we start our application, let's preview first the final print document:

Our document is a simple Excel document. It's a Sales Report for a list of power supplies. The data is retrieved from a MySQL database.

Domain

Notice that for each Power Supply entry there's a common set of properties:
id
brand
model
maximum power
price
efficiency

Development

Domain

We'll start our application by declaring the domain object PowerSupply

PowerSupply.java
package org.krams.tutorial.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * A simple POJO containing the common properties of a Power Supply
 * This is an annotated Hibernate entity. 
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Entity
@Table(name = "POWER_SUPPLY")
public class PowerSupply implements Serializable {

 private static final long serialVersionUID = 8634209606034270882L;

 @Id
 @Column(name = "ID")
 @GeneratedValue
 private Long id;
 
 @Column(name = "BRAND")
 private String brand;
 
 @Column(name = "MODEL")
 private String model;
 
 @Column(name = "MAXIMUM_POWER")
 private String maximumPower;
 
 @Column(name = "PRICE")
 private Double price;
 
 @Column(name = "EFFICIENCY")
 private Double efficiency;

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getBrand() {
  return brand;
 }

 public void setBrand(String brand) {
  this.brand = brand;
 }

 public String getModel() {
  return model;
 }

 public void setModel(String model) {
  this.model = model;
 }

 public String getMaximumPower() {
  return maximumPower;
 }

 public void setMaximumPower(String maximumPower) {
  this.maximumPower = maximumPower;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public Double getEfficiency() {
  return efficiency;
 }

 public void setEfficiency(Double efficiency) {
  this.efficiency = efficiency;
 }
 
}
PowerSupply is a simple POJO containing six private fields. Each of these fields have been annotated with @Column and assigned with corresponding database column names.
ID
BRAND
MODEL
MAXIMUM_POWER
PRICE
EFFICIENCY

Service

We'll be declaring a single service named DownloadService. This service is the heart of the application that will process and retrieve the report document.

The service will run the following steps:
1. Build the report layout
 2. Add the datasource to a HashMap parameter
 3. Compile the report layout
 4. Generate the JasperPrint object
 5. Export to a particular format, ie. XLS
 6. Set the HttpServletResponse properties
 7. Write to the output stream

DownloadService.java
package org.krams.tutorial.service;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.krams.tutorial.domain.PowerSupply;
import org.krams.tutorial.report.Exporter;
import org.krams.tutorial.report.Layouter;
import org.krams.tutorial.report.Writer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import ar.com.fdvs.dj.core.DynamicJasperHelper;
import ar.com.fdvs.dj.core.layout.ClassicLayoutManager;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import javax.annotation.Resource;

/**
 * Service for processing DynamicJasper reports
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Service("downloadService")
@Transactional
public class DownloadService {

 private static Logger logger = Logger.getLogger("service");
 
 @Resource(name="sessionFactory")
 private SessionFactory sessionFactory;
 
 /**
  * Processes the download for Excel format.
  * It does the following steps:
  * <pre>1. Build the report layout
  * 2. Retrieve the datasource
  * 3. Compile the report layout
  * 4. Generate the JasperPrint object
  * 5. Export to a particular format, ie. XLS
  * 6. Set the HttpServletResponse properties
  * 7. Write to the output stream
  * </pre>
  */
 @SuppressWarnings("unchecked")
 public void downloadXLS(HttpServletResponse response) throws ColumnBuilderException, ClassNotFoundException, JRException {
  logger.debug("Downloading Excel report");

  // 1. Build the report layout
  DynamicReport dr = Layouter.buildParentReportLayout();

  // 2. Add the datasource to a HashMap parameter
  HashMap params = new HashMap(); 
  // Here we're adding a custom datasource named "dynamicReportDs". 
  // It's the name we put in the Layouter
  params.put("dynamicReportDs", getDatasource());
  
  // 3. Compile the report layout
  // This will regardless if you activate the .setWhenNoDataAllSectionNoDetail() property or not
  // in the parentReportBuilder under the Layouter class. However you're FORCED 
  // to provide a dummy datasource for the parent report
  JasperReport jr = DynamicJasperHelper.generateJasperReport(dr, new ClassicLayoutManager(), params);
  
  // 4. Generate the JasperPrint object which also fills the report with data
  // This will regardless if you activate the .setWhenNoDataAllSectionNoDetail() property or not
  // in the parentReportBuilder under the Layouter class. However you're FORCED 
  // to provide a dummy datasource for the parent report
  JasperPrint jp = JasperFillManager.fillReport(jr, params, getJRDummyDatasource() );

  // We can also combine compilation (3) and generation (4) in a single line
  // This will only work if you add the .setWhenNoDataAllSectionNoDetail() property
  // in the parentReportBuilder under the Layouter class
  //JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), params);
  
  // Create the output byte stream where the data will be written
  ByteArrayOutputStream baos = new ByteArrayOutputStream();

  // 5. Export to XLS format
  Exporter.exportToXLS(jp, baos);
  
  // 6. Set the response properties
  String fileName = "SalesReport.xls";
  response.setHeader("Content-Disposition", "inline; filename=" + fileName);
  // Make sure to set the correct content type
  response.setContentType("application/vnd.ms-excel");
  // Assign the byte stream's size
  response.setContentLength(baos.size());

  // 7. Write to response stream
  Writer.write(response, baos);
 }
 
 /**
  * Retrieves a Java List datasource.
  * <p>
  * The data is retrieved from a Hibernate HQL query.
  * @return
  */
 @SuppressWarnings("unchecked")
 private List getDatasource() {
  logger.debug("Retrieving datasource");
  
      // Retrieve session
  Session session = sessionFactory.getCurrentSession();
  // Create query for retrieving products
  Query query = session.createQuery("FROM PowerSupply");
  // Execute query
  List<PowerSupply> result = query.list();

  // Return the datasource
  return result;
 }
 
 /**
  * Retrieves a dummy JRDataSource. 
  * @return
  */
 @SuppressWarnings("unchecked")
 private JRDataSource getJRDummyDatasource() {
  logger.debug("Retrieving JRdatasource");
  
  // Return the datasource
  return null;
 }
}
This service is our download service for generating the report document. It should be clear what each line of code is doing. Notice that in step 2 we have to declare the datasource as a key in the HashMap parameter:
HashMap params = new HashMap(); 
  // Here we're adding a custom datasource named "dynamicReportDs". 
  // It's the name we put in the Layouter
  params.put("dynamicReportDs", getDatasource());

Also, we've declared a getDatasource() method that retrieves a list of PowerSupply.
List<PowerSupply> result = query.list();
return result;

Furthermore, we declared a getJRDummyDatasource() method to generate an empty list. This is required if you want to use steps 3 and 4.

Alternatively, you can combine steps 3 and 4 and avoid this dummy datasource entirely:
// We can also combine compilation (3) and generation (4) in a single line
// This will only work if you add the .setWhenNoDataAllSectionNoDetail() property
// in the parentReportBuilder under the Layouter class
JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), params);

The service has been divided into separate classes to encapsulate specific jobs.

The Layouter

The purpose of the Layouter is to layout the design of the report. Here's where we declare the dynamic columns and special properties of the document. At the end of the class, we appended an HQL query

Layouter.java
package org.krams.tutorial.report;

import java.util.Date;

import org.apache.log4j.Logger;

import ar.com.fdvs.dj.core.DJConstants;
import ar.com.fdvs.dj.core.layout.ClassicLayoutManager;
import ar.com.fdvs.dj.domain.DJDataSource;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import ar.com.fdvs.dj.domain.builders.DJBuilderException;
import ar.com.fdvs.dj.domain.builders.FastReportBuilder;
import ar.com.fdvs.dj.domain.builders.SubReportBuilder;
import ar.com.fdvs.dj.domain.entities.Subreport;

/**
 * Everything under the package org.krams.tutorial.dynamicjasper are settings imposed by DynamicJasper (not Jasper)
 *<p>
 * Builds the report layout, the template, the design, the pattern or whatever synonym you may want to call it.
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Layouter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Builds the report layout. This doesn't have any data yet. This is your template.
  * 
  * @return the layout
  */
 public static DynamicReport buildChildReportLayout() throws ColumnBuilderException, ClassNotFoundException {
  
  // Create an instance of FastReportBuilder
  FastReportBuilder drb = new FastReportBuilder();
  
  // Create columns
  // The column fields must match the name and type of the properties in your datasource
  drb.addColumn("Id", "id",  Long.class.getName(), 50)
        .addColumn("Brand", "brand", String.class.getName(), 50)
        .addColumn("Model", "model" , String.class.getName(), 50)
        .addColumn("Max Power", "maximumPower", String.class.getName(), 50)
        .addColumn("Price", "price", Double.class.getName(), 50)
        .addColumn("Efficiency", "efficiency", Double.class.getName(), 50)
        .setPrintColumnNames(true)
        
         // Disables pagination
        .setIgnorePagination(true)
        
        // Experiment with this numbers to see the effect
        .setMargins(0, 0, 0, 0) 
        
        // Set the title shown inside the Excel file
        .setTitle("Sales Report") 
        
        // Set the subtitle shown inside the Excel file
        .setSubtitle("This report was generated at " + new Date()) 
        
  // Set to true if you want to constrain your report within the page boundaries
  // For longer reports, set it to false
        .setUseFullPageWidth(true);

  // Set the name of the file
        //drb.setReportName("Sales Report");
        
        // Build the report layout. It doesn't have any data yet!
        DynamicReport dr = drb.build();
        
        // Return the layout
        return dr;
 }
 
 /**
  * Builds the parent report layout. This doesn't have any data yet. This is your template.
  * 
  * @return the layout
  */
 public static DynamicReport buildParentReportLayout() throws ColumnBuilderException, ClassNotFoundException {
  
  // Create an instance of FastReportBuilder
  FastReportBuilder parentReportBuilder = new FastReportBuilder();
 
         // Disables pagination
  parentReportBuilder.setIgnorePagination(true)
  
        // Experiment with this numbers to see the effect
        .setMargins(0, 0, 0, 0) 
        
        // This is a critical property!!!
        // If the parentReportBuilder doesn't have an associated datasource, you must set this property!
        .setWhenNoDataAllSectionNoDetail()
        
  // Set to true if you want to constrain your report within the page boundaries
  // For longer reports, set it to false
        .setUseFullPageWidth(true);

  // Set the name of the file
        parentReportBuilder.setReportName("Concat Report");
       
        // Create a child report
  try {
   // Add the dynamicreport to the parent report layout
   parentReportBuilder.addConcatenatedReport(buildChildReportLayout(), 
     new ClassicLayoutManager(), "dynamicReportDs",
           DJConstants.DATA_SOURCE_ORIGIN_PARAMETER,
           DJConstants.DATA_SOURCE_TYPE_COLLECTION);
   
  } catch (DJBuilderException e) {
   logger.error("Unable to concat child report");
   throw new RuntimeException("Unable to concat child report");
  }
  
        // Build the parent report layout. It doesn't have any data yet!
        DynamicReport dr = parentReportBuilder.build();
        
        // Return the layout
        return dr;
 }
 
 
}
The Layouter has two methods buildParentReportLayout() and buildChildReportLayout() for building the parent and the child layout respectively.

To attach the child report to the parent report, we just directly add the DynamicReport
// Add the dynamicreport to the parent report layout
parentReportBuilder.addConcatenatedReport(buildChildReportLayout(), 
  new ClassicLayoutManager(), "dynamicReportDs",
  DJConstants.DATA_SOURCE_ORIGIN_PARAMETER,
  DJConstants.DATA_SOURCE_TYPE_COLLECTION);
Take note. This is one of the ways you can add a child report in DynamicJasper. You can also add the child report as a Subreport. See the Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport tutorial.

The Exporter

The purpose of the Exporter is to export the JasperPrint object into different formats, like Excel, PDF, and CSV. Our current implementation exports the document as an Excel format.

Exporter.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import org.apache.log4j.Logger;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRXlsAbstractExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsExporter;

/**
 * Everything under the package org.krams.tutorial.jasper are settings imposed by Jasper (not DynamicJasper)
 * <p>
 * An exporter for exporting the report in various formats, i.e Excel, PDF, CSV. Here we declare a PDF exporter
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Exporter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Exports a report to XLS (Excel) format. You can declare another export here, i.e PDF or CSV.
  * You don't really need to create a separate class or method for the exporter. You can call it
  * directly within your Service or Controller.
  * 
  * @param jp the JasperPrint object
  * @param baos the ByteArrayOutputStream where the report will be written
  */
 public static void exportToXLS(JasperPrint jp, ByteArrayOutputStream baos) throws JRException {
  // Create a JRXlsExporter instance
  JRXlsExporter exporter = new JRXlsExporter();
  
  // Here we assign the parameters jp and baos to the exporter
  exporter.setParameter(JRExporterParameter.JASPER_PRINT, jp);
        exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
  
        // Excel specific parameters
        // Check the Jasper (not DynamicJasper) docs for a description of these settings. Most are 
        // self-documenting
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);
        
        // Retrieve the exported report in XLS format
        exporter.exportReport();
 }
}
It's worth mentioning that this class has nothing to do with DynamicJasper. Everything is Jasper-related configuration.

The Writer

The purpose of the Writer is to write the "exported" document to the output stream. Once the document has been written to the stream, the user will receive the document ready to be downloaded.

Writer.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

/**
 * Writes the report to the output stream
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Writer {

 private static Logger logger = Logger.getLogger("service");
 /**
  * Writes the report to the output stream
  */
 public static void write(HttpServletResponse response, ByteArrayOutputStream baos) {
  
  logger.debug("Writing report to the stream");
  try {
   // Retrieve the output stream
   ServletOutputStream outputStream = response.getOutputStream();
   // Write to the output stream
   baos.writeTo(outputStream);
   // Flush the stream
   outputStream.flush();

  } catch (Exception e) {
   logger.error("Unable to write report to the output stream");
  }
 }
}

Controller

We've completed the domain and service layer of the application. Since we're developing a Spring MVC web application, we're required to declare a controller that will handle the user's request.

DownloadController.java
package org.krams.tutorial.controller;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRException;
import org.apache.log4j.Logger;
import org.krams.tutorial.service.DownloadService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;

/**
 * Handles download requests
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Controller
@RequestMapping("/download")
public class DownloadController {

 private static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="downloadService")
 private DownloadService downloadService;

 /**
  * Downloads the report as an Excel format. 
  * 

* Make sure this method doesn't return any model. Otherwise, you'll get * an "IllegalStateException: getOutputStream() has already been called for this response" */ @RequestMapping(value = "/xls", method = RequestMethod.GET) public void getXLS(HttpServletResponse response, Model model) throws ColumnBuilderException, ClassNotFoundException, JRException { logger.debug("Received request to download report as an XLS"); // Delegate to downloadService. Make sure to pass an instance of HttpServletResponse downloadService.downloadXLS(response); } }

DownloadController is a simple controller that handles download requests. It delegates report generation to the DownloadService. Notice we're required to pass the HttpServletResponse to the service.

Database Configuration

We've completed the MVC module of the application. However we haven't created yet the Hibernate configuration and the MySQL database.

Our first task is to create an empty MySQL database.

Here are the steps:
1. Run MySQL
2. Open MySQL admin
3. Create a new database mydb

In this tutorial I've setup a local MySQL database and used phpmyadmin to administer it.

Next, we'll be declaring a hibernate-context.xml configuration file. Its purpose is to contain all of Spring-related configuration for Hibernate.

hibernate-context.xml

This configuration requires two external configurations further:

spring.properties
# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc:mysql://localhost/mydb
app.jdbc.username=root
app.jdbc.password=

#hibernate properties
hibernate.config=/WEB-INF/hibernate.cfg.xml

hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  
<hibernate-configuration>
 <session-factory>
  <!-- We're using a MySQL database so the dialect needs to be MySQL as well -->
  <!-- Also we want to use MySQL's InnoDB engine -->
  <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
  
  <!-- Enable this to see the Hibernate generated SQL statements in the logs -->
  <property name="show_sql">false</property>
  
  <!-- Setting this to 'create' will drop our existing database and re-create a new one.
    This is only good for testing. In production, this is a bad idea! -->
  <property name="hbm2ddl.auto">create</property>
 </session-factory>
</hibernate-configuration>

The Import.SQL

After declaring all the Hibernate-related configuration, let's now declare a SQL script that will populate our database with a sample data automatically.

import.sql
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Corsair', 'CMPSU-750TX', '750W', '109.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Antec', 'NEO ECO 620C', '620W', '69.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'OCZ', 'OCZ700MXSP', '700W', '89.99', '0.86')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Thermaltake', 'W0070RUC', '430W', '43.99', '0.65')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'COOLER MASTER', 'RS-460-PSAR-J3', '460W', '29.99', '0.70')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Rosewill', 'RG530-S12', '530W', '54.99', '0.80')
Make sure to place this document under the classpath. Hibernate will automatically import the contents of this document everytime your start the application. This is dictated by the hbm2ddl.auto setting we declared in the hibernate.cfg.xml earlier.

We're not required to create this import.sql file. We could of course create a MySQL SQL script and import it directly to the database, or add the data manually in the database. I just believe this is convenient for development purposes.

Spring MVC Configuration

We've declared all the necessary classes and Hibernate-related configuration of the application. However, we haven't created yet the required Spring MVC configuration.

Let's begin with the web.xml
web.xml
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/krams/*</url-pattern>
 </servlet-mapping>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml.

spring-servlet.xml
<!-- Declare a view resolver for resolving JSPs -->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
      p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
By convention, we must declare an applicationContext.xml as well.

applicationContext.xml
<!-- Activates various annotations to be detected in bean classes -->
 <context:annotation-config />
 
 <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
  For example @Controller and @Service. Make sure to set the correct base-package-->
 <context:component-scan base-package="org.krams.tutorial" />
 
 <!-- Configures the annotation-driven Spring MVC Controller programming model.
 Note that, with Spring 3.0, this tag works in Servlet MVC only!  -->
 <mvc:annotation-driven /> 
 
 <!-- Loads Hibernate related configuration -->
 <import resource="hibernate-context.xml" />

Run the Application

We've completed the application. Our last task is to run the application and download the report.

To run the application, open your browser and enter the following URL:
http://localhost:8080/spring-djasper-hibernate-concatsr/krams/download/xls
This will automatically download the report document. Again, here's the final screenshot of the document:

Conclusion

That's it. We've managed to build a simple Spring MVC 3 application with reporting capabilities. We used DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. Lastly, we used Jasper's JRDataSource to wrap our Java List and used the wrapper object as the data source. Remember the data is still retrieved from a MySQL database.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-dynamicjasper-integration-tutorial/

You can download the project as a Maven build. Look for the spring-djasper-hibernate-concatdr.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share