The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
# Client which demonstrates the functionality of the server.  First start
# the server, and then call the client:
#     ./server.pl --verbose=2
#     ./client.pl

# This scripts shows 3 SOAP calls which are defined via a WSDL, and
# one which is created by hand.

# This file is also included as example in the XML::Compile::SOAP
# distribution.  There, rpc-literal, rpc-encoded and shorter versions
# are shown as well.

# Thanks to Thomas Bayer, for providing this example service
#    See http://www.thomas-bayer.com/names-service/

# Author: Mark Overmeer, Januari 24 2009
# Using:  XML::Compile               1.00
#         XML::Compile::SOAP         2.00
#         XML::Compile::SOAP::Daemon 2.00
# Copyright by the Author, under the terms of Perl itself.
# Feel invited to contribute your examples!

# Of course, all Perl programs start like this!
use warnings;
use strict;

# constants, change this if needed (also in the server script)
use constant SERVERHOST => 'localhost';
use constant SERVERPORT => '8877';

# we need to redirect the endpoint as specified in the WSDL to our
# own server.
my $service_address = 'http://'.SERVERHOST.':'.SERVERPORT;

# To make Perl find the modules without the package being installed.
use lib '../../lib', '.';

# All the used XML stuff
use XML::Compile::WSDL11;
use XML::Compile::Transport::SOAPHTTP;
use XML::Compile::SOAP11;

# Other useful modules
use Data::Dumper;          # Data::Dumper is your friend.
$Data::Dumper::Indent = 1;

# Errors are reported via Log::Report, normal user interaction not.
use Log::Report   'example', syntax => 'SHORT';

use Getopt::Long  qw/:config no_ignore_case bundling/;
use List::Util    qw/first/;

my $format_list;
format =
   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
   $format_list
.

# Forward declarations
sub show_trace($$);
sub get_countries();
sub get_name_info();
sub get_names_in_country();
sub get_name_count();
sub try_stub();

#### MAIN

#
# Some standard command-line processing
#

my $mode = 0;

GetOptions
 # 3 ways to set the verbosity for Log::Report dispatchers
   'v+'        => \$mode  # -v -vv -vvv
 , 'verbose=i' => \$mode  # --verbose=2  (0..3)
 , 'mode=s'    => \$mode  # --mode=DEBUG (DEBUG,ASSERT,VERBOSE,NORMAL)
 , 'server=s'  => \$service_address
   or die "stopped\n";

die "No filenames expected on the command-line"
   if @ARGV;

# XML::Compile::* uses Log::Report.
dispatcher PERL => 'default', mode => $mode;

#
# For nice user interaction; nothing to do with SOAP
#

use Term::ReadLine;
my $term = Term::ReadLine->new('namesservice');

#
# Let all calls share the transport object
# If you need an SSL connection, or other complex transport configuration,
# you can provide your own preconfigured user_agent (LWP::UserAgent object).
#

my $transporter = XML::Compile::Transport::SOAPHTTP->new
  ( address    => $service_address
# , user_agent => ...
  );

my $http = $transporter->compileClient;

# or, when you need to change something to the message sent:
#   my $http = $transporter->compileClient(hook => \&transport_hook); 
# see implementation of transport_hook() far below.

#
# Get the WSDL and Schema definitions
#

my $wsdl = XML::Compile::WSDL11->new('namesservice.wsdl');
$wsdl->importDefinitions('namesservice.xsd');

#
# Pick one of these tests
#

my $answer = '';
while(lc $answer ne 'q')
{
    print <<__SELECTOR;

    Which call do you like to see:
      1) getCountries
      2) getNameInfo
      3) getNamesInCountry
      4) getNameCount, not defined by WSDL
      5) tryStub, in WSDL but not implemented
      6) request WSDL file
      q) quit demo
__SELECTOR

    print <<__HELP unless $mode;
    (Run this script with -v to get some stats.  -vvv shows much more)
__HELP
    print "\n";

    $answer = $term->readline("Pick one of above [1-6,q] ");
    chomp $answer;

       if($answer eq '1') { get_countries() }
    elsif($answer eq '2') { get_name_info()  }
    elsif($answer eq '3') { get_names_in_country() }
    elsif($answer eq '4') { get_name_count() }
    elsif($answer eq '5') { try_stub() }
    elsif($answer eq '6') { get_wsdl() }
    elsif(lc $answer ne 'q' && length $answer)
    {   print "Illegal choice\n";
    }
}

exit 0;

sub show_trace($$)
{   my ($answer, $trace) = @_;
    $mode > 0 or return;

    $trace->printTimings;
    $trace->printRequest;
    $trace->printResponse;

    print Dumper $answer
        if $mode > 1;
}

#
# procedure getCountries
#

sub get_countries()
{   my $getCountries = $wsdl->compileClient
      ( 'getCountries'
      , transporter => $http
      );

    my ($answer, $trace) = $getCountries->();
    show_trace $answer, $trace;

    if(my $fault_raw = $answer->{Fault})
    {   my $fault_nice = $answer->{$fault_raw->{_NAME}};
        warning __x"Cannot get list of countries: {reason}"
           , reason => $fault_nice->{reason};
        return;
    }

    my $countries = $answer->{parameters}{country} || [];

    print "getCountries() lists ",scalar(@$countries)," countries:\n";
    foreach my $country (sort @$countries)
    {   print "   $country\n";
    }
}

#
# Second example
#

sub get_name_info()
{
    my $name = $term->readline("Personal name for info: ");
    chomp $name;

    length $name or return;

    my $getNameInfo = $wsdl->compileClient
      ( 'getNameInfo'
      , transport => $http
      );

    my ($answer, $trace) = $getNameInfo->(name => $name);
    show_trace $answer, $trace;

    unless(defined $answer)
    {   warning __x"No answer received";
        return;
    }

    if($answer->{Fault})
    {   warning __x"Lookup for '{name}' failed: {text}"
          , name => $name, text => $answer->{Fault}{faultstring};
        return;
    }

    my $nameinfo = $answer->{parameters}{nameinfo};
    print "The name '$nameinfo->{name}' is\n";
    print "    male: ", ($nameinfo->{male}   ? 'yes' : 'no'), "\n";
    print "  female: ", ($nameinfo->{female} ? 'yes' : 'no'), "\n";
    print "  gender: $nameinfo->{gender}\n" if $nameinfo->{gender};
    print "and used in countries:\n";

    my $countries = $nameinfo->{countries}{country} || [];
    $format_list = join ', ', @$countries;
    write;
}

#
# Third example
#

sub get_names_in_country()
{
    my $getCountries      = $wsdl->compileClient
      ( 'getCountries'
      , transport => $http
      );

    my $getNamesInCountry = $wsdl->compileClient
     ( 'getNamesInCountry'
     , transport  => $http
     );

    my ($answer1, $trace1) = $getCountries->();
    show_trace $answer1, $trace1;

    if($answer1->{Fault})
    {   warning __x"cannot get countries: {text}"
           , text => $answer1->{Fault}{faultstring};
        return;
    }

    my $countries = $answer1->{parameters}{country};

    my $country;
    while(1)
    {   $country = $term->readline("Most common names in which country? ");
        chomp $country;
        $country eq '' or last;
        print "  please specify a country name.\n";
    }

    # find the name case-insensitive in the list of available countries
    my $name = first { /^\Q$country\E$/i } @$countries;

    unless($name)
    {   $name = 'other countries';
        print "Cannot find name '$country', defaulting to '$name'\n";
        print "Available countries are:\n";
        $format_list = join ', ', @$countries;
        write;
    }

    print "Most common names in $name:\n";
    my ($answer2, $trace2) = $getNamesInCountry->(country => $name);
    show_trace $answer2, $trace2;

    # print $trace2->response->as_string;
    if(my $fault2 = $answer2->{Fault})
    {   warning __x"cannot get names in country:\n  {code}\n  {text}"
          , code => $fault2->{faultcode}, text => $fault2->{faultstring};
        return;
    }

    my $names    = $answer2->{parameters}{name};
    unless($names)
    {   print "No data available for country `$name'\n";
        return;
    }

    $format_list = join ', ', @$names;
    write;
}

#
# This next example demonstrates how to use SOAP without WSDL
#

sub get_name_count()
{
    ### if you execute the following lines in the initiation phase of
    # your program, you can reuse it.  For clarity of the demo, all
    # initiations are made on this unusual spot.
    #
    use MyExampleCalls;
    $wsdl->importDefinitions(\@my_additional_schemas);

    my $soap11 = XML::Compile::SOAP11::Client->new(schemas => $wsdl);
    my $encode = $soap11->compileMessage(SENDER   => @get_name_count_input);
    my $decode = $soap11->compileMessage(RECEIVER => @get_name_count_output);

    # you could use the $http object, defined earlier, to share the
    # connection, but this is more fun ;-)
    my $send   = $transporter->compileClient
      ( soap      => $soap11
      , action    => '#getNameCount' # optional soapAction in HTTP header
      );

    my $getNameCount = $soap11->compileClient
      ( name      => 'getNameCount'  # symbolic name only for trace and errors
      , encode    => $encode
      , decode    => $decode
      , transport => $send
      );
    #
    ### end of re-usable structures

    my $country;
    while(1)
    {   $country = $term->readline("Number of names in which country? ");
        chomp $country;
        $country eq '' or last;
        print "  please specify a country name.\n";
    }

    my ($answer, $trace) = $getNameCount->(request => {country => $country});
    show_trace $answer, $trace;

    if($answer->{Fault})
    {   warning "cannot get names in country: {text}"
           , text => $answer->{Fault}{faultstring};
        return;
    }

    print "Country $country has $answer->{answer}{count} names defined\n";
}

#
# procedure tryStub
# added to the WSDL by hand, to demonstrate what happens when the
# server does not implement a procedure which is listed in the
# interface.
#

sub try_stub()
{   my $try_stub = $wsdl->compileClient('tryStub', transporter => $http);

    my ($answer, $trace) = $try_stub->();

    my $fault = $answer->{Fault}
        or panic "should return a fault";

    # print $trace->response->as_string;

    print __x"the stub answers with the (expected) error:\n  {reason}\n"
      , reason => $fault->{faultstring};

}

#
# get_wsdl
# Many SOAP servers publish a WSDL. This is not provided by the
# SOAP interface, but an HTTP server "trick".
#

sub get_wsdl()
{   use LWP::UserAgent;
    my $ua   = LWP::UserAgent->new;
    my $resp = $ua->get("$service_address?WSDL");
    if($resp)
    {    print "received ".$resp->as_string;
    }
    else
    {   print "no WSDL file published\n";
    }
}

#
# transport_hook() demonstrates how the HTTP message can be modified
# just before transmission/reply checked before decoding.
#

sub transport_hook($$$)
{   my ($request, $trace, $transp) = @_;   # $transp = ::SOAPHTTP object

    # take the unlaying transport layer
    my $ua = $transp->userAgent;
    trace "hook ua " . Dumper($ua);

    # do something with the message before it's being send
    my $len = length($request->content);
    $request->header( My_Header => "Added Header msg length $len" );
    trace "hook request " . Dumper($request);
    trace "hook request content " . $request->content;

    # call the remote server
    my $response = $ua->request($request);

    # modify/check the received answer
    trace "hook response " . Dumper($response);

    # back to normal SOAP
    $response;
};