Friday, April 11, 2014

Restfull services in Java using Apache CXF with integration testing using embedded JETTY

Apache CXF is one of most popular implementation for restfull services in Java. In this post I'm going to show how to use it for rest service developing and how to test it automatically(integration testing) by JUnit using Jetty.

With growing popularity of HTML5/JavaScript popularity of restfull services is growing too.  Today combination of client HTML/Javascript and resfull services on server side is becoming a common approach. That approach helps to decompose development into 2 independent levels:
 - client level(HTML, JS engines/libraries)
- server level(restfull services, dao, etc)
Of course client level can be implemented in different ways: php, python.... in this post showed java implementation.

Structure of web application which use java for server-side:





In this post unveiled only one rectangle: RESTFULL SERVICES. I choose Apache CXF realization for REST, Spring as a "glue"(integrator), Jetty and Apache http client for integration testing.

Directory structure of application:



1. Main project file

Let's start with pom.xml!
in pom file we have to add spring dependencies, apache cxf and jetty.

<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>com.demien</groupId>
    <artifactId>cxf-sprig-rest</artifactId>
    <packaging>war</packaging>
    <version>1.0.0</version>

    <name>CXF Spring REST</name>
    
    <properties>
        <maven.test.skip>true</maven.test.skip>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>3.1.1.RELEASE</spring.version>
    </properties>   
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>2.5.3</version>
        </dependency>

        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>jsr311-api</artifactId>
            <version>1.1.1</version>
        </dependency>

        <!-- TEST -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
                
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-jaxrs</artifactId>
            <version>1.9.11</version>
        </dependency>       
        
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>9.1.0.M0</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-webapp</artifactId>
            <version>9.1.0.M0</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.3.3</version>
        </dependency>

    </dependencies>
    
    <build>
        <finalName>spring-cxf-rest</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>


2. Rest Service

For rest service we will use standard Java annotation :




package com.demien.cxfspringrest.service;

import javax.jws.WebService;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@WebService(serviceName = "testService")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/testService")
public class TestService {
//localhost:8080/spring-cxf-rest/services/rest/testService/sayhello 
    
    @GET
    @Path("/sayhello")
    public String sayHello() {
        return "Hello world!";
    }

}

3. Spring context - ApplicationContext.xml

In application context we defined our service bean and in JAXRS tags  - our rest connector which will forward requests with path /rest to our service bean.  


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd 
        http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd"
    default-lazy-init="false">


    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <context:component-scan base-package="com.demien" />


    <jaxrs:server id="myServices" address="/rest">
        <jaxrs:providers>
            <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
        </jaxrs:providers>
        <jaxrs:serviceBeans>
            <ref bean="testService" />
        </jaxrs:serviceBeans>
        <jaxrs:extensionMappings>
            <entry key="xml" value="application/xml" />
            <entry key="json" value="application/json" />
        </jaxrs:extensionMappings>
    </jaxrs:server>

    <bean id="testService" class="com.demien.cxfspringrest.service.TestService"/>
    

</beans>


4. Web.xml

Here we have to register spring context listener and add file name of our application context file.



<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" 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">
    <display-name>cxf-spring-rest-services</display-name>
    
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:ApplicationContext.xml           
        </param-value>
    </context-param>
    
    <listener>
        <listener-class> org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    
    <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>/services/*</url-pattern>
    </servlet-mapping>
     
        
</web-app>

5. Running...

Now we are ready to run our application. We can build it by mvn package command, deploy war file into tomcat, start tomcat and open service link by browser. In chrome i got next message:


- that's ok, our service returning "Hello world!" sentence without html tags, that is why browser complains. Right click/show page source and we can finally see our message:


6. Utils for integration testing

Our application works well, but we can make it better! Now to  be sure that our application works well we have to do a lot of steps: re-build it, deploy into tomcat, restart tomcat, open link by browser. Can we simplify that? Of course! First of all let's make tool(Server) for starting our application without Tomcat. 

Server:


package com.demien.cxfspringrest.utils;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class RestTestServer {
    public final String TEST_CONTEXT = "/spring-cxf-rest";
    public final int TEST_PORT = 8080;
    private Server server;
    private String mode;
    
    public RestTestServer() {
        
    }
    
    public RestTestServer(String mode) {
        this.mode=mode;
    }   

    public void start() throws Exception {

        if (server == null) {
            server = new Server(TEST_PORT);
            WebAppContext context = new WebAppContext();
            context.setDescriptor("src/main/webapp/WEB-INF/web.xml");
            context.setResourceBase("src/main/webapp");
            context.setContextPath(TEST_CONTEXT);
            context.setParentLoaderPriority(true);
            server.setHandler(context);
            server.start();
            System.out.println("Server started at http://localhost:"+TEST_PORT+TEST_CONTEXT);
            if (mode==null || !mode.equals("JUNIT")) {
                server.join();
            }
            //
        }
        //         
    }
    
    public void stop() throws Exception
    {
        if (server != null)
        {
            server.stop();
            server.join();
            server.destroy();
            server = null;
        }
    }
}

It just starts our application using jetty server. The "mode" variable serves for correct working(Start/Stop) in JUnit/regular mode. If we started our application as server(regular mode) - we want it work unit we stopped it. But in JUnit it has to be stopped after request execution. 


Now we can start our server by executing only one command via main() procedure or in junit test: 
public class StartServer {

 public static void main(String[] args) throws Exception {
  new RestTestServer().start();

 }

}

Of course, don't forget to stop Tomcat if it's still running :)
Now we can open our previous link and realized that it works fine on out server without tomcat. 

Next step aimed to get rid of link opening: we have to create rest client which will call rest services on server, created on previous step.


Client:

package com.demien.cxfspringrest.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class RestTestClient {
    
    public String executeGetRequest(String url) throws ClientProtocolException, IOException {
        StringBuilder result=new StringBuilder();
          HttpClient client = new DefaultHttpClient();
          HttpGet request = new HttpGet(url);
          HttpResponse response = client.execute(request);
          BufferedReader rd = new BufferedReader (new InputStreamReader(response.getEntity().getContent()));
          String line = "";
          while ((line = rd.readLine()) != null) {
            //System.out.println(line);
              result.append(line);
          }
          
          return result.toString();
    }

}

Client is ready and we can finally make a test. 

As you can see, our client executes only GET request, so, in real life it have to be extended for POST/PUT requests.



7. Integration test

Our test is pretty simple: it starts server with our application, call service, checks if result equals expected value, stops server.



package com.demien.services;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.demien.cxfspringrest.utils.RestTestClient;
import com.demien.cxfspringrest.utils.RestTestServer;

public class TestServiceTest {
    private static RestTestServer server = new RestTestServer("JUNIT");
    private static RestTestClient client=new RestTestClient();
    
    @BeforeClass
    public static void init() throws Exception {
        server.start();
    }

    @AfterClass
    public static void finish() throws Exception {
        server.stop();
    }
    

    @Test
    public void test() throws Exception {
        System.out.println("Main test is started");
        String value=client.executeGetRequest("http://localhost:8080/spring-cxf-rest/services/rest/testService/sayhello");
        System.out.println("value="+value);
        assertEquals(value, "Hello world!");
        System.out.println("Main test is finished");
    }

}

8. Result

Now we can execute our JUnit test. Results are following:
Our service was successfully tested: received value equals expected.
Now we can test our application without deploying it on Tomcat - just by running JUnit tests.
  





Source code can be downloaded from here.

No comments:

Post a Comment