WSDL (Web Service Definition Language) is an XML based document which described a set of Web Services either based on SOAP or XML/RPC. By using a WSDL document it is possible to describe, in a formal way, the interface to any Web Services. The WSDL document contains the end-point (URL to the server offering the service), the SOAPAction (needed to call the right routine), the procedure names and a description of the input and output parameters.
AWS provides two tools to work with WSDL documents:
ada2wsdl
which creates a WSDL document from an Ada package spec.
wsdl2aws
which create the interfaces to use a Web Service or to implement Web Services. With this tool the SOAP interface is completely abstracted out, users will deal only with Ada API. All the SOAP marshaling will be created automatically.
Note that this tool is based on ASIS.
ada2wsdl can be used on any Ada spec file to generated a WSDL document. The Ada spec is parsed using ASIS.
The simplest way to use it is:
$ ada2wsdl simple.ads
Given the following Ada spec file:
package Simple is
function Plus (Value : in Natural) return Natural;
end Simple;
It will generate the following WSDL document:
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="Simple"
targetNamespace="urn:aws:Simple"
xmlns:tns="urn:aws:Simple"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<message name="Plus_Request">
<part name="Value" type="xsd:int"/>
</message>
<message name="Plus_Response">
<part name="Result" type="xsd:int"/>
</message>
<portType name="Simple_PortType">
<operation name="Plus">
<input message="tns:Plus_Request"/>
<output message="tns:Plus_Response"/>
</operation>
</portType>
<binding name="Simple_Binding" type="tns:Simple_PortType">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="Plus">
<soap:operation soapAction="Plus"/>
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:aws:Simple"
use="encoded"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:aws:Simple"
use="encoded"/>
</output>
</operation>
</binding>
<service name="Simple_Service">
<port name="Simple_Port" binding="tns:Simple_Binding">
<soap:address location="http://.../"/>
</port>
</service>
</definitions>
The value of the name attribute in the description node is the name of the WSDL document (the name of the Ada spec package). On the portType section we have the description of the Ada Plus function. Something important to note is that in Ada a function does not have a named return parameter, ada2wsdl use Result for the response. Both the input and output parameter are mapped to SOAP xsd:int type.
Note that the SOAP address generated by default (http://.../) must be edited manually or specified using ada2wsdl‘s -a option.
This is of course a very simple example. ada2wsdl does support lot more complex specs and will map Ada records, arrays, enumerations, derived types to a corresponding XML schema definition. See section below for a description of the mapping.
ada2wsdl parse Ada records, arrays, derived types, enumerations, procedures and functions and generate the corresponding WSDL document. In this section we describe the mapping between Ada and WSDL.
Mapped to a Character schema definition:
<simpleType name="Character">
<restriction base="xsd:string">
<length value="1"/>
</restriction>
</simpleType>}
Mapped to a type schema definition:
type Number is new Integer;
is defined as:
<simpleType name="Number" targetNamespace="http://soapaws/WSDL_C_pkg/">
<restriction base="xsd:int"/>
</simpleType>
Mapped to a type schema definition:
type Small is range 1 .. 10;
is defined as:
<simpleType name="Small" targetNamespace="http://soapaws/WSDL_C_pkg/">
<restriction base="xsd:byte"/>
</simpleType>}
Mapped to an enumeration schema definition. For example:
type Color is (Red, Green, Blue);
is defined as:
<simpleType name="Color">
<restriction base="xsd:string">
<enumeration value="Red"/>
<enumeration value="Green"/>
<enumeration value="Blue"/>
</restriction>
</simpleType>
Mapped to a struct schema definition. For example:
type Rec is record
A : Integer;
B : Float;
C : Long_Float;
D : Character;
E : Unbounded_String;
F : Boolean;
end record;
is defined as:
<complexType name="Rec">
<all>
<element name="A" type="xsd:int"/>
<element name="B" type="xsd:float"/>
<element name="C" type="xsd:double"/>
<element name="D" type="tns:Character"/>
<element name="E" type="xsd:string"/>
<element name="F" type="xsd:boolean"/>
</all>
</complexType>
Mapped to an array schema definition. For example:
type Set_Of_Rec is array (Positive range <>) of Rec;
is defined as:
<complexType name="Set_Of_Rec">
<complexContent>
<restriction base="soap-enc:Array">
<attribute ref="soap-enc:arrayType" wsdl:arrayType="tns:Rec[]"/>
</restriction>
</complexContent>
</complexType>
This part is a bit delicate. A record field must be constrained but a SOAP arrays is most of the time not constrained at all. To support this AWS use a safe access array component. Such a type is built using a generic runtime support package named SOAP.Utils.Safe_Pointers. This package implements a reference counter for the array access and will release automatically the memory when no more reference exists for a given object.
For example, let’s say that we have an array of integer that we want to put inside a record:
type Set_Of_Int is array (Positive range <>) of Integer;
The first step is to create the safe array access support:
type Set_Of_Int_Access is access Set_Of_Int;
package Set_Of_Int_Safe_Pointer is
new SOAP.Utils.Safe_Pointers (Set_Of_Int, Set_Of_Int_Access);
Note that the name Set_Of_Int_Safe_Pointer (<type>_Safe_Pointer) is mandatory (and checked by ada2wsdl) to achieve interoperability with wsdl2aws. Working with WSDL documents.
From there the safe array access can be placed into the record:
type Complex_Rec is record
SI : Set_Of_Int_Safe_Pointer.Safe_Pointer;
end record;
To create a Safe_Pointer given a Set_Of_Int one must use Set_Of_Int_Safe_Pointer.To_Safe_Pointer routine. Accessing individual items is done with SI.Item (K).
These Ada definitions are fully recognized by ada2wsdl and will generate standard array and record WSDL definitions as seen above:
<complexType name="Set_Of_Int">
<complexContent>
<restriction base="soap-enc:Array">
<attribute ref="soap-enc:arrayType" wsdl:arrayType="xsd:int[]"/>
</restriction>
</complexContent>
</complexType>
<complexType name="Complex_Rec">
<all>
<element name="SI" type="tns:Set_Of_Int"/>
</all>
</complexType>
Usage: ada2wsdl [options] ada_spec
ada2wsdl options are:
This section describe how to use a Web Service. Let’s say that we want to use the Barnes & Noble Price Quote service. The WSDL document for this service can be found at http://www.xmethods.net/sd/2001/BNQuoteService.wsdl. In summary this document says that there is a service named getPrice taking as input a string representing the ISBN number and returning the price as floating point.
The first step is to generate the client interface (stub):
$ wsdl2aws -noskel http://www.xmethods.net/sd/2001/BNQuoteService.wsdl
This will create many files, the interesting one at this point is bnquoteservice-client.ads, inside we have:
function getPrice (isbn : in String) return Float;
-- Raises SOAP.SOAP_Error if the procedure fails
Let’s call this service to find out the price for The Sword of Shannara Trilogy book:
with Ada.Text_IO;
with BNQuoteService.Client;
procedure Price is
use Ada;
ISBN : constant String := "0345453751";
-- The Sword of Shannara Trilogy ISBN
package LFIO is new Text_IO.Float_IO (Float);
begin
Text_IO.Put_Line ("B&N Price for The Sword of Shannara Trilogy");
LFIO.Put (BNQuoteService.Client.getPrice (ISBN), Aft => 2, Exp => 0);
end Price;
That’s all is needed to use this Web Service. This program is fully functional, It is possible to build it and to run it to get the answer.
Building a Web Service can also be done from a WSDL document. Let’s say that you are Barnes & Noble and that you want to build Web Service getPrice as described in the previous section.
You have created the WSDL document to specify the service spec. From there you can create the skeleton:
$ wsdl2aws -nostub http://www.xmethods.net/sd/2001/BNQuoteService.wsdl
This will create many files, the interesting one here is bnquoteservice-server.ads, inside we have:
Port : constant := 80;
generic
with function getPrice (isbn : in String) return Float;
function getPrice_CB
(SOAPAction : in String;
Payload : in SOAP.Message.Payload.Object;
Request : in AWS.Status.Data) return AWS.Response.Data;
This is a SOAP AWS‘s callback routine that can be instantiated with the right routine to retrieve the price of a book given its ISBN number. A possible implementation of such routine could be:
function getPrice (isbn : in String) return Float is
begin
if isbn = "0987654321" then
return 45.0;
elsif ...
end getPrice;
function SOAP_getPrice is new BNQuoteService.Server.getPrice_CB (getPrice);
SOAP_getPrice is a SOAP AWS‘s callback routine (i.e. it is not a standard callback). To use it there is different solutions:
This generic function can be used to translate a standard callback based on AWS.Status.Data into a SOAP callback routine:
function getPrice_Wrapper is new SOAP.Utils.SOAP_Wrapper (SOAP_getPrice);
The routine getPrice_Wrapper can be used as any other AWS’s callback routines. Note that inside this wrapper the XML payload is parsed to check the routine name and to retrieve the SOAP parameters. To call this routine the payload needs to be parsed (we need to know which routine has be invoked). In this case we have parsed the XML payload twice, this is not efficient.
This solution is more efficient if there is many SOAP procedures as the payload is parsed only once:
function CB (Request : in Status.Data) return Response.Data is
SOAPAction : constant String := Status.SOAPAction (Request);
Payload : constant SOAP.Message.Payload.Object :=
SOAP.Message.XML.Load_Payload (AWS.Status.Payload (Request));
Proc : constant String :=
SOAP.Message.Payload.Procedure_Name (Payload);
begin
if SOAPAction = "..." then
if Proc = "getPrice" then
return SOAP_getPrice (SOAPAction, Payload, Request);
elsif ...
...
end if;
else
...
end if;
Note that the port to be used by the AWS server is described into the server spec.
Usage: wsdl2aws [options] <file|URL>
It is possible to pass a WSDL file or direct wsdl2aws to a WSDL document on the Web by passing it’s URL.
wsdl2aws options are:
Specify the name of the server’s procedure main to generate. If file <filename>.amt (Ada Main Template) is present, it uses this template file to generate the main procedure. The template can reference the following variable tags:
The name of the service as described into the WSDL document. This tag can be used to include the right units:
with @_SOAP_SERVICE_@.Client;
with @_SOAP_SERVICE_@.CB;
The name of the generated unit. This is the name of the procedure that will be created:
procedure @_UNIT_NAME_@ is
begin
...
The wsdl2aws tool read a WSDL document and creates a root package and a set of child packages as described below:
It is hard to know all current limitations as the WSDL and SOAP world is quite complex. We list there all known limitations:
Using both tools together is an effective way to build rapidely a SOAP server. It can be said that doing so is quite trivial in fact. Let’s take the following spec:
package Graphics is
type Point is record
X, Y : Float;
end record;
function Distance (P1, P2 : in Point) return Float;
-- Returns the distance between points P1 and P2
end Graphics;
We do not show the body here but we suppose it is implemented. To build a server for this service it is as easy as:
$ ada2wsdl -a http://localhost:8787 -o graphics.wsdl graphics.ads
The server will be available on localhost at port 8787:
$ wsdl2aws -cb -main server -types graphics graphics.wsdl
$ gnatmake server -largs ...}
Options