Sunday, April 27, 2014

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

This post is following previous one. In previous one it was only one service which was returning text message. Now - "level up" - it will be few DTO's,  few services for them and of course few integration tests. To decrease amount of code I will use generics and inheritance.

Structure of application:



Main (pom.xml) file:
<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-js</artifactId>
    <packaging>war</packaging>
    <version>1.0.0</version>

    <name>CXF Spring REST JS</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-js</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>



1. DTO's(domain package)

It will be three DTO objects : Region, Country and Location and they will be  inherited from BaseDomain class. I made it this way just for ID field which will be present in every my DTO.
 So listing of BaseDomain:

package com.demien.cxfspringrestjs.domain;

public abstract class BaseDomain {
    private Integer id;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
    
}


I will show example of only one - Location DTO because other are similar: 



package com.demien.cxfspringrestjs.domain;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlRootElement;

//@XmlRootElement(name = "Location")
public class Location extends BaseDomain implements Serializable {

    private static final long serialVersionUID = 3477913378452357045L;
    private String streetAdress;
    private String postalCode;
    private String city;
    private String stateProvince;
    private Country country;

    public Location() {
    }

    public String getStreetAdress() {
        return streetAdress;
    }

    public void setStreetAdress(String streetAdress) {
        this.streetAdress = streetAdress;
    }

    public String getPostalCode() {
        return postalCode;
    }

    public void setPostalCode(String postalCode) {
        this.postalCode = postalCode;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStateProvince() {
        return stateProvince;
    }

    public void setStateProvince(String stateProvince) {
        this.stateProvince = stateProvince;
    }

    public Country getCountry() {
        return country;
    }

    public void setCountry(Country country) {
        this.country = country;
    }

    @Override
    public String toString() {
        return "[ Id=" + getId() + ", streetAdress=" + streetAdress
                + ", postalCode=" + postalCode + ", city=" + city
                + ", stateProvince=" + stateProvince + ", country=" + country
                + ']';
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Location)) {
            return false;
        }
        Location location = (Location) o;
        if (this.getId().equals(location.getId())) {
            return true;
        } else {
            return false;
        }
    }

}


As you can see, it's a regular DTO. On class definition I put annotation @XmlRootElement which is commented. If you want to work with objects in XML format - you need that annotation(uncomment it) for Jaxb parser. In this example I use JSON format, objects are transmitted as Json strings so I don't need that annotation.

2.Utilites.

In project there are some util classes. Some of them were present in previous post(RestTestClient, RestTestServer), some(JsonHelper, ObjectDataPopulator) are new.   

2.1 JsonHelper - utility for converting objects Json->Object, Object->Json.





package com.demien.cxfspringrestjs.utils;

import java.io.IOException;
import java.util.List;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

public class JsonHelper {
    
    private static ObjectMapper mapper = new ObjectMapper(); 
    
    public static Object Json2Object(String json, Class cl) throws JsonHelperException {    
        return Json2ObjectMain(json, cl, false);
    }   
    
    public static Object Json2ObjectList(String json, Class cl) throws  JsonHelperException{
        return Json2ObjectMain(json, cl, true);
    }
    
    private static Object Json2ObjectMain(String json, Class cl, boolean asList) throws JsonHelperException {
        if (json==null || json.length()==0) {
            return null;
        }
        Object result=null;
        try {
            if (asList) {
                result=mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, cl));
            } else {
                result=mapper.readValue(json, cl);  
            }
        } catch (Exception e) {
            throw new JsonHelper.JsonHelperException("Exception in Json2Object. Exception:"+e.getMessage()+". JSON="+json);
        }
        
        return result;
    }
    
    public static String object2json(Object object) throws JsonGenerationException, JsonMappingException, IOException {
        String result=mapper.writeValueAsString(object);
        return result;
    }
public static class JsonHelperException extends Exception { public JsonHelperException(String message) { super(message); } }}

2.2. Object data populator 

It's util for tests - it fills empty object with random generated values.  Here I made population only for Integer and String type. In other projects it has to be extended for supporting other types. 

package com.demien.cxfspringrestjs.utils;


import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import com.demien.cxfspringrestjs.domain.BaseDomain;
import com.demien.cxfspringrestjs.domain.Country;

public class ObjectDataPopulator {

    public static void main(String[] args) throws IllegalArgumentException,
            IllegalAccessException, InstantiationException {

        Country country = new Country();
        country = (Country) ObjectDataPopulator.populate(country);
        System.out.println(country.toString());
    }

    public static Object populate(BaseDomain instance)
            throws IllegalArgumentException, IllegalAccessException,
            InstantiationException {
        instance.setId(getRandomInteger());
        List<Field> fields = getAllFields(instance);
        for (Field eachField : fields) {
            eachField.setAccessible(true);
            if (eachField.getType().equals(Integer.class)) {
                eachField.set(instance, getRandomInteger());
            } else if (eachField.getType().equals(String.class)) {
                eachField.set(instance, getRandomString());
             // here should be generators for other standard types like Float, Long....
            } else {
                // processing aggregated objects
                if (eachField.getType().newInstance() instanceof BaseDomain) {
                    eachField.set(instance, populate((BaseDomain) eachField
                            .getType().newInstance()));
                }               
            }
        }

        return instance;

    }

    private static Integer getRandomInteger() {
        return new Integer((int) (Math.random() * 1000));
    }

    private static String getRandomString() {
        StringBuffer result = new StringBuffer();
        String[] letters = new String[] { "A", "B", "C", "D", "E", "F", "G" };
        int length = (int) (Math.random() * 15) + 5;
        for (int i = 0; i < length; i++) {
            int pos = (int) (Math.random() * letters.length);
            result.append(letters[pos]);
        }
        return result.toString();
    }

    private static List<Field> getAllFields(Object instance) {
        Field[] fields = instance.getClass().getDeclaredFields();
        List<Field> result = new ArrayList<Field>();
        for (int i = 0; i < fields.length; i++) {
            if (!java.lang.reflect.Modifier.isFinal(fields[i].getModifiers())
                    && !java.lang.reflect.Modifier.isStatic(fields[i]
                            .getModifiers())) {
                result.add(fields[i]);
            }
        }
        return result;
    }
}

2.3 RestTestClient
This class was extended. In previous post it supported GET requests only. Here it is supporing  POST requests also. 

package com.demien.cxfspringrestjs.utils;

import java.io.BufferedReader;

import org.apache.http.NameValuePair;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;

import com.demien.cxfspringrestjs.utils.JsonHelper.JsonHelperException;

public class RestTestClient {

    public static final int RESPONSE_OK = 200;

    public static class GetResponceDataException extends Exception {
        private static final long serialVersionUID = 5296532593594106875L;

        public GetResponceDataException(String message) {
            super(message);
        }
    }

    public static String getResponseData(HttpResponse response)
            throws Exception {
        StringBuilder result = new StringBuilder();
        try {
            BufferedReader rd = new BufferedReader(new InputStreamReader(
                    response.getEntity().getContent()));
            String line = "";
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            throw new Exception("Exception in getResponseData:"
                    + e.getMessage() + ".");
        }
        return result.toString();
    }

    private Object Json2Object(String src, Class cl, boolean asList)
            throws JsonHelperException {
        Object result = null;
        if (asList) {
            result = JsonHelper.Json2ObjectList(src, cl);
        } else {
            result = JsonHelper.Json2Object(src, cl);
        }
        return result;
    }

    public HttpResponse sendGet(String url) throws ClientProtocolException,
            IOException {
        HttpClient client = HttpClientBuilder.create().build();
        HttpGet get = new HttpGet(url);
        get.setHeader(
                "Accept",
                "text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8");
        get.setHeader("Content-Type", "application/json");
        HttpResponse response = client.execute(get);

        return response;
    }

    public String sendGetStringResult(String url) throws Exception {
        return getResponseData(sendGet(url));
    }

    public Object sendGetObjectResult(String url, Class cl) throws Exception {
        return sendGetObjectResult(url, cl, false);
    }

    public Object sendGetObjectResult(String url, Class cl, boolean asList)
            throws Exception {
        String src = sendGetStringResult(url);
        Object result = null;
        if (asList) {
            result = JsonHelper.Json2ObjectList(src, cl);
        } else {
            result = JsonHelper.Json2Object(src, cl);
        }
        return result;
    }

    public HttpResponse sendPost(String url, List<NameValuePair> postParams)
            throws Exception {

        HttpClient client = HttpClientBuilder.create().build();
        HttpPost post = new HttpPost(url);
        post.setHeader("Accept",
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        post.setHeader("Content-Type", "application/x-www-form-urlencoded");

        if (postParams != null) {
            post.setEntity(new UrlEncodedFormEntity(postParams));
        }

        HttpResponse response = client.execute(post);

        return response;
    }

    public HttpResponse sendPost(String url, Object object) throws Exception {

        HttpClient client = HttpClientBuilder.create().build();
        HttpPost post = new HttpPost(url);
        String json = JsonHelper.object2json(object);
        StringEntity input = new StringEntity(json);
        input.setContentType("application/json");
        post.setEntity(input);

        post.setHeader(
                "Accept",
                "text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8");
        post.setHeader("Content-Type", "application/json");
        HttpResponse response = client.execute(post);

        return response;
    }

    public String sendPostStringResult(String url,
            List<NameValuePair> postParams) throws IllegalStateException,
            IOException, Exception {
        return getResponseData(sendPost(url, postParams));
    }

    public Object sendPostObjectResult(String url,
            List<NameValuePair> postParams, Class cl)
            throws IllegalStateException, IOException, Exception {
        return sendPostObjectResult(url, postParams, cl, false);
    }

    public Object sendPostObjectResult(String url,
            List<NameValuePair> postParams, Class cl, boolean asArray)
            throws IllegalStateException, IOException, Exception {
        String src = sendPostStringResult(url, postParams);
        return Json2Object(src, cl, asArray);
    }

}

2.4 RestTestServer
This class was not changed from previous post.
 

package com.demien.cxfspringrestjs.utils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class RestTestServer {
    public final static String TEST_CONTEXT = "/spring-cxf-rest";
    public final static 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;
        }
    }
}

3. Services. 

As I said before, to decrease amount of code I will use generic and inheritance: all logic will be in BaseService class and other services(RegionService, CountryService, LocationService) will be just empty classes with service path declaration only!
BaseService class is the "mastermind" of application - all rest service logic(all rest services) defined here:


  


package com.demien.cxfspringrestjs.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;

import com.demien.cxfspringrestjs.domain.BaseDomain;
import com.demien.cxfspringrestjs.utils.JsonHelper;
import com.demien.cxfspringrestjs.utils.JsonHelper.JsonHelperException;

public class BaseService<T extends BaseDomain> {

    private List<T> entityList = new ArrayList<T>();
    public static final String HELLO = "Hello world!";
    
    public static final String SERVICE_HELLO="sayhello";
    public static final String SERVICE_ENTITY_LIST="entitylist";
    public static final String SERVICE_GETBYID="getbyid";
    public static final String SERVICE_ADD="add";
    public static final String SERVICE_UPDATE="update";
    public static final String SERVICE_DELETE="delete";
    
    private Class<T> cl;
    
    public BaseService(Class<T> cl) {
        this.cl=cl;
    }

    @GET
    @Path("/sayhello")
    public String sayHello() {
        return HELLO;
    }

    @GET
    @Path("/entitylist")
    @Produces("application/json")
    public Collection<T> getEntityList() {
        return entityList;
    }

    public T getEntityById(Integer id) {
        T result = null;
        for (T entity : entityList) {
            if (entity.getId().equals(id)) {
                result = entity;
            }
        }
        return result;
    }

    @GET
    @Path("/getbyid")
    @Produces("application/json")
    public Response getEntity(@QueryParam("id") Integer id) {
        T result =getEntityById(id);
        return Response.ok(result).build();
    }

    @POST
    @Path("/add")
    @Consumes("application/json")
    @Produces("application/json")
    public Response addEntity(String json) throws JsonHelperException  {
        T entity=(T)JsonHelper.Json2Object(json, cl);
        
        T searchResult = getEntityById(entity.getId());
        if (searchResult == null) {
            entityList.add(entity);
        }
        return Response.ok(entity).build();
    }

    @POST
    @Path("/update")
    @Consumes("application/json")
    @Produces("application/json")
    public Response updateEntity(String json) throws JsonHelperException {
        T entity=(T)JsonHelper.Json2Object(json, cl);
        T forUpdate = getEntityById(entity.getId());
        if (forUpdate != null) {
            forUpdate = entity;
        }
        return Response.ok(forUpdate).build();
    }

    @POST
    @Path("/delete")
    @Consumes("application/json")
    @Produces("application/json")
    public Response deleteEntity(String json) throws JsonHelperException {
        T entity=(T)JsonHelper.Json2Object(json, cl);
        entityList.remove(entity);
        return Response.ok().build();
    }

}

Services RegionService, CountryService, LocationService which are visible "outside" for rest clients are just extending BaseService class with defenition of service base URL. Example of LocationService: 




package com.demien.cxfspringrestjs.service;

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

import com.demien.cxfspringrestjs.domain.Location;

@WebService(serviceName = "locationService")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/locationService")
public class LocationService extends BaseService<Location> {
    
    public LocationService(){
        super(Location.class);
    }
}

Other (RegionService, CountryService) are similar, differences are : another path in @Path annotation ("/regionService" and "/countryService") and another classes in constructor (Region.class and Country.class). 

All methods with services for add, update delete are inherited from BaseService class. So, in case of any changes in logic, only one class have to be changed, and that is great! 


4. Application context file

It is similar to previous post, only new services was added:


<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="RegionService" />
            <ref bean="CountryService" />           
            <ref bean="LocationService" />
        </jaxrs:serviceBeans>
        <jaxrs:extensionMappings>
            <entry key="xml" value="application/xml" />
            <entry key="json" value="application/json" />
        </jaxrs:extensionMappings>
    </jaxrs:server>
        
    <bean id="RegionService" class="com.demien.cxfspringrestjs.service.RegionService"/>
    <bean id="CountryService" class="com.demien.cxfspringrestjs.service.CountryService"/>
    <bean id="LocationService" class="com.demien.cxfspringrestjs.service.LocationService"/>

</beans>

5. Testing

Integration testing part in similar to previous post, but we have to test several services and they have several methods. For that I used similar services inheritance approach: one BaseServiceTest generic class with whole test logic, and three empty test classes(RegionServiceTest, CountryServiceTest, LocationServiceTest) which are extending BaseServiceTest class. All @Test methods will be inherited - so we have to define them only one time - in BaseServiceTest class. 



package com.demien.services;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.List;

import org.apache.http.HttpResponse;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.demien.cxfspringrestjs.domain.BaseDomain;
import com.demien.cxfspringrestjs.service.BaseService;
import com.demien.cxfspringrestjs.utils.ObjectDataPopulator;
import com.demien.cxfspringrestjs.utils.RestTestClient;
import com.demien.cxfspringrestjs.utils.RestTestServer;

public abstract class BaseServiceTest<T extends BaseDomain> {
    protected static RestTestServer server = new RestTestServer("JUNIT");
    protected static RestTestClient client = new RestTestClient();

    protected final String BASE_URL = "http://localhost:"
            + RestTestServer.TEST_PORT + RestTestServer.TEST_CONTEXT
            + "/services/rest/";
    protected final String SERVICE_URL;
    private final Class<T> cl;

    public BaseServiceTest(String sevicePrexif, Class<T> cl) {
        SERVICE_URL = BASE_URL + sevicePrexif + "/";
        this.cl = cl;
    }

    private T getTestEntity() throws InstantiationException,
            IllegalAccessException {
        T entity = cl.newInstance();
        ObjectDataPopulator.populate(entity);
        return entity;
    }

    @BeforeClass
    public static void init() throws Exception {
        server.start();
    }

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

    @Test
    public void sayHello() throws Exception {
        String value = client.sendGetStringResult(SERVICE_URL
                + BaseService.SERVICE_HELLO);
        System.out.println("value=" + value);
        assertEquals(BaseService.HELLO, value);
    }

    @SuppressWarnings("unchecked")
    private T getById(Integer id) throws Exception {
        T result = (T) client.sendGetObjectResult(SERVICE_URL
                + BaseService.SERVICE_GETBYID + "?id=" + id, cl);
        return result;
    }

    private void addEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseService.SERVICE_ADD, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    private void updateEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseService.SERVICE_UPDATE, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    private void deleteEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseService.SERVICE_DELETE, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    @SuppressWarnings({ "unchecked" })
    private List<T> getEntityList() throws IllegalStateException, IOException,
            Exception {

        List<T> result = (List<T>) client.sendGetObjectResult(SERVICE_URL
                + BaseService.SERVICE_ENTITY_LIST, cl, true);
        return result;
    }

    @Test
    public void addEntityTest() throws Exception {
        T testEntity = getTestEntity();
        addEntity(testEntity);

        T resultEntity = getById(testEntity.getId());
        assertNotNull(resultEntity);
        assertEquals(testEntity, resultEntity);
    }

    @Test
    public void updateEntityTest() throws Exception {
        T testEntity = getTestEntity();
        addEntity(testEntity);
        Integer id = testEntity.getId();

        T forUpdate = getTestEntity();
        forUpdate.setId(id);
        updateEntity(forUpdate);

        T searchEntity = getById(id);
        assertEquals(forUpdate, searchEntity);
    }

    @Test
    public void deleteEntityTest() throws Exception {
        T testEntity = getTestEntity();
        addEntity(testEntity);
        Integer id = testEntity.getId();

        deleteEntity(testEntity);

        T searchEntity = getById(id);
        assertNull(searchEntity);
    }

    @Test
    public void getEntityListTest() throws Exception {
        T testEntity1 = getTestEntity();
        addEntity(testEntity1);
        T testEntity2 = getTestEntity();
        addEntity(testEntity2);

        List<T> entityList = getEntityList();
        assertNotNull(entityList);
        assertTrue(entityList.contains(testEntity1));
        assertTrue(entityList.contains(testEntity2));

    }

}

And now all we have to do is to extend this class and add correct constructor with service URL and data class. Example of LocationServiceTest (others are similar):


package com.demien.services;

import com.demien.cxfspringrestjs.domain.Location;

public class LocationServiceTest extends BaseServiceTest<Location> {
    public LocationServiceTest() {
        super("locationService", Location.class);
    }

}

And that's it! No code except constructor! All @Test methods will be inherited! 

Let's run test for our application: 



For every(which are "empty" - only constructor) test service class (RegionServiceTest, CountryServiceTest, LocationServiceTest) were executed 5 test (total 5*3=15) which were defined in parent BaseTestService class. 


 

Source codes can be downloaded from here.

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.