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.
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
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.
Below are lists of the server-side and client-side features of WSF/Jython.
- 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
- 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
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:
- The annotation will start as
#@annotate. - Each attribute should be within double quotations.
- The
returnattribute should equate to the return type of the method. - The
operationNameattribute should equate to the operation name of the method. - Each input parameter should have the variable name and its value.
- Each parameter should have a unique reference name.
Listing 2. Annotations
#@annotate("returns=int", "operationName=deduct", var1="integer", var2="integer")
|
When deploying a Jython script, you must follow the guidelines given below:
- Annotating a script should only be done according to the annotation mechanism specified above.
- Parameter names (reference variables) should have unique names.
- If there is no return type, you must specify it as
hashNone. - 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> |
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:
- Set the desired request payload and options. You can set the desired payload either in plain text form or as a WSMessage.
- Create a WSClient instance. You can use the WSClient instance to consume the service. Then set options as arguments to the constructor.
- 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.
- 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.
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)
|
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)
|
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"})
|
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"})
|
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)
|
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>
|
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.
Learn
- See the WSO2 Web service Framework for Jython
product home page.
- See the Apache Axis2 Web service engine home
page.
- Take the Jython tutorial at the Jython Project
home page.
- Read the article, "Deploying a Python Service on Axis2."
- Read the article, "Invoking Enterprise Web Services using Jython."
- Check out My
developerWorks: Find or create groups, blogs, and
activities about Web development or anything else that
interests you.
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®.





