An introduction to the Web services framework for Jython

The open source SOA company's (WSO2) Web services framework for Jython (WSF/Jython) provides a simple approach to creating and consuming Web services in Jython. This framework integrates the Apache Axis2 Web services engine with Jython, extending all the power and versatility of the Axis2 engine to Jython users. Now, with just a few lines of code, Jython users can enjoy the benefits of Service-Oriented Architecture (SOA) using Web services. Web service clients written using the WSF/Jython framework can invoke enterprise Web services that require WS-Security. WSF/Jython also supports sending binary attachments as MTOM.

Share:

Heshan T. Suriyaarachchi (heshan.ucsc@yahoo.com), Undergraduate, University of Colombo

Heshan SuriyaarachchiHeshan is a final-year Computer Science Undergraduate at University of Colombo School of Computing (UCSC). His interests are Web services, SOA, Distributed Computing, and Cryptography. He developed the WSO2 Web Services Framework for Jython during his internship at WSO2 Inc.



06 October 2009

Also available in Chinese Japanese

Service-Oriented Architecture (SOA) is an architectural style that guides the creation of collaborative services that are loosely coupled and independent of their implementation technologies. The concept of SOA is an evolution of much older concepts of distributed and modular programming and represents a model in which functionality is decomposed into distinct units called services, where a service can be classified as an automated computing action that provides computing logic to content. The fundamental building blocks of SOA are services. Web services are an implementation methodology that adopts standard protocols to execute SOA. Web services intend to reconnect the fragmented middleware arena, making interoperability the highest priority. In the blooming space of Cloud computing, we see an industry trend towards a componentized software ecosystem built on Software as a Service (SaaS), Hardware as a Service (HaaS), and Platform as a Service (PaaS). Therefore, it is essential to know the basic building blocks of SOA to grasp Cloud computing concepts.

For this article, you should have a basic understanding of Apache Axis2, as it will help you understand the solution architecture better. Although it would be an advantage to have prior Axis2 knowledge, you can also use WSF/Jython to expose and consume Jython Web services.

WSF/Jython architecture

Apache Axis2 is a SOAP processing engine. It consumes an incoming SOAP message and retrieves the information. This information is given to the Web service business logic. Afterwards, a SOAP message is generated from the result obtained from the business logic and passed back to the client. However, a problem will arise if the business logic is in Jython.

The solution to the requirement of exposing a Jython Web service in Axis2 lies within the pluggable deployer concept of Axis2. A deployer enables the dynamic nature of Axis2 to deploy services. In order to expose services written in Jython, a custom deployer is written together with a Jython message receiver. Python data types are dynamic as opposed to the static data types in XML schema. Therefore, within the deployer, a mapping is required between the Python data types and the XML schema data types. This process is known as data binding. Thereafter, with the help of data binding and method annotations, an XML schema is generated for the Jython service. This XML schema, together with meta data pertaining to an Axis service, is given to the Axis2 engine. The Axis2 engine creates the Web Service Description Language (WSDL), and thus the Jython service will be exposed to the world as a Web service.

This particular solution requires communicating between Jython scripts and Java™ classes. Jython, which is the implementation of the Python programming language in Java, is used to achieve this purpose. It is a programming hybrid. It exhibits the strengths of both its parents, namely, Java and Python. Since Jython is completely written in Java, scripts written using Jython will run on top of any compliant Java Virtual Machine (JVM). The Jython interpreter will also support many shortcuts, which will enable it to use existing Java libraries.

This architecture identifies two basic actions a SOAP processor should perform: sending and receiving SOAP messages. It provides two pipes (or flows) to perform these two basic actions. These pipes are used for sending and receiving data. The Axis engine implements these two pipes, and these pipes are named In Pipe and Out Pipe. Complex Message Exchange Patterns (MEPs) are constructed by combining these two pipes.

Figure 1. WSF/Jython architecture
A box labeled Client Side, containing Jython Client, Jython client API, Jython JAR, Axis2 Client API, Handlers and Transport Sender communicates through SOAP to box labeled Server Side, containing Transport Receiver, Handlers, Jython Message Receiver, Jython JAR, and Jython Web Service.

Extensibility of the SOAP processing model is provided through handlers. When a SOAP message is being processed, the handlers that are registered will be executed. These handlers can be registered in global, service, or operation scope, and the final handler chain is calculated by combining the handlers from all the scopes.

The handlers act as interceptors, and they process parts of the SOAP message and provide add-on services. Usually, handlers work on the SOAP headers, yet, they may access or change the SOAP body as well.

When a SOAP message is being sent through the Client API, an Out Pipe activates. The Out Pipe will invoke the handlers and terminate with a Transport Sender that sends the SOAP message to the target endpoint. The SOAP message is received by a Transport Receiver at the target endpoint, which reads the SOAP message and starts the In Pipe. The In Pipe consists of handlers and ends with the Jython Message Receiver, which consumes the SOAP message and hands it over to the application.

In a nutshell, the incoming SOAP message is received by the Transport Listener, and it is passed through the handler chain. Then, it is given to the Jython Message Receiver, which traverses through the AXIs Object Model (AXIOM) structure and retrieves the required information. AXIOM is the XML infoset model that was developed for Apache Axis2. This retrieved information is passed on to the Jython service where an AXIOM is created from the returned object. Then, it is sent back, through the handler chain and the Transport Sender, which in turn sends the SOAP message to the SOAP endpoint to which the message was destined to. The process explained above takes place for each and every SOAP message that is exchanged.

WSF/Jython features

Below are lists of the server-side and client-side features of WSF/Jython.

Server-side features

  • Support for exposing services written in Jython
  • DataBinding support using a simple annotation mechanism
  • Automated WSDL generation
  • Ability to expose all enterprise features of Axis2 to services written in Jython

Client-side features

  • Support for invoking Web services in a simple, clean manner
  • Ability to use WS-Addressing when invoking services
  • Ability to invoke services which require WS-Security
  • Ability to send binary attachments using MTOM

Writing a Jython service

Let's write a simple Jython script that has an operation named deduct. It takes two input variables and returns the difference of those two numbers. Since we have to perform XML schema generation, we must annotate our Jython script. An annotated Jython method is given in the example below.

Listing 1. Simple service
#@annotate("returns=int", "operationName=deduct", var1="integer", var2="integer")
def deduct(var1,var2):
  	var3 = var1 - var2
	return var3

The set of instructions to be followed while annotating the Jython script is as follows:

  1. The annotation will start as #@annotate.
  2. Each attribute should be within double quotations.
  3. The return attribute should equate to the return type of the method.
  4. The operationName attribute should equate to the operation name of the method.
  5. Each input parameter should have the variable name and its value.
  6. Each parameter should have a unique reference name.
Listing 2. Annotations
#@annotate("returns=int", "operationName=deduct", var1="integer", var2="integer")

Usage restrictions

When deploying a Jython script, you must follow the guidelines given below:

  1. Annotating a script should only be done according to the annotation mechanism specified above.
  2. Parameter names (reference variables) should have unique names.
  3. If there is no return type, you must specify it as hashNone.
  4. Add the following line to your axis.xml (Listing 3):
Listing 3. Line to add to axis.xml
<deployer extension=".py" directory="scripts" 
class="org.wso2.wsf.jython.deployer.JythonDeployer">
</deployer>

A simple service

Based on the above instructions on annotations, you can write a simple Jython service, given below, and expose it as a Web service with the help of the Jython deployer.

Listing 4. Sample service
#@annotate("returns=double", "operationName=f", a="double")
def f(a):
    return a

#@annotate("returns=int", "operationName=add", var1="integer", var2="integer")
def add(var1,var2):
    return var1+var2

#@annotate("returns=double", "operationName=deduct", var1="double", var2="double")
def deduct(var1,var2):
    return var1-var2

#@annotate("returns=int", "operationName=addTwo", var1="integer", var2="integer",
var3=(a="string", b="integer"))
def addTwo(var1,var2,var3):
    return var1+var2

#@annotate("returns=int", "operationName=doComplexStuff", var1="integer", 
var2="(a="integer", b="integer")", var3="(a="string", b="integer")")
def doComplexStuff(var1,var2,var3):
    return var1

class MyClass:
    #@annotate("returns=integer", "operationName=MyClass.multiply", var1="integer",
    var2="integer")
    def multiply(var1,var2):
        return var1*var2

Writing a Jython Web service client

You've seen how to write a Web service in Jython. Now let's look at how you can write a Jython service client to consume a service. The client has to prepare the payload, send a request to the service, and then receive and process the response. The steps to be followed when implementing a Jython Web service client include:

  1. Set the desired request payload and options. You can set the desired payload either in plain text form or as a WSMessage.
  2. Create a WSClient instance. You can use the WSClient instance to consume the service. Then set options as arguments to the constructor.
  3. Send request and receive response. Invoke the request() method passing the message as a parameter. This method returns a WSMessage instance, representing the response message.
  4. Consume the response. Process the response in line with client business logic.
Listing 5. Preparing the request message
req_message = WSMessage(req_payload_string, {"to" :END_POINT})

In the above code fragment, a WSMessage instance is created with a payload to be sent in the request and the service endpoint. The "to" element of the option hash is mapped to the address location of the service. In other words, the "to" address indicates where the request should be sent.

Listing 6. Sending a request and receiving a response
client = WSClient({}, LOG_FILE_NAME)   
res_message = client.request(req_message)

For sending a request with the input message created earlier, you need a WSClient instance. You pass the message to be sent to the service to the request() method. This will send the payload contained in the given message and receive the response and return a message instance with the response payload.

The minimum requirements for consuming a Web service using the WSF/Jython API are the payload and service endpoint URI. We discussed how these can be done in the above section. The advantage of using the WSF/Jython extension is that it supports more than just SOAP. You can use WS-Addressing, MTOM, and WS-Security when consuming Web services. You can also invoke services using REST style calls. The way in which these options can be associated with the WSMessage and WSClient is described below.

Using SOAP

You can use the "use_soap" option at the client level to specify the SOAP version to be used.

Listing 7. Using SOAP
client = WSClient({"use_soap" : "true"}, LOG_FILE_NAME)

Using REST

Web services using REST style calls can be done by setting the "use_soap" option to "false". In case of REST style of invocation, you can use either the HTTP POST method or the HTTP GET method.

Listing 8. Using REST
# REST with HTTP POST
client = WSClient({  "to" : END_POINT,
                     "http_method" : "POST",
                     "use_soap" : "false"},
                  LOG_FILE_NAME)

# REST with HTTP GET
client = WSClient({  "to" : END_POINT,
                     "http_method" : "GET",
                     "use_soap" : "false"},
                  LOG_FILE_NAME)

Attachments with MTOM

Listing 9. Attachments with MTOM
req_message = WSMessage(req_payload_string, {"to" :END_POINT,
                                "attachments" : {"myid1" : "first attachment", 
				                 "myid2" : "second attachment"}})

When sending attachments, you can configure a client either to send the attachment in an optimized format or a non-optimized format. If the attachment is sent in binary optimized format, file content will be sent as it is, out of the SOAP body, using MIME headers, and the payload would have an XOP:Include element, referring to the MIME part that contains the binary attachment. In case of a binary non-optimized format, the attachment content will be sent in the payload itself, as a base64 encoded string.

Listing 10. Configuring in an optimized format or a non-optimized format
# send attachments binary optimized
client = WSClient({"use_mtom" : "true"})

# send attachments binary non-optimized
client = WSClient({"use_mtom" : "false"})

Using WS-Addressing

There are two basic requirements that you have to specify when using WS-Addressing on the client side with WSF/Jython. One is that you have to provide a WS-Addressing action at the message level. The other is that you have to enable the use of WS-Addressing at the client level.

Listing 11. WS-Addressing
req_message = WSMessage(req_payload_string,
		         {"to" : "http://localhost/echo_service_addr/echo",
                          "action" : "http://jython.wsf.wso2.org/samples/echoString"})
              
client = WSClient({"use_wsa" : "true"})

In the above sample code fragment, the WS-Addressing action is set using the "action" element of the options dictionary passed to the WSMessage constructor. WS-Addressing is enabled with the "use_wsa" option passed to the WSClient constructor.

In addition to action, there are other WS-Addressing-related SOAP headers that can be sent in a message. WSF/Jython supports these headers as properties at the message level or as options at the client level. An example is shown below:

Listing 12. Properties at the message level or options at the client level
req_message = WSMessage(req_payload_string,
	                        {"to" : "http://www.company.com/order_processing/process",
	                         "action" : "http://jython.wsf.wso2.org/samples/order",
	                         "from" : "http://www.company.com/order_placing/place",
	                         "reply_to" : "http://www.company.com/billing/bill",
	                         "fault_to" : "http://www.company.com/re_odering/order"})

client = WSClient({"use_wsa" : "true"})

Using WS-Security

Note that in order to run security clients or services, you should engage WS-Addressing, and then create the client using the policy object.

Listing 13. WS-Security
req_message = WSMessage(req_payload_string,
                           {"to" : "http://localhost/samples/security_service/callback",
                            "action" : "http://jython.axis2.org/samples/echoString"})

client = WSClient({"use_wsa" : "true",
                   "policy" : "Policy_Path"})

res_message = client.request(req_message)

Running samples

Set up the environment by adding Jython jar and necessary axis2 jars to the classpath. Then add the WSF/Jython jar to the classpath. Once you have set up the environment correctly, you should be able to run Jython scripts that are compliant with WSF/Jython API specifications. WSF/Jython is shipped with a shell script that will set this environment for you. This makes it possible to execute a client script by simply providing its absolute path. The following three listings show an example.

Listing 14. Command for running a sample
sh wsfjython.sh /home/heshan/wsf-jython/jython/
distribution/target/wsf-jython-SNAPSHOT-bin/samples/amazon.py
Listing 15. Amazon Web service client
from org.wso2.wsf.jython.client import WSClient
from org.wso2.wsf.jython.client import WSFault
from org.wso2.wsf.jython.client import WSMessage

req_payload_string = "<ItemSearch><Service>AWSECommerceService</Service> 
		      <SearchIndex>Books</SearchIndex>
		      <AWSAccessKeyId>XXXXXXXXXXXXXXXXXXX</AWSAccessKeyId>
		      <Operation>ItemSearch</Operation>
		      <Keywords>sri lanka travel books</Keywords></ItemSearch>"
LOG_FILE_NAME = "/home/heshan/IdeaProjects/MRclient/src/python_amazon.log"
END_POINT = "http://webservices.amazon.com/onca/xml"

try:
    client = WSClient({  "http_method" : "GET",
                         "use_soap" : "false"},
                      LOG_FILE_NAME)
    req_message = WSMessage(req_payload_string, {"to" :END_POINT})
    print " Sending OM      : " , req_payload_string
    res_message = client.request(req_message)
    print " Response Message: " , res_message

except WSFault, e:
    e.printStackTrace();
Listing 16. Response
<ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2005-10-05">
    <OperationRequest>
        <HTTPHeaders><Header Name="UserAgent" Value="Axis2" /></HTTPHeaders>
        <RequestId>114HR356NB5H18RR5WNM</RequestId>
        <Arguments><Argument Name="SearchIndex" Value="Books" />
        <Argument Name="Service" Value="AWSECommerceService" />
        <Argument Name="Keywords" Value="sri lanka travel books" />
	<Argument Name="Operation" Value="ItemSearch" />
        <Argument Name="AWSAccessKeyId" Value="XXXXXXXXXXXXXXXXXXX" />
        </Arguments><Errors><Error>
	<Code>AWS.InvalidParameterValue</Code>
        <Message>XXXXXXXXXXXXXXXXXXX is not a valid value for AWSAccessKeyId.
	Please change this value and retry your request.</Message>
        </Error></Errors>
    </OperationRequest>
</ItemSearchResponse>

Conclusion

This framework integrates the Apache Axis2 engine with Jython, enabling you to use the power and versatility of the Apache Axis2 Web service middleware engine in Jython. Since this framework supports the code first approach, with the help of a few lines of code, you can get a client and a service up and running without much effort. Because the framework itself is integrated to Apache Axis2, you can use a wide array of functionalities that Axis2 supports. The supported features include attachments with MTOM, SOAP, REST, and WS-* standards such as WS-Addressing and WS-Security. Furthermore, it is an open source distribution, and therefore you don't have to pay a licensing fee or a subscription. If a feature you need is not there, you can request it via the mailing list, or you can tweak it yourself and contribute to the community.

Resources

Learn

Get products and technologies

  • Download IBM product evaluation versions and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=433346
ArticleTitle=An introduction to the Web services framework for Jython
publish-date=10062009