Showing posts with label Web Service. Show all posts
Showing posts with label Web Service. Show all posts

Friday, October 24, 2014

Performing a simple load test on WSO2 ESB proxy using Apache Jmeter

In this post I'm  explaining how you could perform a simple load test on a WSO2 ESB proxy or any Web service using Apache Jmeter which is a free and very easy to use tool. Also, please note that this post describes only how to setup a very basic test case and it's intended audience is people who has no experience in Jmeter (I'm not an expert too).

In this example I will described how you can create a test plan where you do a simple load test by sending same message over and over again to a ESB proxy or a web service.

Bear minimum required to perform a test

After launching Jmeter the following screen will appear. On the left pane you will see an icon named “Test Plan”.
  • Right click on “Test plan” → Select “Add” → Select “Threads (Users)” → Select “Thread Group”. Here you can configure the number of threads that you going to use for the test. The number of threads are analogues to number of simulated users. Also you can specify the Loop Count or number of requests sent by each thread. This where you decide on the load that you going to use for the test
    • Therefore, if you set “Number of Threads (users)” = 5 and “Loop Count” = 100. The test will send 5 * 100 = 500 requests to the web service.
  • Now we have to set the SOAP request that's going to be used in the test. For this example we will be sending the same hard coded request over and over again. To add the request right click on the “Thread Group” → Select “Add” → Select “Sampler” → Select “SOAP/XML-RPC Request”

Above two  are the basic steps you need carry out to perform a test using Jmeter for a SOAP service. Now if you click "Start", JMeter will send the specified number of requests to the proxy or the web service. In addition to above, below I have described couple of JMeter components that may also be useful when building a real world test plan.

Setting HTTP headers


Let's say the proxy service you are trying to test is secured, so that you need to send the “Authorization” header. For fulfilling this requirement you have to use the “HTTP Header Manager”. To add a HTTP header manager right click on the “SOAP/XML-RPC Request” → select “Config Element” → select “HTTP Header Manager” . In this component you can set the headers as required.


Viewing and testing responses


You can add listeners to see the responses form the service. To add a Listener, right click on the “SOAP/XML-RPC Request” → Select “Listener” and from there you can select various types of listeners. Following is a screen shot of “View Result Tree” Listener which is a very primitive but still a very useful listner.


From this listener you can view the request and response payloads for each message along with the status of the message.

In order to validate the response coming from the service you can use “Assertion”. In an assertion you can set various validations on the response. Following is a “Response Assertion” where I'am testing if the “Text” elements in the response “Contains” the text “IBM”.

If a certain response does not meet the condition in Assertion, in the “Listener” the response status will be marked as "error" with a red exclamation mark. To add an “Assertion” right click on the “SOAP/XML-RPC Request” → Select “Assertion” and from here select the type of assertion you want.


Monday, May 19, 2014

Avoiding "Provider net.sf.saxon.TransformerFactoryImpl not found" error in Axis2

Scenario

When I'm trying to access the WSDL's of  web services deployed in a Axis2 SOAP engine the following error is occurring,

javax.xml.transform.TransformerFactoryConfigurationError: Provider net.sf.saxon.TransformerFactoryImpl not found
    at javax.xml.transform.TransformerFactory.newInstance(Unknown Source)
    at org.apache.ws.commons.schema.XmlSchema.serialize_internal(XmlSchema.java:505)
    at org.apache.ws.commons.schema.XmlSchema.write(XmlSchema.java:478)
    at org.apache.axis2.description.AxisService2WSDL11.generateOM(AxisService2WSDL11.java:215)
    at org.apache.axis2.dataretrieval.WSDLDataLocator.outputInlineForm(WSDLDataLocator.java:131)
    at org.apache.axis2.dataretrieval.WSDLDataLocator.getData(WSDLDataLocator.java:73)
    at org.apache.axis2.dataretrieval.AxisDataLocatorImpl.getData(AxisDataLocatorImpl.java:81)
    at org.apache.axis2.description.AxisService.getData(AxisService.java:2980)
    at org.apache.axis2.description.AxisService.getWSDL(AxisService.java:1653)
    at org.apache.axis2.description.AxisService.printWSDL(AxisService.java:1421)
    at org.wso2.carbon.core.transports.util.Wsdl11Processor$1.printWSDL(Wsdl11Processor.java:43)
    at org.wso2.carbon.core.transports.util.AbstractWsdlProcessor.printWSDL(AbstractWsdlProcessor.java:86)
    at org.wso2.carbon.core.transports.util.Wsdl11Processor.process(Wsdl11Processor.java:57)
    at org.wso2.carbon.transport.nhttp.api.NHttpGetProcessor.processWithGetProcessor(NHttpGetProcessor.java:137)
    at org.wso2.carbon.transport.nhttp.api.NHttpGetProcessor.process(NHttpGetProcessor.java:277)
    at org.apache.synapse.transport.nhttp.ServerWorker.run(ServerWorker.java:256)
    at org.apache.axis2.transport.base.threads.NativeWorkerPool$1.run(NativeWorkerPool.java:173)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:662)
[2014-05-19 17:56:10,980] ERROR - DefaultHttpGetProcessor Error processing request
org.apache.axis2.dataretrieval.DataRetrievalException: Provider net.sf.saxon.TransformerFactoryImpl not found
    at org.apache.axis2.dataretrieval.AxisDataLocatorImpl.getData(AxisDataLocatorImpl.java:85)
    at org.apache.axis2.description.AxisService.getData(AxisService.java:2980)
    at org.apache.axis2.description.AxisService.getWSDL(AxisService.java:1653)
    at org.apache.axis2.description.AxisService.printWSDL(AxisService.java:1421)
    at org.wso2.carbon.core.transports.util.Wsdl11Processor$1.printWSDL(Wsdl11Processor.java:43)
    at org.wso2.carbon.core.transports.util.AbstractWsdlProcessor.printWSDL(AbstractWsdlProcessor.java:86)
    at org.wso2.carbon.core.transports.util.Wsdl11Processor.process(Wsdl11Processor.java:57)
    at org.wso2.carbon.transport.nhttp.api.NHttpGetProcessor.processWithGetProcessor(NHttpGetProcessor.java:137)
    at org.wso2.carbon.transport.nhttp.api.NHttpGetProcessor.process(NHttpGetProcessor.java:277)
    at org.apache.synapse.transport.nhttp.ServerWorker.run(ServerWorker.java:256)
    at org.apache.axis2.transport.base.threads.NativeWorkerPool$1.run(NativeWorkerPool.java:173)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:662)
Caused by: javax.xml.transform.TransformerFactoryConfigurationError: Provider net.sf.saxon.TransformerFactoryImpl not found
    at javax.xml.transform.TransformerFactory.newInstance(Unknown Source)
    at org.apache.ws.commons.schema.XmlSchema.serialize_internal(XmlSchema.java:505)
    at org.apache.ws.commons.schema.XmlSchema.write(XmlSchema.java:478)
    at org.apache.axis2.description.AxisService2WSDL11.generateOM(AxisService2WSDL11.java:215)
    at org.apache.axis2.dataretrieval.WSDLDataLocator.outputInlineForm(WSDLDataLocator.java:131)
    at org.apache.axis2.dataretrieval.WSDLDataLocator.getData(WSDLDataLocator.java:73)
    at org.apache.axis2.dataretrieval.AxisDataLocatorImpl.getData(AxisDataLocatorImpl.java:81)
    ... 13 more

Reason

In one of the web service deployed in my server, it has used Saxon XML transformer implementation instead of default java XML transformer implementation.  Therefore, in it's code it has added the following static initialization block

1
2
3
static{
    System.setProperty("javax.xml.transform."net.sf.saxon.TransformerFactoryImpl");
} 

The setting of this system property is the reason for this error.  Also some can set this system property from command line using -Djavax.xml.transform.TransformerFactory=cnet.sf.saxon.TransformerFactoryImpl

Workaround

Remove the above static initialization block which sets the javax.xml.transfrom system property. Then, when instantiating TransfromFactory in the code, explicitly instantiate the saxon trasform factory with the full qualified class name as follows,

1
TransformerFactory fact = new net.sf.saxon.TransformerFactoryImpl()

In this way still you can use the  Saxon transform implementaion without casuing any complications. When the system property is set, all the instantiations of transform factories which not uses  the full qualified name of the transform factory class will be initialized with net.sf.saxon.TransfromerFactoryImpl and then it might lead to errors if they assume the default java xml transform implementation.

After recompiling and deployed the service the exception was not thrown

Saturday, January 18, 2014

Writing an Axis2 web service from the scratch using AXIOM in Eclipse

This sample demonstrates how to write a  web service to be deployed in Axis2 SOAP engine using AXIOM in 9 simple steps.  I have used Eclipse as the IDE for this sample.

First let's look at how to write  the web service and then we'll see how to deploy it in Axis2. And at last how to test the service using SoapUI.

To follow this sample you need Axis2 and Eclipse downloaded in your computer. The directory in which you have installed axis will be refered to as <AXIS2_HOME> bellow. For this example I'm using Axis2 version 1.6.2.

This sample service is a order processing service which has only three basic operations
  • AddOrder - Allows users to add orders(Only argument)
  • GetPrice - Allow users to  get the price of a specified symbol(Argument and return value)
  • GetOrders - Allow users to  retrieve back all placed orders(Only Return value)

Writing the Service

1. Create a new "Java Project" and lets name it as "SampleOrderProcessingService".

2. Then add,
  • axiom-api-x.x.xx 
  • axiom-dom.x.x.xx 
  • axiom-impl.x.x.xx 
as external JARs to the project. These JARs can be found in <AXIS2_HOME>/lib


3. After exporting the JARs you add the following code listing in a class with what ever the name you like, lets name it as "SampleOrderProcessingService.java".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package orderprocessing.service;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMText;


public class SampleOrderProcessingService {

 private HashMap<String, Double> orders = new HashMap<String, Double>(); 
 
 public static final String OMnamespace = "http://orderprocessingservice.com";
 
 OMFactory factory = OMAbstractFactory.getOMFactory();
    OMNamespace OMNamespaceObj = factory.createOMNamespace(OMnamespace, "ns");
 
    public void AddOrder(OMElement element) throws XMLStreamException{
  element.build();
        element.detach();
        
        OMElement symbol = element.getFirstChildWithName(new QName(OMnamespace, "symbol"));
        OMElement price = element.getFirstChildWithName(new QName(OMnamespace, "price"));
       
        orders.put(symbol.getText(), new Double(price.getText()));
        
        System.out.println("Order Added. [Symbol=" + symbol.getText() 
                           + ", Price=" + price.getText() +"]");
      }

     public OMElement GetPrice(OMElement element)throws XMLStreamException{
  element.build();
        element.detach();

        OMElement symbol = element.getFirstChildWithName(new QName(OMnamespace, "symbol"));
        
        String result = "NONE";
        if (orders.containsKey(symbol.getText())){
          Double price = (Double) orders.get(symbol.getText());
          result = Double.toString(price);
        }

        //Root element of the response can have any name
        OMElement root = factory.createOMElement("result", OMNamespaceObj);
        OMElement value = factory.createOMElement("value", OMNamespaceObj);
        OMText resultText = factory.createOMText(value, result);
        
        value.addChild(resultText);
        root.addChild(value);
        
        System.out.println("Price Retrived. [Symbol=" + symbol.getText() + ", Price=" + value.getText() +"]");
        
        return root;
     }

     public OMElement GetOrders(OMElement element)throws XMLStreamException{
  element.build();
        element.detach();
        
  Iterator<Entry<String, Double>> it = orders.entrySet().iterator();
  
  OMElement root = factory.createOMElement("OrderQueryResult", OMNamespaceObj);
  
     while (it.hasNext()) {
         Entry<String, Double> pair = it.next();
         
         OMElement order = factory.createOMElement("Order", OMNamespaceObj);
         OMElement symbol = factory.createOMElement("symbol", OMNamespaceObj);
         OMElement price = factory.createOMElement("price", OMNamespaceObj);
         
         OMText symbolText = factory.createOMText(symbol, pair.getKey());
         OMText priceText = factory.createOMText(price, Double.toString(pair.getValue()));
         
         symbol.addChild(symbolText);
         price.addChild(priceText);
         order.addChild(symbol);
         order.addChild(price);
         root.addChild(order);
         
         System.out.println("Oder Retrived. [Symbol=" + pair.getKey() + ", Price=" + pair.getValue().toString() +"]");
     }
  return root;
 }
}

Notes :  
AddOrder method represents AddOrder operation in the web service and it takes order as  an argument and returns nothing. If you take a look at the method body you will see that the method is expecting it's request to have child elements called "symbol" and "price". Therefor the clients must meet this expectation when sending requests, otherwise the service will fail. So the request must look like something similar to bellow(not exactly),

<AddOrder>
    <symbol>sym</symbol>
    <price>10.2</price>
</AddOrder>

Once the order is received it will fetch the symbol and price and store it in its cache.

It must be noted that clients of axiom services must be aware of the expected request format by the service and adhere to that format when sending requests.

The GetPrice method takes  symbol as an argument and it returns the price as its return value. The returned XML structure is created using AXIOM in the method body and it will look like as follows,

<result>
    <value>10.2<value>
</result>

GetOrders method takes an dummy argument. This is due to the fact that Axis2 does not have a message handler which handles operations that has only return values. It creates a XML tree which contains all the orders and returns it.

Writing the services.xml

After finishing writing the service you have to specify the operations and other information in a file called services.xml for axis2 to be able to deploy it.

4. Create a folder called META-INF in your project at the root level.

5. Then add a file called services.xml into it.


6. Add the following contents to the services.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<service name="SampleOrderProcessingService" scope="application">
    <description>
        Sample Order Processing Service
    </description>
    <operation name="AddOrder">
        <messageReceiver class="org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver"/>
    </operation>
    <operation name="GetPrice">
        <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
    </operation>
   <operation name="GetOrders">
        <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
    </operation>
    <parameter name="ServiceClass">orderprocessing.service.SampleOrderProcessingService</parameter>
</service>

Notes : 
The web service is named as "SampleOrderProcessingService" and is given a description as "Sample Order Processing Serviece"(Line 2-5).

For each operation in the service you must include a <operation> element. The "name" attribute must match the method name in the class we wrote in step 3. So there are there operation elements for each method in the class. And also it's possible to have methods that are not exposed as operations in the java class.

Another important thing is the messageReceiver specified in the operation. This message receiver must match the  method signature of the method corresponding to the operation.

Operation | Method Signature                        | Message Handler
----------------------------------------------------------------------------
AddOrder  | Only arguments, no return value         | RawXMLINOnlyMessageReceiver
GetPrice  | Takes an argument, returns a value      | RawXMLINOutMessageReceiver
GetOrders | Takes a dummy argument, returns a value | RawXMLINOutMessageReceiver

GetOrders Method had to use a INOut messages receiver and take a dummy argument, though it does not require to take an argument. This is because Axis2 does not have a OUTOnly message receiver

At Last, you have to specify the full qualified name of the service class(Line 15)

Deploying the service

Now all you have to do is pack  .class file and services.xml into a .aar and deploy it in Axis2 server.

7. Right click on the project and Export it as a JAR (SampleOrderProcessingService.jar) to a desired location


8.  Rename the .jar file to have extension .aar. Change SampleOrderProcessingService.jar to  SampleOrderProcessingService.aar

This is because .aar is the archive type recognized by Axis.

9. Copy  SampleOrderProcessingService.aar file into <AXIS2_HOME>/repository/services and Axis2 will automatically deploy your service(You don't have to restart Axis2 server). If the service is properly deployed you will see something like follows in your terminal,


Also you must be able to see SampleOrderProcessingService listed if you goto http://localhost:8080/axis2/services/


Testing the service

Once the service is deployed, we can test it to verify it's functionality. For this purpose lets use SoapUI.


Before sending any request make sure that xmlns:ser is set to the namespace given in the server class. In this case xmlns:ser="http://orderprocessingservice.com" since we have defined namespace to be "http://orderprocessingservice.com" in line:21 of the service class.

Following is a valid AddOrder message. Note that we have to add <symbol> and
<price> elements to the request.


When you send this message, you must be able to see a message similar to below in the terminal which runs the Axis2 server

Order Added. [Symbol=sym1, Price=10.2]

Likewise you can send requests to other operations, bellow screenshot shows the requests along with their responses for GetOrders and GetPrice operations.


That's all from this post. Hope it helped you. Also I'm expecting to do a post on how to write an AXIOM client to consume this service. Please leave your comments and feedback.