Passing an array of objects when calling a document/literal web service with perl soap::lite

One recent problem I encountered was that I had to write a PERL client for an existing document/literal web service. The service is using polymorphism– there are two methods:

Product[] getProducts1(Products[] products1);
Product[] getProducts2();

“Product” is only an interface. Product1 and Product2 are concret classes that implement the “product’ interface. and the objects in the products array are either Product1 objects or Product2 objects;

Interface Product {
    public String getName();
    public void setName(String name);
    public void setIndex( int index);
    public int getIndex();

}

class Product1:Product{
...
}

class Product2:Product{
...
}

By default Soap::lite sends RPC/Encoding messages to the web services. Fortunately there are some resources for how to call a document/literal services such as this MSDN article . The client perl snippet I have below can call the second method “getProducts2” and retrieve the products succesfully:

#!perl -w
  use SOAP::Lite +trace => 'debug';

  my $soap_response = SOAP::Lite
    -> uri('http://localhost:2372/jaxrpc-simple/types')
   -> on_action( sub { join '/', 'http://localhost:2372/jaxrpc-simple/types', $_[1] } )
    -> proxy('http://mmpower:2372/jaxrpc-simple/simple')
    ->getProducts2();

if ($soap_response->fault) {
    print $soap_response->faultcode, " ", $soap_response->faultstring, "\n";
}    

  print $soap_response;
  $results =   $soap_response->result;

for my $t ($soap_response->valueof('//getProducts2Response/result')) {
  while ( my ($key, $value) = each(%$t) ) {
        print "$key => $value\n";
    }
}    

#use custom deserializer to deserialzie it ro objects if necessary
...

But still I don’t know how to call the first method since I need to pass an array of “Products” as a parameter; As suggested in OO Perl packages are used as classes, and objects are normally blessed references of arrays or hashes. This is not straight forward though. After try and error multiple times finally the following code works:

use Tie::IxHash;

{
package Product2;
    sub new {
        my $class = $_[0];
        my %objref;
        $t = tie(%objref, Tie::IxHash);
        %objref = (
                     index    => $_[1],
                     name  => $_[2],
                     ppp => $_[3]
                 );

    bless \%objref, $class;
    return \%objref;
    }

}

{
package Product1;

    sub new {
        my $class = $_[0];
        my %objref;
        $t = tie(%objref, Tie::IxHash);
        %objref = (
                     index    => $_[1],
                     name  => $_[2],
                     ooo => $_[3],
                 );
        @keys = keys %objref;
    @values = values %objref;
    bless \%objref, $class;
    return \%objref;
    }

}

use SOAP::Lite ( +trace => 'debug', maptype => {} );

my $service1 = SOAP::Lite
    -> uri('http://localhost:2372/jaxrpc-simple/types')
   ->maptype({Product1=>'http://localhost:2372/jaxrpc-simple/types', Product2=>'http://localhost:2372/jaxrpc-simple/types', SOAPStruct => 'http://localhost:2372/jaxrpc-simple/types'})
   -> on_action( sub { join '/', 'http://localhost:2372/jaxrpc-simple/types', $_[1] } )
    -> proxy('http://localhost:2372/jaxrpc-simple/simple');

    $service1->serializer->namespaces({"http://schemas.xmlsoap.org/soap/encoding/"=>"SOAP-ENC",
                                        "http://schemas.xmlsoap.org/soap/envelope/"=>"SOAP-ENV","http://www.w3.org/2001/XMLSchema"=>"xsd",
                                        "http://www.w3.org/2001/XMLSchema-instance"=>"xsi"});

my @products;

@products[0] = Product1->new(2,'pro1','ooo1');
@products[1] = Product2->new(3,'pro2','ppp1');
@products[2] = Product1->new(4,'pro3','ooo3');
@products[3] = Product2->new(5,'poro4','pppll4');

my @params = ( SOAP::Data->name("arrayOfProduct_1" => @products)->attr({xmlns => ''}));

my $method = SOAP::Data->name('getProducts')-> attr({xmlns => 'http://localhost:2372/jaxrpc-simple/types'});

$soap_response = $service1->call($method => @params);

if ($soap_response->fault) {
    print $soap_response->faultcode, " ", $soap_response->faultstring, "\n";
}    

for my $t ($soap_response->valueof('//getProductsResponse/result')) {
  while ( my ($key, $value) = each(%$t) ) {
        print "$key => $value\n";
    }
}

Several things to note in the above code:

  • needs to use the “maptype()” method to map the namespace for the Product1 and Product2.
  • need to change the XSD and XSI namespaces to 2001 version. by default soap::lite sets them to “http://www.w3.org/1999/XMLSchema” and “http://www.w3.org/1999/XMLSchema-instance”;
  • since object instance variable is stored in a hash. In perl the default Hash does not gurantee hash key order, so when a “Product1” object is passed as a parameter, the generated soap message is like:
            1
           oooooo
         prodcut1
    
    

    but the server side expects the elements of a product to be in the strict order “index”, “name” and “ooo”, not “index”, “ooo” and “name”. So I have to use a special hash that preserves the key order.

About: mmpower

Software Architect & Soccer Fan 黑超白袜 = IT 民工 + 摇滚大叔


Leave a Reply

Your email address will not be published. Required fields are marked *