Aloha from Hawaii! Shame on me to not blog for months. Well, honestly I have been quite busy during the past few months. With the big move to Hawaii in December and the recent launch of Floify kept me occupied. Although moving to a new place is fun, getting settled on an island can sometimes be more stressful. Now all that is taken care, there is no good reason to not blog from rainbow nation 🙂

Recently, I was working with an evaluator implementing a proof of concept where they had a requirement for scheduling workflows from a web service request. In Flux, this can be easily implemented as a trigger which typically waits for an event to occur. Flux supports popular triggers out of the box, which are either based on timer or database condition or file or audit trail or mail. While ago, I implemented a custom web service trigger which supports both SOAP and HTTP requests and expedites the workflow to the subsequent step. The sample testcase below shows how web service trigger can be implemented in your Flux workflows.

The sample client code that expedites the trigger is shown below.

With this plugin, your workflows can be designed to trigger based on web service requests and it also allows users to configure an embedded web server that runs part of the workflow. If this were one shot workflows, this nicely fits the bill. If the requirement were to support a recurring workflow, I would classify this approach as heavy weight mainly because it spins a web server for each run of your workflow which may not be ideal for high performance workflows. It makes more sense to reuse a single web server instance that accepts requests and triggers a workflow template from the repository. I do not believe this can be easily supported without making some core changes in Flux. But, it is not the end of the world, there is a more efficient way to implement this in Flux today by using Flux Cluster APIs.

Flux 7.11 Operations Console exposes set of APIs that allows clients to talk to Flux engine cluster via simple HTTP interface. In essence, Flux Opsconsole acts as a web service trigger for the cluster. You can find the documentation of these APIs here. The API that schedules a workflow template from repository will be available in 7.11.4. If you would like to try out this, you can request a 7.11.4 build from Flux support, we would be happy to send one on your way. The Operations Console deploys HTTP resources that can be accessed in a number of ways. If you were a Java shop, you would consider something similar shown in the Gist below. This sample uses Jersey APIs to POST a multipart request to the endpoint. You just need to toss in restaction.jar which is part of the Flux distribution.

In this example, we add three properties to a multipart request. The first one “template” is required and specifies the name of the template in the repository. The “category” and “rate” are optional variables that will be made available in your workflow instance. You can add as many data points that you would like to pass on to your workflow instance. You can also optionally customize the name of the workflow instance that you would like to spin off from the repository by setting the “namespace” property in the request.

There is another API that might interest you as well. This API schedules a given flow chart to the engine. The major difference here is your are exporting a workflow file to the engine instead of spinning an instance from existing repository template.

Enjoy developing in Flux and Mahalo for stopping by!

Possibly Related Posts:


Flux’s RestAction is the go-to construct for users orchestrating Flux workflows that involve web service integration. Be it HTTP-based services or SOAP-based services that implement HTTP binding, RestAction comes very handy for developers. Flux has a WebServiceAction which talks SOAP exclusively for services that implement WS-* such as Addressing, Security etc. RestAction has neat XPath integration, which allows users to navigate the response using XPath query and bind the result to domain model that can be seamlessly used in your workflow context.

In this post, let us look at a simple Weather Web Service which supports both SOAP and HTTP binding. I will be using Eric’s simple test framework to demonstrate the use of RestAction and WebServiceAction in your Flux workflow.

Let us see how to invoke the Weather web service using the traditional WebServiceAction that uses the WSDL. We pass the zip code as a parameter to GetCityForecastByZIP operation and the response is handled using a JavaAction. In this case the response is bound to ForecastReturn complex type.

package flux.test;

import com.cdyne.ws.weatherws.Forecast;
import com.cdyne.ws.weatherws.ForecastReturn;
import flux.*;
import org.junit.Test;

import java.net.URL;
import java.util.Properties;

public class WeatherWebServicesTest extends AbstractFluxTest {

 @Test
 public void testWebServiceAction() throws Exception {
   log.info("[START] FluxTest.testWebServiceAction");
   String namespace = "/FluxFlowChart";

   FlowChart flowChart = engineHelper.makeFlowChart(namespace);

   WebServiceAction webServiceAction = flowChart.makeWebService("GET Weather Info");

   // Specify the WSDL the web service action is to use.
   webServiceAction.setWsdl(new URL("http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL"));

   // Specify the signature of the method to call.
   webServiceAction.setListenerSignature("GetCityForecastByZIP(String)");

   // Specify the arguments to pass to the method.
   webServiceAction.setArgument(0, "59102");

   JavaAction javaAction = flowChart.makeJavaAction("Java Action");
   javaAction.setListener(WebServiceActionListener.class);

   webServiceAction.addFlow(javaAction);

   engine.put(flowChart);

   waitForRuns(namespace, 1, 5, 30);

   log.info("[END] FluxTest.testWebServiceAction");
 }
}

The ActionListener implementation that processes the ForecastReturn from WebServiceAction.

Let us invoke the same operation using RestAction, which uses the HTTP binding. Here we set the query parameter “ZIP” and bind the response to ForecastReturn POJO (generated using: xjc -wsdl http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl).

  @Test
  public void testRestActionGET() throws Exception {
    log.info("[START] FluxTest.testRestActionGET");
    String namespace = "/FluxFlowChart";

    FlowChart flowChart = engineHelper.makeFlowChart(namespace);

    RestAction restAction = flowChart.makeRestAction("GET Weather Info");

    // Specify the URL the rest action is to use.
    restAction.setBaseUrl(new URL("http://wsf.cdyne.com/WeatherWS/Weather.asmx/GetCityForecastByZIP"));

    // Specify the Action type.
    restAction.setActionType(RestActionType.GET);

    // Specify the Response type.
    restAction.setResponseType(ForecastReturn.class);

    // Specify the query parameters to be sent as part of the request.
    Properties queryParams = new Properties();
    queryParams.put("ZIP", "59102");

    restAction.setQueryParameters(queryParams);

    JavaAction javaAction = flowChart.makeJavaAction("Java Action");
    javaAction.setListener(RestActionListener.class);

    restAction.addFlow(javaAction);

    engine.put(flowChart);

    waitForRuns(namespace, 1, 5, 30);

    log.info("[END] FluxTest.testRestActionGET");
  }

In this example, we are performing an XPath query that returns an array of Forecast elements.

  @Test
  public void testRestActionXPathArray() throws Exception {
    log.info("[START] FluxTest.testRestActionXPathArray");
    String namespace = "/FluxFlowChart";

    FlowChart flowChart = engineHelper.makeFlowChart(namespace);

    RestAction restAction = flowChart.makeRestAction("GET Weather Info");

    // Specify the URL the rest action is to use.
    restAction.setBaseUrl(new URL("http://wsf.cdyne.com/WeatherWS/Weather.asmx/GetCityForecastByZIP"));

    // Specify the Action type.
    restAction.setActionType(RestActionType.GET);

    // Specify the query parameters to be sent as part of the request.
    Properties queryParams = new Properties();
    queryParams.put("ZIP", "59102");

    restAction.setQueryParameters(queryParams);

    // Specify the XML namespace, if one defined
    Properties namespaces = new Properties();
    namespaces.put("ws", "http://ws.cdyne.com/WeatherWS/");
    restAction.setNamespaces(namespaces);

    // Specify the XPath expression using the defined namespace
    restAction.setXpathExpression("/ws:ForecastReturn/ws:ForecastResult/ws:Forecast");// this xpath returns an array of Forecast elements

    // Specify the XPath response type.
    restAction.setXpathResponseType(Forecast[].class);

    JavaAction javaAction = flowChart.makeJavaAction("Java Action");
    javaAction.setListener(RestActionListener.class);

    restAction.addFlow(javaAction);

    engine.put(flowChart);

    waitForRuns(namespace, 1, 5, 30);

    log.info("[END] FluxTest.testRestActionXPathArray");
  }

In this example, we are performing an XPath query that returns the first Forecast element from the array.

  @Test
  public void testRestActionXPathPOJO() throws Exception {
    log.info("[START] FluxTest.testRestActionXPathPOJO");
    String namespace = "/FluxFlowChart";

    FlowChart flowChart = engineHelper.makeFlowChart(namespace);

    RestAction restAction = flowChart.makeRestAction("GET Weather Info");

    // Specify the URL the rest action is to use.
    restAction.setBaseUrl(new URL("http://wsf.cdyne.com/WeatherWS/Weather.asmx/GetCityForecastByZIP"));

    // Specify the Action type.
    restAction.setActionType(RestActionType.GET);

    // Specify the query parameters to be sent as part of the request.
    Properties queryParams = new Properties();
    queryParams.put("ZIP", "59102");

    restAction.setQueryParameters(queryParams);

    // Specify the XML namespace, if one defined
    Properties namespaces = new Properties();
    namespaces.put("ws", "http://ws.cdyne.com/WeatherWS/");
    restAction.setNamespaces(namespaces);

    // Specify the XPath expression using the defined namespace
    restAction.setXpathExpression("/ws:ForecastReturn/ws:ForecastResult/ws:Forecast[1]");// this xpath returns the first element from the list

    // Specify the XPath response type.
    restAction.setXpathResponseType(Forecast.class);

    JavaAction javaAction = flowChart.makeJavaAction("Java Action");
    javaAction.setListener(RestActionListener.class);

    restAction.addFlow(javaAction);

    engine.put(flowChart);

    waitForRuns(namespace, 1, 5, 30);

    log.info("[END] FluxTest.testRestActionXPathPOJO");
  }

In this example, we are performing an XPath query that returns an attribute of ForecastReturn element.

  @Test
  public void testRestActionXPathAttribute() throws Exception {
    log.info("[START] FluxTest.testRestActionXPathAttribute");
    String namespace = "/FluxFlowChart";

    FlowChart flowChart = engineHelper.makeFlowChart(namespace);

    RestAction restAction = flowChart.makeRestAction("GET Weather Info");

    // Specify the URL the rest action is to use.
    restAction.setBaseUrl(new URL("http://wsf.cdyne.com/WeatherWS/Weather.asmx/GetCityForecastByZIP"));

    // Specify the Action type.
    restAction.setActionType(RestActionType.GET);

    // Specify the query parameters to be sent as part of the request.
    Properties queryParams = new Properties();
    queryParams.put("ZIP", "59102");

    restAction.setQueryParameters(queryParams);

    // Specify the XML namespace, if one defined
    Properties namespaces = new Properties();
    namespaces.put("ws", "http://ws.cdyne.com/WeatherWS/");
    restAction.setNamespaces(namespaces);

    // Specify the XPath expression using the defined namespace
    restAction.setXpathExpression("/ws:ForecastReturn/ws:City/text()");// this xpath returns the name of the city

    // Specify the XPath response type.
    restAction.setXpathResponseType(String.class);

    JavaAction javaAction = flowChart.makeJavaAction("Java Action");
    javaAction.setListener(RestActionListener.class);

    restAction.addFlow(javaAction);

    engine.put(flowChart);

    waitForRuns(namespace, 1, 5, 30);

    log.info("[END] FluxTest.testRestActionXPathAttribute");
  }

The ActionListener implementation that processes various results from the RestAction is shown below.

Here is a screenshot of the test output from Intellij.

You need to toss in the following jars from Flux distribution:

flux.jar
lib/restaction.jar
lib/webserviceaction.jar
lib/commons-logging-1.1.1.jar
examples/software_developers/web_services/lib/weatherws-generated.jar

Test dependencies include:

log4j-1.2.15.jar
junit-4.9.jar


Download the latest Flux 7.11 version here and check it out. The samples are available in flux-goodies bitbucket repository. You can also find more samples ($FLUX_HOME/examples/software_developers/web_services and $FLUX_HOME/examples/software_developers/rest_services) in the latest Flux 7.11.2 distribution. Enjoy!

Possibly Related Posts:


There are two features that are very cool in the recent CXF 2.3 release that deserves its mention in the release notes/features document as they prove to be quite useful and powerful in certain use cases. Advanced search capabilities and Atom Logging features make CXF a compelling choice for developers looking for this support in JAX-RS frameworks. I first read about these features when Sergey introduced their availability in 2.3 snapshots.

In this post, I am going to explore FIQL based advanced search query support in CXF. I have experimented Atom logger before and it deserves a separate blog post and when used with FIQL, they are more powerful.

Introduction to FIQL Expressions in CXF

The Feed Item Query Language (FIQL, pronounced “fickle”) is a draft specification by Mark Nottingham that proposes URL-friendly syntax for expressing filters that operate on the feed entries. An FIQL expression is composed of one or more constraints, related to each other with Boolean operators. FIQL expressions yield Boolean values: True or False.

Let us look at each of the operators defined in the draft spec with an example FIQL expression and the equivalent SQL query produced by CXF.

Two boolean operators:
“;” is the Boolean AND operator; it yields True for a particular entry if both operands evaluate to True, otherwise False.

/sakila/searchActors?_s=firstname==PENELOPE;lastname==GUINESS
SELECT * FROM actor WHERE lastname = 'GUINESS' AND firstname = 'PENELOPE'

“,” is the Boolean OR operator; it yields True if either operand evaluates to True, otherwise False.

/sakila/searchActors?_s=lastname==MONROE,lastname==GUINESS
SELECT * FROM actor WHERE (lastname = 'MONROE') OR (lastname = 'GUINESS')

Two comparison operators are applicable to simple text comparisons:
“==” yields True if the string value (as per XPath) of any selected node matches the argument; otherwise False.

/sakila/searchActors?_s=lastname==MONROE
SELECT * FROM actor WHERE lastname = 'MONROE'

“!=” yields True if the string value of every selected node does not match the argument; otherwise False.

/sakila/searchActors?_s=lastname!=MONROE
SELECT * FROM actor WHERE lastname <> 'MONROE'

If the argument string begins or ends with an asterisk character (“*”), it acts as a wild card, matching any characters preceding or following (respectively) that position.

/sakila/searchActors?_s=lastname==PEN*
SELECT * FROM actor WHERE firstname LIKE 'PEN%'

[Note: The spec mentions that text comparisons should allow case insensitive textual content. CXF text comparison is case sensitive.]

Four operators are relevant to date comparisons:
“==” yields True if the point in time specified in the argument matches that indicated by the string-value of the selected node; otherwise False.

/sakila/searchRentals?_s=returndate==2005-08-31T20:00:25.000%2B06:00
SELECT * FROM rental WHERE returndate = 'Wed Aug 31 08:00:25 MDT 2005'

[Note: The database contains the following timestamp for the returndate ‘2005-08-31T20:00:25-06:00’, but when I query this using the above FIQL expression, it returned no matching entry. I tried another similar expression with no success: /sakila/searchRentals?_s=returndate==2005-08-31T20:00:25Z]

“!=” yields True if the point in time specified in the argument does not match that indicated by the string-value of the selected node; otherwise False.

/sakila/searchRentals?_s=returndate!=2005-08-31T20:00:25.000%2B06:00
SELECT * FROM rental WHERE returndate <> 'Wed Aug 31 08:00:25 MDT 2005'

“=lt=” yields True if the point in time specified in the argument follows that indicated by the string-value of the selected node; otherwise False.

/sakila/searchRentals?_s=rentaldate=lt=2005-05-27T00:00:00.000%2B00:00
SELECT * FROM rental WHERE rentaldate < 'Thu May 26 18:00:00 MDT 2005'

“=le=” yields True if the point in time specified in the argument follows that indicated by the string-value of the selected node, or is equal to it; otherwise False.

/sakila/searchRentals?_s=returndate=le=2005-05-27T00:00:00.000%2B00:00
SELECT * FROM rental WHERE returndate <= 'Thu May 26 18:00:00 MDT 2005'

“=gt=” yields True if the point in time specified in the argument precedes that indicated by the string-value of the selected node; otherwise False.

/sakila/searchRentals?_s=returndate=gt=2005-08-30T00:00:00.000%2B00:00
SELECT * FROM rental WHERE returndate > 'Mon Aug 29 18:00:00 MDT 2005'

“=ge=” yields True if the point in time specified in the argument precedes that indicated by the string-value of the selected node, or is equal to it; otherwise False.

/sakila/searchRentals?_s=returndate=ge=2005-09-01T00:00:00.000%2B00:00
SELECT * FROM rental WHERE returndate >= 'Wed Aug 31 18:00:00 MDT 2005'

Some advanced FIQL expressions prove to be very powerful when dealing with date range and relative comparison:

/sakila/searchRentals?_s=returndate=gt=%2DP5Y2M
SELECT * FROM rental WHERE returndate > 'Wed Aug 17 14:53:44 MDT 2005'
 (assuming processing on Oct 17th, 2010)

/sakila/searchRentals?_s=returndate=ge=2005-05-27T00:00:00.000%2B00:00;rentaldate=le=2005-06-27T00:00:00.000%2B00:00
SELECT * FROM rental WHERE rentaldate <= 'Sun Jun 26 18:00:00 MDT 2005' AND returndate >= 'Thu May 26 18:00:00 MDT 2005'

Four operators are relevant to numeric comparisons:

“==” yields True if the string-value of the selected node is numerically equal to the argument; otherwise False.

/sakila/searchFilms?_s=filmid==1;rentalduration!=0
SELECT * FROM film WHERE filmid = '1' AND rentalduration <> '0'

“!=” yields True if the string-value of the selected node is not numerically equal to the argument; otherwise False.

/sakila/searchFilms?_s=filmid!=1;rentalduration!=0
SELECT * FROM film WHERE filmid <> '1' AND rentalduration <> '0'

“=lt=” yields True if the string-value of the selected node evaluates as numerically less than the argument; otherwise, False.

/sakila/searchFilms?_s=filmid=lt=2;rentalduration!=0
SELECT * FROM film WHERE filmid < '2' AND rentalduration <> '0'

“=le=” yields True if the string-value of the selected node evaluates as numerically less than the argument, or as equal to it; otherwise, False.

/sakila/searchFilms?_s=filmid=le=2;rentalduration!=0
SELECT * FROM film WHERE filmid <= '2' AND rentalduration <> '0'

“=gt=” yields True if the string-value of the selected node evaluates as numerically greater than the argument; otherwise, False.

/sakila/searchFilms?_s=filmid=gt=995;rentalduration!=0
SELECT * FROM film WHERE filmid > '995' AND rentalduration <> '0'

“=ge=” yields True if the string-value of the selected node evaluates as numerically greater than the argument, or as equal to it; otherwise, False.

/sakila/searchFilms?_s=filmid=ge=995;rentalduration!=0
SELECT * FROM film WHERE filmid >= '995' AND rentalduration <> '0'

I believe this covers majority of the FIQL operators used in various proportions as documented in the spec and these FIQL expressions are not just examples, all of these searches can be tested with our sample Sakila database.

Sakila Database

Sakila is a MySQL sample database that represents a DVD rental store and comes with some useful data. This is handy for demonstrations and the schema is well designed. I will be using this sample database to demonstrate the advanced search capabilities using CXF. Java IDEs provide a decent support to reverse engineer JPA entities from a database schema. Here is one which walks through creating JPA entities using a NetBeans IDE from the Sakila database. I will be using these generated entities in this example with Hibernate as the JPA provider. The entire example is available as a maven project in Git, so feel free to check this out.

The meat of this example lies in the SakilaResource implemented as a CXF JAX-RS endpoint. In this code, a SearchContext is injected to the resource which will be used in the resource methods to get the SearchCondition specified in the query. FiqlParser does the magic for you in parsing an FIQL expression to construct these search conditions.

@Path("/sakila")
public class SakilaResource {

    @Context
    private SearchContext searchContext;

    List<Actor> actors = new ArrayList<Actor>();
    List<Film> films = new ArrayList<Film>();
    List<Rental> rentals = new ArrayList<Rental>();

    public SakilaResource() {
       // JPA Plumbing omitted for clarity.
    }

    @GET
    @Produces("application/xml")
    @Path("searchActors")
    public List<Actor> searchActors() {
        SearchCondition<Actor> sc = searchContext.getCondition(Actor.class);

        if (sc == null) {
            throw new NotFoundException("Invalid search query.");
        }
        System.out.println(sc.toSQL("actor"));

        List<Actor> found = sc.findAll(actors);
        if (found.size() == 0) {
            throw new NotFoundException("No matching actor found.");
        }
        return found;
    }

    @GET
    @Produces("application/xml")
    @Path("searchFilms")
    public List<Film> searchFilms() {
        SearchCondition<Film> sc = searchContext.getCondition(Film.class);

        if (sc == null) {
            throw new NotFoundException("Invalid search query.");
        }
        System.out.println(sc.toSQL("film"));

        List<Film> found = sc.findAll(films);
        if (found.size() == 0) {
            throw new NotFoundException("No matching film found.");
        }
        return found;
    }

    @GET
    @Produces("application/xml")
    @Path("searchRentals")
    public List<Rental> searchRentals() {
        SearchCondition<Rental> sc = searchContext.getCondition(Rental.class);

        if (sc == null) {
            throw new NotFoundException("Invalid search query.");
        }
        System.out.println(sc.toSQL("rental"));

        List<Rental> found = sc.findAll(rentals);
        if (found.size() == 0) {
            throw new NotFoundException("No matching rental found.");
        }
        return found;
    }
}

One can think of FIQL based search support in CXF is somewhat similar to using Hibernate Search to query JPA entities. I think Hibernate Search is a heavy weight solution which uses Lucene as the search provider and it requires annotating your entities. FIQL support in CXF is light weight and implementing advanced search capabilities in resources is a child’s play. Unlike Hibernate Search, CXF FIQL cannot be used to perform joins on multiple entities as the FIQL spec was designed for syndication feed data model.

In CXF, there are couple options to test these search conditions and they are easy to develop. One approach is to use the search extension API. You can find an example in the javadoc of SimpleSearchCondition.

The other approach is the traditional CXF way of using HTTP centric WebClient.

public class SakilaSearchTest {

    static Server server;

    @BeforeClass
    public static void setUp() {
        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
        sf.setResourceClasses(SakilaResource.class);
        sf.getInInterceptors().add(new LoggingInInterceptor());
        sf.getOutInterceptors().add(new LoggingOutInterceptor());
        sf.setResourceProvider(SakilaResource.class, new SingletonResourceProvider(new SakilaResource()));
        sf.setAddress("http://localhost:9000");
        server = sf.create();
    }

    @Test
    public void searchActors() {
        //http://localhost:9000/sakila/searchActors?_s=firstname==PENELOPE
        WebClient wc = WebClient.create("http://localhost:9000/sakila/searchActors?_s=firstname%3D%3DPENELOPE");
        Collection<? extends Actor> actors = wc.getCollection(Actor.class);
        assertEquals(4, actors.size());
    }

    @Test
    public void searchFilms() {
        //http://localhost:9000/sakila/searchFilms?_s=rating==PG;rentalduration!=0;title==SANTA*
        WebClient wc = WebClient.create("http://localhost:9000/sakila/searchFilms?_s=rating%3D%3DPG;rentalduration%21%3D0;title%3D%3DSANTA*");
        Collection<? extends Film> films = wc.getCollection(Film.class);
        assertEquals(1, films.size());
    }

    @Test
    public void searchRentals() {
        //http://localhost:9000/sakila/searchRentals?_s=rentaldate=lt=2005-05-27T00:00:00.000%2B00:00
        WebClient wc = WebClient.create("http://localhost:9000/sakila/searchRentals?_s=rentaldate%3Dlt%3D2005-05-27T00:00:00.000%2B00:00");
        Collection<? extends Rental> rentals = wc.getCollection(Rental.class);
        assertEquals(278, rentals.size());
    }

    @AfterClass
    public static void tearDown() {
        server.destroy();
    }
}

One thing I noticed with WebClient is that I need to encode certain operators specified in the search condition and this is inconsistent when testing with a web browser. I would expect the java API should take care of this for you instead of providing partially encoded URLs. Also, the date comparison can be tricky, although it works for most cases discussed above.

Possibly Related Posts:


JAXB is the defacto OXM binding framework for the Java platform which performs marshalling (serializing Java to XML) and unmarshalling (deserializing XML to Java) using the standard Java APIs. I must admit that JAXB is a smart addition to the Java Web services technologies stack. It reminds me of days where I used to write custom serializers/deserializers using the first generation Web services stack Axis. It used to scare away developers using complex types in their applications because of its complexity. I do agree data binding frameworks has its limitations, but no technology is a “silver-bullet”. When it comes to programming with Web services (whether JAXWS or JAXRS), JAXB is a first-class citizen in every Web services frameworks (Axis2, CXF, Jersey, Metro, RESTEasy, Wink) out there for Java developers.

IDEs have become very smart these days and they provide nice tooling around these technologies, which makes development of applications based on web services, a child’s play. XML schemas have become the natural choice for data modeling, due to its wide adoption in enterprise application integration. While, JAXB fits the bill nicely when programming in Java, it sometimes can be verbose primarily due to the heavy use of its annotations and requiring changes to source code. There may be cases where users would not have access to sources and annotating them is not an option for them. For others, it does not make sense to annotate just for the sake of mapping objects from one format to other. There is good news for these users that do want to use JAXB with out any intrusion to their code.

JAXB framework provides customization options that makes it possible to use it seamlessly without modifying source code and abstracting out those metadata in configuration files.

I am going to discuss about two popular options that helps achieve this goal.
1. MOXy from EclipseLink.
2. JAXBIntroductions from JBoss ESB.

While both these projects specifically address the concern of using annotations on your application model, EclipseLink MOXy provides advanced customizations on marshalling your domain objects. Let us build a simple Jersey application using these options for our vanilla domain model. You guessed it right, its time to dive into our Maven POM.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <name>MOXy/JAXBIntro</name>
    <groupId>jersey-moxy-jaxbintro</groupId>
    <artifactId>jersey-moxy-jaxbintro</artifactId>
    <version>1.0</version>

    <dependencies>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>jboss.jaxbintros</groupId>
            <artifactId>jboss-jaxb-intros</artifactId>
            <version>1.0.2.GA</version>
        </dependency>        
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>1.3</version>
        </dependency>
        <dependency>
            <groupId>com.sun.grizzly</groupId>
            <artifactId>grizzly-servlet-webserver</artifactId>
            <version>1.9.18-i</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>EclipseLink Repo</id>
            <url>http://www.eclipse.org/downloads/download.php?r=1&amp;nf=1&amp;file=/rt/eclipselink/maven.repo</url>
        </repository>
        <repository>
            <id>maven2-repository.dev.java.net</id>
            <name>Java.net Repository for Maven</name>
            <url>http://download.java.net/maven/2/</url>
            <layout>default</layout>
        </repository>
        <repository>
            <id>jboss-public-repository-group</id>
            <name>JBoss Public Maven Repository Group</name>
            <url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
            <layout>default</layout>
            <releases>
                <enabled>true</enabled>
                <updatePolicy>never</updatePolicy>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>never</updatePolicy>
            </snapshots>
        </repository>
    </repositories>
</project>

To keep this example simple, I would like to reuse the entities such as Customer, Address and PhoneNumber used in MOXy examples. They are simple POJOs and do not carry any metadata on them. Here is the Customer POJO (others are omitted for brevity):

public class Customer {

    private String name;
    private Address address;
    private List<PhoneNumber> phoneNumber;

    public Customer() {
        phoneNumber = new ArrayList<PhoneNumber>();
    }

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public List<PhoneNumber> getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(List<PhoneNumber> phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}

The MOXy configuration file(eclipselink-oxm.xml) which defines the metadata for these entities is shown below.

<?xml version="1.0"?>
<xml-bindings
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        version="2.1">
    <java-types>
        <java-type name="com.mycorp.model.Customer">
            <xml-root-element/>
            <xml-type prop-order="name address phoneNumber"/>
            <java-attributes>
                <xml-element java-attribute="name" xml-path="personal-info/name/text()"/>
                <xml-element java-attribute="address" xml-path="contact-info/address"/>
                <xml-element java-attribute="phoneNumber" xml-path="contact-info/phone-number"/>
            </java-attributes>
        </java-type>
        <java-type name="com.mycorp.model.PhoneNumber">
            <xml-root-element/>
            <java-attributes>
                <xml-attribute java-attribute="type"/>
                <xml-value java-attribute="value"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

You may want to reference how these metadata translates to Java annotations as detailed in this wiki example. As you may notice, this configuration is fairly simple to understand and provides more flexibility in terms of how your marshalled XML data may look like.

So far, we have seen this from the MOXy perspective and now let us see how this would be used in a Jersey application. For this, we need to implement a ContextResolver which can be used to instantiate and inject a custom JAXBContext onto the message body writer for our domain model. Here is our implementation of a ContextResolver that uses EclipseLink API JAXBContextFactory to create a JAXBContext which will be used for our marshalling business in Jersey. The configuration file (eclipselink-oxm.xml) dictates how the XML is generated by this marshaller.

import com.mycorp.model.Customer;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import java.util.*;

@Provider
public final class JAXBContextResolverForMOXy implements ContextResolver<JAXBContext> {

    private final JAXBContext context;
    private final Set<Class> types;
    private final Class[] cTypes = {Customer.class};

    public JAXBContextResolverForMOXy() throws Exception {
        this.types = new HashSet(Arrays.asList(cTypes));
        Map<String, Source> metadataSourceMap = new HashMap<String, Source>();
        metadataSourceMap.put("com.mycorp.model", new StreamSource(this.getClass().getResourceAsStream("/eclipselink-oxm.xml")));
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadataSourceMap);
        this.context = JAXBContextFactory.createContext(cTypes, properties);
    }

    public JAXBContext getContext(Class<?> objectType) {
        return (types.contains(objectType)) ? context : null;
    }
}

Now, we have the JAXBContextResolver that can be injected to our MessageBodyWriter to perform marshalling. The Customer MessageBodyWriter is the key which wires Customer objects to an externalized JAXB configuration using the custom marshaller injected by Jersey runtime. Here is the trivial implementation of our Customer MBW.

@Provider
@Produces("application/xml")
public class CustomerMessageBodyWriter implements MessageBodyWriter<Customer> {

    @Context
    ContextResolver<JAXBContext> contextResolver;

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return Customer.class.isAssignableFrom(type);
    }

    @Override
    public long getSize(Customer customer, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(Customer customer, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
        JAXBContext context = contextResolver.getContext(type);
        try {
            context.createMarshaller().marshal(customer, entityStream);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}

Now, it is time to wire all these parts together to see the magic. Here is our simple CustomerResource that just returns a Customer representation.

@Path("customers")
@Produces("application/xml")
public class CustomerResource {

    @GET
    public Customer getCustomer() {
        Customer customer = new Customer();
        customer.setName("Jane Doe");

        Address address = new Address();
        address.setStreet("123 Any Street");
        address.setCity("My Town");
        customer.setAddress(address);

        PhoneNumber workPhoneNumber = new PhoneNumber();
        workPhoneNumber.setType("work");
        workPhoneNumber.setValue("613-555-1111");
        customer.getPhoneNumber().add(workPhoneNumber);

        PhoneNumber cellPhoneNumber = new PhoneNumber();
        cellPhoneNumber.setType("cell");
        cellPhoneNumber.setValue("613-555-2222");
        customer.getPhoneNumber().add(cellPhoneNumber);

        return customer;
    }
}

Accessing the URL, http://localhost:9998/jaxb/customers, produces the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <personal-info>
        <name>Jane Doe</name>
    </personal-info>
    <contact-info>
        <address>
            <city>My Town</city>
            <street>123 Any Street</street>
        </address>
        <phone-number type="work">613-555-1111</phone-number>
        <phone-number type="cell">613-555-2222</phone-number>
    </contact-info>
</customer>

Let us do a similar exercise using JAXBIntroductions which follows a similar pattern. The configuration file(intro-config.xml) of JAXBIntros is shown below.

<?xml version = "1.0" encoding = "UTF-8"?>
<jaxb-intros xmlns="http://www.jboss.org/xsd/jaxb/intros">
    <Class name="com.mycorp.model.Customer">
        <XmlAccessorType value="FIELD"/>
        <XmlType propOrder="name,address,phoneNumber"/>
        <XmlRootElement  name="customer"/>      

        <Field name="name">
            <XmlElement name="name"/>
        </Field>
        <Field name="address">
            <XmlElement name="address"/>
        </Field>
        <Field name="phoneNumber">
            <XmlElementWrapper name="phoneNumbers"/>
        </Field>
    </Class>
</jaxb-intros>

We have to write a new ContextResolver that uses JAXBIntroductions APIs to introduce the JAXB annotations for a given JAXBContext via a RuntimeInlineAnnotationReader implementation.

import com.mycorp.model.Customer;
import com.sun.xml.bind.api.JAXBRIContext;
import org.jboss.jaxb.intros.IntroductionsAnnotationReader;
import org.jboss.jaxb.intros.IntroductionsConfigParser;
import org.jboss.jaxb.intros.configmodel.JaxbIntros;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import java.util.*;

@Provider
public class JAXBContextResolverForJAXBIntroductions implements ContextResolver<JAXBContext> {

    private final JAXBContext context;
    private final Set<Class> types;
    private final Class[] cTypes = {Customer.class};

    public JAXBContextResolverForJAXBIntroductions() throws Exception {
        this.types = new HashSet(Arrays.asList(cTypes));
        JaxbIntros config = IntroductionsConfigParser.parseConfig(this.getClass().getResourceAsStream("/intro-config.xml"));
        IntroductionsAnnotationReader reader = new IntroductionsAnnotationReader(config);
        Map<String, Object> jaxbConfig = new HashMap<String, Object>();
        jaxbConfig.put(JAXBRIContext.ANNOTATION_READER, reader);
        this.context = JAXBContext.newInstance(cTypes, jaxbConfig);
    }

    public JAXBContext getContext(Class<?> objectType) {
        return (types.contains(objectType)) ? context : null;
    }
}

Accessing the URL, http://localhost:9998/jaxb/customers, produces the following XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <name>Jane Doe</name>
    <address>
        <city>My Town</city>
        <street>123 Any Street</street>
    </address>
    <phoneNumbers>
        <phoneNumber>
            <type>work</type>
            <value>613-555-1111</value>
        </phoneNumber>
        <phoneNumber>
            <type>cell</type>
            <value>613-555-2222</value>
        </phoneNumber>
    </phoneNumbers>
</customer>

Hopefully, these examples demonstrate the benefits of using OXM tools to simplify generating XML using custom JAXB marshallers in Jersey or any other JAX-RS provider, without requiring JAXB annotations on their domain model. While, both these options are easier to implement, you may want to choose the one which fits your needs. For most cases, JAXBIntroductions should suffice as it is lightweight. If you are looking for converting JPA entities to/from XML (via JAXB), then I would prefer MOXy.

Download the project sources here. Feedback/suggestions are always welcome 🙂

Update (8/17) : Fixed the output produced from JAXBIntroductions, as it was a copy/paste error in the original post. Now, you can notice the difference between the marshalled XML in both cases. Thanks Blaise for pointing this out and providing reference to your blog post which explains how MOXy uses XPath mapping extension to add in the “personal-info” and “contact-info” grouping elements to our marshalled XML.

Possibly Related Posts:


JAX-RS runtime allows applications to supply entity providers which maps services between representations (via @Produces and @Consumes) and their Java types. The entity provider interfaces MessageBodyReader and MessageBodyWriter defines the contract that supports the conversion of a stream to a Java type and vice versa.

Lets write a simple JAXRS provider for handling a SOAPMessage on the client-side.

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.xml.soap.*;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

@Provider
@Consumes(MediaType.TEXT_XML)
@Produces(MediaType.TEXT_XML)
public class SoapProvider implements MessageBodyWriter<SOAPMessage>, MessageBodyReader<SOAPMessage> {
    public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
        return SOAPMessage.class.isAssignableFrom(aClass);
    }

    public SOAPMessage readFrom(Class<SOAPMessage> soapEnvelopeClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> stringStringMultivaluedMap, InputStream inputStream) throws IOException, WebApplicationException {
        try {
            MessageFactory messageFactory = MessageFactory.newInstance();
            StreamSource messageSource = new StreamSource(inputStream);
            SOAPMessage message = messageFactory.createMessage();
            SOAPPart soapPart = message.getSOAPPart();
            soapPart.setContent(messageSource);
            return message;
        } catch (SOAPException e) {
            e.printStackTrace();
        }
        return null;
    }

    public long getSize(SOAPMessage soapMessage, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    public void writeTo(SOAPMessage soapMessage, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> stringObjectMultivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
        try {
            soapMessage.writeTo(outputStream);
        } catch (SOAPException e) {
            e.printStackTrace();
        }
    }

    public boolean isReadable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
        return aClass.isAssignableFrom(SOAPMessage.class);
    }
}

I used Jersey client to invoke the Weather Web Service from CDYNE. You can use any wsdl2java tool (I used CXF) to generate the types from the WSDL. Jersey would use the registered provider to map the SOAPMessage to the request and response. And finally, we bind the SOAPBody to the WSDL type using JAXB.

import com.cdyne.ws.weatherws.GetCityWeatherByZIPResponse;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.LoggingFilter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.soap.*;

public class RESTyClient {
    public static void main(String[] args) throws Exception {
        ClientConfig config = new DefaultClientConfig();
        config.getClasses().add(SoapProvider.class);
        Client c = Client.create(config);
        c.addFilter(new LoggingFilter());

        MessageFactory messageFactory = MessageFactory.newInstance();
        SOAPMessage message = messageFactory.createMessage();
        SOAPPart soapPart = message.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        SOAPBody body = envelope.getBody();
        SOAPElement bodyElement = body.addChildElement(envelope.createName("GetCityWeatherByZIP", "", "http://ws.cdyne.com/WeatherWS/"));
        bodyElement.addChildElement("ZIP").addTextNode("59102");
        message.saveChanges();

        WebResource service = c.resource("http://ws.cdyne.com/WeatherWS/Weather.asmx");

        // POST the request
        ClientResponse cr = service.header("SOAPAction", "\"http://ws.cdyne.com/WeatherWS/GetCityWeatherByZIP\"").post(ClientResponse.class, message);
        message = cr.getEntity(SOAPMessage.class);

        JAXBContext ctx = JAXBContext.newInstance(GetCityWeatherByZIPResponse.class);
        Unmarshaller um = ctx.createUnmarshaller();
        GetCityWeatherByZIPResponse response = (um.unmarshal(message.getSOAPPart().getEnvelope().getBody().extractContentAsDocument(), GetCityWeatherByZIPResponse.class)).getValue();
        System.out.println("City : " + response.getGetCityWeatherByZIPResult().getCity());
        System.out.println("Temperature : " + response.getGetCityWeatherByZIPResult().getTemperature());
    }
}

Here is a sample output from the test.

Dec 3, 2009 10:02:04 PM com.sun.jersey.api.client.filter.LoggingFilter log
INFO: 1 * Client out-bound request
1 > POST http://ws.cdyne.com/WeatherWS/Weather.asmx
1 > SOAPAction: "http://ws.cdyne.com/WeatherWS/GetCityWeatherByZIP"
1 > 
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
      <GetCityWeatherByZIP xmlns="http://ws.cdyne.com/WeatherWS/">
        <ZIP>59102</ZIP>
      </GetCityWeatherByZIP>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Dec 3, 2009 10:02:04 PM com.sun.jersey.api.client.filter.LoggingFilter log
INFO: 1 * Client in-bound response
1 < 200
1 < Content-Length: 762
1 < X-AspNet-Version: 2.0.50727
1 < X-Powered-By: ASP.NET
1 < Cache-Control: no-cache
1 < Pragma: no-cache
1 < Content-Type: text/xml; charset=utf-8
1 < Server: Microsoft-IIS/7.0
1 < Date: Fri, 04 Dec 2009 05:02:21 GMT
1 < Expires: -1
1 < 
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetCityWeatherByZIPResponse xmlns="http://ws.cdyne.com/WeatherWS/">
            <GetCityWeatherByZIPResult>
                <Success>true</Success>
                <ResponseText>City Found</ResponseText>
                <State>MT</State>
                <City>Billings</City>
                <WeatherStationCity>Billings</WeatherStationCity>
                <WeatherID>3</WeatherID>
                <Description>Mostly Cloudy</Description>
                <Temperature>16</Temperature>
                <RelativeHumidity>51</RelativeHumidity>
                <Wind>SW23</Wind>
                <Pressure>30.11F</Pressure>
                <Visibility />
                <WindChill />
                <Remarks />
            </GetCityWeatherByZIPResult>
        </GetCityWeatherByZIPResponse>
    </soap:Body>
</soap:Envelope>

City : Billings
Temperature : 16

In fact, this should work across the same way with any other JAX-RS Clients (I know there a quite a few). I have tried with the new kid on the block Apache Wink, which looks very interesting, still in incubation, but I am impressed with their documentation, as I hardly find useful docs with other projects in incubation stage. And the client APIs are similar to that of Jersey client, so it was fairly easy to write one using Wink Client. I will have to reserve another blog entry for exploring Wink’s USP.

import com.cdyne.ws.weatherws.GetCityWeatherByZIPResponse;
import org.apache.wink.client.ClientConfig;
import org.apache.wink.client.ClientResponse;
import org.apache.wink.client.Resource;
import org.apache.wink.client.RestClient;

import javax.ws.rs.core.Application;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.soap.*;
import java.util.HashSet;
import java.util.Set;

public class WinkClient {
    public static void main(String[] args) throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance();
        SOAPMessage message = messageFactory.createMessage();
        SOAPPart soapPart = message.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        SOAPBody body = envelope.getBody();
        SOAPElement bodyElement = body.addChildElement(envelope.createName("GetCityWeatherByZIP", "", "http://ws.cdyne.com/WeatherWS/"));
        bodyElement.addChildElement("ZIP").addTextNode("59102");
        message.saveChanges();

        ClientConfig config = new ClientConfig();

        // Create new JAX-RS Application
        Application app = new Application() {
            @Override
            public Set<Class<?>> getClasses() {
                HashSet<Class<?>> set = new HashSet<Class<?>>();
                set.add(SoapProvider.class);
                return set;
            }
        };
        config.applications(app);

        RestClient client = new RestClient(config);

        // create the resource instance to interact with
        Resource resource = client.resource("http://ws.cdyne.com/WeatherWS/Weather.asmx");

        // issue the request
        ClientResponse cr = resource.contentType("text/xml").post(ClientResponse.class, message);
        message = cr.getEntity(SOAPMessage.class);

        JAXBContext ctx = JAXBContext.newInstance(GetCityWeatherByZIPResponse.class);
        Unmarshaller um = ctx.createUnmarshaller();
        GetCityWeatherByZIPResponse response = (um.unmarshal(message.getSOAPPart().getEnvelope().getBody().extractContentAsDocument(), GetCityWeatherByZIPResponse.class)).getValue();
        System.out.println("Response : " + response.getGetCityWeatherByZIPResult().getCity());
        System.out.println("Response : " + response.getGetCityWeatherByZIPResult().getTemperature());
    }
}

Here are the maven dependencies:

    <dependencies>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.1.4.1</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.wink</groupId>
            <artifactId>wink-client</artifactId>
            <version>1.0-incubating</version>
        </dependency>        
    </dependencies>

While there are better tools and frameworks which addresses the complexity of building SOAP messages, it was not a big deal to invoke web services from HTTP Clients, after all its just a HTTP POST.

Possibly Related Posts:


SSL enabled RESTful services are quite easier to develop and test using Jersey, Grizzly and RestTemplate.

Jersey (resource development)
Grizzly Web Server (resource configuration and deployment)
Spring 3 RestTemplate backed by Commons HTTP Client (resource access)

In a moment, you will notice how all these nicely fit the bill. Let us start with the POM for the maven fans.

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.web</artifactId>
            <version>3.0.0.M4</version>
        </dependency>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>1.1.2-ea</version>
        </dependency>
        <dependency>
            <groupId>com.sun.grizzly</groupId>
            <artifactId>grizzly-servlet-webserver</artifactId>
            <version>1.9.18a</version>
        </dependency>
        <dependency>
            <groupId>org.apache.log4j</groupId>
            <artifactId>com.springsource.org.apache.log4j</artifactId>
            <version>1.2.15</version>
        </dependency>
    </dependencies>

Configuring Log4j is very useful as you could see the commons Client debug messages and http wire headers which are quite useful for debugging in case if you were lost in translation.

Putting together all these pieces working did not take much of my time. I did not have to do anything fancy here as I just reused most of the sample code from the Jersey HTTPS sample and Commons HTTP Client SSL sample.

Lets dive into the Spring Config, which does most of the wiring of HTTP Client and RestTemplate.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="sslClient" class="spring3.restclient.RestSSLClient">
        <constructor-arg ref="restTemplate"/>
        <constructor-arg ref="credentials"/>
    </bean>

    <bean id="httpClientParams" class="org.apache.commons.httpclient.params.HttpClientParams">
        <property name="authenticationPreemptive" value="true"/>
        <property name="connectionManagerClass"
                  value="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
    </bean>
    <bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
        <constructor-arg ref="httpClientParams"/>
    </bean>
    <bean id="credentials" class="org.apache.commons.httpclient.UsernamePasswordCredentials">
        <constructor-arg value="admin"/>
        <constructor-arg value="adminadmin"/>
    </bean>
    <bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory">
        <constructor-arg ref="httpClient"/>
    </bean>

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <constructor-arg ref="httpClientFactory"/>
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            </list>
        </property>
    </bean>
</beans>

The below code configures a Grizzly Server with SSL support for server side certificates, Basic Auth filter and Jersey resource servlet.

import com.sun.grizzly.SSLConfig;
import com.sun.grizzly.http.embed.GrizzlyWebServer;
import com.sun.grizzly.http.servlet.ServletAdapter;
import com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.samples.https_grizzly.auth.SecurityFilter;

import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;

public class GrizzlyServer {

    private static GrizzlyWebServer webServer;

    public static final URI BASE_URI = getBaseURI();

    private static URI getBaseURI() {
        return UriBuilder.fromUri("https://localhost/").port(getPort(4463)).build();
    }

    private static int getPort(int defaultPort) {
        String port = System.getenv("JERSEY_HTTP_PORT");
        if (null != port) {
            try {
                return Integer.parseInt(port);
            } catch (NumberFormatException e) {
            }
        }
        return defaultPort;
    }

    protected static void startServer() {

        webServer = new GrizzlyWebServer(getPort(4463), ".", true);

        // add Jersey resource servlet

        ServletAdapter jerseyAdapter = new ServletAdapter();
        jerseyAdapter.addInitParameter("com.sun.jersey.config.property.packages", "server.https.auth;server.https.resource");
        jerseyAdapter.setContextPath("/");
        jerseyAdapter.setServletInstance(new ServletContainer());

        // add security filter (which handles http basic authentication)
        jerseyAdapter.addInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, SecurityFilter.class.getName());
        // add authorization filter
        jerseyAdapter.addInitParameter(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, RolesAllowedResourceFilterFactory.class.getName());

        webServer.addGrizzlyAdapter(jerseyAdapter, new String[]{"/"});


        // Grizzly ssl configuration
        SSLConfig sslConfig = new SSLConfig();

        sslConfig.setNeedClientAuth(true); // don't work - known grizzly bug, will be fixed in 2.0.0

        // set up security context
        String keystore_server = Thread.currentThread().getContextClassLoader().getResource("keystore_server").getFile();
        String truststore_server = Thread.currentThread().getContextClassLoader().getResource("truststore_server").getFile();

        sslConfig.setKeyStoreFile(keystore_server); // contains server keypair
        sslConfig.setKeyStorePass("secret");
        sslConfig.setTrustStoreFile(truststore_server); // contains client certificate
        sslConfig.setTrustStorePass("secret");

        webServer.setSSLConfig(sslConfig);

        // turn server side client certificate authentication on

//        ((SSLSelectorThread) webServer.getSelectorThread()).setNeedClientAuth(true);

        try {
            // start Grizzly embedded server //
            System.out.println(String.format("Jersey app started with WADL at %sapplication.wadl", BASE_URI));
            webServer.start();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    protected static void stopServer() {
        webServer.stop();
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        startServer();
        System.out.println("Hit return to stop...");
        System.in.read();
        stopServer();
    }
}

Here’s slightly modified version of the sample Jersey Security filter which would handle the HTTP basic authentication on the server. The auth helper classes (AuthenticationExceptionMapper, AuthenticationException) are found here.

package com.sun.jersey.samples.https_grizzly.auth;

import com.sun.jersey.api.container.MappableContainerException;
import com.sun.jersey.core.util.Base64;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import java.security.Principal;

public class SecurityFilter implements ContainerRequestFilter {

    @Context
    UriInfo uriInfo;
    private static final String REALM = "HTTPS Example authentication";

    public ContainerRequest filter(ContainerRequest request) {
        User user = authenticate(request);
        request.setSecurityContext(new Authorizer(user));
        return request;
    }

    private User authenticate(ContainerRequest request) {
        // Extract authentication credentials
        String authentication = request.getHeaderValue(ContainerRequest.AUTHORIZATION);
        if (authentication == null) {
            throw new MappableContainerException
                    (new AuthenticationException("Authentication credentials are required", REALM));
        }
        if (!authentication.startsWith("Basic ")) {
            return null;
            // additional checks should be done here
            // "Only HTTP Basic authentication is supported"
        }
        authentication = authentication.substring("Basic ".length());
        String[] values = new String(Base64.base64Decode(authentication)).split(":");
        if (values.length < 2) {
            throw new WebApplicationException(400);
            // "Invalid syntax for username and password"
        }
        String username = values&#91;0&#93;;
        String password = values&#91;1&#93;;
        if ((username == null) || (password == null)) {
            throw new WebApplicationException(400);
            // "Missing username or password"
        }

        // Validate the extracted credentials
        User user = null;

        if (username.equals("john") && password.equals("secret")) {
            user = new User("john", "user");
            System.out.println("USER 'John Doe' AUTHENTICATED");
        } else if (username.equals("jane") && password.equals("secret")) {
            user = new User("jane", "user");
            System.out.println("USER 'Jane Doe' AUTHENTICATED");
        } else if (username.equals("admin") && password.equals("adminadmin")) {
            user = new User("admin", "admin");
            System.out.println("ADMIN AUTHENTICATED");
        } else {
            System.out.println("USER NOT AUTHENTICATED");
            throw new MappableContainerException(new AuthenticationException("Invalid username or password\r\n", REALM));
        }
        return user;
    }

    public class Authorizer implements SecurityContext {

        private User user;
        private Principal principal;

        public Authorizer(final User user) {
            this.user = user;
            this.principal = new Principal() {

                public String getName() {
                    return user.username;
                }
            };
        }

        public Principal getUserPrincipal() {
            return this.principal;
        }

        public boolean isUserInRole(String role) {
            return (role.equals(user.role));
        }

        public boolean isSecure() {
            return "https".equals(uriInfo.getRequestUri().getScheme());
        }

        public String getAuthenticationScheme() {
            return SecurityContext.BASIC_AUTH;
        }
    }

    public class User {

        public String username;
        public String role;

        public User(String username, String role) {
            this.username = username;
            this.role = role;
        }
    }
}
&#91;/sourcecode&#93;

The resource class is very simple. The resource methods are access controlled using the JSR-250 annotation @RolesAllowed. The methods are self-explanatory and they are just coded for illustration, not a fool-proof implementation. In this sample, the Grizzly server would perform server-side certificate authentication and HTTP Basic authentication, in addition to basic authorization checks.

&#91;sourcecode language='java'&#93;
import com.sun.jersey.core.util.Base64;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/")
public class HttpsResource {

    @GET
    @RolesAllowed({"admin"})
    @Path("/locate/{username}")
    public Response getUserLocation(@Context HttpHeaders headers, @PathParam("username") String username) {
        // you can get username from HttpHeaders
        System.out.println("Service: GET / User Location for : " + username + " requested by " + getUser(headers));
        return Response.ok("Billings, Montana").type(MediaType.TEXT_PLAIN).build();
    }

    @GET
    @RolesAllowed({"admin", "user"})
    public Response getUserPin(@Context HttpHeaders headers) {
        // you can get username from HttpHeaders
        System.out.println("Service: GET / User Pin for: " + getUser(headers));
        return Response.ok("1234").type(MediaType.TEXT_PLAIN).build();
    }

    private String getUser(HttpHeaders headers) {
        String auth = headers.getRequestHeader("authorization").get(0);

        auth = auth.substring("Basic ".length());
        String&#91;&#93; values = new String(Base64.base64Decode(auth)).split(":");

        String username = values&#91;0&#93;;
        String password = values&#91;1&#93;;

        return username;
    }
}
&#91;/sourcecode&#93;

The following steps guide to create sample client and server certificates using the JDK keytool utility. The self-signed certificates are used for demonstration purposes only. In reality, this would be performed by a Certificate Authority (for ex: Verisign).

<strong><ul>generate client and server keys:</ul></strong>

keytool -genkey -keystore keystore_client -alias clientKey -dname "CN=www.aruld.info, OU=R&D, O=Vasun Technologies, L=Billings, ST=Montana, C=US"
keytool -genkey -keystore keystore_server -alias serverKey -dname "CN=www.aruld.info, OU=R&D, O=Vasun Technologies, L=Billings, ST=Montana, C=US"

<strong><ul>generate client and server certificates:</ul></strong>

keytool -export -alias clientKey -rfc -keystore keystore_client > client.cert
keytool -export -alias serverKey -rfc -keystore keystore_server > server.cert

<strong><ul>import certificates to corresponding truststores:</ul></strong>

keytool -import -alias clientCert -file client.cert -keystore truststore_server
keytool -import -alias serverCert -file server.cert -keystore truststore_client


SSL helper classes (AuthSSLProtocolSocketFactory, AuthSSLX509TrustManager, AuthSSLInitializationError) for the client-side are used from the Commons Client SSL <a href="http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/">contrib</a> samples.

RestTemplate is injected into the RestSSLClient which uses the Commons Client APIs to set the credentials and configures the keystore and truststore on the client-side.


import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.contrib.ssl.AuthSSLProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.HashMap;

public class RestSSLClient {
    private final RestTemplate restTemplate;
    private final HttpClient client;
    private Credentials credentials;
    private static final int HTTPS_PORT = 4463;
    private static final String HTTPS_GET = "https://localhost:4463/";
    private static final String HTTPS_GET_LOCATION = "https://localhost:4463/locate/{username}";
    private static final String HTTPS = "https";
    private static final String HTTPS_HOST = "localhost";

    public RestSSLClient(RestTemplate restTemplate, Credentials credentials) {
        this.restTemplate = restTemplate;
        this.credentials = credentials;
        CommonsClientHttpRequestFactory factory = (CommonsClientHttpRequestFactory) restTemplate.getRequestFactory();
        this.client = factory.getHttpClient();
        client.getState().setCredentials(AuthScope.ANY, credentials);
        try {
            URL keystore_client = Thread.currentThread().getContextClassLoader().getResource("keystore_client").toURI().toURL();
            URL truststore_client = Thread.currentThread().getContextClassLoader().getResource("truststore_client").toURI().toURL();
            ProtocolSocketFactory protocolSocketFactory = new AuthSSLProtocolSocketFactory(keystore_client, "secret",
                    truststore_client, "secret");
            Protocol authhttps = new Protocol(HTTPS, protocolSocketFactory, HTTPS_PORT);
            Protocol.registerProtocol(HTTPS, authhttps);
            client.getHostConfiguration().setHost(HTTPS_HOST, HTTPS_PORT, authhttps);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    public void setCredentials(String user, String pass) {
        this.credentials = new UsernamePasswordCredentials(user, pass);
        client.getState().setCredentials(AuthScope.ANY, credentials);
    }

    public String get() {
        return restTemplate.getForObject(HTTPS_GET, String.class);
    }

    public String getLocation(String user) {
        Map<String, String> vars = new HashMap<String, String>();
        vars.put("username", user);
        return restTemplate.getForObject(HTTPS_GET_LOCATION, String.class, vars);
    }
}

The test code which invokes the SSL configured resource is shown below.

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Spring3RestSSLClient {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-ssl.xml");
        RestSSLClient client = applicationContext.getBean("sslClient", RestSSLClient.class);
        System.out.println("John's Location : " + client.getLocation("john"));
        System.out.println("Jane's Location : " + client.getLocation("jane"));
        client.setCredentials("john", "secret");
        System.out.println("John Doe's Pin : " + client.get());
        client.setCredentials("jane", "secret");
        System.out.println("Jane Doe's Pin : " + client.get());
    }
}

WADL for this resource can be accessed from https://localhost:4463/application.wadl. You could access the URL from a browser as the server side client certificate authentication is disabled in the GrizzlyServer. (Uncommenting line # 70 would enable server side client cert auth, but this would force the browser to use the generated client keys). Test it yourself, you would be presented with a basic auth dialog (valid user/pass/role: admin/adminadmin/admin, john/secret/user, jane/secret/user) and you could access the resource methods with specified roles. I have tested with Firefox and Chrome. Enjoy!

Possibly Related Posts:


The central API for accessing RESTful services in Spring 3 is RestTemplate. It is like any other template mechanism provided by Spring for the client side access. The default implementation uses the java.net package for creating HTTP requests. RestTemplate can be backed by Commons HttpClient using ClientHttpRequestFactory. Using Commons HttpClient as the backend implementation supports basic authentication in the requests. The javadocs were the single source of information for Spring3 REST support until M2, but you can find the latest reference documentation with M3 release here. Chapter 18 covers REST support in Spring 3. You can also read Arjen’s blog posts here and here which outlines the REST support in Spring 3 both on the server-side and client-side.

I was playing with the RestTemplate and was quite impressed with it as it comes handy for Spring developers. But, this implementation is no different than most other REST Client APIs. Long story short, this is yet another attempt to provide a non-standard Client for accessing RESTful resources. I am leaning more towards the need for a standard JAX-RS client API and I hope JAX-RS 2.0 fuels that interest. Lets explore with an example how Spring 3 provides native support for accessing RESTful resources using RestTemplate.

Twitter is one of the most widely used service which has a decent REST API with basic authentication.

Lets start with the POM dependencies. I had built the latest Spring3 M3 snapshot on my box, but you should be able to fetch them from Spring maven snapshots repository.

<repositories>
    <repository>
        <id>SpringSource Enterprise Bundle Repository - External Bundle Milestones</id>
        <url>http://repository.springsource.com/maven/bundles/milestone</url>
    </repository>
    <repository>
        <id>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</id>
        <url>http://repository.springsource.com/maven/bundles/release</url>
    </repository>
    <repository>
        <id>SpringSource Enterprise Bundle Repository - External Bundle Releases</id>
        <url>http://repository.springsource.com/maven/bundles/external</url>
    </repository>
</repositories>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
            </configuration>
        </plugin>
    </plugins>
</build>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>org.springframework.web</artifactId>
        <version>3.0.0.M3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>org.springframework.xml</artifactId>
        <version>1.5.5.A</version>
    </dependency>
    <dependency>
        <groupId>org.apache.log4j</groupId>
        <artifactId>com.springsource.org.apache.log4j</artifactId>
        <version>1.2.15</version>
    </dependency>
    <dependency>
        <groupId>commons-httpclient</groupId>
        <artifactId>commons-httpclient</artifactId>
        <version>3.1</version>
    </dependency>
</dependencies>

The Spring configuration defines the beans required for this sample.

<?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:oxm="http://www.springframework.org/schema/oxm"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
    <bean id="twitterClient" class="spring3.restclient.TwitterClient">
        <constructor-arg ref="restTemplate"/>
        <constructor-arg ref="credentials"/>
    </bean>

    <bean id="httpClientParams" class="org.apache.commons.httpclient.params.HttpClientParams">
        <property name="authenticationPreemptive" value="true"/>
        <property name="connectionManagerClass"
                  value="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
    </bean>
    <bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
        <constructor-arg ref="httpClientParams"/>
    </bean>
    <bean id="credentials" class="org.apache.commons.httpclient.UsernamePasswordCredentials">
        <constructor-arg value="username"/>
        <constructor-arg value="password"/>
    </bean>
    <bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory">
        <constructor-arg ref="httpClient"/>
    </bean>

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <constructor-arg ref="httpClientFactory"/>

        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
                    <property name="marshaller" ref="jaxbMarshaller"/>
                    <property name="unmarshaller" ref="jaxbMarshaller"/>
                </bean>
                <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
            </list>
        </property>
    </bean>

    <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>twitter.model.Statuses</value>
            </list>
        </property>
    </bean>
</beans>

MarshallingHttpMessageConverter is used to marshal/unmarshal JAXB beans. Statuses is a JAXB bean which is bound to the response returned by GET on the Twitter URI https://twitter.com/statuses/friends_timeline.xml. FormHttpMessageConverter is used to construct form parameters to POST on the Twitter URI https://twitter.com/statuses/update.xml.

Now, we will see the RestTemplate in action. You can see the code is just a one liner.

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.auth.AuthScope;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.util.MultiValueMap;
import org.springframework.util.LinkedMultiValueMap;
import twitter.model.Statuses;

import java.util.HashMap;
import java.util.Map;

public class TwitterClient {

    private final RestTemplate restTemplate;

    private final Credentials credentials;

    private static final String twitterGet = "https://twitter.com/statuses/friends_timeline.xml?count={count}";
    private static final String twitterPost = "https://twitter.com/statuses/update.xml";

    public TwitterClient(RestTemplate restTemplate, Credentials credentials) {
        this.restTemplate = restTemplate;
        this.credentials = credentials;
        CommonsClientHttpRequestFactory factory = (CommonsClientHttpRequestFactory) restTemplate.getRequestFactory();
        HttpClient client = factory.getHttpClient();
        client.getState().setCredentials(AuthScope.ANY, credentials);
    }

    public Statuses getStatuses() {
        Map<String, String> vars = new HashMap<String, String>();
        vars.put("count", "5");
        return restTemplate.getForObject(twitterGet, Statuses.class, vars);
    }

    public void setStatus(String status) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        map.add("status", status);
        restTemplate.postForLocation(twitterPost, map);
    }
}

The test code which posts the status to your Twitter account is shown below.

public class Spring3RestClientTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        TwitterClient twitter = applicationContext.getBean("twitterClient", TwitterClient.class);
        twitter.setStatus("Spring RestTemplate rocks!");
        Statuses statuses = twitter.getStatuses();
        for (Statuses.Status status : statuses.getStatus()) {
            System.out.println("Text : " + status.getText());
            System.out.println("User : " + status.getUser().getScreenName());
        }
    }
}

Here is an example output of the above test which lists recent five status messages from Twitter:
=====================================================
Text : Spring RestTemplate rocks!
User : aruld
Text : Just finished celebrating with the SpringSource & Hyperic teams at Tres Agaves. Really good to meet everyone! http://twitpic.com/4l1hb
User : cbeams
Text : 8 Java Puzzlers for JavaOne done. Wow. Soon we’ll scrape right through the bottom of the barrel.
User : gafter
Text : Disappointed my Maredsous 8 is all gone. I should’ve bought more.
User : dandiep
Text : Headed to San Francisco for a week of Core Spring training & looking forward to it!
User : cbeams
=====================================================

RestTemplate does serves its purpose, but it could be improved to support additional convenient methods for accessing URIs with query parameters. The other thing I noticed is about making HTTP POST with a name-value pair using postForLocation API. I could not find any details from the documentation so far. For instance, I would like to do a post to the URI http://twitter.com/statuses/update.xml with the form data “status=This is my current status” sent in the HTTP body. I am not sure whether this is possible with the current implementation. I will dig into this further and keep posted on what I find.

Update (5/4): Thanks to Arjen for clarifying the URITemplate use within query parameters and the FormHttpMessageConverter for using Form parameters. The blog has been updated to use these techniques. You could POST a twitter message just fine 🙂

Update (5/10): Updated the reference documentation to the official M3 build from SpringSource and updated the maven POM to use the M3 dependency.

Possibly Related Posts:


Jersey 1.0.3 was released this week. This release has quite a few interesting new features and improvements to some of its existing functionality. Paul’s blog entry gives the specifics of this release. One of the cool features in this release is the ability to inject WadlApplicationContext in resources. This provides access to the WADL JAXB representation for your restful application. Paul already discussed about its usage in the Jersey dev mailing list. I wanted to see all these in action and you won’t believe that it took just few minutes to see styled WADL documentation in action.

The following URI paths renders XSL styled WADL documentation for resources :

http://localhost:9998/application.wadl

http://localhost:9998/wadl

Here is a preview of the documentation (This works in Chrome out of the box, no luck with Firefox and IE):

Jersey WADL documentation

I used Mark’s WADL to HTML documentation stylesheet for styling the documentation.

The HTTP response for WADL GET recorded by Firebug:

HTTP Response for WADL GET

Here is the POM for interested:

    <dependencies>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.1</version>
        </dependency>

        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.1.10</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>1.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey.test.framework</groupId>
            <artifactId>jersey-test-framework</artifactId>
            <version>1.0.3</version>
            <scope>test</scope>
        </dependency>

The WadlResource below shows how the WadlApplicationContext injection happens and the JAXBContext is retrieved from WadlApplicationContext. The marshaller allows you to set the property which can be used to specify an XML preamble, in this case WADL to HTML documentation stylesheet embedded in the XML headers. The rest all is the usual stuff you know.

@Produces({"application/vnd.sun.wadl+xml", "application/xml"})
@Singleton
@Path("wadl")
public class WadlResource {

    private static final Logger LOGGER = Logger.getLogger(WadlResource.class.getName());

    private static final String XML_HEADERS = "com.sun.xml.bind.xmlHeaders";

    private WadlApplicationContext wadlContext;

    private Application application;

    private byte[] wadlXmlRepresentation;

    public WadlResource(@Context WadlApplicationContext wadlContext) {
        this.wadlContext = wadlContext;
        this.application = wadlContext.getApplication();
    }

    @GET
    public synchronized Response getWadl(@Context UriInfo uriInfo) {
        if (wadlXmlRepresentation == null) {
            if (application.getResources().getBase() == null) {
                application.getResources().setBase(uriInfo.getBaseUri().toString());
            }
            try {
                final Marshaller marshaller = wadlContext.getJAXBContext().createMarshaller();
                marshaller.setProperty(XML_HEADERS, "<?xml-stylesheet type='text/xsl' href='http://www.mnot.net/webdesc/wadl_documentation.xsl'?>");
                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                final ByteArrayOutputStream os = new ByteArrayOutputStream();
                marshaller.marshal(application, os);
                wadlXmlRepresentation = os.toByteArray();
                os.close();
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Could not marshal wadl Application.", e);
                return javax.ws.rs.core.Response.ok(application).build();
            }
        }
        return Response.ok(new ByteArrayInputStream(wadlXmlRepresentation)).build();
    }
}

MyWadlResource extends from WadlResource with the relative URL at “application.wadl”. There is already a patch submitted by James Strachan which will allow using WADL with implicit views. I hope this becomes available for the next 1.1.0-ea release.

@Path("application.wadl")
public class MyWadlResource extends WadlResource {
  public MyWadlResource(@Context WadlApplicationContext wadlContext) {
    super(wadlContext);
  }
}

The usual standalone Server code is shown below.

public class Server {
    public static void main(String[] args) throws IOException {
        final String baseUri = "http://localhost:9998/";
        final Map<String, String> initParams = new HashMap<String, String>();
        initParams.put("com.sun.jersey.config.property.packages", "jersey.wadl.resources");
        System.out.println("Starting grizzly...");
        SelectorThread threadSelector = GrizzlyWebContainerFactory.create(baseUri, initParams);
        System.out.println(String.format("Jersey app started with WADL available at %sapplication.wadl or %swadl\n"
                + "Try out %s\nHit enter to stop it...", baseUri, baseUri, baseUri));
        System.in.read();
        threadSelector.stopEndpoint();
        System.exit(0);
    }
}

Here is a simple test written using the new Jersey test framework and by default it deploys the resources to the Grizzly container. The Application class is serialized as a JAXB bean. You then have access to all the WADL components.

public class WadlTest extends JerseyTest {

  public WadlTest() throws Exception {
    super("jersey.wadl.resources");
  }

  @Test
  public void wadlGet() {
    ClientResponse cr = webResource.path("wadl").get(ClientResponse.class);
    Application a = cr.getEntity(com.sun.research.ws.wadl.Application.class);
    Method m = (Method)a.getResources().getResource().get(0).getMethodOrResource().get(0);
    Response r = m.getResponse();
    List<JAXBElement<RepresentationType>> representations = r.getRepresentationOrFault();
    for (JAXBElement<RepresentationType> representation : representations) {
      RepresentationType type = representation.getValue();
      System.out.println("MediaType : " + type.getMediaType());
    }
  }
}

The other notable support in this release is integration with Guice 2.0. Maturity and quality is always the driving factor behind every Jersey release. No compromises whatsoever. Well done Paul and team!

The project sources can be downloaded from here.

Possibly Related Posts:


I was playing with CXF 2.2 over the weekend. I would like to share my experience in building a simple blogger service using CXF which provides seamless access to SOAP and REST web services. You would soon realize how services design and development has become easier with this new CXF release. Lets dive into the crux.

The blogger service exposes reading and writing operations. All blogging operations (CRUD) are exposed as REST and read operations are exposed as SOAP. This approach enables service containment and provides controlled access to services. This service produces XML and JSON content and the negotiation is done by the client.

The class hierarchy is shown below.

Blogger class hierarchy

Maven dependencies for this project are:

        <dependency>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <artifactId>servlet-api</artifactId>
            <groupId>javax.servlet</groupId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <artifactId>cxf-rt-transports-http</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <groupId>org.apache.cxf</groupId>
            <version>2.2</version>
        </dependency>

BlogReader interface enables REST and SOAP access for the blogger service read operations.

public interface BlogReader {
    @WebMethod
    @GET
    @Path("{id}")
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Entry getEntry(@PathParam("id") Long id);

    @WebMethod
    @GET
    @Path("/list")
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Entries getEntries();    
}

BlogWriter interface enables REST access for the blogger service write operations.

public interface BlogWriter {
    @POST
    Response addEntry(Entry entry);

    @PUT
    Response updateEntry(Entry entry);

    @DELETE
    @Path("{id}")
    Response deleteEntry(@PathParam("id") Long id);
}

Blogger interface unifies SOAP and REST access to the blogger service.

@Path("/")
@WebService
public interface Blogger extends BlogReader, BlogWriter {
}

Here is a trivial implementation of the blogger service. This implementation shows how resources can be made service oriented without any extra effort.

public class BloggerImpl implements Blogger {
    static Map<Long, Entry> blogDb = new TreeMap<Long, Entry>();
    private AtomicLong counter = new AtomicLong();

    public Response addEntry(Entry entry) {
        System.out.println("adding entry..");
        entry.setId(counter.incrementAndGet());
        blogDb.put(entry.getId(), entry);
        System.out.println("Created blog entry " + entry.getId());
        return Response.created(URI.create("/" + entry.getId())).build();
    }

    public Response updateEntry(Entry entry) {
        System.out.println("Updated blog entry " + entry.getId());
        Entry oldEntry = blogDb.get(entry.getId());
        Response r;
        if (oldEntry != null) {
            r = Response.ok().build();
            blogDb.put(entry.getId(), entry);
        } else {
            r = Response.notModified().build();
        }
        return r;
    }

    public Response deleteEntry(Long id) {
        System.out.println("Removed blog entry " + id);
        Entry entry = blogDb.get(id);
        Response r;
        if (entry != null) {
            r = Response.ok().build();
            blogDb.remove(id);
        } else {
            r = Response.notModified().build();
        }
        return r;        
    }

    public Entry getEntry(@PathParam("id") Long id) {
        System.out.println("Fetching blog entry " + id);
        Entry entry = blogDb.get(id);
        if (entry == null) {
          throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
        return entry;
    }

    public Entries getEntries() {
        Entries entries = new Entries();
        entries.setEntry(blogDb.values());
        return entries;
    }
}

Entry and Entries classes are simple JAXB beans.

The Server code starts both the CXF REST and SOAP services which uses the embedded Jetty server. You could easily configure the content filtering just by suffixing to the URI (.xml or .json) as defined in the extension mappings.

public class Server {
    public static void main(String[] args) {
        startSoapServer();
        startRestServer();
    }

    private static void startRestServer() {
        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
        sf.setResourceClasses(Blogger.class);
        sf.getInInterceptors().add(new LoggingInInterceptor());
        sf.getOutInterceptors().add(new LoggingOutInterceptor());
        sf.setResourceProvider(Blogger.class, new SingletonResourceProvider(new BloggerImpl()));
        sf.setAddress("http://localhost:9000/rest/blog");
        Map<Object, Object> mappings = new HashMap<Object, Object>();
        mappings.put("xml", "application/xml");
        mappings.put("json", "application/json");
        sf.setExtensionMappings(mappings);
        sf.create();
        System.out.println("Rest Server started @ " + sf.getAddress());
    }

    private static void startSoapServer() {
        JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
        sf.setServiceClass(BlogReader.class);
        BloggerImpl blogger = new BloggerImpl();
        sf.setServiceBean(blogger);
        sf.setAddress("http://localhost:9000/soap/blog");
        sf.getInInterceptors().add(new LoggingInInterceptor());
        sf.getOutInterceptors().add(new LoggingOutInterceptor());
        sf.create();
        System.out.println("Soap Server started @ " + sf.getAddress());
    }
}

CXF JAXRS client API comes in different flavors. In this example, I used the WebClient which acts like a browser in interacting with web resources. The API client.back(true) if true then goes back to baseURI otherwise it goes back to a previous path segment, just like browsers. The tests are self-explanatory. I was able to perform all the CRUD operations using the WebClient API.

public class RestClient {
    public static void main(String[] args) {
        String baseUri = "http://localhost:9000/rest/blog";
        WebClient client = WebClient.create(baseUri);

        Entry entry = new Entry();
        entry.setDate(new Date());
        entry.setTitle("CXF in Action!");
        entry.setContent("CXF rocks!");
        Response response = client.post(entry);

        System.out.println("POST Response : " + response.getStatus());
        System.out.println("Location : " + response.getMetadata().getFirst("Location"));

        client.path("/1.xml");
        entry = client.get(Entry.class);
        System.out.println("entry.id : " + entry.getId() );

        client.back(true);
        entry.setContent("CXF 2.2 rocks!");
        response = client.put(entry);
        System.out.println("PUT Response : " + response.getStatus());

        client.path("/1.xml");
        entry = client.get(Entry.class);
        System.out.println("entry.id : " + entry.getContent());

        client.back(true);
        client.path("/1.xml");
        response = client.delete();
        System.out.println("DELETE Response : " + response.getStatus());

        client.back(true);
        response = client.post(entry);
        System.out.println("POST Response : " + response.getStatus());
        System.out.println("Location : " + response.getMetadata().getFirst("Location"));
    }
}

The SoapClient queries the added blog entries from the REST interface.

public class SoapClient {
    public static void main(String[] args) {
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.getInInterceptors().add(new LoggingInInterceptor());
        factory.getOutInterceptors().add(new LoggingOutInterceptor());

        factory.setServiceClass(BlogReader.class);
        factory.setAddress("http://localhost:9000/soap/blog");
        BlogReader blogger = (BlogReader) factory.create();
        Entry entry = blogger.getEntry(2L);
        System.out.println("entry title : " + entry.getTitle());
        System.out.println("entry content : " + entry.getContent());
    }
}

But, this gives you an idea how CXF services can promote reusability across various design approaches and making life simpler for developers. I know this is fairly basic example and definitely requires further refining. I will keep this for another post. Stay tuned.

[Update: 3/25] The delete method would throw “java.net.ProtocolException: HTTP method DELETE doesn’t support output” due to a bug in HttpConduit in CXF 2.2. This got fixed in the latest trunk. Thanks to Sergey for fixing this. In order to use the 2.2.1 snapshot repository, you need to define the following maven snapshot repository in your POM with dependency version 2.2.1-SNAPSHOT.

        <repository>
            <name>Apache CXF snapshots repository</name>
            <url>https://repository.apache.org/content/repositories/snapshots</url>
            <id>cxf-snapshots</id>
        </repository>

Possibly Related Posts:


I have been waiting for this release for quite some time mainly because it fully supports JSR 311 API and its very own Client API. It could become one stop solution for developers building and deploying SOAP & REST web services.

Major features of this release include :

~ JAX-RS 1.0 support
~ WS-SecurityPolicy support
~ WS-SecureConversation support
~ WS-Trust support (client side only)

CXF is becoming the fourth successful implementation of the JSR 311 API (Jersey, RESTEasy, Restlet). Its ability to expose a service to both worlds (REST and SOAP) could potentially attract more developers and further accelerate its adoption in enterprises.

The voting process has begun. The official release should be available sometime this week. I have always been a fan of CXF programming model and REST support makes me even more happy 🙂

Update (3/19): CXf 2.2 release available now.

Possibly Related Posts: