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:


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:


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:


LoanDB is a loan originations system reference implementation. This project will be developed using Java technology frameworks such as Spring (Web Beans in future?), Hibernate (JPA), CXF (JAXWS) and Jersey (JAXRS). The concepts are based on my original toy implementation for the loan processing NetBeans tutorial. The tutorial was primarily focused on developing JPA and web services based applications in NetBeans IDE, so you won’t find the real meat. I am thinking of designing various modules around the LoanDB core and I am expecting this to be more useful for Java developers in understanding how these frameworks help in solving traditional enterprise application integration problems and further help in developing less maintainable code, which is rather more critical in enterprises. Loan Originations system is a very good candidate for implementing SOA based solution and typically involves integration with many heterogeneous systems within network and outside the network. This is going to be real fun developing this project.

I plan to blog more about the frameworks used in this project shortly. Currently, I am in the process of designing the persistence layer, which is the core of this project. Some prototype code out there in SVN. But remember, right now there are too many moving parts before I flush out something concrete, so I have no release plans yet 🙂

Possibly Related Posts:


Jersey Jersey is a JSR 311 reference implementation for the JAX-RS spec (The Java API for RESTful Web Services). The JSR development is nearing its completion, but for most part the RI is stable enough for developers to start playing with RESTful services. JAX-RS is an elegant API built around the powerful REST architecture and modeled using resource providers. These resources can be described using WADL (Web Application Description Language). Let us build a basic movie resource which lists top box office movies. Just download the latest distribution of Jersey. I am using 0.8 release for this illustration. NetBeans 6.1 does provide support a Jersey plugin for 0.7 release. Add the jars from the distribution to the project classpath. JAX-RS requires JDK 1.5 as it uses annotations for implementing web resources.

The below XSD defines a basic movie schema.


<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
              elementFormDefault="unqualified"> 
<xsd:element name="movieCollection">     
   <xsd:complexType>
      <xsd:sequence>
         <xsd:element name="movie" type="movieDetails" minOccurs="1" 
                          maxOccurs="unbounded"/>         
      </xsd:sequence>
   </xsd:complexType>
</xsd:element>
   <xsd:complexType name="movieDetails">
      <xsd:sequence>
         <xsd:element name="title" type="xsd:string"/>
         <xsd:element name="genres" type="xsd:string"/>
         <xsd:element name="directedBy" type="xsd:string"/>
      </xsd:sequence>
      <xsd:attribute name="rank" type="xsd:int"/>
   </xsd:complexType>
</xsd:schema>

Let us use JAXB to generate the Java classes for this schema using the “xjc” tool. This will generate MovieCollection and MovieDetails types in addition to the ObjectFactory. These types will be used in building the response object for our movie collection.

We will implement the “TopBoxOffice” web resource using the JAX-RS APIs.


import com.sun.jersey.spi.resource.Singleton;
import restful.impl.jaxb.MovieCollection;
import restful.impl.jaxb.MovieDetails;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.core.Response;
import static java.lang.System.out;

@Singleton
@Path("/boxoffice")
public class TopBoxOffice {
    private MovieCollection collection = new MovieCollection();
    private final String HTTP_RESPONSE_DATE_HEADER = "EEE, dd MMM yyyy HH:mm:ss zzz";
    private final String DATE_ONE = (new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, Locale.US)).format(new Date(1));
    private static int rank = 0;
    
    public TopBoxOffice() {        
        init();
    }    
    
    @GET    
    @Path("/movies")
    @ProduceMime({"application/xml", "application/json"})
    public Response getTopBoxOfficeMoviesThisWeek() {                                
        out.println("Invoking getTopBoxOfficeMoviesThisWeek() ...");
        //Pragma and Expires headers are set to disable caching in IE
        return Response.ok(collection).header("Pragma", "no-cache").header("Expires", DATE_ONE).build();
    }
    
    final void init() {        
        MovieDetails movie = constructMovieObject("Indiana Jones and the Kingdom of the Crystal Skull", "Steven Spielberg", "Action/Adventure and Sequel");
        collection.getMovie().add(movie);
        movie = constructMovieObject("The Chronicles of Narnia: Prince Caspian", "Andrew Adamson", "Action/Adventure, Science Fiction/Fantasy and Adaptation");
        collection.getMovie().add(movie);
        movie = constructMovieObject("Iron Man", "Jon Favreau", "Action/Adventure, Science Fiction/Fantasy and Adaptation");
        collection.getMovie().add(movie);
        movie = constructMovieObject("What Happens in Vegas", "Tom Vaughan", "Comedy");
        collection.getMovie().add(movie);
        movie = constructMovieObject("Speed Racer", "Larry Wachowski, Andy Wachowski", "Action/Adventure, Science Fiction/Fantasy and Adaptation");
        collection.getMovie().add(movie);
        movie = constructMovieObject("Baby Mama", "Michael McCullers", "Comedy");
        collection.getMovie().add(movie);
        movie = constructMovieObject("Made of Honor", "Paul Weiland", "Comedy");
        collection.getMovie().add(movie);
        movie = constructMovieObject("Forgetting Sarah Marshall", "Nicholas Stoller", "Comedy");
        collection.getMovie().add(movie);
        movie = constructMovieObject("Harold and Kumar Escape From Guantanamo Bay", "Jon Hurwitz, Hayden Schlossberg", "Comedy and Sequel");
        collection.getMovie().add(movie);
        movie = constructMovieObject("The Visitor", "Tom McCarthy", "Comedy and Drama");
        collection.getMovie().add(movie);       
    }
    
    final MovieDetails constructMovieObject(String title, String direction, String genres) {        
        MovieDetails movie = new MovieDetails();
        movie.setTitle(title);
        movie.setRank(++rank);
        movie.setDirectedBy(direction);
        movie.setGenres(genres);
        return movie;
    }
}

The Singleton annotation ensures that only one instance of this class will be instantiated per web application. The movie collection resource will be populated when the first request to the movie list resource occurs. Remember this is RI specific feature and not part of JSR 311 API. The Path annotation defines the URI path to access the web resource. The resource is annotated using GET which populates top box office movies in the response. The response will be rendered using either XML or JSON. The default media type will be XML as it is the first one in the list provided to the ProduceMime annotation. If you need JSON response, you need to set the “Accept” header to “application/json” type. This will render JSON response. If you notice the response code, we are adding couple headers which basically disables caching in IE. You may set as many HTTP response headers on the response using the javax.ws.rs.core.Response class. This resource only implements the basic HTTP GET operation. Developing PUT, POST and DELETE operations on the resource can be quite easy too. The resource can be backed by a database where these HTTP operations can be easily translated into CRUD actions operating on the data.

The resource can now be deployed in a HTTP server. Let us use the Grizzly HTTP server to quickly deploy our resource and do some testing. Grizzly jars are bundled as part of the Jersey distribution. Add the required jars to the classpath. The below code starts the HTTP server at port 9090.


import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
import static java.lang.System.*;

public class Server {
    public static void main(String[] args) throws Exception {        
        SelectorThread server = GrizzlyServerFactory.create("http://localhost:9090/");
        out.println("Server running, hit return to stop...");
        in.read();
        out.println("Stopping server");
        server.stopEndpoint();
        exit(0);
        out.println("Server stopped");
    }    
}

If you want to use this within a servlet container, you can use the standard web.xml to describe a Jersey webapp.


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

Now you can access the URL “http://localhost:9090/boxoffice/movies” from a browser.
You should see the XML response as shown below.


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<movieCollection>
	<movie rank="1">
		<title>Indiana Jones and the Kingdom of the Crystal Skull</title>
		<genres>Action/Adventure and Sequel</genres>
		<directedBy>Steven Spielberg</directedBy>
	</movie>
	<movie rank="2">
		<title>The Chronicles of Narnia: Prince Caspian</title>
		<genres>Action/Adventure, Science Fiction/Fantasy and Adaptation</genres>
		<directedBy>Andrew Adamson</directedBy>
	</movie>
	<movie rank="3">
		<title>Iron Man</title>
		<genres>Action/Adventure, Science Fiction/Fantasy and Adaptation</genres>
		<directedBy>Jon Favreau</directedBy>
	</movie>
	<movie rank="4">
		<title>What Happens in Vegas</title>
		<genres>Comedy</genres>
		<directedBy>Tom Vaughan</directedBy>
	</movie>
	<movie rank="5">
		<title>Speed Racer</title>
		<genres>Action/Adventure, Science Fiction/Fantasy and Adaptation</genres>
		<directedBy>Larry Wachowski, Andy Wachowski</directedBy>
	</movie>
	<movie rank="6">
		<title>Baby Mama</title>
		<genres>Comedy</genres>
		<directedBy>Michael McCullers</directedBy>
	</movie>
	<movie rank="7">
		<title>Made of Honor</title>
		<genres>Comedy</genres>
		<directedBy>Paul Weiland</directedBy>
	</movie>
	<movie rank="8">
		<title>Forgetting Sarah Marshall</title>
		<genres>Comedy</genres>
		<directedBy>Nicholas Stoller</directedBy>
	</movie>
	<movie rank="9">
		<title>Harold and Kumar Escape From Guantanamo Bay</title>
		<genres>Comedy and Sequel</genres>
		<directedBy>Jon Hurwitz, Hayden Schlossberg</directedBy>
	</movie>
	<movie rank="10">
		<title>The Visitor</title>
		<genres>Comedy and Drama</genres>
		<directedBy>Tom McCarthy</directedBy>
	</movie>
</movieCollection>

In order to test the JSON rendering, let us write a Client developed using HTTP Client library. You need to have commons-logging and commons-codec in addition to the commons-httpclient jars in the classpath.


import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.httpclient.Header;
import static java.lang.System.out;

public class Client {

  public static void main(String args[]) throws Exception {
    String url = "http://localhost:9090/boxoffice/movies";
    out.println("Sent HTTP GET request to the endpoint " + url);
    GetMethod get = new GetMethod(url);
    get.addRequestHeader("Accept", "application/json");
    HttpClient httpclient = new HttpClient();

    StringBuffer buffer = null;
    try {
      httpclient.executeMethod(get);

      if (get.getStatusCode() == HttpStatus.SC_OK) {
        InputStream is = get.getResponseBodyAsStream();
        Header[] headers = get.getResponseHeaders();
        for (Header header : headers) {
            out.println(header.getName() + " : " + header.getValue());
        }
        BufferedReader in = new BufferedReader(new InputStreamReader(is));
        buffer = new StringBuffer();
        String line = null;
        while ((line = in.readLine()) != null) {
          buffer.append(line);
        }
      } else {
        out.println("GET action raised an error: " + get.getStatusLine());
      }

    } finally {
      // Release current connection to the connection pool once you are done
      get.releaseConnection();
    }

    out.println("JSON RESPONSE : " + buffer.toString());
  }
}

The output of this program is shown below.


Sent HTTP GET request to the endpoint http://localhost:9090/boxoffice/movies
Pragma : no-cache
Expires : Wed, 31 Dec 1969 17:00:00 MST
Content-Type : application/json
Transfer-Encoding : chunked
Date : Tue, 27 May 2008 04:13:28 GMT
JSON RESPONSE : 
{"movieCollection":
{"movie":[
{"@rank":"1","title":"Indiana Jones and the Kingdom of the Crystal Skull","genres":"Action/Adventure and Sequel","directedBy":"Steven Spielberg"},
{"@rank":"2","title":"The Chronicles of Narnia: Prince Caspian","genres":"Action/Adventure, Science Fiction/Fantasy and Adaptation","directedBy":"Andrew Adamson"},
{"@rank":"3","title":"Iron Man","genres":"Action/Adventure, Science Fiction/Fantasy and Adaptation","directedBy":"Jon Favreau"},
{"@rank":"4","title":"What Happens in Vegas","genres":"Comedy","directedBy":"Tom Vaughan"},
{"@rank":"5","title":"Speed Racer","genres":"Action/Adventure, Science Fiction/Fantasy and Adaptation","directedBy":"Larry Wachowski, Andy Wachowski"},
{"@rank":"6","title":"Baby Mama","genres":"Comedy","directedBy":"Michael McCullers"},
{"@rank":"7","title":"Made of Honor","genres":"Comedy","directedBy":"Paul Weiland"},
{"@rank":"8","title":"Forgetting Sarah Marshall","genres":"Comedy","directedBy":"Nicholas Stoller"},
{"@rank":"9","title":"Harold and Kumar Escape From Guantanamo Bay","genres":"Comedy and Sequel","directedBy":"Jon Hurwitz, Hayden Schlossberg"},
{"@rank":"10","title":"The Visitor","genres":"Comedy and Drama","directedBy":"Tom McCarthy"}
]}
}

You can also use curl to test the above behavior. This approach is easier than the earlier one, if you have access to curl.


curl -i -H "Accept: application/json" http://localhost:9090/boxoffice/movies

WADL can be accessed at http://localhost:9090/application.wadl. This provides basic resource description as shown below.


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://research.sun.com/wadl/2006/10">
	<resources base="http://localhost:9090/">
		<resource path="/boxoffice">
			<resource path="/movies">
				<method name="GET">
					<response>
						<representation mediaType="application/xml"/>
						<representation mediaType="application/json"/>
					</response>
				</method>
			</resource>
		</resource>
	</resources>
</application>

RESTful web services has more potential use cases and Jersey provides extensions to the library so that developers can customize as per their needs.
The resource provider SPI allows pluggable custom resource providers. JAX-RS also supports pluggable type system for encoding and decoding of a Java type to/from an entity of an HTTP response/request. Some of these advanced concepts deserve more space and possibly another post.

Possibly Related Posts:


Apache CXF is an open services framework. It is more than just a Web Services stack. Its simple, powerful, and promising. Today, the CXF project graduated from incubation as a top-level Apache project. It has made several stable releases since its incubation. I know projects which still prefer to use first generation Axis due to its simplicity. XFire is also popular like Axis, but made a significant difference in providing highly performing XML based web services. The power and simplicity of XFire and feature rich Celtix (branded as ESB) merged to form Apache CeltixXFire (CXF).

IMO, CXF wins over Axis2 programming model. CXF documentation is an area for improvement. It has a lot of powerful undocumented features. The primary source of getting these information is cxf-user mailing list. The people are amazingly helpful. It features most of its core developers offering their timely help to the user community.

I developed a simple loan processing application based on JPA and CXF using the NetBeans 6.1. This is a simple standalone Java application, but still demonstrates the power of persistence and integration technologies in combination with a powerful IDE which provides a platform for developing quality software. I specifically used NetBeans for this illustration because its very productive and intuitive for developer of any class. MySQL support in NetBeans 6.1 has improved phenomenally. You can manage the MySQL database from within NetBeans IDE. This is yet another useful integration for RAD apart from many of its other key offerings which I am not going to discuss in this post.

You can access this step-by-step tutorial in the NetBeans community website.

Rock with NetBeans 6.1 today and contribute to the community.

NetBeans 6.1

(4/30/08 Update): Changed the new Apache CXF project URL. Also, updated the tutorial to use Apache CXF 2.1 release and NetBeans 6.1 final release and used soapUI plugin for testing secure web services. Thanks to Vidhya for suggesting soapUI tool. Nice tooling around web services testing.

Possibly Related Posts: