NAME

XML::Compile::SOAP - base-class for SOAP implementations

INHERITANCE

XML::Compile::SOAP has extra code in
  XML::Compile::SOAP::Encoding

XML::Compile::SOAP is extended by
  XML::Compile::SOAP11
  XML::Compile::SOAP12

SYNOPSIS

** WARNING: Implementation not finished (but making progress)
** see README.todo in distribution!!!

use XML::Compile::SOAP11::Client;
use XML::Compile::Util qw/pack_type/;

# There are some (hidden) differences between SOAP1.1 and 1.2
my $client = XML::Compile::SOAP11::Client->new;

# load extra schemas always explicitly
$client->schemas->importDefinitions(...);

# !!! THE NEXT STEPS ARE ONLY REQUIRED WHEN YOU DO NOT HAVE A WSDL
# !!! SEE XML::Compile::WSDL11 IF YOU HAVE A WSDL FILE

my $h1el = pack_type $myns, $some_element;
my $b1el = "{$myns}$other_element";  # same, less clean

my $encode_query = $client->compileMessage
  ( 'SENDER'
  , header   => [ h1 => $h1el ]
  , body     => [ b1 => $b1el ]
  , destination    => [ h1 => 'NEXT' ]
  , mustUnderstand => 'h1'
  , encodings => { b1 => { use => 'literal' }}
  );

my $decode_response = $client->compileMessage
  ( 'RECEIVER'
  , header    => [ h2 => $h2el ]
  , body      => [ b2 => $b2el ]
  , faults    => [ ... ]
  , encodings => { h2 => { use => 'literal' }}
  );

my $http = XML::Compile::SOAP::HTTPClient->new
  ( action  => 'http://...'
  , address => $server
  );

# In nice, small steps:

my @query    = (h1 => ..., b1 => ...);
my $request  = $encode_query->($query);
my ($response, $trace) = $http->($request);
my $answer   = $decode_response->($response);
use Data::Dumper;
warn Dumper $answer;     # see: a HASH with h2 and b2!
if($answer->{Fault}) ... # error

# Simplify your life

my $call   = $client->compileClient
  ( kind      => 'request-response'
  , request   => $encode_query
  , response  => $decode_response
  , transport => $http
  );

my $result = $call->(h1 => ..., b1 => ...);
print $result->{h2}->{...};
print $result->{b2}->{...};

my ($result, $trace) = $call->(...);

DESCRIPTION

This module handles the SOAP protocol. The first implementation is SOAP1.1 (http://www.w3.org/TR/2000/NOTE-SOAP-20000508/), which is still most often used. The SOAP1.2 definition (http://www.w3.org/TR/soap12/) is quite different; this module tries to define a sufficiently abstract interface to hide the protocol differences.

On the moment, XML-RPC is not supported. There are many more limitations, which can be found in the README.todo file which is part of the distribution package.

METHODS

Constructors

$obj->new(OPTIONS)

    Create a new SOAP object. You have to instantiate either the SOAP11 or SOAP12 sub-class of this, because there are quite some differences (which can be hidden for you)

    Option            --Default
    encoding_ns         <required>
    envelope_ns         <required>
    media_type          application/soap+xml
    schema_instance_ns  $schema_ns . '-instance'
    schema_ns           <required>
    schemas             created internally
    version             <required>

    . encoding_ns => URI

    . envelope_ns => URI

    . media_type => MIMETYPE

    . schema_instance_ns => URI

    . schema_ns => URI

    . schemas => XML::Compile::Schema object

      Use this when you have already processed some schema definitions. Otherwise, you can add schemas later with $soap->schames->importDefinitions()

    . version => STRING

      The simple string representation of the protocol.

Accessors

$obj->encodingNS

$obj->envelopeNS

$obj->prefixPreferences(TABLE, NEW, [USED])

    NEW is a HASH or ARRAY-of-PAIRS which define prefix-to-uri relations, which are added to the list defined in the TABLE (a HASH-of-HASHes). When USED is set, then it will show-up in the output message. At compile-time, the value of USED is auto-detect.

    This method is called for the soap specification preferred namespaces, and for your compileMessage(prefixes).

$obj->schemaInstanceNS(() {shift->{schemains}})

$obj->schemaNS

$obj->schemas

$obj->version

Single messages

$obj->compileMessage(('SENDER'|'RECEIVER'), OPTIONS)

    The payload is defined explicitly, where all headers and bodies are specified as ARRAY containing key-value pairs (ENTRIES). When you have a WSDL file, these ENTRIES are generated automatically.

    To make your life easy, the ENTRIES use a label (a free to choose key, the part name in WSDL terminology), to ease relation of your data with the type where it belongs to. The element of an entry (the value) is defined as an any element in the schema, and therefore you will need to explicitly specify the element to be processed.

    Option        --Default
    body            []
    destination     []
    encodings       {}
    faults          []
    header          undef
    mustUnderstand  []
    prefixes        {}
    role            ULTIMATE
    roles           []
    style           'document'

    . body => ENTRIES

      ARRAY of PAIRS, defining a nice LABEL (free of choice but unique, also w.r.t. the header and fault ENTRIES). The LABEL will appear in the Perl HASH only, to be able to refer to a body element in a simple way.

    . destination => ARRAY

      Writers only. Indicate who the target of the header entry is. By default, the end-point is the destination of each header element.

      The ARRAY contains a LIST of key-value pairs, specifing an entry label followed by an actor (soap1.1) or role (soap1.2) URI. You may use the predefined actors/roles, like 'NEXT'. See roleURI() and roleAbbreviation().

    . encodings => HASH-of-HASHes

      Message components can be encoded, as defined in WSDL. Typically, some message part has a binding use="encoded" and encodingStyle and namespace parameters. The encodings are organized per label.

    . faults => ENTRIES

      The SOAP1.1 and SOAP1.2 protocols define fault entries in the answer. Both have a location to add your own additional information: the type(-processor) is to specified here, but the returned information structure is larger and differs per SOAP implementation.

    . header => ENTRIES

      ARRAY of PAIRS, defining a nice LABEL (free of choice but unique) and an element reference. The LABEL will appear in the Perl HASH, to refer to the element in a simple way.

    . mustUnderstand => STRING|ARRAY-OF-STRING

      Writers only. The specified header entry labels specify which elements must be understood by the destination. These elements will get the mustUnderstand attribute set to 1 (soap1.1) or true (soap1.2).

    . prefixes => HASH

      For the sender only: add additional prefix definitions. All provided names will be used always.

    . role => URI|ARRAY-OF-URI

      Readers only. One or more URIs, specifying the role(s) you application has in the process. Only when your role contains ULTIMATE, the body is parsed. Otherwise, the body is returned as uninterpreted XML tree. You should not use the role NEXT, because every intermediate node is a NEXT.

      All understood headers are parsed when the actor (soap1.1) or role (soap1.2) attribute address the specified URI. When other headers emerge which are not understood but carry the mustUnderstood attribute, an fault is returned automatically. In that case, the call to the compiled subroutine will return undef.

    . roles => ARRAY-OF-URI

      Alternative for option role

    . style => 'document'|'rpc'

Sender (internals)

$obj->sender(ARGS)

$obj->writerCreateBody(BODY-DEFS, NAMESPACE-TABLE)

$obj->writerCreateFault(FAULT-DEFS, NAMESPACE-TABLE, FAULTTYPE)

$obj->writerCreateHeader(HEADER-DEFS, NS-TABLE, UNDERSTAND, DESTINATION)

$obj->writerEncstyleHook(NAMESPACE-TABLE)

$obj->writerHook(NAMESPACE, LOCAL, ACTIONS)

Receiver (internals)

$obj->readerEncstyleHook

$obj->readerHook(NAMESPACE, LOCAL, ACTIONS)

$obj->readerParseBody(BODYDEF)

$obj->readerParseFaults(FAULTSDEF)

$obj->readerParseHeader(HEADERDEF)

$obj->receiver(ARGS)

Transcoding

SOAP defines encodings, especially for XML-RPC.

Helpers

$obj->replyMustUnderstandFault(TYPE)

    Produce an error structure to be returned to the sender.

$obj->roleAbbreviation(URI)

    Translate a role URI into a simple string, if predefined. See roleURI().

$obj->roleURI(URI|STRING)

    Translates actor/role/destination abbreviations into URIs. Various SOAP protocol versions have different pre-defined STRINGs, which can be abbreviated for readibility. Returns the unmodified URI in all other cases.

    SOAP11 only defines NEXT. SOAP12 defines NEXT, NONE, and ULTIMATE.

DETAILS

Using the produced calls

First, you compile the call either via a WSDL file (see XML::Compile::WSDL11), or in small manual steps, as described in the next section. So, in one of both ways, you end-up with

# compile once
my $call = $soap->compileClient(...);

# and call often
my $anwer = $call->(%request);  # list of pairs
my $anwer = $call->(\%request); # same, but HASH
my $anwer = $call->(\%request, 'UTF-8');  # same

But what is the structure of %request and $answer ? Well, there are various syntaxes possible: from structurally perfect, to user-friendly.

First, find out which data structures can be present. When you compiled your messages explicitly, you have picked your own names. When the call was initiated from a WSDL file, then you have to find the names of the message parts which can be used. The component names are for header blocks, body blocks, headerfaults, and (body) faults.

Let's say that the WSDL defines this (ignoring all name-space issues)

<message name="GetLastTradePriceInput">
  <part name="count" type="int" />
  <part name="request" element="TradePriceRequest"/>
</message>

<message name="GetLastTradePriceOutput">
  <part name="answer" element="TradePrice"/>
</message>

<definitions ...>
 <binding ...>
  <operation ...>
   <input>
    <soap:header message="GetLastTradePriceInput" part="count"
    <soap:body message="GetLastTradePriceInput" parts="request"
   <output>
    <soap:body message="GetLastTradePriceOutput"

The input message needs explicitly named parts, in this case, where the output message simply uses all defined in the body. So, the input message has one header block count, and one body block request. The output message only has one body block answer.

Then, the definitions of the blocks:

<element name="TradePriceRequest">
  <complexType>
    <all>
      <element name="tickerSymbol" type="string"/>

<element name="TradePrice">
  <complexType>
    <all>
      <element name="price" type="float"/>

Now, calling the compiled function can be done like this:

my $anwer = $call->(count => 5, request => {tickerSymbol => 'IBM'});
my $anwer = $call->(
    {count => 5, request => {tickerSymbol => 'IBM'}}, 'UTF-8');

However, in this case you may simplify the call. First, all pairs which use known block names are collected. Then, if there is exactly one body block which is not used yet, it will get all of the left over names. So... in this case, you could also use

my $got = $call->(count => 5, tickerSymbol => 'IBM');

This does not work if the block element is a simple type. In most existing SOAP schemas, this simplification probably is possible.

The $got is a HASH, which will not be simplified. The return might be (Data::Dumper is your friend)

$got = { answer => { price => 16.3 } }

To access the value use

printf "%.2f US\$\n", $got->{answer}->{price};
printf "%.2f US\$\n", $got->{answer}{price};

Faults

Faults and headerfaults are a slightly different story: the type which is specified with them is not of the fault XML node itself, but of the details sub-element within the standard fault structure.

When producing the data for faults, you must be aware of the fact that the structure is different for SOAP1.1 and SOAP1.2. When interpreting faults, the same problems are present, although the implementation tries to help you.

Check whether SOAP1.1 or SOAP1.2 is used by looking for a faultcode (SOAP1.1) or a Code (SOAP1.2) field in the data:

if(my $fault = $got->{Fault})
{  if($fault->{faultcode}) { ... SOAP1.1 ... }
   elsif($fault->{Code})   { ... SOAP1.2 ... }
   else { die }
}

In either protocol case, the following will get you at a compatible structure:

if(my $fault = $got->{Fault})
{   my $decoded = $got->{$fault->{_NAME}};
    print $decoded->{code};
    ...
}

See the respective manuals XML::Compile::SOAP11 and XML::Compile::SOAP12 for the (ugly) specifics.

Calling SOAP without WSDL

See the manual page of XML::Compile::WSDL11 to see how simple you can use this module when you have a WSDL file at hand. The creation of a correct WSDL file is NOT SIMPLE.

When using SOAP without WSDL file, it gets a little bit more complicate to use: you need to describe the content of the messages yourself. The following example is used as test-case t/10soap11.t, directly taken from the SOAP11 specs section 1.3 example 1.

# for simplification
my $TestNS   = 'http://test-types';
use XML::Compile::Util qw/SCHEMA2001/;
my $SchemaNS = SCHEMA2001;

First, the schema (hopefully someone else created for you, because they can be quite hard to create correctly) is in file myschema.xsd

<schema targetNamespace="$TestNS"
  xmlns="$SchemaNS">

<element name="GetLastTradePrice">
  <complexType>
     <all>
       <element name="symbol" type="string"/>
     </all>
  </complexType>
</element>

<element name="GetLastTradePriceResponse">
  <complexType>
     <all>
        <element name="price" type="float"/>
     </all>
  </complexType>
</element>

<element name="Transaction" type="int"/>
</schema>

Ok, now the program you create the request:

use XML::Compile::SOAP11;
use XML::Compile::Util  qw/pack_type/;

my $soap   = XML::Compile::SOAP11->new;
$soap->schemas->importDefinitions('myschema.xsd');

my $get_price = $soap->compileMessage
  ( 'SENDER'
  , header =>
     [ transaction => pack_type($TestNS, 'Transaction') ]
  , body  =>
     [ request => pack_type($TestNS, 'GetLastTradePrice') ]
  , mustUnderstand => 'transaction'
  , destination    => [ transaction => 'NEXT http://actor' ]
  );

INPUT is used in the WSDL terminology, indicating this message is an input message for the server. This $get_price is a WRITER. Above is done only once in the initialization phase of your program.

At run-time, you have to call the CODE reference with a data-structure which is compatible with the schema structure. (See XML::Compile::Schema::template() if you have no clue how it should look) So: let's send this:

# insert your data
my %data_in = (transaction => 5, request => {symbol => 'DIS'});
my %data_in = (transaction => 5, symbol => 'DIS'); # alternative

# create a XML::LibXML tree
my $xml  = $get_price->(\%data_in, 'UTF-8');
print $xml->toString;

And the output is:

<SOAP-ENV:Envelope
   xmlns:x0="http://test-types"
   xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <x0:Transaction
      mustUnderstand="1"
      actor="http://schemas.xmlsoap.org/soap/actor/next http://actor">
        5
    </x0:Transaction>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <x0:GetLastTradePrice>
      <symbol>DIS</symbol>
    </x0:GetLastTradePrice>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Some transport protocol will sent this data from the client to the server. See XML::Compile::SOAP::HTTPClient, as one example.

On the SOAP server side, we will parse the message. The string $soap contains the XML. The program looks like this:

my $server = $soap->compileMessage # create once
 ( 'RECEIVER'
 , header => [ transaction => pack_type($TestNS, 'Transaction') ]
 , body   => [ request => pack_type($TestNS, 'GetLastTradePrice') ]
 );

my $data_out = $server->($soap);   # call often

Now, the $data_out reference on the server, is stucturally exactly equivalent to the %data_in from the client.

Encodings

SEE ALSO

This module is part of XML-Compile-SOAP distribution version 0.59, built on November 05, 2007. Website: http://perl.overmeer.net/xml-compile/

LICENSE

Copyrights 2007 by Mark Overmeer. For other contributors see ChangeLog.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See http://www.perl.com/perl/misc/Artistic.html