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:


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:


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:


Spring Framework 3.0 M1 was released last week during the SpringOne Americas event, which looks very promising with EL and REST becoming top themes. Lot of coverage on the event has been blogged, and you can find one of my favorite entry by Solomon here. Juergen outlines some of the new features implemented in this milestone release here.

SVN repository for Spring Framework 3.0 is hosted by SpringSource at https://src.springframework.org/svn/spring-framework/trunk.
The build requires JDK 6, but Spring Framework 3.0 requires Java 5 or above. Not sure how this would work when building with a later Java version.

Other interesting note is that some parts of Spring JavaConfig will be merged with Spring Core. SJC is really powerful and you can almost do everything XML configuration can do. It would be nice to see both XML and Java configuration part of the core framework.

Possibly Related Posts:


It has been a wonderful week with my ramblings with CXF and SSL. But, it all ended up in good mood. Sometimes, it is so hard to find how to use certain features with CXF. I am a great fan of CXF because of its simplicity and intuitive feature set. It provides an array of integration points with best of breed technologies, but sadly most of the crux is buried under its system tests or CXF user mailing list archive. The documentation front is really lacking its meat from the “source”. So, people end up debugging CXF which is the only weapon to invade your configuration issues.

The community around CXF is very healthy and gained substantial momentum in all fronts for the past year or so. CXF user mailing list is such an awful resource. Its the first resort to get your queries answered and if you don’t get a response, don’t wait, just debug yourself. The resources are very limited and their support is really appreciated.

I wanted to contribute a sample for programmatic setup of SSL with CXF using embedded Jetty, so people get it right at the first time. So, I ported the sample “wsdl_first_https” which is bundled with CXF 2.1.1 distribution to a “java_first_https” scenario. The “wsdl_first_https” sample uses Spring based configuration for SSL. Though this scenario seem to fit well where the application uses spring as its underlying DI framework. For people who wish not to care about Spring, why would they want to unnecessarily bundle Spring jars for achieving CXF/SSL support in their application. But, this all depends on what approach you chose to build your services with CXF (either using Spring config or Java APIs). This is just a simple case for building this demo in “no-Spring” way. It opens a whole world of possibilities for dynamically configuring security in your embedded Jetty container.

Lets take the simple Greeter service which greets the client with its name sent as an input parameter.

@WebService
public interface Greeter {

    String greetMe(String me);
}


@javax.jws.WebService(name = "Greeter", serviceName = "SOAPService",
                      targetNamespace = "http://apache.org/hello_world_soap_http")
public class GreeterImpl implements Greeter {
    private static final Logger LOG =
        Logger.getLogger(GreeterImpl.class.getPackage().getName());

    public String greetMe(String me) {
        LOG.info("Executing operation greetMe");
        System.out.println("Executing operation greetMe");
        System.out.println("Message received: " + me + "\n");
        return "Hello " + me;
    }
}

The demo uses the same client and server certificate stores shipped with “wsdl_first_https” demo in CXF 2.1.1. So, don’t forget to include them when running this demo.

The Server side SSL configuration using CXF Java APIs is shown below.

public class Server {

    protected Server() throws Exception {
        System.out.println("Starting Server");
        String address = "https://localhost:9001/SoapContext/SoapPort";
        JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
        sf.setServiceClass(Greeter.class);
        sf.setAddress(address);

        Greeter implementor = new GreeterImpl();
        sf.getServiceFactory().setInvoker(new BeanInvoker(implementor));

        sf = configureSSLOnTheServer(sf, 9001);
        org.apache.cxf.endpoint.Server server = sf.create();
        String endpoint = server.getEndpoint().getEndpointInfo().getAddress();
        
        System.out.println("Server started at " + endpoint);
    }

    private JaxWsServerFactoryBean configureSSLOnTheServer(JaxWsServerFactoryBean sf, int port) {
        try {
            TLSServerParameters tlsParams = new TLSServerParameters();
            KeyStore keyStore = KeyStore.getInstance("JKS");
            String password = "password";
            File truststore = new File("certs\\cherry.jks");
            keyStore.load(new FileInputStream(truststore), password.toCharArray());
            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore, password.toCharArray());
            KeyManager[] km = keyFactory.getKeyManagers();
            tlsParams.setKeyManagers(km);

            truststore = new File("certs\\truststore.jks");
            keyStore.load(new FileInputStream(truststore), password.toCharArray());
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(keyStore);
            TrustManager[] tm = trustFactory.getTrustManagers();
            tlsParams.setTrustManagers(tm);
            FiltersType filter = new FiltersType();
            filter.getInclude().add(".*_EXPORT_.*");
            filter.getInclude().add(".*_EXPORT1024_.*");
            filter.getInclude().add(".*_WITH_DES_.*");
            filter.getInclude().add(".*_WITH_NULL_.*");
            filter.getExclude().add(".*_DH_anon_.*");
            tlsParams.setCipherSuitesFilter(filter);
            ClientAuthentication ca = new ClientAuthentication();
            ca.setRequired(true);
            ca.setWant(true);
            tlsParams.setClientAuthentication(ca);
            JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory();
            factory.setTLSServerParametersForPort(port, tlsParams);
        } catch (KeyStoreException kse) {
            System.out.println("Security configuration failed with the following: " + kse.getCause());
        } catch (NoSuchAlgorithmException nsa) {
            System.out.println("Security configuration failed with the following: " + nsa.getCause());
        } catch (FileNotFoundException fnfe) {
            System.out.println("Security configuration failed with the following: " + fnfe.getCause());
        } catch (UnrecoverableKeyException uke) {
            System.out.println("Security configuration failed with the following: " + uke.getCause());
        } catch (CertificateException ce) {
            System.out.println("Security configuration failed with the following: " + ce.getCause());
        } catch (GeneralSecurityException gse) {
            System.out.println("Security configuration failed with the following: " + gse.getCause());
        } catch (IOException ioe) {
            System.out.println("Security configuration failed with the following: " + ioe.getCause());
        }

        return sf;
    }

    public static void main(String args[]) throws Exception {
        System.out.println("The server's security configuration will be done programatically.");
        System.out.println();
        new Server();
        System.out.println("Server ready...");

        Thread.sleep(5 * 60 * 1000);
        System.out.println("Server exiting");
        System.exit(0);
    }
}

The SSL server configuration using Java APIs exactly maps to spring config “CherryServer.cxf” used in the distribution.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  ** This file configures the Cherry Server.
 -->

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sec="http://cxf.apache.org/configuration/security"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration"
  xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
  xsi:schemaLocation="
       http://cxf.apache.org/configuration/security  		      http://cxf.apache.org/schemas/configuration/security.xsd
            http://cxf.apache.org/transports/http/configuration
            http://cxf.apache.org/schemas/configuration/http-conf.xsd
            http://cxf.apache.org/transports/http-jetty/configuration
            http://cxf.apache.org/schemas/configuration/http-jetty.xsd
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

  <http:destination name="{http://apache.org/hello_world_soap_http}GreeterPort.http-destination"> 
  </http:destination>

  <httpj:engine-factory>
   <httpj:engine port="9001">
    <httpj:tlsServerParameters>
      <sec:keyManagers keyPassword="password">
           <sec:keyStore type="JKS" password="password" 
                file="certs/cherry.jks"/>
      </sec:keyManagers>
      <sec:trustManagers>
          <sec:keyStore type="JKS" password="password"
               file="certs/truststore.jks"/>
      </sec:trustManagers>
      <sec:cipherSuitesFilter>
        <!-- these filters ensure that a ciphersuite with
          export-suitable or null encryption is used,
          but exclude anonymous Diffie-Hellman key change as
          this is vulnerable to man-in-the-middle attacks -->
        <sec:include>.*_EXPORT_.*</sec:include>
        <sec:include>.*_EXPORT1024_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:include>.*_WITH_NULL_.*</sec:include>
        <sec:exclude>.*_DH_anon_.*</sec:exclude>
      </sec:cipherSuitesFilter>
      <sec:clientAuthentication want="true" required="true"/>
    </httpj:tlsServerParameters>
   </httpj:engine>
  </httpj:engine-factory>
</beans>

The Client side SSL configuration using CXF Java APIs is shown below.

public class Client {

    private static void configureSSLOnTheClient(Object c) {
        org.apache.cxf.endpoint.Client client = ClientProxy.getClient(c);
        HTTPConduit httpConduit = (HTTPConduit) client.getConduit();

        try {
            TLSClientParameters tlsParams = new TLSClientParameters();
            tlsParams.setDisableCNCheck(true);

            KeyStore keyStore = KeyStore.getInstance("JKS");
            String trustpass = "password";

            File truststore = new File("certs\\truststore.jks");
            keyStore.load(new FileInputStream(truststore), trustpass.toCharArray());
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(keyStore);
            TrustManager[] tm = trustFactory.getTrustManagers();
            tlsParams.setTrustManagers(tm);

            truststore = new File("certs\\wibble.jks");
            keyStore.load(new FileInputStream(truststore), trustpass.toCharArray());
            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore, trustpass.toCharArray());
            KeyManager[] km = keyFactory.getKeyManagers();
            tlsParams.setKeyManagers(km);

            FiltersType filter = new FiltersType();
            filter.getInclude().add(".*_EXPORT_.*");
            filter.getInclude().add(".*_EXPORT1024_.*");
            filter.getInclude().add(".*_WITH_DES_.*");
            filter.getInclude().add(".*_WITH_NULL_.*");
            filter.getExclude().add(".*_DH_anon_.*");
            tlsParams.setCipherSuitesFilter(filter);

            httpConduit.setTlsClientParameters(tlsParams);
        } catch (KeyStoreException kse) {
            System.out.println("Security configuration failed with the following: " + kse.getCause());
        } catch (NoSuchAlgorithmException nsa) {
            System.out.println("Security configuration failed with the following: " + nsa.getCause());
        } catch (FileNotFoundException fnfe) {
            System.out.println("Security configuration failed with the following: " + fnfe.getCause());
        } catch (UnrecoverableKeyException uke) {
            System.out.println("Security configuration failed with the following: " + uke.getCause());
        } catch (CertificateException ce) {
            System.out.println("Security configuration failed with the following: " + ce.getCause());
        } catch (GeneralSecurityException gse) {
            System.out.println("Security configuration failed with the following: " + gse.getCause());
        } catch (IOException ioe) {
            System.out.println("Security configuration failed with the following: " + ioe.getCause());
        }
    }

    public static void main(String args[]) {
        System.out.println("The client's security configuration will be done programatically.");
        System.out.println();
        String address = "https://localhost:9001/SoapContext/SoapPort";
        JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
        proxyFactory.setServiceClass(Greeter.class);
        proxyFactory.setAddress(address);

        Greeter client = (Greeter) proxyFactory.create();
        configureSSLOnTheClient(client);

        System.out.println("Invoking greetMe...");
        try {
            String resp = client.greetMe(System.getProperty("user.name"));
            System.out.println("Server responded with: " + resp);
            System.out.println();

        } catch (Exception e) {
            System.out.println("Invocation failed with the following: " + e.getCause());
            System.out.println();
        }

    }
}

The SSL client configuration using Java APIs exactly maps to spring config “WibbleClient.cxf” used in the distribution.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  ** This file configures the Wibble Client
  -->

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sec="http://cxf.apache.org/configuration/security"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
  xsi:schemaLocation="
           http://cxf.apache.org/configuration/security
           http://cxf.apache.org/schemas/configuration/security.xsd
           http://cxf.apache.org/transports/http/configuration
           http://cxf.apache.org/schemas/configuration/http-conf.xsd
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

  <http:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit">
    <http:tlsClientParameters disableCNCheck="true">
      <sec:trustManagers>
          <sec:keyStore type="JKS" password="password"
               file="certs/truststore.jks"/>
      </sec:trustManagers>
      <sec:keyManagers keyPassword="password">
           <sec:keyStore type="JKS" password="password" 
                file="certs/wibble.jks"/>
      </sec:keyManagers>
      <sec:cipherSuitesFilter>
        <!-- these filters ensure that a ciphersuite with
          export-suitable or null encryption is used,
          but exclude anonymous Diffie-Hellman key change as
          this is vulnerable to man-in-the-middle attacks -->
        <sec:include>.*_EXPORT_.*</sec:include>
        <sec:include>.*_EXPORT1024_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:include>.*_WITH_NULL_.*</sec:include>
        <sec:exclude>.*_DH_anon_.*</sec:exclude>
      </sec:cipherSuitesFilter>
    </http:tlsClientParameters>
   </http:conduit>
</beans> 

You can download the NetBeans project here.

Possibly Related Posts:


For those who missed JavaOne this year, Sun has published the conference slides here a month later. I find these very useful especially for Java geeks as it provides an opportunity to learn some of the technical advancements around the Java platform and the momentum around the technology is healthy.

My top contenders this year were:
Building SOA Applications with JAX-WS, JAXRS, JAXB, and Ajax by Mark Hansen
Let’s Resync Concurrency Features in JDK™ 7 by Brian Goetz
Defective Java™ Code: Turning WTF code into a learning experience by William Pugh
More Effective Java by Joshua Bloch
Design Patterns Reconsidered by Alex Miller
JAX-RS: The Java™ API for RESTful Web Services by Marc Hadley and Paul Sandoz
Top 10 Patterns for Scaling Out Java™ Applications by Cameron Purdy
Groovy and Grails: Changing the Landscape of Java™ Platform, Enterprise Edition (Java EE Platform) Patterns by Graeme Rocher
Choosing a Java Web Framework: A Comparison by Richard Pack
Java™ Servlet 3.0 API: What’s new and exciting by Rajiv Mordani

Possibly Related Posts:


In my earlier blog entry on Jersey, I used HTTPClient API and curl command line utility as the clients. I had not mentioned about the Jersey Client API. It is part of the Jersey distribution. I would prefer using Jersey Client API as it is modeled around the concepts of JAX-RS spec. Let us quickly re-write our client using the Jersey API and see how easy is to write a client in Java.


import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import restful.impl.jaxb.MovieCollection;
import restful.impl.jaxb.MovieDetails;

import static java.lang.System.out;

public class JerseyClient {
    public static void main(String args[]) {
        Client client = Client.create();
        WebResource r = client.resource("http://localhost:9090/boxoffice/movies");
        MovieCollection movies = r.accept("application/xml").get(MovieCollection.class);
        for (MovieDetails movie : movies.getMovie()) {
            out.println("Title : " + movie.getTitle());
            out.println("Genres : " + movie.getGenres());
            out.println("Directed By : " + movie.getDirectedBy());
            out.println("Rank : " + movie.getRank());
        }        
    }
}

This program will print all the top box office movie details in the console. I will be discussing more about this client API in future posts.

The Jersey user forum is the first place to check for any Jersey related issues. The Jersey team is amazingly helpful in resolving any issues. You can also visit Paul’s blog here, Marc’s blog here and Jakub’s blog here. You can bookmark these blogs as they are really informative. Grab the Jersey team’s JavaOne 2008 presentation slide deck here.

You can download the NetBeans project for this sample movie application here.

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:


Message brokering markets were once dominated by heavy weights and required huge investments by enterprises for implementing such solutions. Vendors made huge bucks out of it by selling such solutions and support. Is this still considered a niche market? I personally don’t think so. I am definitely not against vendors offering such solutions, but the point I am making here is whether this market still deserves huge investments when mature open source alternatives are available.

You possibly have two options. Buy an OTS solution and forget about maintenance nightmare, typically argued by the so called “Enterprise Architects” because they assume it’s a safe bet. Such decisions make your enterprise tightly coupled with these kinds of solutions. You may never know sometimes you end up paying huge bucks for feature requests and lobbying for license costs and finally stuck with the custom solution for years. This choice comes at a huge price, but at some convenience as one need not blame you for lack of a feature or offering a buggy solution. Does this convenience worth anymore?

Historically, enterprises chose such solutions mainly because of the support and maintenance that comes bundled with the solution and they always awarded such contracts to market leaders. Finally, when the vendor decides to release newer versions of the product, customers had to upgrade and sometimes forcefully migrated to a newer version, just because earlier version does not scale or does not support a feature or possibly discontinued.

On the contrary, some enterprises (mostly small to mid-size) consider the option of implementing such messaging solution using open source alternatives and people who make this choice need to be really smart because of the liability that comes with their decision. In most situations, such decisions are made by experts who are aware of the complexity involved in such undertaking. For most part it will be straightforward to adopt such a solution and sometimes it could be painful when there is lack of technical expertise within the enterprise. But, it will definitely be a rewarding experience when working with smart people in such integrations. The open source community offers their best support in resolving any issues. This option will surely benefit enterprises in terms of licensing and support costs. But, the key assumption here is that you bet on your expertise on such engagements.

ActiveMQ 5.1Open source solutions are increasingly becoming a compelling choice for enterprises because of their mature feature set and wide adoption backed by strong user community. ActiveMQ is one such project in ASF where you see tons of features available to its users. I had experience working on similar commercial products, but ActiveMQ is absolutely mind blowing. Simplicity wins the heart of enterprise developers. Some commercial offerings take days to install and configure which requires special hardware (high-end servers) and sometimes on-site training from vendor. With projects like ActiveMQ, it is becoming more and more productive for teams to develop, test and roll out integration solutions in a shorter period of time. It just takes minutes to install and configure ActiveMQ in your desktop.

I recently downloaded ActiveMQ and played around with it. Let us analyze their startup messages and dissect some of them and see what they offer. This is just a tip of an iceberg.


 (1) D:\\apache-activemq-5.1.0\\bin>activemq.bat
 (2) ACTIVEMQ_HOME: D:\\apache-activemq-5.1.0\\bin\\..
 (3) ACTIVEMQ_BASE: D:\\apache-activemq-5.1.0\\bin\\..
 (4) Loading message broker from: xbean:activemq.xml
 (5) INFO  BrokerService                  - Using Persistence Adapter: AMQPersistenceAdapter(D:\\apache-activemq-5.1.0\\bin\\..\\data)
 (6) INFO  BrokerService                  - ActiveMQ 5.1.0 JMS Message Broker (localhost) is starting
 (7) INFO  BrokerService                  - For help or more information please see:http://activemq.apache.org/
 (8) INFO  AMQPersistenceAdapter          - AMQStore starting using directory: D:\\apache-activemq-5.1.0\\bin\\..\\data
 (9) INFO  KahaStore                      - Kaha Store using data directory D:\\apache-activemq-5.1.0\\bin\\..\\data\\kr-store\\state
(10) INFO  AMQPersistenceAdapter          - Active data files: []
(11) INFO  KahaStore                      - Kaha Store using data directory D:\\apache-activemq-5.1.0\\bin\\..\\data\\kr-store\\data
(12) INFO  TransportServerThreadSupport   - Listening for connections at: tcp://nandi:61616
(13) INFO  TransportConnector             - Connector openwire Started
(14) INFO  TransportServerThreadSupport   - Listening for connections at: ssl://nandi:61617
(15) INFO  TransportConnector             - Connector ssl Started
(16) INFO  TransportServerThreadSupport   - Listening for connections at: stomp://nandi:61613
(17) INFO  TransportConnector             - Connector stomp Started
(18) INFO  TransportServerThreadSupport   - Listening for connections at: xmpp://nandi:61222
(19) INFO  TransportConnector             - Connector xmpp Started
(20) INFO  NetworkConnector               - Network Connector default-nc Started
(21) INFO  BrokerService                  - ActiveMQ JMS Message Broker (localhost, ID:nandi-64041-1211065443740-0:0) started
(22) INFO  log                            - Logging to org.slf4j.impl.JCLLoggerAdapter(org.mortbay.log) via org.mortbay.log.Slf4jLog
(23) INFO  log                            - jetty-6.1.9
(24) INFO  WebConsoleStarter              - ActiveMQ WebConsole initialized.
(25) INFO  /admin                         - Initializing Spring FrameworkServlet 'dispatcher'
(26) INFO  log                            - ActiveMQ Console at http://0.0.0.0:8161/admin
(27) INFO  log                            - ActiveMQ Web Demos at http://0.0.0.0:8161/demo
(28) INFO  log                            - RESTful file access application at http://0.0.0.0:8161/fileserver
(29) INFO  log                            - Started SelectChannelConnector@0.0.0.0:8161
(30) INFO  FailoverTransport              - Successfully connected to tcp://localhost:61616

The following features are enabled by default when an ActiveMQ broker is started.

Line 8 – AMQStore is the default storage for AcitveMQ 5 and above.
Line 11 – KahaStore is an optimal performance storage solution used for message persistance.
Line 12 – OpenWire is a cross language Wire Protocol which allows native access to ActiveMQ from a number of different languages and platforms.
Line 14 – SSL transport allows clients to connect to a remote ActiveMQ broker using SSL over a TCP socket.
Line 16 – Stomp is used by non-Java clients talk to ActiveMQ server and other message brokers.
Line 18 – XMPP is used to connect to the broker & send and receive messages (Jabber).
Line 26 – ActiveMQ Web Console for managing the broker services such as queues, topics and subscriptions.
Line 28 – REST-ful API to messaging (JMS). Browsing of queues implemented using pluggable views such as ATOM and RSS feeds.
Line 30 – Failover transport used for reconnection.

There are tons of other features which is beyond the scope of this discussion. Some of the notable features include embedded message broker which comes handy in unit testing. You can refer to its complete feature set here. There are sub-projects within ActiveMQ such as NMS (for .NET) and CMS (for C++) which provides unified access to ActiveMQ from other programming language environments. Camel is another sub-project which implements enterprise integration patterns. Its a topic of interest for another blog entry.

This is more than compelling to adopt this solution. Enterprises sometimes face real complexity in terms of cost of ownership issues when trying to adopt such open source solutions. But, this could be overcome by choosing support offerings from companies like IONA, Covalent (now part of SpringSource) and OpenLogic. This may still work out to be cost effective when compared to OTS solutions. The market should favor such healthy adoption.

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: