Hi, Mario.
Thank you for the great contribution!
I just converted the document to a wiki page @
http://cwiki.apache.org/confluence/display/TUSCANYWIKI/Modeling+and+Implementing+a+Tuscany+Application+out+of+a+WSDL+File
with some minor formatting. The community can further enhance it.
Thanks,
Raymond
----- Original Message -----
From: "Antollini, Mario" <[EMAIL PROTECTED]>
To: <tuscany-dev@ws.apache.org>
Sent: Sunday, November 04, 2007 11:37 AM
Subject: Tutorial: Modeling and Implementing a Tuscany Application out of a
WSDL File
Hello,
I just wanted to contribute to the community a Tutorial I recently wrote
called "Modeling and Implementing a Tuscany Application out of a WSDL File".
I am just sending plain text, but after receiving any feedback (which will
actually be greatly appreciated) me, or any Tuscany contributor/committer,
can enrich it and make it available either via Tuscany's wikipage or as a
PDF file(?).
Thanks and best regards,
Mario
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Modeling and Implementing a Tuscany Application out of a WSDL File
The main focus of this document is to report and share the experience gained
after creating a new Tuscany application whose starting point was a WSDL
file. In other words, this document will serve as guide for those interested
in creating a Tuscany application out of a given WSDL. It not only describes
the steps followed but also mentions the difficulties faced and their
resolution. Moreover, it could also serve as the ground basis for creating a
WSDL2Composite tool (à la Axis2's WSDL2Java).
Introduction
In the same way the business logic for a Web Service can be created
following a top-down approach (i.e. specify the interface first, then write
the code which sticks to it), we had the need to create a Tuscany
application which adhered to a Web Service Interface already defined.
What we tried to accomplished was the challenge to model and implement an
Amazon-like application running as a Tuscany Application (from now on
Shopping Store). Of course, we wanted this application to be fully
compatible to the Amazon's WSDL
(http://ecs.amazonaws.com/AWSECommerceService/2007-05-14/AWSECommerceService.wsdl).
This way, any given (Web) application written to interact with Amazon
through its WS interface (e.g. http://www.openlaszlo.org/node/198) would be
seamlessly communicating with the application entirely coded as a Tuscany
application.
Having understood the intention of this document we can proceed to detail
the steps carried out to build the application.
Specifying the Web Service Interface
Following a top-down approach, the first step is to define the interface of
the Tuscany service/s which will be exposed as Web Services. Once the
interfaces are defined, a WSDL file which expresses/describes the methods,
its parameters and returned types must be written.
In the particular case of the application which gave birth to this document,
the WSDL file was taken from Amazon. It is important to make clear that in
its first phase of development the Shopping Store application was intended
to provide only a Shopping Cart service, and this document only covers this
initial phase. Therefore, since Amazon's WSDL is composed of a lot of
different methods/operations it had to be reduced and only the operations
related to the Shopping Cart remained. Here you can see the final version of
the WSDL file called shoppingstore.wsdl:
https://issues.apache.org/jira/secure/attachment/12368949/ShoppingStore.wsdl.
It is worth mentioning that the soap address was modified in order for it to
point to the location where the Shopping Cart service will be running.
Generate Stub and Skeleton Code
Now, what we need to do is to start writing the source code which offers the
functionality/operations offered by the WSDL.
The first natural thing to do is to translate the interfaces of the Shopping
Store application (which only exposes the Shopping Cart service so far) to
the Java language. In order to accomplish that we will use the wsdl2java
tool available in Axis2 (version 1.2). We will use this tool for two
purposes:
1 - Generate the stub code ("client-side") which will facilitate the effort
required to develop a standalone Java client which will interact with the
Shopping Cart via WS invocations.
2 - Generate the skeleton code ("server-side") which we will only use to
understand the (Java) interface our Shopping Cart service will stick to.
What we need to do is to create an script file with the following content
(assuming Axis2-1.2 was already downloaded, unzipped and the %AXIS2_HOME%
environment variable has been set):
Windows:
set CURRENT_DIR=.
setlocal EnableDelayedExpansion
set AXIS2_CLASS_PATH=%AXIS2_HOME%
FOR %%c in ("%AXIS2_HOME%\lib\*.jar") DO set
AXIS2_CLASS_PATH=!AXIS2_CLASS_PATH!;%%c
java -cp "%AXIS2_CLASS_PATH%" org.apache.axis2.wsdl.WSDL2Java -d
jaxbri -ss -ssi -g -uri .\ShoppingStore.wsdl
endlocal
Linux:
AXIS2_CLASSPATH=""
for f in "$AXIS2_HOME/lib/*.jar"
do
AXIS2_CLASSPATH="$AXIS2_CLASSPATH":$f
done
java -cp $AXIS2_CLASSPATH org.apache.axis2.wsdl.WSDL2Java -d
jaxbri -ss -ssi -g -uri ./ShoppingStore.wsdl
Note that the data binding used was JAXB (denoted by the option "-d
jaxbri"). Different errors/problems occurred at different stages of the
development of the ToyApp when other data bindings were used. JAXB was the
one which presented the less inconveniences throughout the development of
the ToyApp.
Place the script file in the same directory where the WSDL file is located
and then run it. Once the script finishes executing, the following directory
structure will be created in the current directory:
<CURRENT_DIR>/src/com/amazon/webserices/awsecommerceservice/_2007_05_14
and many files will show up in there.
After running the script commands, it is necessary to modify the source code
that has been generated, otherwise we will get runtime errors (very
difficult to solve at that time actually). What we need to do is to look for
the line * @XmlType(name = "", propOrder = { * in the following files and
add their names within the empty quotes: CartAdd.java, CartAddResponse.java,
CartClear.java, CartClearResponse.java, CartCreate.java,
CartCreateResponse.java, CartGet.java and CartGetResponse.java. For example,
in the case of CartAdd.java, the string * @XmlType(name = "", propOrder =
{ * must be replaced by * @XmlType(name = "CartAdd", propOrder = { *, and in
the case of CartCreateResponse.java, it must look like * @XmlType(name =
"CartCreateResponse", propOrder = { *.
All the generated files must be used for marshaling and unmarshaling
purposes, therefore they must be included at both compile- and run-time.
Thus, the best thing to do is to compile the generated code and pack it into
a jar file. In order for it to compile, we need to include the following
jars in a "lib" directory: axiom-api-1.2.4.jar, axis2-kernel-1.2.jar,
jaxb-api-2.0.2.jar, stax-api-1.0.1.jar and wsdl4j-1.6.2.jar. Also create an
empty directory called "build" and another one called "dist" and then add
the following lines to a build script:
Windows:
javac
.\src\com\amazon\webservices\awsecommerceservice\_2007_05_14\*.java -cp
.\lib\jaxb-api-2.0.2.jar;.\lib\stax-api-1.0.1.jar;.\lib\axiom-api-1.2.4.jar;.\lib\axis2-kernel-1.2.jar;.\lib\wsdl4j-1.6.2.jar;.\build
-d build
jar cvfM .\dist\AWS2007_05_14.jar -C .\build .
Linux:
javac
./src/com/amazon/webservices/awsecommerceservice/_2007_05_14/*.java -cp
`cygpath -wp
./lib/jaxb-api-2.0.2.jar:./lib/stax-api-1.0.1.jar:./lib/axiom-api-1.2.4.jar:./lib/axis2-kernel-1.2.jar:./lib/wsdl4j-1.6.2.jar:./build`
-d build -Xlint:unchecked
jar cvfM ./dist/AWS2007_05_14.jar -C ./build .
After executing it, a file called AWS2007_05_14.jar will be located in the
"dist" directory. It can be used in both both client and server side
projects to reduce coding effort.
Coding Server-Side Business Logic
Even though we now count with a library which is in charge of marshaling and
unmarshalling the remote invocations, we still need to take a look at one of
its source files: ShoppingStoreServiceSkeletonInterface.java. This file is
very useful for the server-side code since it describes the method names,
their parameters and the return type of the operations the service will
expose. Here you can see (a neat version of) the generated file:
/**
* ShoppingStoreServiceSkeletonInterface.java
*/
package com.amazon.webservices.awsecommerceservice._2007_05_14;
public interface ShoppingStoreServiceSkeletonInterface {
public CartGetResponse CartGet (CartGet cartGet);
public CartAddResponse CartAdd(CartAdd cartAdd);
public CartCreateResponse CartCreate(CartCreate cartCreate);
public CartModifyResponse CartModify(CartModify cartModify);
public CartClearResponse CartClear(CartClear cartClear);
}
Take into account that this skeleton file was generated using the reduced
version of the Amazon WSDL, therefore it only contains the methods related
to the Shopping Cart service.
We are now ready to implement the business logic for the Shopping Cart
service...
First, let us create the following directory structure (this will be a new
"project", thus create it in a new location):
-
-> this directory will contain everything needed to generate the jar
containing the Shopping Store app.
- wsdl
shoppingstore.wsdl
-> the reduced Amazon WSDL
shoppingstore.composite
-> the content of this file will be explained
later
-
-> this directory will contain everything needed to run the Shopping
Store application
-
-> lib required to compile and run the Shopping Store application
AWS2007_05_14.jar
-> the jar file we have just
created
-
-> directory containing the source code of the Shopping Store
application (we will be explain it later)
- shoppingstore
- server
ShoppingStoreServer.java
- services
- cart
CartService.java
CartServiceImpl.java
Now, let us specify its interface (which, in this case, will be identical to
the skeleton shown before) in a java file named CartService.java:
package shoppingstore.services.cart;
import org.osoa.sca.annotations.Remotable;
import com.amazon.webservices.awsecommerceservice._2007_05_14.*;
@Remotable
public interface CartService {
public CartCreateResponse CartCreate(CartCreate cartCreate);
public CartAddResponse CartAdd(CartAdd cartAdd);
public CartModifyResponse CartModify(CartModify cartModify);
public CartClearResponse CartClear(CartClear cartClear);
public CartGetResponse CartGet(CartGet cartGet);
}
The code was annotated with the @Remotable annotation. It indicates that the
interface is designed to be used for remote communication. Remotable
interfaces are intended to be used for coarse grained services. Operations
parameters and return values are passed by-value.
Now, we need to implement the above methods. A proper implementation of the
shopping cart logic is out of scope of this document; therefore, let us just
implement a basic cart service which only expects one item per invocation to
the CartAdd method and which just stores the cart and its items in main
memory. Let us create a new file called CartServiceImpl.java:
package shoppingstore.services.cart;
import org.osoa.sca.annotations.Scope;
import java.util.HashMap;
import com.amazon.webservices.awsecommerceservice._2007_05_14.*;
@Scope("COMPOSITE")
public class CartServiceImpl implements CartService {
private static long ID = 0;
//Associate usedID with CartInstance
private HashMap<String, Cart> cartsHash = new
HashMap<String, Cart>();
public CartCreateResponse CartCreate(CartCreate cartCreate)
{
CartCreateResponse cartCreateResponse = new
CartCreateResponse();
Cart cart =
getCart(cartCreate.getAWSAccessKeyId());
//If user has already created a cart during
this session
if(cart != null){
cartCreateResponse.getCart().add(cart);
return cartCreateResponse;
}
cart = new Cart();
cart.setCartId(this.generateID());
cart.setCartItems(new CartItems());
//Add Cart to the HashMap
addCart(cartCreate.getAWSAccessKeyId(),
cart);
cartCreateResponse.getCart().add(cart);
return cartCreateResponse;
}
public CartAddResponse CartAdd(CartAdd cartAdd) {
CartAddResponse cartAddResponse = new
CartAddResponse();
CartAddRequest cartAddRequest =
cartAdd.getRequest().get(0);
//Retreive the cart associated with this
user
Cart cart =
getCart(cartAdd.getAWSAccessKeyId());
//If user did not create a Cart for this
session
if(cart == null){
cartAddResponse.getCart().add(new
Cart());
return cartAddResponse;
}
CartItem cartItem = new CartItem();
cartItem.setASIN(cartAddRequest.getItems().getItem().get(0).getASIN());
cartItem.setQuantity(cartAddRequest.getItems().getItem().get(0).getQuantity().toString());
cart.getCartItems().getCartItem().add(cartItem);
cartAddResponse.getCart().add(cart);
return cartAddResponse;
}
public CartGetResponse CartGet(CartGet cartGet) {
CartGetResponse cartGetResponse = new
CartGetResponse();
Cart cart =
getCart(cartGet.getAWSAccessKeyId());
if(cart == null){
cartGetResponse.getCart().add(new
Cart());
return cartGetResponse;
}
cartGetResponse.getCart().add(cart);
return cartGetResponse;
}
public CartModifyResponse CartModify(CartModify cartModify)
{
//Implementation here...
}
public CartClearResponse CartClear(CartClear cartClear) {
//Implementation here...
}
private synchronized String generateID(){
ID++;
return String.valueOf(ID);
}
private Cart getCart(String usedId){
Cart cart = null;
System.out.println(this.cartsHash.toString());
cart = this.cartsHash.get(cartId);
return cart;
}
private void addCart(String userId, Cart cart){
this.cartsHash.put(cartId, cart);
}
}
It is worth highlighting the fact that this class was annotated with a
@Scope("COMPOSITE") annotation. This annotation is important since component
implementations can either manage their own state or allow the SCA runtime
to do so. In the latter case, the implementation scope specifies a
visibility and lifecycle contract an implementation has with the SCA
runtime. Invocations on a service offered by a component will be dispatched
by the SCA runtime to an implementation instance according to the semantics
of its implementation scope. In this case, the @Scope("COMPOSITE") tells the
runtime to dispatch all service requests to the same implementation instance
for the lifetime of the containing composite. This way, we can be sure that
we can maintain the session data for each user (i.e. retrieving the active
cart and its items).
Shopping Cart SCA Composite
Now that our Shopping Cart service has been implemented, we need to
integrate it into Tuscany. In order to do that we need to define a composite
file; let us call it shoppingstore.composite.
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0"
targetNamespace="http://webservices.amazon.com/AWSECommerceService/2007-05-14"
xmlns:tns="http://webservices.amazon.com/AWSECommerceService/2007-05-14"
xmlns:db="http://tuscany.apache.org/xmlns/databinding/1.0"
name="ShoppingStore">
<component name="CartServiceComponent">
<service name="CartService" >
<interface.wsdl
interface="http://webservices.amazon.com/AWSECommerceService/2007-05-14#wsdl.interface(AWSECommerceServicePortType)"
/>
<binding.ws />
<db:databinding name="jaxb" />
</service>
<implementation.java
class="shoppingstore.services.cart.CartServiceImpl" />
</component>
</composite>
This composite file, called ShoppingStore, contains only one component, the
CartServiceComponent, which is offering its service via a web service
interface. It is worth mentioning that the name of the service being offered
by the Cart component (i.e. CartService) must be equal to the Java interface
of the cart service (i.e. CartService.java).
Setting the Service
Now that we have a Tuscany Service modeled and implemented we need to get it
running. Therefore, what we are going to do is to use the embedded server
Tuscany provides in its releases. Thus, let us code the Java class that will
have the main method and will prepare the service to start "listening" for
requests, let us call it ShoppingStoreServer.java:
package shoppingstore.server;
import java.io.IOException;
import org.apache.tuscany.sca.host.embedded.SCADomain;
public class ShoppingStoreServer {
public static void main(String[] args) {
SCADomain scaDomain =
SCADomain.newInstance("shoppingstore.composite");
try {
System.out.println("ShoppingStore
server started (press enter to shutdown)");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
scaDomain.close();
System.out.println("ShoppingStore server
stopped");
}
}
The code above creates a new instance of the ShoppingStore composite and
leaves it running awaiting for requests through its web service interface
(as described in its composite file).
Running the Composite
We have already coded everything we need. Now, we need to properly set the
environment to run the composite...
1 - Install Tuscany: Download it
(http://cwiki.apache.org/TUSCANY/sca-java-releases.html) and uncompress it
on the directory where you want it to be located (e.g.
/opt/tuscany-sca-1.0-incubating).
2 - Compile the Shopping Store code (replace <TUSCANY_HOME> with the
directory where Tuscany is located):
Windows:
#set TUSCANY_HOME=<TUSCANY_HOME>
#javac .\src\shoppingstore\server\*.java
.\src\shoppingstore\services\cart\*.java -cp
.\lib\AWS2007_05_14.jar;%TUSCANY_HOME%\lib\tuscany-sca-manifest.jar -d build
Linux:
#TUSCANY_HOME="<TUSCANY_HOME>"
#javac ./src/shoppingstore/server/*.java
./src/shoppingstore/services/cart/*.java -cp
./lib/AWS2007_05_14.jar:$TUSCANY_HOME/lib/tuscany-sca-manifest.jar -d build
3 - Create and arrange the required jar files: in order to run the Shopping
Store composite, we first need to generate the required jar files. Let us
begin:
3.1 - Create shoppingstore.jar: this file must contain the
compile code for the application, its composite file and the reduced version
of the Amazon WSDL. We will generate the jar file out of the files contained
in the build directory since it already has the files in the structure we
need them to be. Run:
Windows:
#jar cvfM
.\dist\shoppingstore.jar -C .\build .
Linux:
#jar cvfM
./dist/shoppingstore.jar -C ./build .
3.2 - Now, copy the lib directory (along with the contained
AWS2007_05_14.jar file) into the "dist" directory. It must look like this:
-dist
- lib
AWS2007_05_14.jar
shoppingstore.jar
4 - Run the service: In order to run the service, run the following command:
Windows:
#set TUSCANY_HOME=<TUSCANY_HOME>
#java -cp
%TUSCANY_HOME%\lib\tuscany-sca-manifest.jar;.\dist\shoppingstore.jar;.\dist\lib\AWS2007_05_14.jar
shoppingstore.server.ShoppingStoreServer
Linux:
#TUSCANY_HOME="<TUSCANY_HOME>"
#java -cp
$TUSCANY_HOME/lib/tuscany-sca-manifest.jar:./dist/shoppingstore.jar:./dist/lib/AWS2007_05_14.jar
shoppingstore.server.ShoppingStoreServer
5 - Test the service: Instead of writing a standalone Java client which will
invoke our web service, we can test it is working correctly in a much easier
way. Let us just create a telnet connection to the port where Tuscany is
listening for requests and let us send a SOAP message over it. So, the steps
would be:
5.1 Execute
#telnet <HOSTNAME> <PORT>
(e.g. #telnet localhost 8080)
and a connection to Tuscany's web server
will be established.
5.2 Now, paste the following text into the terminal in order
to create a Cart:
POST /CartServiceComponent HTTP/1.0
Accept-Language: en
Content-Type: text/xml; charset="utf-8"
Content-Id: soap-envelope
Content-Length: 328
SOAPAction: http://soap.amazon.com
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<CartCreate
xmlns="http://webservices.amazon.com/AWSECommerceService/2007-05-14">
<AWSAccessKeyId>112233</AWSAccessKeyId>
<Request/>
</CartCreate>
</soapenv:Body>
</soapenv:Envelope>
There are only two relevant things the reader needs to understand about this
XML text:
1 - The operation being executed is denoted by the tag "CartCreate"
2 - There is only one parameter this operation receives, the AWSAccessKeyId
(which univocally identifies an Amazon user) value (equal to 112233 in this
case)
After a few moments of pasting the XML text, the Cart service will respond
with an XML where the ID of the Cart assigned to the user is shown (just for
debugging purposes). It is an incremental value, so the first user will get
CartId = 1, the second client to create a Cart will be assigned the Cart
with ID number 2, an so on.
The Cart service running within Tuscany displays the activity being carried
out upon each request, this information can be useful for the readers to
double check that the service is properly processing the requests being
sent.
After creating the Cart, Items can be added to it. The following message
does that:
POST /CartServiceComponent HTTP/1.0
Accept-Language: en
Content-Type: text/xml; charset="utf-8"
Content-Id: soap-envelope
Content-Length: 428
SOAPAction: http://soap.amazon.com
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<CartAdd
xmlns="http://webservices.amazon.com/AWSECommerceService/2007-05-14">
<AWSAccessKeyId>112233</AWSAccessKeyId>
<Request>
<Items>
<Item>
<ASIN>Item001</ASIN>
<Quantity>2</Quantity>
</Item>
</Items>
</Request>
</CartAdd>
</soapenv:Body>
</soapenv:Envelope>
Let us try to understand this XML file:
1 - The operation being executed is denoted by the tag "CartAdd"
2 - The AWSAccessKeyId must have associated a Cart to it (i.e. a CartCreate
operation has been executed for this same user)
3 - The Item is composed of ASIN (Amazon's ID for Items) and the quantity of
those items to be added to the cart.
Again, the Cart service will respond with a XML text showing that the item
has being received and processed.
Conclusions
What we have learnt after reading this document is to create a Tuscany
application out of a WSDL file. Following this top-down approach is the
natural way of modeling Tuscany applications, and is very convenient when
the WSDL file is already available.
Throughout this document, all the steps required to have a running Tuscany
service were detailed. However, it is important to point that although we
reached to a application offering its service via a WS interface, we could
have exposed it via other interfaces and transports since Tuscany offers
such intrinsic flexibility.
Even though we came across some inconveniences when coding the application
which led us to write this document, Tuscany proved to be a good choice to
model and implement a service-oriented application following a top down
approach.
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]