Saturday, January 11, 2014

Test your Axis2 service without deploy in server

Hi All,

This is a topic that I tried out during last two days and finally I was able to find a proper mechanism to do it. During these days I'm under a training and I assigned some exercise to complete. So the one exercise is to study the Axis2 and write service and client. I wrote a service and generate the .aar by maven plugin axis2-aar-maven-plugin and deploy it in a WSO2 application server. It support to deploy the .aar file.

/**
 * Order model class
 */
public class Order {

    private Integer orderNumber;
    private String customerName;
    private Date orderDate;

    public Integer getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(Integer orderNumber) {
        this.orderNumber = orderNumber;
    }

    public String getCustomerName() {
        return customerName;
    }

    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }

    public Date getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }
}

/**
 * OrderService used to handle order process
 */
public interface OrderService {

    /**
     * Add order to process
     *
     * @param order {@link org.wso2.axis.model.Order} object which contains order details
     * @return message to the user
     */
    String addOrder(Order order);

    /**
     * Get order details by order number
     *
     * @param orderNumber order number to get order details
     * @return {@link org.wso2.axis.model.Order} object which contains order details
     */
    Order getOrderByOrderNumber(Integer orderNumber);

    /**
     * Get all oder details
     *
     * @return List of {@link org.wso2.axis.model.Order} objects which contains order details
     */
    List<Order> getAllOderDetails();
}

/**
 * Implementation of {@link org.wso2.axis.service.OrderService}
 */
public class OrderServiceImpl implements OrderService {

    private static List<Order> orderList = new ArrayList<Order>();

    /**
     * Add order to process
     *
     * @param order {@link org.wso2.axis.model.Order} object which contains order details
     * @return message to the user
     */
    @Override
    public String addOrder(Order order) {
        String message = null;
        if(OrderValidator.validateOrder(order)){
            orderList.add(order);
            message = "Order added successfully. ["+order.getOrderNumber()+", "+order.getOrderDate()+", "+order.getCustomerName()+"]";
        } else {
            message = "Order not added. Please check your data.";
        }
        return message;
    }

    /**
     * Get order details by order number
     *
     * @param orderNumber order number to get order details
     * @return {@link org.wso2.axis.model.Order} object which contains order details
     */
    @Override
    public Order getOrderByOrderNumber(Integer orderNumber) {
        Order orderFound = null;
        if(orderNumber != null){
            for (Order order : orderList) {
                if(order.getOrderNumber().equals(orderNumber)){
                    orderFound = order;
                }
            }
        }
        return orderFound;
    }

    /**
     * Get all oder details
     *
     * @return List of {@link org.wso2.axis.model.Order} objects which contains order details
     */
    @Override
    public List<Order> getAllOderDetails() {
        return orderList;
    }
}

/**
 * Validate order object
 */
public class OrderValidator {

 public static boolean validateOrder(Order order) {
  boolean validate = false;
  if (order != null) {
   if (order.getOrderNumber() != null && order.getCustomerName() != null &&
       order.getOrderDate() != null) {
    validate = true;
   }
  }
  return validate;
 }
}

<?xml version="1.0" encoding="UTF-8"?>
<service name="OrderService" targetNamespace="http://service.axis.wso2.org">

    <schema schemaNamespace="http://service.axis.wso2.org"/>

    <description>Order web service</description>

    <!--compulsory parameter-->
    <parameter name="ServiceClass" locked="false">org.wso2.axis.service.impl.OrderServiceImpl</parameter>

    <!--common message receivers-->
    <messageReceivers>
        <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"
                         class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
        <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
                         class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
    </messageReceivers>

    <!--addOder operation-->
    <operation name="addOrder" namespace="http://service.axis.wso2.org">
        <actionMapping>urn:addOrder</actionMapping>
    </operation>

    <!--getOrderByOrderNumber operation-->
    <operation name="getOrderByOrderNumber" namespace="http://service.axis.wso2.org">
        <actionMapping>urn:getOrderByOrderNumber</actionMapping>
    </operation>

    <!--getAllOderDetails operation-->
    <operation name="getAllOderDetails" namespace="http://service.axis.wso2.org">
        <actionMapping>urn:getAllOderDetails</actionMapping>
    </operation>

</service>

Add following dependencies to pom.xml

        <dependency>
            <groupId>training.exercise</groupId>
            <artifactId>exercise-utils</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.axis2</groupId>
            <artifactId>axis2</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.axis2</groupId>
            <artifactId>axis2-adb</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.axis2</groupId>
            <artifactId>axis2-transport-http</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.axis2</groupId>
            <artifactId>axis2-transport-local</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.axis2</groupId>
            <artifactId>axis2-xmlbeans</artifactId>
            <version>1.6.1</version>
        </dependency>

Add axis2-aar-maven-plugin under <plugins></plugins>

<!--Axis Archive generate plugin-->
            <plugin>
                <groupId>org.apache.axis2</groupId>
                <artifactId>axis2-aar-maven-plugin</artifactId>
                <version>${axis2.version}</version>
                <configuration>
                    <aarName>OrderService</aarName>
                    <servicesXmlFile>${basedir}/src/main/resources/META-INF/services.xml</servicesXmlFile>
                    <includeDependencies>false</includeDependencies>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>aar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

So the next step is to create a client to test the service. This also can done with axis2-wsdl2code-maven-plugin easily. This will generate call back handler and service stub.

<!--Axis client generate plugin-->
            <plugin>
                <groupId>org.apache.axis2</groupId>
                <artifactId>axis2-wsdl2code-maven-plugin</artifactId>
                <version>${axis2.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>wsdl2code</goal>
                        </goals>
                        <configuration>
                            <wsdlFile>${basedir}/src/main/resources/wsdl/OrderService.wsdl</wsdlFile>
                            <databindingName>adb</databindingName>
                            <packageName>org.wso2.axis.stub</packageName>
                            <outputDirectory>${basedir}/src/main/java</outputDirectory>
                            <flattenFiles>true</flattenFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

/**
 * OrderServiceCallbackHandler Callback class, Users can extend this class and
 * implement
 * their own receiveResult and receiveError methods.
 */
public abstract class OrderServiceCallbackHandler {

/*
 * OrderServiceStub java implementation
 */
public class OrderServiceStub extends org.apache.axis2.client.Stub {

Using service stub directly is not a good practice. Therefore I wrote a wrapper class to call the stub methods. This makes directly call the service method and get the result.

/**
 * Wrapper class to call the {@see org.wso2.axis.stub.OrderServiceStub}
 */
public class OrderServiceClient {

    private static final Logger logger = LoggerFactory.getLogger(OrderServiceClient.class);
    private String endPoint;

    public OrderServiceClient(String endPoint) {
        this.endPoint = endPoint;
    }

    /**
     * call the addOrder web service method
     *
     * @param order {@see Order} object which contains data
     * @return result message
     */
    public String addOrder(Order order){
        String result = null;
        try {
            //instantiate OrderServiceStub
            OrderServiceStub orderServiceStub = getOrderServiceStub();
            //instantiate AddOrder
            OrderServiceStub.AddOrder addOrder = new OrderServiceStub.AddOrder();
            //instantiate Order
            OrderServiceStub.Order order1 = new OrderServiceStub.Order();
            //Marshalling data
            order1.setCustomerName(order.getCustomerName());
            order1.setOrderDate(order.getOrderDate());
            order1.setOrderNumber(order.getOrderNumber());
            addOrder.setOrder(order1);
            //call addOrder
            OrderServiceStub.AddOrderResponse addOrderResponse = orderServiceStub.addOrder(addOrder);
            result = addOrderResponse.get_return();
        } catch (AxisFault axisFault) {
            axisFault.printStackTrace();
            logger.error("Exception occurred in Service End Point", axisFault);
        } catch (RemoteException e) {
            e.printStackTrace();
            logger.error("Exception occurred in calling service", e);
        }
        return result;
    }

    /**
     * call the getOrderByOrderNumber web service method
     *
     * @param orderNumber order number of given order
     * @return {@see Order} object which contains data
     */
    public Order getOrderByOrderNumber(Integer orderNumber){
        Order order = new Order();
        try {
            //instantiate OrderServiceStub
            OrderServiceStub orderServiceStub = getOrderServiceStub();
            //instantiate GetOrderByOrderNumber
            OrderServiceStub.GetOrderByOrderNumber getOrderByOrderNumber = new OrderServiceStub.GetOrderByOrderNumber();
            //set order number
            getOrderByOrderNumber.setOrderNumber(orderNumber);
            //call getOrderByOrderNumber
            OrderServiceStub.GetOrderByOrderNumberResponse orderByOrderNumber =
                    orderServiceStub.getOrderByOrderNumber(getOrderByOrderNumber);
            OrderServiceStub.Order order1 = orderByOrderNumber.get_return();
            //Unmarshalling order
            order.setOrderNumber(order1.getOrderNumber());
            order.setOrderDate(order1.getOrderDate());
            order.setCustomerName(order1.getCustomerName());
        } catch (AxisFault axisFault) {
            logger.error("Exception occurred in Service End Point", axisFault);
        } catch (RemoteException e) {
            logger.error("Exception occurred in calling service", e);
        }
        return order;
    }

    /**
     * call the getAllOrderDetails web service method
     *
     * @return
     */
    public List<Order> getAllOderDetails() {
        List<Order> orderList = new ArrayList<Order>();
        try {
            //instantiate OrderServiceStub
            OrderServiceStub orderServiceStub = getOrderServiceStub();
            //instantiate GetAllOrderDetails
            OrderServiceStub.GetAllOderDetails getAllOderDetails = new OrderServiceStub.GetAllOderDetails();
            //call getAllOrderDetails
            OrderServiceStub.GetAllOderDetailsResponse allOderDetails =
                    orderServiceStub.getAllOderDetails(getAllOderDetails);
            //unmarshalling order
            OrderServiceStub.Order[] orders = allOderDetails.get_return();
   for (OrderServiceStub.Order order : orders) {
    Order order1 = new Order();
                order1.setOrderNumber(order.getOrderNumber());
                order1.setOrderDate(order.getOrderDate());
                order1.setCustomerName(order.getCustomerName());
                orderList.add(order1);
   }
        } catch (AxisFault axisFault) {
            axisFault.printStackTrace();
            logger.error("Exception occurred in Service End Point", axisFault);
        } catch (RemoteException e) {
            e.printStackTrace();
            logger.error("Exception occurred in calling service", e);
        }
        return orderList;
    }

    /**
     * OrderServiceStub factory method
     *
     * @return {@see OrderServiceStub} object
     * @throws AxisFault
     */
    private OrderServiceStub getOrderServiceStub() throws AxisFault {
        return new OrderServiceStub(endPoint);
    }
}
 
Then I wrote a test class to test the web service through client. But to do all these, the prerequisite is the service must be up and running. :) The problem is each and every time when I build the project service must be available otherwise test cases fails. :( I can skip the test case and build the project but it's not the proper way to do it.

/**
 * Test class for OrderServiceClient
 *
 */
public class OrderServiceClientTest {

    public static final String ORDER_SERVICE_END_POINT="http://as.wso2.org:9763/services/OrderService?wsdl";
    
 @Test
 public void testAddOrder() {
        OrderServiceClient orderServiceClient = getOrderServiceClient();
        Order order = createOrder(1, new Date(), "Indika Sampath");
        String result = orderServiceClient.addOrder(order);
        Assert.assertNotNull(result);
        System.out.println(result);
    }

    /**
     * OrderServiceClient factory method
     *
     * @return
     */
    private OrderServiceClient getOrderServiceClient() {
        return new OrderServiceClient(ORDER_SERVICE_END_POINT);
    }

    /**
     * Order factory method
     *
     * @param orderNumber
     * @param orderDate
     * @param customerName
     * @return
     */
 private Order createOrder(Integer orderNumber, Date orderDate, String customerName) {
  Order order = new Order();
  order.setOrderNumber(orderNumber);
  order.setOrderDate(orderDate);
  order.setCustomerName(customerName);
  return order;
 }

}

So what I want is find out is the way to dynamically create the axis2 service. Axis2 has rich set of features. I found that it's possible to programmatically  create and deploy the service by providing the service class.

/**
 * Test class for OrderServiceClient
 *
 */
public class OrderServiceClientTest implements ServiceObjectSupplier {

    private OrderService orderService = new OrderServiceImpl();
    private static int port = RemoteUtil.findFreePort();
    private AxisServer server = new AxisServer();

    @BeforeClass(alwaysRun = true)
    public void setUp() throws Exception {
        setupAxisServer(OrderService.class.getName(), OrderServiceClientTest.class.getName());
    }

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

 @Test
 public void testAddOrder() {
        OrderServiceClient orderServiceClient = getOrderServiceClient();
        Order order = createOrder(1, new Date(), "Indika Sampath");
        String result = orderServiceClient.addOrder(order);
        Assert.assertNotNull(result);
        System.out.println(result);
    }

    /**
     * OrderServiceClient factory method
     *
     * @return
     */
    private OrderServiceClient getOrderServiceClient() {
        return new OrderServiceClient("http://localhost:" + port +
                "/axis2/services/OrderService");
    }

    /**
     * Order factory method
     *
     * @param orderNumber
     * @param orderDate
     * @param customerName
     * @return
     */
 private Order createOrder(Integer orderNumber, Date orderDate, String customerName) {
  Order order = new Order();
  order.setOrderNumber(orderNumber);
  order.setOrderDate(orderDate);
  order.setCustomerName(customerName);
  return order;
 }

    /**
     * Setup and start Axis-Server.
     *
     * @param serviceClassName
     * @throws org.apache.axis2.AxisFault
     */
    private void setupAxisServer(String serviceClassName, String serviceObjectSupplierClassName)
            throws AxisFault,
            IllegalAccessException,
            InstantiationException {
        // Setup the custom port
        Map<String, TransportInDescription> transports =
                server.getConfigurationContext()
                        .getAxisConfiguration()
                        .getTransportsIn();
        for (Map.Entry<String, TransportInDescription> objectEntry : transports.entrySet()) {
            TransportInDescription desc = objectEntry.getValue();
            Parameter param = desc.getParameter("port");
            param.setValue(Integer.toString(port));
        }
        server.getConfigurationContext().getAxisConfiguration()
                .addParameter(Constants.SERVICE_OBJECT_SUPPLIER, serviceObjectSupplierClassName);
        //deploy OrderService
        server.deployService(serviceClassName);
    }

    /**
     * Callback-Method to retrieve the Service
     *
     * @param theService
     * @return Object
     */
    public Object getServiceObject(AxisService theService) {
        String service = theService.getName();
        if (service.equals("OrderService")) {
            return orderService;
        }
        return null;
    }
}

Note that I have to do some changes in generated stub (OrderServiceStub) to execute test due to namespace issue with programmatically deploy service. You can write a client inside the test method and test the service method. :)

@Test
    public void testGetOrderByOrderNumber() throws AxisFault {
  RPCServiceClient serviceClient = getRpcServiceClient();
  QName operationName = new QName("http://service.axis.wso2.org", "addOrder");
  Order order = createOrder(1, new Date(), "Indika Sampath");
  Object[] operationArguments = new Object[] { order };
  Class[] returnTypes = new Class[] { String.class };
  Object[] response =
                      serviceClient.invokeBlocking(operationName, operationArguments,
                                                   returnTypes);
  String result = (String) response[0];
  Assert.assertNotNull(result);
  System.out.println(result);

        /*OrderServiceClient orderServiceClient = getOrderServiceClient();
        Order order = createOrder(1, new Date(), "Indika Sampath");
        String result = orderServiceClient.addOrder(order);
        Assert.assertNotNull(result);
        System.out.println(result);*/
    }

    /**
     * Axis2 Client stuff
     *
     * @return
     * @throws org.apache.axis2.AxisFault
     */
    private RPCServiceClient getRpcServiceClient() throws AxisFault {
        RPCServiceClient serviceClient = new RPCServiceClient();
        Options options = serviceClient.getOptions();
        EndpointReference targetEPR =
                new EndpointReference("http://localhost:" + port +
                        "/axis2/services/OrderService");
        options.setTo(targetEPR);
        return serviceClient;
    }

You don't need to use mockup framework for testing. You can easily test your web services as they real time deploy in the server. :)