Monday, December 3, 2012

REST API using CXF and JBoss-WS


A couple of months ago, Fandry was nice enough to post a simple example of REST services using CXF and Spring in JBoss 7.  His goal here was to use CXF as the sole implementation for both REST and SOAP-based services.

Here's a neighborly update from Finagle to get Fandry's example up and running, starting from a fresh JBoss AS 7.1.1 install and the shiny new JBossWS 4.1.0 release from the JBoss Community.

Then we'll have to add some dependencies that JBossWS misses.  To do this, we're going to modify the [JBOSS_INSTALL_DIR]/modules directory to fix some needed dependencies that JBossWS doesn't add during installation.


Step 1:  Install JBoss-WS


Starting from a fresh, un-Finagled JBoss install, download jbossws-cxf-4.1.0.Final from here.

Unzip the file and follow the installation directions found in [unzipped]/docs/Install.txt.  This will require ANT.  Make sure you add the -Dspring=true property option during installation.

Step 2:  Get additional dependencies

First, we need some additional Spring JARs that were left out of the spring module that JBossWS created.  For JBossWS 4.1.0.Final, it appears that Spring version 3.0.7.RELEASE is the correct version to use.  Different versions of JBossWS may vary (check the [JBOSS_INSTALL_DIR]/modules/org/springframework/spring/main/spring-core.jar/META-INF/MANIFEST.MF to verify the needed version of Spring)

spring-web-3.0.7.RELEASE.jar:
Download from maven repo

Rename to spring-web.jar, and place into [JBOSS_INSTALL_DIR]/modules/org/springframework/spring/main

cxf-rt-frontend-jaxrs-2.6.0.jar:
Download from maven repo

Rename to cxf-rt-frontend-jaxrs.jar, and place into [JBOSS_INSTALL_DIR]/modules/org/apache/cxf/impl/main


Step 3:  Edit JBoss Modules files:


a.  Edit [JBOSS_INSTALL_DIR]/modules/org/springframework/spring/main/module.xml

Add a resource-root element referencing the spring-web.jar that you've added, and a dependency element referencing the javax.servlet.api module.  The file should now look like this:

[JBOSS_INSTALL_DIR]/modules/org/springframework/spring/main/module.xml:
<module xmlns="urn:jboss:module:1.1" name="org.springframework.spring">
    <resources>
        <resource-root path="spring-aop.jar"/>
        <resource-root path="spring-asm.jar"/>
        <resource-root path="spring-beans.jar"/>
        <resource-root path="spring-context.jar"/>
        <resource-root path="spring-core.jar"/>
        <resource-root path="spring-expression.jar"/>
        <resource-root path="spring-jms.jar"/>
        <resource-root path="spring-tx.jar"/>
        <resource-root path="spring-web.jar"/> <!-- Added -->  
    </resources>
    <dependencies>
        <module name="javax.api" />
        <module name="javax.servlet.api" />  <!-- Added -->
        <module name="javax.jms.api" />
        <module name="javax.annotation.api" />
        <module name="org.apache.commons.logging" />
        <module name="org.jboss.vfs" />
    </dependencies>
</module>



b.  Edit [JBOSS_INSTALL_DIR]/modules/org/apache/cxf/impl/main/module.xml

Add a resource-root element referencing the cxf-rt-frontend-jaxrs.jar that you've added. The file should now look like this:

[JBOSS_INSTALL_DIR]/modules/org/apache/cxf/impl/main/module.xml:
<module xmlns="urn:jboss:module:1.1" name="org.apache.cxf.impl">
    <resources>
        <resource-root path="cxf-rt-bindings-coloc.jar"/>
        <resource-root path="cxf-rt-bindings-object.jar"/>
        <resource-root path="cxf-rt-bindings-soap.jar"/>
        <resource-root path="cxf-rt-bindings-xml.jar"/>
        <resource-root path="cxf-rt-core.jar"/>
        <resource-root path="cxf-rt-databinding-aegis.jar"/>
        <resource-root path="cxf-rt-databinding-jaxb.jar"/>
        <resource-root path="cxf-rt-frontend-jaxrs.jar"/> <!-- Added -->
        <resource-root path="cxf-rt-frontend-jaxws.jar"/>
        <resource-root path="cxf-rt-frontend-simple.jar"/>
        <resource-root path="cxf-rt-management.jar"/>
        <resource-root path="cxf-rt-transports-http.jar"/>
        <resource-root path="cxf-rt-transports-jms.jar"/>
        <resource-root path="cxf-rt-transports-local.jar"/>
        <resource-root path="cxf-rt-ws-addr.jar"/>
        <resource-root path="cxf-rt-ws-mex.jar"/>
        <resource-root path="cxf-rt-ws-policy.jar"/>
        <resource-root path="cxf-rt-ws-rm.jar"/>
        <resource-root path="cxf-rt-ws-security-jandex.jar"/>
        <resource-root path="cxf-rt-ws-security.jar"/>
        <resource-root path="cxf-services-sts-core.jar"/>
        <resource-root path="cxf-tools-common.jar"/>
        <resource-root path="cxf-tools-java2ws.jar"/>
        <resource-root path="cxf-tools-validator.jar"/>
        <resource-root path="cxf-tools-wsdlto-core.jar"/>
        <resource-root path="cxf-tools-wsdlto-databinding-jaxb.jar"/>
        <resource-root path="cxf-tools-wsdlto-frontend-jaxws.jar"/>
        <resource-root path="cxf-xjc-boolean.jar"/>
        <resource-root path="cxf-xjc-dv.jar"/>
        <resource-root path="cxf-xjc-ts.jar"/>
    </resources>
    <dependencies>
        <module name="asm.asm" />
        <module name="javax.api" />
        <module name="javax.annotation.api" />
        <module name="javax.jms.api" />
        <module name="javax.jws.api" />
        <module name="javax.mail.api" />
        <module name="javax.resource.api" />
        <module name="javax.servlet.api" />
        <module name="javax.wsdl4j.api" />
        <module name="javax.ws.rs.api" />
        <module name="javax.xml.bind.api" services="import"/>
        <module name="com.sun.xml.bind" services="import"/>
        <module name="javax.xml.soap.api" />
        <module name="javax.xml.stream.api" />
        <module name="javax.xml.ws.api" />
        <module name="org.apache.commons.lang" />
        <module name="org.apache.neethi" />
        <module name="org.apache.velocity" />
        <module name="org.apache.xml-resolver" />
        <module name="org.apache.ws.xmlschema" />
        <module name="org.apache.ws.security" />
        <module name="org.apache.santuario.xmlsec" />
        <module name="org.joda.time" />
        <module name="org.opensaml" />
        <module name="org.springframework.spring" optional="true" />
        <module name="org.apache.cxf" export="true"/>
    </dependencies>
</module>


Step 4:  Create and deploy Fandry's example project:


The REST (ha ha) of this project follows the same project structure as Fandry's example, as shown in the image at the beginning of his post.  I used JBoss Tools to create a sample Java EE Web Project using the jboss-javaee6-webapp archetype, and then just deleted everything I didn't need.

The only files that have changed are jboss-deployment-structure.xml and pom.xml, which have been changed to pull the dependencies from the JBoss Modules system we just updated.  Below are all files provided in the original example, beginning with the two files that have changed.

pom.xml:
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.finagle</groupId>
    <artifactId>jboss7-jbossws-cxf-spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>REST using JBossWS 4.1.0 and Spring</name>
    <description>For use on JBoss AS 7.1 / EAP 6, generated from the jboss-javaee6-webapp archetype</description>
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jboss.bom.version>1.0.0.Final</jboss.bom.version>
        <cxf.version>2.6.0</cxf.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <!-- JBoss distributes a complete set of Java EE 6 APIs -->
            <dependency>
                <groupId>org.jboss.bom</groupId>
                <artifactId>jboss-javaee-6.0-with-tools</artifactId>
                <version>${jboss.bom.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.jboss.bom</groupId>
                <artifactId>jboss-javaee-6.0-with-hibernate</artifactId>
                <version>${jboss.bom.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.jboss.spec.javax.ws.rs</groupId>
            <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1.1</version>
                <configuration>
                    <failOnMissingWebXml>true</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jboss.as.plugins</groupId>
                <artifactId>jboss-as-maven-plugin</artifactId>
                <version>7.1.1.Final</version>
            </plugin>
        </plugins>
    </build>
</project>


jboss-deployment-structure.xml:
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.apache.cxf" services="import" />
            <module name="org.apache.cxf.impl" services="import">
                <imports>
                    <include path="META-INF**" />
                    <include path="org**" />
                    <include path="schemas**" />
                </imports>
            </module>
            <module name="org.codehaus.jackson.jackson-jaxrs" services="import" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>


Original Code:

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

@XmlRootElement(name = "order")
public class Order {
 
    private String itemName;
    private int quantity;
    private String customerName;
    
    @XmlElement
    public String getItemName() { return itemName; }
    
    public void setItemName(String itemName) { this.itemName = itemName; }
    
    @XmlElement
    public int getQuantity() { return quantity; }
    
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
    
    @XmlElement
    public String getCustomerName() { return customerName; }
    
    public void setCustomerName(String customerName) { this.customerName = customerName; }
}
 
class OrderList
import java.util.ArrayList;
import java.util.List;

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

@XmlRootElement(name = "OrderList")
public class OrderList {

    List orders;
   
    @XmlElement(name = "order")
    public List getOrder() {
        if (orders == null) {
            orders = new ArrayList();
        }
        return this.orders;
    }
}
 
The web.xml file specifies only the CXF servlet:
<web-app>
    <display-name>Archetype Created Web Application</display-name>

 <servlet>
  <servlet-name>CXFServlet</servlet-name>
  <servlet-class>
   org.apache.cxf.transport.servlet.CXFServlet
  </servlet-class>
   <load-on-startup>1</load-on-startup> 
 </servlet>

 <servlet-mapping>
  <servlet-name>CXFServlet</servlet-name>
  <url-pattern>/api/*</url-pattern>
 </servlet-mapping>
 
</web-app> 
 
The file cxf-servlet.xml specify the service bean and the JAX-RS providers:
 <beans 
    xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:beans='http://www.springframework.org/schema/beans'
 xmlns:jaxrs="http://cxf.apache.org/jaxrs"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 http://cxf.apache.org/jaxrs 
 http://cxf.apache.org/schemas/jaxrs.xsd
 http://www.springframework.org/schema/context 
 http://www.springframework.org/schema/context/spring-context.xsd">
  

  <jaxrs:server id="restContainer" address="/" >
  
            <jaxrs:serviceBeans>
        <ref bean="simpleService" />
     </jaxrs:serviceBeans>  
     
           <jaxrs:providers>
               <ref bean="jaxbProvider"/>
               <ref bean="jsonProvider"/> 
           </jaxrs:providers>
      
        </jaxrs:server>
     
     <bean id="simpleService" class="com.acme.poc.restapi.cxf.service.SimpleServiceImpl" />
  <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"/>  
  <bean id="jsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
  
</beans> 
 
The SimpleService interface: 
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;  

@Path("/order/")
public interface SimpleService {
 @GET
 @Produces("application/xml")
 @Path("{orderId}")
 public Order getOrderXml(@PathParam ("orderId") int id);
 
 @GET
 @Produces("application/json") 
 @Path("/")
 public Order getOrderJson(@QueryParam("id") @DefaultValue("-1") String strId);
 
 @GET
 @Produces("application/xml")
 @Path("all")
 public OrderList getAllOrders();
} 
 
The SimpleServiceImpl class: 
import java.util.ArrayList;
import java.util.List;

public class SimpleServiceImpl implements SimpleService {
 
 List list = new ArrayList();
 
 public SimpleServiceImpl () {
         Order order = new Order();
         order.setItemName("Veggie Pizza");
         order.setQuantity(9);
         order.setCustomerName("OptumInsight");
         list.add(order);
         
         order = new Order();
         order.setItemName("Green Salad");
         order.setQuantity(2);
         order.setCustomerName("OptumInsight");
         list.add(order);
  }
 
 @Override
    public Order getOrderXml(int id) {
   return getOrder(id);
    }
 
 @Override
 public Order getOrderJson(String strId) {
  int id = Integer.valueOf(strId);
  return getOrder(id);
 }
 
 @Override
 public OrderList getAllOrders() {
  OrderList fullList = new OrderList();
        for(Order order : list) {
         fullList.getOrder().add(order);
        }
        return fullList;
 }
 
 // Common method returning an Order POJO
 public Order getOrder(int id) {
  if ((id > 0) && (id <= list.size())) {
   return list.get(id - 1);
  }
         else
          return null;
 }
}









2 comments:

  1. Thank you for this! Appreciate it!

    -Francois

    ReplyDelete
  2. Hi, I have tried the steps mentioned on Fandry's page for deploying a REST-ful web-service project using Apache CXF 2.6.2. I have put the cxf-2.6.2.jar file in the org/apache/cxf/main folder and done the entries in the module.xml as advised by you. I have also put the Spring framework JARs after creating the springframework/main folder as advised by him and created the entries in the module.xml there. However, I'm getting the following error message:

    Could not configure component org.apache.cxf.wsn.client.Publisher

    Caused by: org.jboss.as.server.deployment.DeploymentUnitProcessingException: JBA
    S011054: Could not find default constructor for class org.apache.cxf.wsn.client.
    Publisher

    "WebService.war".INSTALL: org.jboss.msc.service.StartException in service jboss.deployment.unit."WebService.war".INSTALL: Failed to process phase INSTALL of deployment "WebService.war"

    Do I need to also deploy the other CXF JARs from the 2.6.2 bundle and create entries for them in the module.xml?

    I haven't made any changes to the jboss-deployment-structure XML yet - is that mandatory for CXF to work?

    ReplyDelete