One Java Service POJO for AMF/XML/JSON with Spring BlazeDS & Jersey JAX-RS


Introduction

I was reading Christophe Coenraets’ blog post on RESTful services with jQuery and Java using JAX-RS and Jersey and it got me thinking…in order to serve technology agnostic Java Service POJOs to HTML5/JavaScript clients as well as Flex/AIR/Flash clients without writing two service layers and a whole bunch of extra code, why not just put Jersey’s JAX-RS framework next to Spring BlazeDS framework?

Tutorial Goal: Create one Java Service POJO capable of handling RESTful services with XML/JSON over HTTP as well as AMF over HTTP (for RemoteObjects with the Flash Platform) and demonstrate this via a Sencha ExtJS (JavaScript) app and Apache Flex app.

NOTE: I could’ve used Spring’s owen REST framework, but I found Jersey easier than Spring’s 3.02 impl that I’m using and Christophe already banged this out in Jersey so I really just needed to add the Flex part with Spring BlazeDS — big thanks to @ccoenraets for kicking this post off for me! …the point is you can use either Spring or Jersey’s REST impl.

The following is a quick example of a simple Java CRUD Service for beers (because, well, I like beers — Untappd) that serves both HTML5/JavaScript clients via XML/JSON over HTTP as well as Flex clients via AMF over HTTP using Spring BlazeDS.

Download Sample Code

Assumptions

  • This post assumes you know what RESTful Services are and have read RESTful services with jQuery and Java using JAX-RS and Jersey so you’re familiar with Jersey.
  • This post assumes you’re familiar with Flex and using BlazeDS for RemoteObject calls with the Flash Platform.
  • This post assumes you’re familiar with JavaScript and/or Sencha ExtJS (a JavaScript framework similar to Flex with a robust UI library, services, and prescribed MVC architecture).
  • This example uses a Tomcat 7 server running on http://localhost:8080/SpringBlazeDSJerseyServer and will refer to that URL throughout the post.

How’s It Work At A High Level?
Both Spring BlazeDS and Jersey work off of the same simple principle of servlet filtering which is configured in web.xml and explained in detail further down in the article — in our example the following mappings take care of everything

Spring BlazeDS = http://localhost:8080/SpringBlazeDSJerseyServer/messagebroker/*

Jersey = http://localhost:8080/SpringBlazeDSJerseyServer/rest/*

Bottom line, these frameworks sit next to each other in the servlet container and do not interfere with each other or use each other in any way. These simply share the same Java Service so you’re not coding one for Flex and one for HTML/JavaScript.

One Java Service to Rule Them All

Let’s take a quick peak at the Java Service BeerService and see what’s so special about it. The key to exposing this service to both BlazeDS and Jersey is in the annotations; review the comments above the class to see what each does.

package com.webappsolution.springbdsjersey.service.impl;

/**
 * <b>Spring BlazeDS Annotations</b>
 * <p>
 * The annotation Service is used to tell Spring this object is a service bean.
 * The annotation RemotingDestination is used to expose it as a Flex Remoting destination.
 * The annotation RemotingInclude above individual methods is used to expose public methods of the service to Flex
 * as opposed to RemotingExclude which hides them from a Flex client.
 * </p>
 *
 * <b>Jersey JAX-RS Annotations</b>
 * <p>
 * The annotation Path is used to define the base URL pattern (after the app server's context) to map to this RESTful Jersey service.
 * The annotation GET above individual methods is used to define the HTTP action/verb associated with the service method.
 * The annotation Produces above individual methods is used to define the request and response types for the service method.
 * The annotation GET Path("{id}") above individual methods is used to add specific method parameters for the service method via REST path.
 * </p>
 */
@Service("beerService")
@RemotingDestination
@Path("/beer")
public class BeerService implements IBeerService
{
	private static Log logger = LogFactory.getLog(BeerService.class);

	BeerDAO dao = new BeerDAO();

	@Override
	@RemotingInclude // BDS
	@GET // JERSEY
	@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) // JERSEY
	public List<BeerDTO> findAll()
	{
		logger.debug("findAll");
		return dao.findAll();
	}

	@Override
	@RemotingInclude // BDS
	@GET @Path("{id}") // JERSEY
	@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) // JERSEY
	public BeerDTO findById(@PathParam("id") String id)
	{
		logger.debug("findById: " + id);
		return dao.findById(Integer.parseInt(id));
	}

	@Override
	public BeerDTO create()
	{
		logger.debug("create");
		return null;
	}

	@Override
	public BeerDTO update()
	{
		logger.debug("update");
		return null;
	}

	@Override
	public void delete()
	{
		logger.debug("delete");
	}

}

Next we’ll need to add an annotation to our simple BeerDTO so Jersey knows how to convert it to XML or JSON; take note of the annotation XMLRootElement:

@XmlRootElement
public class BeerDTO
{
	private int id;
	private String name;
	private String brewery;

	public BeerDTO()
	{
		// TODO Auto-generated constructor stub
	}

	public int getId()
	{
		return id;
	}

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

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public void setBrewery(String brewery)
	{
		this.brewery = brewery;
	}

	public String getBrewery()
	{
		return brewery;
	}
}

This has no effect on our DTO when being used by Spring BlazeDS.

After that the other big piece to getting this thing cranking is the required JARs (which I packaged up in the WAR so you can play easily) and checking out the web descriptor for the app (web.xml) as it maps incoming servlet requests for Jersey and BlazeDS appropriately.

web.xml - Spring BlazeDS config

<!-- ================================================= -->
<!-- SPRING BLAZEDS PROJECT SETUP -->
<!-- ================================================= -->
<!--	This will configure the BlazeDS message broker as a Spring-managed bean using the simple message-broker tag.
		This will bootstrap the BlazeDS message broker. When you use the message-broker tag without mapping child elements,
		all incoming DispatcherServlet requests are mapped to the MessageBroker.
		You can add mapping child elements if you need more control.
		Any requests hitting the URL http://SpringBlazeDSJerseyServer/messagebroker/* will get mapped to Spring BlazeDS.
                The component scanning for Spring BlazeDS services is done in app-context-flex.xml.
-->
<!-- ================================================= -->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		classpath:/spring/config/applicationContext.xml
	</param-value>
</context-param>

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
	<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>
<servlet>
	<servlet-name>flexspring</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value></param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>flexspring</servlet-name>
	<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>

web.xml - Jersey config

<!-- ================================================= -->
<!-- JERSEY -->
<!-- ================================================= -->
<!--	Configure Jersey JAX-RS to handle RESTful services
		Any requests hitting the URL http://SpringBlazeDSJerseyServer/rest/*
		will get mapped to Jersey. The param "com.sun.jersey.config.property.packages"
		with a value of "com.webappsolution.springbdsjersey.service" tells Jersey
		the package to scan for Jersey enabled services. You can add more than 1.
-->
<!-- ================================================= -->
<servlet>
	<servlet-name>Jersey</servlet-name>
	<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
	<init-param>
		<param-name>com.sun.jersey.config.property.packages</param-name>
		<param-value>com.webappsolution.springbdsjersey.service</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>Jersey</servlet-name>
	<url-pattern>/rest/*</url-pattern>
</servlet-mapping>

Quick HTTP REST Test
Start the Server and hit the following URL: http://localhost:8080/SpringBlazeDSJerseyServer/rest/beer and you should see an XML list of Beers. This is good and means you’re ready to test the Flex side.

As Christophe points out, you can also use the cURL command in terminal to see the results:

  • Get All Beers: curl -i -X GET http://localhost:8080/SpringBlazeDSJerseyServer/rest/beer
  • Get Beer By ID: curl -i -X GET http://localhost:8080/SpringBlazeDSJerseyServer/rest/beer/1
  • Get All Beers - XML: curl -i -X GET http://localhost:8080/SpringBlazeDSJerseyServer/rest/beer -H ‘Accept:application/xml’
  • Get All Beers - JSON: curl -i -X GET http://localhost:8080/SpringBlazeDSJerseyServer/rest/beer -H ‘Accept:application/json’

Flex RemoteObject Beer Service Example
Our simple Flex example has a button that invokes the RemoteObject BeerService.findAll() method and uses the ArrayCollection response to populate a DataGrid.

protected var beerService:RemoteObject;

protected function init(event:FlexEvent):void
{
	this.beerService = new RemoteObject("beerService");
	this.beerService.endpoint = "http://localhost:8080/SpringBlazeDSJerseyServer/messagebroker/amf";
	this.beerService.addEventListener(ResultEvent.RESULT, onBeerServiceResult);
	this.beerService.addEventListener(FaultEvent.FAULT, onBeerServiceRFault);
}

protected function onBeerServiceBtnClick(event:MouseEvent):void
{
	this.beerService.findAll();
}

protected function onBeerServiceResult(event:ResultEvent):void
{
	this.beerDG.dataProvider = event.result as ArrayCollection;
}

protected function onBeerServiceRFault(event:FaultEvent):void
{
	Alert.show("Beer Service Error = " + event.fault.faultDetail, "Error");
}

Assuming your server is still running, run the Flex app and click the “Get Beers!” button and watch the DataGrid populate with some solid brews. Your Flex app should look like:

Sencha ExtJS RESTful Beer Service Example
Similarly, our simple ExtJS example has a button that invokes the RESTful service method BeerService.findAll() via a Proxy within a store that requests JSON as the data format– the response is also mapped back to the Grid.

First, let’s define the matching Beer Model on the client — this matches our BeerDTO on the server and is similar to how we’d mapped Flex ActionScript objects to the same Java BeerDTO.

Ext.define('Beer', {
	extend: 'Ext.data.Model',
	fields:
		[ 'id', 'name', 'brewery' ],
});

Next we’ll create a Store that uses a REST proxy to interact with the Jersey Services and is responsible for mapping the Beer Model we just created to the JOSN request and responses from Jersey. There’s some additional CRUD code that isn’t actually being used in this post but will in the future.

var store = Ext.create('Ext.data.Store', {
    autoLoad: false,
    autoSync: true,
    model: 'Beer',
    proxy: {
    	headers: {
            'accept': 'application/json'
        },
        type: 'rest',
        url: 'http://localhost:8080/SpringBlazeDSJerseyServer/rest/beer',
        reader: {
            type: 'json',
            root: 'beerDTO'
        },
        writer: {
            type: 'json'
        }
    },
    listeners: {
        write: function(store, operation){
            var record = operation.getRecords()[0],
                name = Ext.String.capitalize(operation.action),
                verb;

            if (name == 'Destroy') {
                record = operation.records[0];
                verb = 'Destroyed';
            } else {
                verb = name + 'd';
            }
            Ext.example.msg(name, Ext.String.format("{0} user: {1}", verb, record.getId()));

        }
    }
});

Finally we’ll create our Grid and Button to invoke the service:

var grid = Ext.create('Ext.grid.Panel', {
    renderTo: document.body,
    plugins: [rowEditing],
    width: 500,
    height: 300,
    frame: true,
    title: 'Beers',
    store: store,
    iconCls: 'icon-beer',
    columns: [{
        text: 'ID',
        width: 40,
        sortable: true,
        dataIndex: 'id'
    }, {
        text: 'Name',
        flex: 1,
        sortable: true,
        dataIndex: 'name',
        field: {
            xtype: 'textfield'
        }
    }, {
        header: 'Brewery',
        flex: 1,
        sortable: true,
        dataIndex: 'brewery',
        field: {
            xtype: 'textfield'
        }
    }]
});

Ext.create('Ext.Button', {
    text: 'Get Beers!',
    renderTo: Ext.getBody(),
    handler: function() {
    	store.load();
    }
});

Assuming you’re server is still running, run the ExtJS app by hitting the URL http://localhost:8080/SpringBlazeDSJerseyServer/extjs/springblazedsjersey/restful.html and click the “Get Beers!” button and watch the DataGrid populate with some solid brews. Your ExtJS app should look like:

Any questions?

Post to Twitter Tweet This Post

  1. #1 by Rob - March 23rd, 2012 at 10:27

    I have been clicking the Get Beers button for an hour now, and I never get any actual beers. All I get is a webpage with the names of beers ;-)

  2. #2 by brianr - March 23rd, 2012 at 16:22

    @Rob Well played sir!

  3. #3 by mike - August 3rd, 2012 at 20:05

    hi would it be possible publish and subscribe to a feed in the way mx.messaging.* and mx.rpc.* classes handle it in flash?

(will not be published)
  1. No trackbacks yet.