Showing posts with label Spring. Show all posts
Showing posts with label Spring. Show all posts

Tuesday, May 29, 2012

REST API with CXF and Spring

 

For the past couple of years, I have developed REST APIs using RESTEasy since the target deployment platform was JBOSS. More recently I have considered using Apache CXF instead. One reason is that we wanted to use a single technology to create web services (we are using CXF for our SOAP based web services).

In this article I will show how to create a very simple REST web service using CXF and Spring and describe how to deploy it on JBOSS AS 7.0.

I have found several tutorials describing how to create CXF based REST web services but none with Spring  and working when deployed on JBOSS AS 7.0. Part of the proof of concept (POC) code sample I am describing in this article come from Sandeep Bhandari article "REST Web Service Using CXF - Beginner's Tutorial" which I enriched with Spring integration and deployment configuration for JBOSS AS 7.0.




In my POC example, there is not much differences for the POJO classes representing the order resources (I made them a little bit simple):








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 specify 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;
 }
}
 
Finally the pom.xml file for maven: 
<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>

  <groupId>com.acme.poc.restapi</groupId>
  <artifactId>poc_restapi_cxf_client</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>poc_restapi_cxf_client</name>
  <url>http://maven.apache.org</url>
  
    <properties>
      <spring.version>3.1.0.RELEASE</spring.version>
      <cxf.version>2.6.0</cxf.version>
      <jackson.version>1.9.7</jackson.version>
      <junit.version>4.8.1</junit.version>    
   </properties>
  
   <dependencies>
   
     <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>${cxf.version}</version>
   <scope>provided</scope>
     </dependency>
   
     <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-rs-extension-search</artifactId>
      <version>${cxf.version}</version>
    <scope>provided</scope>
     </dependency>
     
     <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-rs-extension-providers</artifactId>
      <version>${cxf.version}</version>
    <scope>provided</scope>
     </dependency>
  
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    
  </dependencies>
  
  <build>
    <finalName>poc_restapi_cxf_client</finalName>
     <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
               <source>1.6</source>
               <target>1.6</target>
               <encoding>UTF-8</encoding>
            </configuration>
         </plugin>
    </plugins>
  </build>
</project>



The file jboss-deployment-structure.xml is used for JBOSS deployment: 
<jboss-deployment-structure>
 <deployment>
  <dependencies>
   <module name="org.apache.cxf" services="import">
    <imports>
     <include path="**" />
    </imports>
   </module>
  </dependencies>
 </deployment>
</jboss-deployment-structure>
 
 
In addition to this, before you deploy on JBOSS, you need to make some modifications on your web application server:

1) Download the latest edition of CXF (2.6.0) and placed just one jar from the distribution into the folder /modules/org/apache/cxf/main/cxf-2.6.0.jar (This contains all of the CXF codebase, except the dependencies)
  
2) Open /modules/org/apache/cxf/main/module.xml
   Remove all resource elements (the JARs)
   Add a new resource element similar to the deleted ones with cxf-2.6.0.jar being the only Jar referenced
   Add dependency to module named: javax.ws.rs.api :


<module xmlns="urn:jboss:module:1.1" name="org.apache.cxf">
   <resources>
        <resource-root path="cxf-2.6.0.jar"/>
        <!-- 
        <resource-root path="cxf-api-2.4.4.jar"/>
          ...
        <resource-root path="cxf-xjc-ts-2.4.0.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.ws.rs.api" />
        <module name="javax.xml.bind.api" services="import"/>
        <module name="com.sun.xml.bind" services="import"/>
        <module name="javax.wsdl4j.api" />
        <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.springframework.spring" optional="true"/>
    </dependencies>
</module> 
 
3) Create folder /modules/org/springframework/spring/main/
   Create module.xml
   Copy all the text from the CXF module.xml into this new one
   Erase the last dependency (itself)
   Erase the resource against cxf JAR  (because this is the copy for spring)
   Download latest Spring framework, and copy all the Spring jars into the folder
   Add all of these JARs to the module.xml inside tag.
   Add dependency to module named: org.apache.commons.logging 


<module xmlns="urn:jboss:module:1.1" name="org.springframework.spring">
    <resources>
  <resource-root path="spring-aop-3.1.0.RELEASE.jar" />
  <resource-root path="spring-asm-3.1.0.RELEASE.jar" />
  <resource-root path="spring-beans-3.1.0.RELEASE.jar" />
  <resource-root path="spring-context-3.1.0.RELEASE.jar" />
  <resource-root path="spring-core-3.1.0.RELEASE.jar" />
  <resource-root path="spring-expression-3.1.0.RELEASE.jar" />
  <resource-root path="spring-web-3.1.0.RELEASE.jar" />
        <!-- Insert resources here -->
    </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.xml.bind.api" services="import"/>
        <module name="com.sun.xml.bind" services="import"/>
        <module name="javax.wsdl4j.api" />
        <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.commons.logging" />
        <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" />
    </dependencies>
</module>

Friday, September 10, 2010

Spring Dependency Injection with JBOSS : the CLASSPATH issue

When facing the problem of deploying web archives (war) to be configured through Spring dependency injection, you probably want to have generic applications that do not have to be recompile every time you deploy them on new configurations.

In my current project I need to configure a REST API with various parameters (host name, database paths, maximum of records per request). For this I use Spring dependency injection where the parameters are injected at run-time via a resource file located outside the war file, in a folder specified by the Windows CLASSPATH variable (my testing and production platforms are windows machine).

First I need to add a windows CLASSPATH system variable (in your system properties/environment variables)  if this variable does not exist. Then I add the resources.xml file directly in the folder specified by CLASSPATH. You can also use a sub-folder but you will need to hard-code the name of the folder in your spring config file - in my case applicationContext.xml located in ./src/main/webapp/WEB-INF/

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-2.5.xsd
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <import resource="classpath:/resources.xml" />
</beans>

My resources.xml looks like this:

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  <bean id="custService" 
                      scope="prototype"
                      class=".....">
   <property name="hostName" value="121.122.123.124"/>
   <property name="databasePath" value="..."/>
   <property name="maxRecordsPerRequest" value="1000"/>
  </bean>
</beans>

One issue you might encounter when you try to deploy your application on JBoss is that the web application server does not take into account the CLASSPATH out-of-the-box (I am using redhat EAP 5.0.X - production setting), but this might be also the case with JBOSS community edition.

Your war file will probably fail to deploy and you will find a bunch of errors in your log file ./jboss-as/server/<setting>/log/server.log  including:

org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: 
Failed to import bean definitions from URL location [classpath:/resources.xml]
Offending resource: ServletContext resource [/WEB-INF/applicationContext.xml]; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException:
IOException parsing XML document from class path resource [resources.xml]; 
nested exception is java.io.FileNotFoundException: 
class path resource [resources.xml] cannot be opened because it does not exist

What is missing is that you need to tell JBoss about your CLASSPATH variable.
Just edit ./jboss-as/bin/run.bat and add the CLASSPATH variable and you will be up and running in no time.

:RESTART
"%JAVA%" %JAVA_OPTS% ^
   -Djava.endorsed.dirs="%JBOSS_ENDORSED_DIRS%" ^
   -classpath "%JBOSS_CLASSPATH%;%CLASSPATH%" ^
   org.jboss.Main -b 0.0.0.0 -c production %*

Tuesday, August 24, 2010

RESTeasy JAX-RS embeddable server and SpringBeanProcessor

TJWS (Tiny Java Web Server and Servlet Container) is a very convenient miniature Java Web Server build as a servlet container with HTTPD servlet providing standard Web server functionality.

I have been using TJWS for testing a REST API to be deployed on JBoss application server. The advantage is that JUnit tests can run without the need to deploy a war file on JBoss. Since I have implemented the REST API with RESTEasy, I am using the embedded TJWS server part of the org.jboss.resteasy.plugins.server.tjws.TJWSEmbeddedJaxrsServer package.

The RESTEasy documentation (chapter 23) describes how to use the embedded container.

@Path("/")public class MyResource {

   @GET
   public String get() { return "hello world"; }
 
   public static void main(String[] args) throws Exception 
   {
      TJWSEmbeddedJaxrsServer tjws = new TJWSEmbeddedJaxrsServer();
      tjws.setPort(8081);
      tjws.getRegistry().addPerRequestResource(MyResource.class);
      tjws.start();
   }
}

As you can see, TJWS is very simple to use. You create an instance of the server, setup the port (this is very useful when for example certain ports are already used - I had to set a specific port for our Hudson continuous builds). Then you specify the class to test and you start the server.

In my JUnit tests, I start the server before each tests and stop it after the tests are completed:

private TJWSEmbeddedJaxrsServer server; 

@Before
    public void start() {
      
     server = new TJWSEmbeddedJaxrsServer();
     server.setPort(SERVER_PORT);
     server.getDeployment().getActualResourceClasses().add(MyResource.class);
     server.start();
    }

@After
    public void stop() {
     server.stop();
    }

Since I am using Spring I was interested to leverage the framework for dependency injection in order to configure certain server settings. However the RESTeasy documentation provides only some pseudo-code example:

public static void main(String[] args) throws Exception 
   {
      final TJWSEmbeddedJaxrsServer tjws = new TJWSEmbeddedJaxrsServer();
      tjws.setPort(8081);

      org.resteasy.plugins.server.servlet.SpringBeanProcessor processor = new SpringBeanProcessor(tjws.getRegistry(), tjws.getFactory();
      ConfigurableBeanFactory factory = new XmlBeanFactory(...);
      factory.addBeanPostProcessor(processor);

      tjws.start();
   }

I had to make some modifications to the code provided as follow:

@Before
    public void start() {
      
     server = new TJWSEmbeddedJaxrsServer();
     server.setPort(SERVER_PORT);
     server.getDeployment().getActualResourceClasses().add(MyResource.class);
     server.start();
     
     Resource resource = new FileSystemResource("src/test/resources/resources.xml");
     ConfigurableListableBeanFactory factory = new XmlBeanFactory(resource);
     SpringBeanProcessor processor = new SpringBeanProcessor(
             server.getDeployment().getDispatcher(),
             server.getDeployment().getRegistry(), 
             server.getDeployment().getProviderFactory());
     processor.postProcessBeanFactory(factory);
    }

Alternatively you can define your Spring resource file in a static string directly in your JUnit test class:

Resource resource = new ByteArrayResource(SPRING_BEAN_CONFIG_FILE.getBytes());