SOAP-Lite

 view release on metacpan or  search on metacpan

lib/SOAP/Lite.pm  view on Meta::CPAN


        $self->value(@_) if @_;
        return $self;
    }
    return $self->{_name};
}

sub attr {
    my $self = ref $_[0]
        ? shift
        : UNIVERSAL::isa($_[0] => __PACKAGE__)
            ? shift->new()
            : __PACKAGE__->new();
    if (@_) {
        $self->{_attr} = shift;
        return $self->value(@_) if @_;
        return $self
    }
    return $self->{_attr};
}

sub type {
    my $self = ref $_[0]
        ? shift
        : UNIVERSAL::isa($_[0] => __PACKAGE__)
            ? shift->new()
            : __PACKAGE__->new();
    if (@_) {
        $self->{_type} = shift;
        $self->value(@_) if @_;
        return $self;
    }
    if (!defined $self->{_type} && (my @types = grep {/^\{$SOAP::Constants::NS_XSI_ALL}type$/o} keys %{$self->{_attr}})) {
        $self->{_type} = (SOAP::Utils::splitlongname(delete $self->{_attr}->{shift(@types)}))[1];
    }
    return $self->{_type};
}

BEGIN {
    no strict 'refs';
    for my $method (qw(root mustUnderstand)) {
        my $field = '_' . $method;
        *$method = sub {
        my $attr = $method eq 'root'
            ? "{$SOAP::Constants::NS_ENC}$method"
            : "{$SOAP::Constants::NS_ENV}$method";
            my $self = UNIVERSAL::isa($_[0] => __PACKAGE__)
                ? shift->new
                : __PACKAGE__->new;
            if (@_) {
                $self->{_attr}->{$attr} = $self->{$field} = shift() ? 1 : 0;
                $self->value(@_) if @_;
                return $self;
            }
            $self->{$field} = SOAP::Lite::Deserializer::XMLSchemaSOAP1_2->as_boolean($self->{_attr}->{$attr})
                if !defined $self->{$field} && defined $self->{_attr}->{$attr};
            return $self->{$field};
        }
    }

    for my $method (qw(actor encodingStyle)) {
        my $field = '_' . $method;
        *$method = sub {
            my $attr = "{$SOAP::Constants::NS_ENV}$method";
            my $self = UNIVERSAL::isa($_[0] => __PACKAGE__)
                ? shift->new()
                : __PACKAGE__->new();
            if (@_) {
                $self->{_attr}->{$attr} = $self->{$field} = shift;
                $self->value(@_) if @_;
                return $self;
            }
            $self->{$field} = $self->{_attr}->{$attr}
                if !defined $self->{$field} && defined $self->{_attr}->{$attr};
            return $self->{$field};
        }
    }
}

sub prefix {
    my $self = ref $_[0]
        ? shift
        : UNIVERSAL::isa($_[0] => __PACKAGE__)
            ? shift->new()
            : __PACKAGE__->new();
    return $self->{_prefix} unless @_;
    $self->{_prefix} = shift;
    if (scalar @_) {
        return $self->value(@_);
    }
    return $self;
}

sub uri {
    my $self = ref $_[0]
        ? shift
        : UNIVERSAL::isa($_[0] => __PACKAGE__)
            ? shift->new()
            : __PACKAGE__->new();
    return $self->{_uri} unless @_;
    my $uri = $self->{_uri} = shift;
    warn "Usage of '::' in URI ($uri) deprecated. Use '/' instead\n"
        if defined $uri && $^W && $uri =~ /::/;
    if (scalar @_) {
         return $self->value(@_);
    }
    return $self;
}

sub set_value {
    my $self = ref $_[0]
        ? shift
        : UNIVERSAL::isa($_[0] => __PACKAGE__)
            ? shift->new()
            : __PACKAGE__->new();
    $self->{_value} = [@_];
    return $self;
}

sub value {
    my $self = ref $_[0] ? shift
        : UNIVERSAL::isa($_[0] => __PACKAGE__)
            ? shift->new()
            : __PACKAGE__->new;
    if (@_) {
        return $self->set_value(@_);
    }
    else {
        return wantarray
            ? @{$self->{_value}}
            : $self->{_value}->[0];
    }
}

sub signature {
    my $self = UNIVERSAL::isa($_[0] => __PACKAGE__)
        ? shift->new()
        : __PACKAGE__->new();
    (@_)
        ? ($self->{_signature} = shift, return $self)
        : (return $self->{_signature});
}

# ======================================================================

package SOAP::Header;

use vars qw(@ISA);
@ISA = qw(SOAP::Data);

# ======================================================================

package SOAP::Serializer;
use SOAP::Lite::Utils;
use Carp ();
use vars qw(@ISA);

@ISA = qw(SOAP::Cloneable SOAP::XMLSchema::Serializer);

BEGIN {
    # namespaces and anonymous data structures
    my $ns   = 0;
    my $name = 0;
    my $prefix = 'c-';
    sub gen_ns { 'namesp' . ++$ns }
    sub gen_name { join '', $prefix, 'gensym', ++$name }
    sub prefix { $prefix =~ s/^[^\-]+-/$_[1]-/; $_[0]; }
}

sub BEGIN {
    no strict 'refs';

    __PACKAGE__->__mk_accessors(qw(readable level seen autotype attr maptype
        namespaces multirefinplace encoding signature on_nonserialized context
        ns_uri ns_prefix use_default_ns));

    for my $method (qw(method fault freeform)) { # aliases for envelope
        *$method = sub { shift->envelope($method => @_) }
    }

    # Is this necessary? Seems like work for nothing when a user could just use
    # SOAP::Utils directly.
    # for my $method (qw(qualify overqualify disqualify)) { # import from SOAP::Utils
    #   *$method = \&{'SOAP::Utils::'.$method};
    # }
}

sub DESTROY { SOAP::Trace::objects('()') }

sub new {
    my $self = shift;
    return $self if ref $self;

    my $class = $self;
    $self = bless {
        _level => 0,
        _autotype => 1,
        _readable => 0,
        _ns_uri => '',
        _ns_prefix => '',
        _use_default_ns => 1,
        _multirefinplace => 0,
        _seen => {},
        _encoding => 'UTF-8',
        _objectstack => {},
        _signature => [],
        _maptype => {},
        _bodyattr => {},
        _headerattr => {},
        _on_nonserialized => sub {Carp::carp "Cannot marshall @{[ref shift]} reference" if $^W; return},
        _encodingStyle => $SOAP::Constants::NS_ENC,
        _attr => {
            "{$SOAP::Constants::NS_ENV}encodingStyle" => $SOAP::Constants::NS_ENC,
        },
        _namespaces => {},
        _soapversion => SOAP::Lite->soapversion,
    } => $class;
    $self->typelookup({
           'base64Binary' =>
              [10, sub { $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/ }, 'as_base64Binary'],
           'zerostring' =>
               [12, sub { $_[0] =~ /^0\d+$/ }, 'as_string'],
            # int (and actually long too) are subtle: the negative range is one greater...
            'int'  =>
               [20, sub {$_[0] =~ /^([+-]?\d+)$/ && ($1 <= 2147483647) && ($1 >= -2147483648); }, 'as_int'],
            'long' =>
               [25, sub {$_[0] =~ /^([+-]?\d+)$/ && $1 <= 9223372036854775807;}, 'as_long'],
            'float'  =>
               [30, sub {$_[0] =~ /^(-?(?:\d+(?:\.\d*)?|\.\d+|NaN|INF)|([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)$/}, 'as_float'],
            'gMonth' =>
               [35, sub { $_[0] =~ /^--\d\d--(-\d\d:\d\d)?$/; }, 'as_gMonth'],
            'gDay' =>
               [40, sub { $_[0] =~ /^---\d\d(-\d\d:\d\d)?$/; }, 'as_gDay'],
            'gYear' =>
               [45, sub { $_[0] =~ /^-?\d\d\d\d(-\d\d:\d\d)?$/; }, 'as_gYear'],
            'gMonthDay' =>
               [50, sub { $_[0] =~ /^-\d\d-\d\d(-\d\d:\d\d)?$/; }, 'as_gMonthDay'],
            'gYearMonth' =>
               [55, sub { $_[0] =~ /^-?\d\d\d\d-\d\d(Z|([+-]\d\d:\d\d))?$/; }, 'as_gYearMonth'],
            'date' =>
               [60, sub { $_[0] =~ /^-?\d\d\d\d-\d\d-\d\d(Z|([+-]\d\d:\d\d))?$/; }, 'as_date'],
            'time' =>
               [70, sub { $_[0] =~ /^\d\d:\d\d:\d\d(\.\d\d\d)?(Z|([+-]\d\d:\d\d))?$/; }, 'as_time'],
            'dateTime' =>
               [75, sub { $_[0] =~ /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?(Z|([+-]\d\d:\d\d))?$/; }, 'as_dateTime'],
            'duration' =>
               [80, sub { $_[0] !~m{^-?PT?$} && $_[0] =~ m{^
                        -?   # a optional - sign
                        P
                        (:? \d+Y )?
                        (:? \d+M )?
                        (:? \d+D )?
                        (:?
                            T(:?\d+H)?
                            (:?\d+M)?
                            (:?\d+S)?
                        )?
                        $
                    }x;
               }, 'as_duration'],
            'boolean' =>
               [90, sub { $_[0] =~ /^(true|false)$/i; }, 'as_boolean'],
            'anyURI' =>
               [95, sub { $_[0] =~ /^(urn:|http:\/\/)/i; }, 'as_anyURI'],
            'string' =>
               [100, sub {1}, 'as_string'],
        });
    $self->register_ns($SOAP::Constants::NS_ENC,$SOAP::Constants::PREFIX_ENC);
    $self->register_ns($SOAP::Constants::NS_ENV,$SOAP::Constants::PREFIX_ENV)
        if $SOAP::Constants::PREFIX_ENV;
    $self->xmlschema($SOAP::Constants::DEFAULT_XML_SCHEMA);
    SOAP::Trace::objects('()');

lib/SOAP/Lite.pm  view on Meta::CPAN

        }
        elsif (!$p && !($prefix = $self->find_prefix($u))) {
            $prefix = gen_ns;
        }

        $self->{'_ns_uri'}         = $u;
        $self->{'_ns_prefix'}      = $prefix;
        $self->{'_use_default_ns'} = 0;
        # $self->register_ns($u,$prefix);
        $self->{'_namespaces'}->{$u} = $prefix;
        return $self;
    }
    return $self->{'_ns_uri'};
}

sub default_ns {
    my $self = shift;
    $self = $self->new() if not ref $self;
    if (@_) {
        my ($u) = @_;
        $self->{'_ns_uri'}         = $u;
        $self->{'_ns_prefix'}      = '';
        $self->{'_use_default_ns'} = 1;
        return $self;
    }
    return $self->{'_ns_uri'};
}

sub use_prefix {
    my $self = shift;
    $self = $self->new() if not ref $self;
    warn 'use_prefix has been deprecated. if you wish to turn off or on the '
        . 'use of a default namespace, then please use either ns(uri) or default_ns(uri)';
    if (@_) {
        my $use = shift;
        $self->{'_use_default_ns'} = !$use || 0;
        return $self;
    } else {
        return $self->{'_use_default_ns'};
    }
}
sub uri {
    my $self = shift;
    $self = $self->new() if not ref $self;
#    warn 'uri has been deprecated. if you wish to set the namespace for the request, then please use either ns(uri) or default_ns(uri)';
    if (@_) {
        my $ns = shift;
        if ($self->{_use_default_ns}) {
           $self->default_ns($ns);
        }
        else {
           $self->ns($ns);
        }
#       $self->{'_ns_uri'} = $ns;
#       $self->register_ns($self->{'_ns_uri'}) if (!$self->{_use_default_ns});
        return $self;
    }
    return $self->{'_ns_uri'};
}

sub encodingStyle {
    my $self = shift;
    $self = $self->new() if not ref $self;
    return $self->{'_encodingStyle'} unless @_;

    my $cur_style = $self->{'_encodingStyle'};
    delete($self->{'_namespaces'}->{$cur_style});

    my $new_style = shift;
    if ($new_style eq "") {
        delete($self->{'_attr'}->{"{$SOAP::Constants::NS_ENV}encodingStyle"});
    }
    else {
        $self->{'_attr'}->{"{$SOAP::Constants::NS_ENV}encodingStyle"} = $new_style;
        $self->{'_namespaces'}->{$new_style} = $SOAP::Constants::PREFIX_ENC;
    }
}

# TODO - changing SOAP version can affect previously set encodingStyle
sub soapversion {
    my $self = shift;
    return $self->{_soapversion} unless @_;
    return $self if $self->{_soapversion} eq SOAP::Lite->soapversion;
    $self->{_soapversion} = shift;

    $self->attr({
        "{$SOAP::Constants::NS_ENV}encodingStyle" => $SOAP::Constants::NS_ENC,
    });
    $self->namespaces({
        $SOAP::Constants::NS_ENC => $SOAP::Constants::PREFIX_ENC,
        $SOAP::Constants::PREFIX_ENV ? ($SOAP::Constants::NS_ENV => $SOAP::Constants::PREFIX_ENV) : (),
    });
    $self->xmlschema($SOAP::Constants::DEFAULT_XML_SCHEMA);

    return $self;
}

sub xmlschema {
    my $self = shift->new;
    return $self->{_xmlschema} unless @_;

    my @schema;
    if ($_[0]) {
        @schema = grep {/XMLSchema/ && /$_[0]/} keys %SOAP::Constants::XML_SCHEMAS;
        Carp::croak "More than one schema match parameter '$_[0]': @{[join ', ', @schema]}" if @schema > 1;
        Carp::croak "No schema match parameter '$_[0]'" if @schema != 1;
    }

    # do nothing if current schema is the same as new
    # return $self if $self->{_xmlschema} && $self->{_xmlschema} eq $schema[0];

    my $ns = $self->namespaces;
    # delete current schema from namespaces
    if (my $schema = $self->{_xmlschema}) {
        delete $ns->{$schema};
        delete $ns->{"$schema-instance"};
    }

    # add new schema into namespaces
    if (my $schema = $self->{_xmlschema} = shift @schema) {
        $ns->{$schema} = 'xsd';
        $ns->{"$schema-instance"} = 'xsi';
    }

    # and here is the class serializer should work with
    my $class = exists $SOAP::Constants::XML_SCHEMAS{$self->{_xmlschema}}
        ? $SOAP::Constants::XML_SCHEMAS{$self->{_xmlschema}} . '::Serializer'
        : $self;

    $self->xmlschemaclass($class);

    return $self;
}

sub headerattr {
    my $self = shift->new();
    return $self->{_headerattr} unless @_;
    $self->{_headerattr} = shift;
    return $self;
}
sub bodyattr {
    my $self = shift->new();
    return $self->{_bodyattr} unless @_;
    $self->{_bodyattr} = shift;
    return $self;
}

lib/SOAP/Lite.pm  view on Meta::CPAN

    # recursive_object($object) has to re-compute the object's id
    if (++$objectstack{ $id } > 1) {
        $self->{ _seen }->{ $id }->{recursive} = 1
    }

    # return if we already saw it twice. It should be already properly serialized
    return if $objectstack{$id} > 2;

    if (UNIVERSAL::isa($object => 'SOAP::Data')) {
        # use $object->SOAP::Data:: to enable overriding name() and others in inherited classes
        $object->SOAP::Data::name($name)
            unless defined $object->SOAP::Data::name;

        # apply ->uri() and ->prefix() which can modify name and attributes of
        # element, but do not modify SOAP::Data itself
        my($name, $attr) = $self->fixattrs($object);
        $attr = $self->attrstoqname($attr);

        my @realvalues = $object->SOAP::Data::value;
        return [$name || gen_name, $attr] unless @realvalues;

        my $method = "as_" . ($object->SOAP::Data::type || '-'); # dummy type if not defined
        # try to call method specified for this type
        no strict qw(refs);
        my @values = map {
            # store null/nil attribute if value is undef
            local $attr->{SOAP::Utils::qualify(xsi => $self->xmlschemaclass->nilValue)} = $self->xmlschemaclass->as_undef(1)
                unless defined;
            $self->can($method) && $self->$method($_, $name || gen_name, $object->SOAP::Data::type, $attr)
                || $self->typecast($_, $name || gen_name, $object->SOAP::Data::type, $attr)
                || $self->encode_object($_, $name, $object->SOAP::Data::type, $attr)
        } @realvalues;
        $object->SOAP::Data::signature([map {join $;, $_->[0], SOAP::Utils::disqualify($_->[1]->{'xsi:type'} || '')} @values]) if @values;
        return wantarray ? @values : $values[0];
    }

    my $class = ref $object;

    if ($class !~ /^(?:SCALAR|ARRAY|HASH|REF)$/o) {
        # we could also check for CODE|GLOB|LVALUE, but we cannot serialize
        # them anyway, so they'll be caught by check below
        $class =~ s/::/__/g;

        $name = $class if !defined $name;
        $type = $class if !defined $type && $self->autotype;

        my $method = 'as_' . $class;
        if ($self->can($method)) {
            no strict qw(refs);
            my $encoded = $self->$method($object, $name, $type, $attr);
            return $encoded if ref $encoded;
            # return only if handled, otherwise handle with default handlers
        }
    }

    if (UNIVERSAL::isa($object => 'REF') || UNIVERSAL::isa($object => 'SCALAR')) {
        return $self->encode_scalar($object, $name, $type, $attr);
    }
    elsif (UNIVERSAL::isa($object => 'ARRAY')) {
        # Added in SOAP::Lite 0.65_6 to fix an XMLRPC bug
        return $self->encodingStyle eq ""
            || $self->isa('XMLRPC::Serializer')
                ? $self->encode_array($object, $name, $type, $attr)
                : $self->encode_literal_array($object, $name, $type, $attr);
    }
    elsif (UNIVERSAL::isa($object => 'HASH')) {
        return $self->encode_hash($object, $name, $type, $attr);
    }
    else {
        return $self->on_nonserialized->($object);
    }
}

sub encode_scalar {
    my($self, $value, $name, $type, $attr) = @_;
    $name ||= gen_name;

    my $schemaclass = $self->xmlschemaclass;

    # null reference
    return [$name, {%$attr, SOAP::Utils::qualify(xsi => $schemaclass->nilValue) => $schemaclass->as_undef(1)}] unless defined $value;

    # object reference
    return [$name, {'xsi:type' => $self->maptypetouri($type), %$attr}, [$self->encode_object($$value)], $self->gen_id($value)] if ref $value;

    # autodefined type
    if ($self->{ _autotype}) {
        my $lookup = $self->{_typelookup};
        no strict qw(refs);
        #for (sort {$lookup->{$a}->[0] <=> $lookup->{$b}->[0]} keys %$lookup) {
        for (@{ $self->{ _typelookup_order } }) {
            my $method = $lookup->{$_}->[2];
            return $self->can($method) && $self->$method($value, $name, $type, $attr)
                || $method->($value, $name, $type, $attr)
                    if $lookup->{$_}->[1]->($value);
        }
    }

    # invariant
    return [$name, $attr, $value];
}

sub encode_array {
    my ($self, $array, $name, $type, $attr) = @_;
    my $items = 'item';

    # If typing is disabled, just serialize each of the array items
    # with no type information, each using the specified name,
    # and do not create a wrapper array tag.
    if (!$self->autotype) {
        $name ||= gen_name;
        return map {$self->encode_object($_, $name)} @$array;
    }

    # TODO: add support for multidimensional, partially transmitted and sparse arrays
    my @items = map {$self->encode_object($_, $items)} @$array;
    my $num = @items;
    my($arraytype, %types) = '-';
    for (@items) { $arraytype = $_->[1]->{'xsi:type'} || '-'; $types{$arraytype}++ }
    $arraytype = sprintf "%s\[$num]", keys %types > 1 || $arraytype eq '-' ? SOAP::Utils::qualify(xsd => $self->xmlschemaclass->anyTypeValue) : $arraytype;

lib/SOAP/Lite.pm  view on Meta::CPAN

        if defined $xmlns && $xmlns eq ''
            || defined $prefix && $prefix eq '';

    $attr->{join ':', xmlns => $prefix || ()} = $xmlns if defined $xmlns;
    $name = join ':', $prefix, $name if $prefix;

    $self->register_ns($xmlns,$prefix) unless ($self->use_default_ns);

    return ($name, $attr);

}

sub toqname {
    my $self = shift;
    my $long = shift;

    return $long unless $long =~ /^\{(.*)\}(.+)$/;
    return SOAP::Utils::qualify $self->namespaces->{$1} ||= gen_ns, $2;
}

sub attrstoqname {
    my $self = shift;
    my $attrs = shift;

    return {
        map { /^\{(.*)\}(.+)$/
            ? ($self->toqname($_) => $2 eq 'type'
                || $2 eq 'arrayType'
                    ? $self->toqname($attrs->{$_})
                    : $attrs->{$_})
            : ($_ => $attrs->{$_})
        } keys %$attrs
    };
}

sub tag {
    my ($self, $tag, $attrs, @values) = @_;

    my $readable = $self->{ _readable };

    my $value = join '', @values;
    my $indent = $readable ? ' ' x (($self->{ _level }-1)*2) : '';

    # check for special attribute
    return "$indent$value" if exists $attrs->{_xml} && delete $attrs->{_xml};

    die "Element '$tag' can't be allowed in valid XML message. Died."
        if $tag !~ /^$SOAP::Constants::NSMASK$/o;

	warn "Element '$tag' uses the reserved prefix 'XML' (in any case)"
		if $tag !~ /^(?![Xx][Mm][Ll])/;

    my $prolog = $readable ? "\n" : "";
    my $epilog = $readable ? "\n" : "";
    my $tagjoiner = " ";
    if ($self->{ _level } == 1) {
        my $namespaces = $self->namespaces;
        foreach (keys %$namespaces) {
            $attrs->{SOAP::Utils::qualify(xmlns => $namespaces->{$_})} = $_
        }
        $prolog = qq!<?xml version="1.0" encoding="@{[$self->encoding]}"?>!
            if defined $self->encoding;
        $prolog .= "\n" if $readable;
        $tagjoiner = " \n".(' ' x 4 ) if $readable;
    }
    my $tagattrs = join($tagjoiner, '',
        map { sprintf '%s="%s"', $_, SOAP::Utils::encode_attribute($attrs->{$_}) }
            grep { $_ && defined $attrs->{$_} && ($_ ne 'xsi:type' || $attrs->{$_} ne '') }
                sort keys %$attrs);

    if ($value gt '') {
        return sprintf("$prolog$indent<%s%s>%s%s</%s>$epilog",$tag,$tagattrs,$value,($value =~ /^\s*</ ? $indent : ""),$tag);
    }
    else {
        return sprintf("$prolog$indent<%s%s />$epilog$indent",$tag,$tagattrs);
    }
}

sub xmlize {
    my $self = shift;
    my($name, $attrs, $values, $id) = @{$_[0]};
    $attrs ||= {};

    local $self->{_level} = $self->{_level} + 1;

    return $self->tag($name, $attrs)
        unless defined $values;

    return $self->tag($name, $attrs, $values)
        unless ref $values eq "ARRAY";

    return $self->tag($name, {%$attrs, href => '#'.$self->multiref_anchor($id)})
        if $self->is_href($id, delete($attrs->{_id}));

    # we have seen this element as a reference
    if (defined $id && $self->{ _seen }->{ $id }->{ multiref}) {
        return $self->tag($name,
            {
                %$attrs, id => $self->multiref_anchor($id)
            },
            map {$self->xmlize($_)} @$values
        );
    }
    else {
        return $self->tag($name, $attrs, map {$self->xmlize($_)} @$values);
    }
}

sub uriformethod {
    my $self = shift;

    my $method_is_data = ref $_[0] && UNIVERSAL::isa($_[0] => 'SOAP::Data');

    # drop prefix from method that could be string or SOAP::Data object
    my($prefix, $method) = $method_is_data
        ? ($_[0]->prefix, $_[0]->name)
        : SOAP::Utils::splitqname($_[0]);

    my $attr = {reverse %{$self->namespaces}};
    # try to define namespace that could be stored as
    #   a) method is SOAP::Data
    #        ? attribute in method's element as xmlns= or xmlns:${prefix}=

lib/SOAP/Lite.pm  view on Meta::CPAN

        #      if $body;
        # must call encode_data on nothing to enforce xsi:nil="true" to be set.
        $body->set_value($parameters ? \$parameters : SOAP::Utils::encode_data()) if $body;
    }
    elsif ($type eq 'fault') {
        SOAP::Trace::fault(@parameters);
        # -> attr({'xmlns' => ''})
        # Parameter order fixed thanks to Tom Fischer
        $body = SOAP::Data-> name(SOAP::Utils::qualify($self->envprefix => 'Fault'))
          -> value(\SOAP::Data->set_value(
                SOAP::Data->name(faultcode => SOAP::Utils::qualify($self->envprefix => $parameters[0]))->type(""),
                SOAP::Data->name(faultstring => SOAP::Utils::encode_data($parameters[1]))->type(""),
                defined($parameters[3])
                    ? SOAP::Data->name(faultactor => $parameters[3])->type("")
                    : (),
                defined($parameters[2])
                    ? SOAP::Data->name(detail => do{
                        my $detail = $parameters[2];
                        ref $detail
                            ? \$detail
                            : SOAP::Utils::encode_data($detail)
                    })
                    : (),
        ));
    }
    elsif ($type eq 'freeform') {
        SOAP::Trace::freeform(@parameters);
        $body = SOAP::Data->set_value(@parameters);
    }
    elsif (!defined($type)) {
        # This occurs when the Body is intended to be null. When no method has been
        # passed in of any kind.
    }
    else {
        die "Wrong type of envelope ($type) for SOAP call\n";
    }

    $self->{ _seen } = {}; # reinitialize multiref table

    # Build the envelope
    # Right now it is possible for $body to be a SOAP::Data element that has not
    # XML escaped any values. How do you remedy this?
    my($encoded) = $self->encode_object(
        SOAP::Data->name(
            SOAP::Utils::qualify($self->envprefix => 'Envelope') => \SOAP::Data->value(
                ($header
                    ? SOAP::Data->name( SOAP::Utils::qualify($self->envprefix => 'Header') => \$header)->attr( $self->headerattr)
                    : ()
                ),
                ($body
                    ? SOAP::Data->name(SOAP::Utils::qualify($self->envprefix => 'Body') => \$body)
                    : SOAP::Data->name(SOAP::Utils::qualify($self->envprefix => 'Body'))
                )->attr( $self->bodyattr),
            )
        )->attr($self->attr)
    );

    $self->signature($parameters->signature) if ref $parameters;

    # IMHO multirefs should be encoded after Body, but only some
    # toolkits understand this encoding, so we'll keep them for now (04/15/2001)
    # as the last element inside the Body
    #                 v -------------- subelements of Envelope
    #                      vv -------- last of them (Body)
    #                            v --- subelements
    push(@{$encoded->[2]->[-1]->[2]}, $self->encode_multirefs) if ref $encoded->[2]->[-1]->[2];

    # Sometimes SOAP::Serializer is invoked statically when there is no context.
    # So first check to see if a context exists.
    # TODO - a context needs to be initialized by a constructor?
    if ($self->context && $self->context->packager->parts) {
        # TODO - this needs to be called! Calling it though wraps the payload twice!
        #  return $self->context->packager->package($self->xmlize($encoded));
    }

    return $self->xmlize($encoded);
}

# ======================================================================

package SOAP::Parser;

sub DESTROY { SOAP::Trace::objects('()') }

sub xmlparser {
    my $self = shift;
    return eval {
        $SOAP::Constants::DO_NOT_USE_XML_PARSER
            ? undef
            : do {
                require XML::Parser;
                XML::Parser->new( NoExpand => 1, Handlers => { Default => sub {} } ) }
            }
            || eval { require XML::Parser::Lite; XML::Parser::Lite->new }
            || die "XML::Parser is not @{[$SOAP::Constants::DO_NOT_USE_XML_PARSER ? 'used' : 'available']} and ", $@;
}

sub parser {
    my $self = shift->new;

    # set the parser if passed
    if (my $parser = shift) {
        $self->{'_parser'} = shift;
        return $self;
    }

    # else return the parser or use XML::Parser::Lite
    return ($self->{'_parser'} ||= $self->xmlparser);
}

sub new {
    my $self = shift;
    return $self if ref $self;
    my $class = $self;
    SOAP::Trace::objects('()');
    return bless {_parser => shift}, $class;
}

sub decode { SOAP::Trace::trace('()');
    my $self = shift;

lib/SOAP/Lite.pm  view on Meta::CPAN

    my $self = shift;
    my $ref = shift;
    my($name, $attrs_ref, $children, $value) = @$ref;

    my %attrs = %{ $attrs_ref };

    $ref->[ _ATTRS ] = \%attrs;        # make a copy for long attributes

    use vars qw(%uris);
    local %uris = (%uris, map {
        do { (my $ns = $_) =~ s/^xmlns:?//; $ns } => delete $attrs{$_}
    } grep {/^xmlns(:|$)/} keys %attrs);

    foreach (keys %attrs) {
        next unless m/^($SOAP::Constants::NSMASK?):($SOAP::Constants::NSMASK)$/;

    $1 =~ /^[xX][mM][lL]/ ||
        $uris{$1} &&
            do {
                $attrs{SOAP::Utils::longname($uris{$1}, $2)} = do {
                    my $value = $attrs{$_};
                    $2 ne 'type' && $2 ne 'arrayType'
                        ? $value
                        : SOAP::Utils::longname($value =~ m/^($SOAP::Constants::NSMASK?):(${SOAP::Constants::NSMASK}(?:\[[\d,]*\])*)/
                            ? ($uris{$1} || die("Unresolved prefix '$1' for attribute value '$value'\n"), $2)
                            : ($uris{''} || die("Unspecified namespace for type '$value'\n"), $value)
                    );
                };
                1;
            }
            || die "Unresolved prefix '$1' for attribute '$_'\n";
  }

    # and now check the element
    my $ns = ($name =~ s/^($SOAP::Constants::NSMASK?):// ? $1 : '');
    $ref->[ _NAME ] = SOAP::Utils::longname(
        $ns
            ? ($uris{$ns} || die "Unresolved prefix '$ns' for element '$name'\n")
            : (defined $uris{''} ? $uris{''} : undef),
        $name
    );

    ($children, $value) = (undef, $children) unless ref $children;

    return $name => ($ref->[4] = $self->decode_value(
        [$ref->[ _NAME ], \%attrs, $children, $value]
    ));
}

sub decode_value {
    my $self = shift;
    my($name, $attrs, $children, $value) = @{ $_[0] };

    # check SOAP version if applicable
    use vars '$level'; local $level = $level || 0;
    if (++$level == 1) {
        my($namespace, $envelope) = SOAP::Utils::splitlongname($name);
        SOAP::Lite->soapversion($namespace) if $envelope eq 'Envelope' && $namespace;
    }

    if (exists $attrs->{"{$SOAP::Constants::NS_ENV}encodingStyle"}) {
        # check encodingStyle
        # future versions may bind deserializer to encodingStyle
        my $encodingStyle = $attrs->{"{$SOAP::Constants::NS_ENV}encodingStyle"};
        # TODO - SOAP 1.2 and 1.1 have different rules about valid encodingStyle values
        #        For example, in 1.1 - any http://schemas.xmlsoap.org/soap/encoding/*
        #        value is valid
        if (defined $encodingStyle && length($encodingStyle)) {
            my %styles = map { $_ => undef } @SOAP::Constants::SUPPORTED_ENCODING_STYLES;
            my $found = 0;
            foreach my $e (split(/ +/,$encodingStyle)) {
                if (exists $styles{$e}) {
                    $found ++;
            }
        }
        die "Unrecognized/unsupported value of encodingStyle attribute '$encodingStyle'"
            if (! $found) && !(SOAP::Lite->soapversion == 1.1 && $encodingStyle =~ /(?:^|\b)$SOAP::Constants::NS_ENC/);
    }
    }
    use vars '$arraytype'; # type of Array element specified on Array itself
    # either specified with xsi:type, or <enc:name/> or array element
    my ($type) = grep { defined }
        map($attrs->{$_}, sort grep {/^\{$SOAP::Constants::NS_XSI_ALL\}type$/o} keys %$attrs),
           $name =~ /^\{$SOAP::Constants::NS_ENC\}/ ? $name : $arraytype;
    local $arraytype; # it's used only for one level, we don't need it anymore

    # $name is not used here since type should be encoded as type, not as name
    my ($schema, $class) = SOAP::Utils::splitlongname($type) if $type;
    my $schemaclass = defined($schema) && $self->{ _xmlschemas }->{$schema}
        || $self;

    if (! exists $_class_loaded{$schemaclass}) {
        no strict qw(refs);
        if (! Class::Inspector->loaded($schemaclass) ) {
            eval "require $schemaclass" or die $@ if not ref $schemaclass;
        }
        $_class_loaded{$schemaclass} = undef;
    }

    # store schema that is used in parsed message
    $self->{ _xmlschema } = $schema if ($schema) && $schema =~ /XMLSchema/;

   # don't use class/type if anyType/ur-type is specified on wire
    undef $class
        if $schemaclass->can('anyTypeValue')
            && $schemaclass->anyTypeValue eq $class;

    my $method = 'as_' . ($class || '-'); # dummy type if not defined
    $class =~ s/__|\./::/g if $class;

    my $id = $attrs->{id};
    if (defined $id && exists $self->hrefs->{$id}) {
        return $self->hrefs->{$id};
    }
    elsif (exists $attrs->{href}) {
        (my $id = delete $attrs->{href}) =~ s/^(#|cid:|uuid:)?//;
        my $type=$1;
        $id=uri_unescape($id) if (defined($type) and $type eq 'cid:');
        # convert to absolute if not internal '#' or 'cid:'
        $id = $self->baselocation($id) unless $type;
        return $self->hrefs->{$id} if exists $self->hrefs->{$id};
        # First time optimization. we don't traverse IDs unless asked for it.
        # This is where traversing id's is delayed from before
        #   - the first time through - ids should contain a copy of the parsed XML
        #     structure! seems silly to make so many copies
        my $ids = $self->ids;
        if (ref($ids) ne 'HASH') {
            $self->ids({});            # reset list of ids first time through
            $self->traverse_ids($ids);
        }
        if (exists($self->ids->{$id})) {
            my $obj = ($self->decode_object(delete($self->ids->{$id})))[1];
            return $self->hrefs->{$id} = $obj;
        }
        else {
            die "Unresolved (wrong?) href ($id) in element '$name'\n";
        }

lib/SOAP/Lite.pm  view on Meta::CPAN

sub parse_schema_element {
    my $element = shift;
    # Current element is a complex type
    if (defined($element->complexType)) {
        my @elements = ();
        if (defined($element->complexType->sequence)) {

            foreach my $e ($element->complexType->sequence->element) {
                push @elements,parse_schema_element($e);
            }
        }
        return @elements;
    }
    elsif ($element->simpleType) {
    }
    else {
        return $element;
    }
}

sub parse {
    my $self = shift->new;
    my($s, $service, $port) = @_;
    my @result;

    # handle imports
    $self->import($s);

    # handle descriptions without <service>, aka tModel-type descriptions
    my @services = $s->service;
    my $tns = $s->{'_attr'}->{'targetNamespace'};
    # if there is no <service> element we'll provide it
    @services = $self->deserializer->deserialize(<<"FAKE")->root->service unless @services;
<definitions>
  <service name="@{[$service || 'FakeService']}">
    <port name="@{[$port || 'FakePort']}" binding="@{[$s->binding->name]}"/>
  </service>
</definitions>
FAKE

    my $has_warned = 0;
    foreach (@services) {
        my $name = $_->name;
        next if $service && $service ne $name;
        my %services;
        foreach ($_->port) {
            next if $port && $port ne $_->name;
            my $binding = SOAP::Utils::disqualify($_->binding);
            my $endpoint = ref $_->address ? $_->address->location : undef;
            foreach ($s->binding) {
                # is this a SOAP binding?
                next unless grep { $_->uri eq 'http://schemas.xmlsoap.org/wsdl/soap/' } $_->binding;
                next unless $_->name eq $binding;
                my $default_style = $_->binding->style;
                my $porttype = SOAP::Utils::disqualify($_->type);
                foreach ($_->operation) {
                    my $opername = $_->name;
                    $services{$opername} = {}; # should be initialized in 5.7 and after
                    my $soapaction = $_->operation->soapAction;
                    my $invocationStyle = $_->operation->style || $default_style || "rpc";
                    my $encodingStyle = $_->input->body->use || "encoded";
                    my $namespace = $_->input->body->namespace || $tns;
                    my @parts;
                    foreach ($s->portType) {
                        next unless $_->name eq $porttype;
                        foreach ($_->operation) {
                            next unless $_->name eq $opername;
                            my $inputmessage = SOAP::Utils::disqualify($_->input->message);
                            foreach my $msg ($s->message) {
                                next unless $msg->name eq $inputmessage;
                                if ($invocationStyle eq "document" && $encodingStyle eq "literal") {
#                  warn "document/literal support is EXPERIMENTAL in SOAP::Lite"
#                  if !$has_warned && ($has_warned = 1);
                                    my ($input_ns,$input_name) = SOAP::Utils::splitqname($msg->part->element);
                                    if ($input_name) {
                                        foreach my $schema ($s->types->schema) {
                                            foreach my $element ($schema->element) {
                                                next unless $element->name eq $input_name;
                                                push @parts,parse_schema_element($element);
                                            }
                                            $services{$opername}->{parameters} = [ @parts ];
                                        }
                                    }
                                }
                                else {
                                    # TODO - support all combinations of doc|rpc/lit|enc.
                                    #warn "$invocationStyle/$encodingStyle is not supported in this version of SOAP::Lite";
                                    @parts = $msg->part;
                                    $services{$opername}->{parameters} = [ @parts ];
                                }
                            }
                        }

                    for ($services{$opername}) {
                        $_->{endpoint}   = $endpoint;
                        $_->{soapaction} = $soapaction;
                        $_->{namespace}  = $namespace;
                        # $_->{parameters} = [@parts];
                    }
                }
            }
        }
    }
    # fix nonallowed characters in package name, and add 's' if started with digit
    for ($name) { s/\W+/_/g; s/^(\d)/s$1/ }
    push @result, $name => \%services;
    }
    return @result;
}

# ======================================================================

# Naming? SOAP::Service::Schema?
package SOAP::Schema;

use Carp ();

sub DESTROY { SOAP::Trace::objects('()') }

sub new {
    my $self = shift;
    return $self if ref $self;
    unless (ref $self) {
        my $class = $self;
        require LWP::UserAgent;
        $self = bless {
            '_deserializer' => SOAP::Schema::Deserializer->new,
            '_useragent'    => LWP::UserAgent->new,
        }, $class;

        SOAP::Trace::objects('()');
    }

    Carp::carp "Odd (wrong?) number of parameters in new()" if $^W && (@_ & 1);
    no strict qw(refs);
    while (@_) {
        my $method = shift;
        $self->$method(shift) if $self->can($method)
    }

    return $self;
}

sub schema {
    warn "SOAP::Schema->schema has been deprecated. "
        . "Please use SOAP::Schema->schema_url instead.";
    return shift->schema_url(@_);

lib/SOAP/Lite.pm  view on Meta::CPAN

        }
        return $self->{'_schema'};
    }
}

sub BEGIN {
    no strict 'refs';
    for my $method (qw(serializer deserializer)) {
        my $field = '_' . $method;
        *$method = sub {
            my $self = shift->new;
            if (@_) {
                my $context = $self->{$field}->{'_context'}; # save the old context
                $self->{$field} = shift;
                $self->{$field}->{'_context'} = $context;    # restore the old context
                return $self;
            }
            else {
                return $self->{$field};
            }
        }
    }

    __PACKAGE__->__mk_accessors(
        qw(endpoint transport outputxml autoresult packager)
    );
    #  for my $method () {
    #    my $field = '_' . $method;
    #    *$method = sub {
    #      my $self = shift->new;
    #      @_ ? ($self->{$field} = shift, return $self) : return $self->{$field};
    #    }
    #  }
    for my $method (qw(on_action on_fault on_nonserialized)) {
        my $field = '_' . $method;
        *$method = sub {
            my $self = shift->new;
            return $self->{$field} unless @_;
            local $@;
            # commented out because that 'eval' was unsecure
            # > ref $_[0] eq 'CODE' ? shift : eval shift;
            # Am I paranoid enough?
            $self->{$field} = shift;
            Carp::croak $@ if $@;
            Carp::croak "$method() expects subroutine (CODE) or string that evaluates into subroutine (CODE)"
                unless ref $self->{$field} eq 'CODE';
            return $self;
        }
    }
    # SOAP::Transport Shortcuts
    # TODO - deprecate proxy() in favor of new language endpoint_url()
    no strict qw(refs);
    for my $method (qw(proxy)) {
        *$method = sub {
            my $self = shift->new;
            @_ ? ($self->transport->$method(@_), return $self) : return $self->transport->$method();
        }
    }

    # SOAP::Seriailizer Shortcuts
    for my $method (qw(autotype readable envprefix encodingStyle
                    bodyattr headerattr
                    encprefix multirefinplace encoding
                    typelookup header maptype xmlschema
                    uri ns_prefix ns_uri use_prefix use_default_ns
                    ns default_ns)) {
        *$method = sub {
            my $self = shift->new;
            @_ ? ($self->serializer->$method(@_), return $self) : return $self->serializer->$method();
        }
    }

    # SOAP::Schema Shortcuts
    for my $method (qw(cache_dir cache_ttl)) {
        *$method = sub {
            my $self = shift->new;
            @_ ? ($self->schema->$method(@_), return $self) : return $self->schema->$method();
        }
    }
}

sub parts {
    my $self = shift;
    $self->packager->parts(@_);
    return $self;
}

# Naming? wsdl
sub service {
    my $self = shift->new;
    return $self->{'_service'} unless @_;
    $self->schema->schema_url($self->{'_service'} = shift);
    my %services = %{$self->schema->parse(@_)->load->services};

    Carp::croak "More than one service in service description. Service and port names have to be specified\n"
        if keys %services > 1;
    my $service = (keys %services)[0]->new;
    return $service;
}

sub AUTOLOAD {
    my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::') + 2);
    return if $method eq 'DESTROY';

    ref $_[0] or Carp::croak qq!Can\'t locate class method "$method" via package \"! . __PACKAGE__ .'\"';

    no strict 'refs';
    *$AUTOLOAD = sub {
        my $self = shift;
        my $som = $self->call($method => @_);
        return $self->autoresult && UNIVERSAL::isa($som => 'SOAP::SOM')
            ? wantarray ? $som->paramsall : $som->result
            : $som;
    };
    goto &$AUTOLOAD;
}

sub call {
    SOAP::Trace::trace('()');
    my $self = shift;

    die "A service address has not been specified either by using SOAP::Lite->proxy() or a service description)\n"
        unless defined $self->proxy && UNIVERSAL::isa($self->proxy => 'SOAP::Client');

    $self->init_context();

    my $serializer = $self->serializer;
    $serializer->on_nonserialized($self->on_nonserialized);

    my $response = $self->transport->send_receive(
        context  => $self, # this is provided for context
        endpoint => $self->endpoint,
        action   => scalar($self->on_action->($serializer->uriformethod($_[0]))),
                # leave only parameters so we can later update them if required
        envelope => $serializer->envelope(method => shift, @_),
        encoding => $serializer->encoding,
        parts    => @{$self->packager->parts} ? $self->packager->parts : undef,
    );

    return $response if $self->outputxml;

    my $result = eval { $self->deserializer->deserialize($response) }
        if $response;

    if (!$self->transport->is_success || # transport fault
        $@ ||                            # not deserializible
        # fault message even if transport OK
        # or no transport error (for example, fo TCP, POP3, IO implementations)
        UNIVERSAL::isa($result => 'SOAP::SOM') && $result->fault) {
        return ($self->on_fault->($self, $@
            ? $@ . ($response || '')
            : $result)
                || $result
        );
        # ? # trick editors
    }
    # this might be trouble for connection close...
    return unless $response; # nothing to do for one-ways

    # little bit tricky part that binds in/out parameters
    if (UNIVERSAL::isa($result => 'SOAP::SOM')
        && ($result->paramsout || $result->headers)
        && $serializer->signature) {
        my $num = 0;
        my %signatures = map {$_ => $num++} @{$serializer->signature};
        for ($result->dataof(SOAP::SOM::paramsout), $result->dataof(SOAP::SOM::headers)) {
            my $signature = join $;, $_->name, $_->type || '';
            if (exists $signatures{$signature}) {
                my $param = $signatures{$signature};
                my($value) = $_->value; # take first value

                # fillup parameters
                if ( reftype( $_[$param] ) ) {
                    if ( reftype( $_[$param] ) eq 'SCALAR' ) {
                        ${ $_[$param] } = $$value;
                    }
                    elsif ( reftype( $_[$param] ) eq 'ARRAY' ) {
                        @{ $_[$param] } = @$value;
                    }
                    elsif ( reftype( $_[$param] ) eq 'HASH' ) {
                        if ( eval { $_[$param]->isa('SOAP::Data') } ) {
                            $_[$param]->SOAP::Data::value($value);
                        }
                        elsif ( reftype($value) eq 'REF' ) {
                            %{ $_[$param] } = %$$value;
                        }
                        else { %{ $_[$param] } = %$value; }
                    }
                    else { $_[$param] = $value; }
                }
                else {
                    $_[$param] = $value;
                }
            }
        }
    }

lib/SOAP/Lite.pm  view on Meta::CPAN

  </soap:Envelope>

=item use_prefix(boolean)

Deprecated. Use the C<ns()> and C<default_ns> methods described above.

Shortcut for C<< serializer->use_prefix() >>. This lets you turn on/off the
use of a namespace prefix for the children of the /Envelope/Body element.
Default is 'true'.

When use_prefix is set to 'true', serialized XML will look like this:

  <SOAP-ENV:Envelope ...attributes skipped>
    <SOAP-ENV:Body>
      <namesp1:mymethod xmlns:namesp1="urn:MyURI" />
    </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>

When use_prefix is set to 'false', serialized XML will look like this:

  <SOAP-ENV:Envelope ...attributes skipped>
    <SOAP-ENV:Body>
      <mymethod xmlns="urn:MyURI" />
    </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>

Some .NET web services have been reported to require this XML namespace idiom.

=item soapversion(optional value)

    $client->soapversion('1.2');

If no parameter is given, returns the current version of SOAP that is being
used by the client object to encode requests. If a parameter is given, the
method attempts to set that as the version of SOAP being used.

The value should be either 1.1 or 1.2.

=item envprefix(QName)

    $client->envprefix('env');

This method is a shortcut for:

    $client->serializer->envprefix(QName);

Gets or sets the namespace prefix for the SOAP namespace. The default is
SOAP.

The prefix itself has no meaning, but applications may wish to chose one
explicitly to denote different versions of SOAP or the like.

=item encprefix(QName)

    $client->encprefix('enc');

This method is a shortcut for:

    $client->serializer->encprefix(QName);

Gets or sets the namespace prefix for the encoding rules namespace.
The default value is SOAP-ENC.

=back

While it may seem to be an unnecessary operation to set a value that isn't
relevant to the message, such as the namespace labels for the envelope and
encoding URNs, the ability to set these labels explicitly can prove to be a
great aid in distinguishing and debugging messages on the server side of
operations.

=over

=item encoding(encoding URN)

    $client->encoding($soap_12_encoding_URN);

This method is a shortcut for:

    $client->serializer->encoding(args);

Where the earlier method dealt with the label used for the attributes related
to the SOAP encoding scheme, this method actually sets the URN to be specified
as the encoding scheme for the message. The default is to specify the encoding
for SOAP 1.1, so this is handy for applications that need to encode according
to SOAP 1.2 rules.

=item typelookup

    $client->typelookup;

This method is a shortcut for:

    $client->serializer->typelookup;

Gives the application access to the type-lookup table from the serializer
object. See the section on L<SOAP::Serializer>.

=item uri(service specifier)

Deprecated - the C<uri> subroutine is deprecated in order to provide a more
intuitive naming scheme for subroutines that set namespaces. In the future,
you will be required to use either the C<ns()> or C<default_ns()> subroutines
instead of C<uri()>.

    $client->uri($service_uri);

This method is a shortcut for:

    $client->serializer->uri(service);

The URI associated with this accessor on a client object is the
service-specifier for the request, often encoded for HTTP-based requests as
the SOAPAction header. While the names may seem confusing, this method
doesn't specify the endpoint itself. In most circumstances, the C<uri> refers
to the namespace used for the request.

Often times, the value may look like a valid URL. Despite this, it doesn't
have to point to an existing resource (and often doesn't). This method sets
and retrieves this value from the object. Note that no transport code is
triggered by this because it has no direct effect on the transport of the
object.

=item multirefinplace(boolean)

    $client->multirefinplace(1);

This method is a shortcut for:

    $client->serializer->multirefinplace(boolean);

Controls how the serializer handles values that have multiple references to
them. Recall from previous SOAP chapters that a value may be tagged with an
identifier, then referred to in several places. When this is the case for a
value, the serializer defaults to putting the data element towards the top of
the message, right after the opening tag of the method-specification. It is
serialized as a standalone entity with an ID that is then referenced at the
relevant places later on. If this method is used to set a true value, the
behavior is different. When the multirefinplace attribute is true, the data
is serialized at the first place that references it, rather than as a separate
element higher up in the body. This is more compact but may be harder to read
or trace in a debugging environment.

=item parts( ARRAY )

Used to specify an array of L<MIME::Entity>'s to be attached to the
transmitted SOAP message. Attachments that are returned in a response can be
accessed by C<SOAP::SOM::parts()>.

=item self

    $ref = SOAP::Lite->self;

Returns an object reference to the default global object the C<SOAP::Lite>
package maintains. This is the object that processes many of the arguments
when provided on the use line.

=back

The following method isn't an accessor style of method but neither does it fit
with the group that immediately follows it:

=over

=item call(arguments)

    $client->call($method => @arguments);

As has been illustrated in previous chapters, the C<SOAP::Lite> client objects
can manage remote calls with auto-dispatching using some of Perl's more
elaborate features. call is used when the application wants a greater degree
of control over the details of the call itself. The method may be built up
from a L<SOAP::Data> object, so as to allow full control over the namespace
associated with the tag, as well as other attributes like encoding. This is
also important for calling methods that contain characters not allowable in
Perl function names, such as A.B.C.

=back

The next four methods used in the C<SOAP::Lite> class are geared towards
handling the types of events than can occur during the message lifecycle. Each
of these sets up a callback for the event in question:

=over

=item on_action(callback)

    $client->on_action(sub { qq("$_[0]") });

Triggered when the transport object sets up the SOAPAction header for an
HTTP-based call. The default is to set the header to the string, uri#method,
in which URI is the value set by the uri method described earlier, and method
is the name of the method being called. When called, the routine referenced
(or the closure, if specified as in the example) is given two arguments, uri
and method, in that order.

.NET web services usually expect C</> as separator for C<uri> and C<method>.
To change SOAP::Lite's behaviour to use uri/method as SOAPAction header, use
the following code:

    $client->on_action( sub { join '/', @_ } );

=item on_fault(callback)

    $client->on_fault(sub { popup_dialog($_[1]) });

Triggered when a method call results in a fault response from the server.
When it is called, the argument list is first the client object itself,
followed by the object that encapsulates the fault. In the example, the fault
object is passed (without the client object) to a hypothetical GUI function
that presents an error dialog with the text of fault extracted from the object
(which is covered shortly under the L<SOAP::SOM> methods).

=item on_nonserialized(callback)

    $client->on_nonserialized(sub { die "$_[0]?!?" });

Occasionally, the serializer may be given data it can't turn into SOAP-savvy
XML; for example, if a program bug results in a code reference or something
similar being passed in as a parameter to method call. When that happens, this
callback is activated, with one argument. That argument is the data item that
could not be understood. It will be the only argument. If the routine returns,
the return value is pasted into the message as the serialization. Generally,
an error is in order, and this callback allows for control over signaling that
error.

=item on_debug(callback)

    $client->on_debug(sub { print @_ });

Deprecated. Use the global +debug and +trace facilities described in
L<SOAP::Trace>

Note that this method will not work as expected: Instead of affecting the
debugging behaviour of the object called on, it will globally affect the
debugging behaviour for all objects of that class.

=back

=head1 WRITING A SOAP CLIENT

This chapter guides you to writing a SOAP client by example.

The SOAP service to be accessed is a simple variation of the well-known
hello world program. It accepts two parameters, a name and a given name,
and returns "Hello $given_name $name".

We will use "Martin Kutter" as the name for the call, so all variants will
print the following message on success:

 Hello Martin Kutter!

=head2 SOAP message styles

There are three common (and one less common) variants of SOAP messages.

These address the message style (positional parameters vs. specified message
documents) and encoding (as-is vs. typed).

The different message styles are:

=over

=item * rpc/encoded

Typed, positional parameters. Widely used in scripting languages.
The type of the arguments is included in the message.
Arrays and the like may be encoded using SOAP encoding rules (or others).

=item * rpc/literal

As-is, positional parameters. The type of arguments is defined by some
pre-exchanged interface definition.

=item * document/encoded

Specified message with typed elements. Rarely used.

=item * document/literal

Specified message with as-is elements. The message specification and
element types are defined by some pre-exchanged interface definition.

=back

As of 2008, document/literal has become the predominant SOAP message
variant. rpc/literal and rpc/encoded are still in use, mainly with scripting
languages, while document/encoded is hardly used at all.

You will see clients for the rpc/encoded and document/literal SOAP variants in
this section.

=head2 Example implementations

=head3 RPC/ENCODED

Rpc/encoded is most popular with scripting languages like perl, php and python
without the use of a WSDL. Usual method descriptions look like this:

 Method: sayHello(string, string)
 Parameters:
    name: string
    givenName: string

Such a description usually means that you can call a method named "sayHello"
with two positional parameters, "name" and "givenName", which both are
strings.

The message corresponding to this description looks somewhat like this:

 <sayHello xmlns="urn:HelloWorld">
   <s-gensym01 xsi:type="xsd:string">Kutter</s-gensym01>
   <s-gensym02 xsi:type="xsd:string">Martin</s-gensym02>
 </sayHello>

Any XML tag names may be used instead of the "s-gensym01" stuff - parameters
are positional, the tag names have no meaning.

A client producing such a call is implemented like this:

 use SOAP::Lite;
 my $soap = SOAP::Lite->new( proxy => 'http://localhost:81/soap-wsdl-test/helloworld.pl');
 $soap->default_ns('urn:HelloWorld');
 my $som = $soap->call('sayHello', 'Kutter', 'Martin');
 die $som->faultstring if ($som->fault);
 print $som->result, "\n";

You can of course use a one-liner, too...

Sometimes, rpc/encoded interfaces are described with WSDL definitions.
A WSDL accepting "named" parameters with rpc/encoded looks like this:

 <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:s="http://www.w3.org/2001/XMLSchema"
   xmlns:s0="urn:HelloWorld"
   targetNamespace="urn:HelloWorld"
   xmlns="http://schemas.xmlsoap.org/wsdl/">
   <types>
     <s:schema targetNamespace="urn:HelloWorld">
     </s:schema>
   </types>
   <message name="sayHello">
     <part name="name" type="s:string" />
     <part name="givenName" type="s:string" />
   </message>
   <message name="sayHelloResponse">
     <part name="sayHelloResult" type="s:string" />
   </message>

   <portType name="Service1Soap">
     <operation name="sayHello">
       <input message="s0:sayHello" />
       <output message="s0:sayHelloResponse" />
     </operation>
   </portType>

   <binding name="Service1Soap" type="s0:Service1Soap">
     <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
         style="rpc" />
     <operation name="sayHello">
       <soap:operation soapAction="urn:HelloWorld#sayHello"/>
       <input>
         <soap:body use="encoded"
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
       </input>
       <output>
         <soap:body use="encoded"
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
       </output>
     </operation>
   </binding>
   <service name="HelloWorld">
     <port name="HelloWorldSoap" binding="s0:Service1Soap">
       <soap:address location="http://localhost:81/soap-wsdl-test/helloworld.pl" />
     </port>
   </service>
 </definitions>

The message corresponding to this schema looks like this:

 <sayHello xmlns="urn:HelloWorld">
   <name xsi:type="xsd:string">Kutter</name>
   <givenName xsi:type="xsd:string">Martin</givenName>
 </sayHello>

A web service client using this schema looks like this:

 use SOAP::Lite;
 my $soap = SOAP::Lite->service("file:say_hello_rpcenc.wsdl");
 eval { my $result = $soap->sayHello('Kutter', 'Martin'); };
 if ($@) {
     die $@;
 }
 print $som->result();

You may of course also use the following one-liner:

 perl -MSOAP::Lite -e 'print SOAP::Lite->service("file:say_hello_rpcenc.wsdl")\
   ->sayHello('Kutter', 'Martin'), "\n";'

A web service client (without a service description) looks like this.

 use SOAP::Lite;
 my $soap = SOAP::Lite->new( proxy => 'http://localhost:81/soap-wsdl-test/helloworld.pl');
 $soap->default_ns('urn:HelloWorld');
 my $som = $soap->call('sayHello',
    SOAP::Data->name('name')->value('Kutter'),
    SOAP::Data->name('givenName')->value('Martin')
 );
 die $som->faultstring if ($som->fault);
 print $som->result, "\n";

=head3 RPC/LITERAL

SOAP web services using the document/literal message encoding are usually
described by some Web Service Definition. Our web service has the following
WSDL description:

 <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:s="http://www.w3.org/2001/XMLSchema"
   xmlns:s0="urn:HelloWorld"
   targetNamespace="urn:HelloWorld"
   xmlns="http://schemas.xmlsoap.org/wsdl/">
   <types>
     <s:schema targetNamespace="urn:HelloWorld">
       <s:complexType name="sayHello">
         <s:sequence>
           <s:element minOccurs="0" maxOccurs="1" name="name"
              type="s:string" />
           <s:element minOccurs="0" maxOccurs="1" name="givenName"
              type="s:string" nillable="1" />
         </s:sequence>
       </s:complexType>

       <s:complexType name="sayHelloResponse">
         <s:sequence>
           <s:element minOccurs="0" maxOccurs="1" name="sayHelloResult"
              type="s:string" />
         </s:sequence>
       </s:complexType>
     </s:schema>
   </types>
   <message name="sayHello">
     <part name="parameters" type="s0:sayHello" />
   </message>
   <message name="sayHelloResponse">
     <part name="parameters" type="s0:sayHelloResponse" />
   </message>

   <portType name="Service1Soap">
     <operation name="sayHello">
       <input message="s0:sayHello" />
       <output message="s0:sayHelloResponse" />
     </operation>
   </portType>

   <binding name="Service1Soap" type="s0:Service1Soap">
     <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
         style="rpc" />
     <operation name="sayHello">
       <soap:operation soapAction="urn:HelloWorld#sayHello"/>
       <input>
         <soap:body use="literal" namespace="urn:HelloWorld"/>
       </input>
       <output>
         <soap:body use="literal" namespace="urn:HelloWorld"/>
       </output>
     </operation>
   </binding>
   <service name="HelloWorld">
     <port name="HelloWorldSoap" binding="s0:Service1Soap">
       <soap:address location="http://localhost:80//helloworld.pl" />
     </port>
   </service>
  </definitions>

The XML message (inside the SOAP Envelope) look like this:


 <ns0:sayHello xmlns:ns0="urn:HelloWorld">
    <parameters>
      <name>Kutter</name>
      <givenName>Martin</givenName>
    </parameters>
 </ns0:sayHello>

 <sayHelloResponse xmlns:ns0="urn:HelloWorld">
    <parameters>
        <sayHelloResult>Hello Martin Kutter!</sayHelloResult>
    </parameters>
 </sayHelloResponse>

This is the SOAP::Lite implementation for the web service client:

 use SOAP::Lite +trace;
 my $soap = SOAP::Lite->new( proxy => 'http://localhost:80/helloworld.pl');

 $soap->on_action( sub { "urn:HelloWorld#sayHello" });
 $soap->autotype(0)->readable(1);
 $soap->default_ns('urn:HelloWorld');

 my $som = $soap->call('sayHello', SOAP::Data->name('parameters')->value(
    \SOAP::Data->value([
        SOAP::Data->name('name')->value( 'Kutter' ),
        SOAP::Data->name('givenName')->value('Martin'),
    ]))
);

 die $som->fault->{ faultstring } if ($som->fault);
 print $som->result, "\n";

=head3 DOCUMENT/LITERAL

SOAP web services using the document/literal message encoding are usually
described by some Web Service Definition. Our web service has the following
WSDL description:

 <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:s="http://www.w3.org/2001/XMLSchema"
    xmlns:s0="urn:HelloWorld"
    targetNamespace="urn:HelloWorld"
    xmlns="http://schemas.xmlsoap.org/wsdl/">
   <types>
     <s:schema targetNamespace="urn:HelloWorld">
       <s:element name="sayHello">
         <s:complexType>
           <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="name" type="s:string" />
               <s:element minOccurs="0" maxOccurs="1" name="givenName" type="s:string" nillable="1" />
           </s:sequence>
          </s:complexType>
        </s:element>

        <s:element name="sayHelloResponse">
          <s:complexType>
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="sayHelloResult" type="s:string" />
            </s:sequence>
        </s:complexType>
      </s:element>
    </types>
    <message name="sayHelloSoapIn">
      <part name="parameters" element="s0:sayHello" />
    </message>
    <message name="sayHelloSoapOut">
      <part name="parameters" element="s0:sayHelloResponse" />
    </message>

    <portType name="Service1Soap">
      <operation name="sayHello">
        <input message="s0:sayHelloSoapIn" />
        <output message="s0:sayHelloSoapOut" />
      </operation>
    </portType>

    <binding name="Service1Soap" type="s0:Service1Soap">
      <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
          style="document" />
      <operation name="sayHello">
        <soap:operation soapAction="urn:HelloWorld#sayHello"/>
        <input>
          <soap:body use="literal" />
        </input>
        <output>
          <soap:body use="literal" />
        </output>
      </operation>
    </binding>
    <service name="HelloWorld">
      <port name="HelloWorldSoap" binding="s0:Service1Soap">
        <soap:address location="http://localhost:80//helloworld.pl" />
      </port>
    </service>
 </definitions>

lib/SOAP/Lite.pm  view on Meta::CPAN

    ->options({compress_threshold => 10000})
    ->handle;

For more information see L<COMPRESSION|SOAP::Transport/"COMPRESSION"> in
L<HTTP::Transport>.

=head1 SECURITY

For security reasons, the existing path for Perl modules (C<@INC>) will be
disabled once you have chosen dynamic deployment and specified your own
C<PATH/>. If you wish to access other modules in your included package you
have several options:

=over 4

=item 1

Switch to static linking:

   use MODULE;
   $server->dispatch_to('MODULE');

Which can also be useful when you want to import something specific from the
deployed modules:

   use MODULE qw(import_list);

=item 2

Change C<use> to C<require>. The path is only unavailable during the
initialization phase. It is available once more during execution. Therefore,
if you utilize C<require> somewhere in your package, it will work.

=item 3

Wrap C<use> in an C<eval> block:

   eval 'use MODULE qw(import_list)'; die if $@;

=item 4

Set your include path in your package and then specify C<use>. Don't forget to
put C<@INC> in a C<BEGIN{}> block or it won't work. For example,

   BEGIN { @INC = qw(my_directory); use MODULE }

=back

=head1 INTEROPERABILITY

=head2 Microsoft .NET client with SOAP::Lite Server

In order to use a .NET client with a SOAP::Lite server, be sure you use fully
qualified names for your return values. For example:

  return SOAP::Data->name('myname')
                   ->type('string')
                   ->uri($MY_NAMESPACE)
                   ->value($output);

In addition see comment about default encoding in .NET Web Services below.

=head2 SOAP::Lite client with a .NET server

If experiencing problems when using a SOAP::Lite client to call a .NET Web
service, it is recommended you check, or adhere to all of the following
recommendations:

=over 4

=item Declare a proper soapAction in your call

For example, use
C<on_action( sub { 'http://www.myuri.com/WebService.aspx#someMethod'; } )>.

=item Disable charset definition in Content-type header

Some users have said that Microsoft .NET prefers the value of
the Content-type header to be a mimetype exclusively, but SOAP::Lite specifies
a character set in addition to the mimetype. This results in an error similar
to:

  Server found request content type to be 'text/xml; charset=utf-8',
  but expected 'text/xml'

To turn off this behavior specify use the following code:

  use SOAP::Lite;
  $SOAP::Constants::DO_NOT_USE_CHARSET = 1;
  # The rest of your code

=item Use fully qualified name for method parameters

For example, the following code is preferred:

  SOAP::Data->name(Query  => 'biztalk')
            ->uri('http://tempuri.org/')

As opposed to:

  SOAP::Data->name('Query'  => 'biztalk')

=item Place method in default namespace

For example, the following code is preferred:

  my $method = SOAP::Data->name('add')
                         ->attr({xmlns => 'http://tempuri.org/'});
  my @rc = $soap->call($method => @parms)->result;

As opposed to:

  my @rc = $soap->call(add => @parms)->result;
  # -- OR --
  my @rc = $soap->add(@parms)->result;

=item Disable use of explicit namespace prefixes

Some user's have reported that .NET will simply not parse messages that use
namespace prefixes on anything but SOAP elements themselves. For example, the
following XML would not be parsed:

  <SOAP-ENV:Envelope ...attributes skipped>
    <SOAP-ENV:Body>
      <namesp1:mymethod xmlns:namesp1="urn:MyURI" />
    </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>

SOAP::Lite allows users to disable the use of explicit namespaces through the
C<use_prefix()> method. For example, the following code:

  $som = SOAP::Lite->uri('urn:MyURI')
                   ->proxy($HOST)
                   ->use_prefix(0)
                   ->myMethod();

Will result in the following XML, which is more palatable by .NET:

  <SOAP-ENV:Envelope ...attributes skipped>
    <SOAP-ENV:Body>
      <mymethod xmlns="urn:MyURI" />
    </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>

=item Modify your .NET server, if possible

Stefan Pharies <stefanph@microsoft.com>:

SOAP::Lite uses the SOAP encoding (section 5 of the soap 1.1 spec), and
the default for .NET Web Services is to use a literal encoding. So
elements in the request are unqualified, but your service expects them to
be qualified. .Net Web Services has a way for you to change the expected
message format, which should allow you to get your interop working.
At the top of your class in the asmx, add this attribute (for Beta 1):

  [SoapService(Style=SoapServiceStyle.RPC)]

Another source said it might be this attribute (for Beta 2):

  [SoapRpcService]

Full Web Service text may look like:

  <%@ WebService Language="C#" Class="Test" %>
  using System;
  using System.Web.Services;
  using System.Xml.Serialization;

  [SoapService(Style=SoapServiceStyle.RPC)]
  public class Test : WebService {
    [WebMethod]
    public int add(int a, int b) {
      return a + b;
    }
  }

Another example from Kirill Gavrylyuk <kirillg@microsoft.com>:

"You can insert [SoapRpcService()] attribute either on your class or on
operation level".

  <%@ WebService Language=CS class="DataType.StringTest"%>

  namespace DataType {

    using System;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Services.Description;

   [SoapRpcService()]
   public class StringTest: WebService {
     [WebMethod]
     [SoapRpcMethod()]
     public string RetString(string x) {
       return(x);
     }
   }
 }

Example from Yann Christensen <yannc@microsoft.com>:

  using System;
  using System.Web.Services;
  using System.Web.Services.Protocols;

  namespace Currency {
    [WebService(Namespace="http://www.yourdomain.com/example")]
    [SoapRpcService]
    public class Exchange {

lib/SOAP/Lite.pm  view on Meta::CPAN

causes random segmentation faults in httpd processes. To fix, try configuring
Apache with the following:

 RULE_EXPAT=no

If you are using Apache 1.3.20 and later, try configuring Apache with the
following option:

 ./configure --disable-rule=EXPAT

See http://archive.covalent.net/modperl/2000/04/0185.xml for more details and
lot of thanks to Robert Barta <rho@bigpond.net.au> for explaining this weird
behavior.

If this doesn't address the problem, you may wish to try C<-Uusemymalloc>,
or a similar option in order to instruct Perl to use the system's own C<malloc>.

Thanks to Tim Bunce <Tim.Bunce@pobox.com>.

=item CGI scripts do not work under Microsoft Internet Information Server (IIS)

CGI scripts may not work under IIS unless scripts use the C<.pl> extension,
opposed to C<.cgi>.

=item Java SAX parser unable to parse message composed by SOAP::Lite

In some cases SOAP messages created by C<SOAP::Lite> may not be parsed
properly by a SAX2/Java XML parser. This is due to a known bug in
C<org.xml.sax.helpers.ParserAdapter>. This bug manifests itself when an
attribute in an XML element occurs prior to the XML namespace declaration on
which it depends. However, according to the XML specification, the order of
these attributes is not significant.

http://www.megginson.com/SAX/index.html

Thanks to Steve Alpert (Steve_Alpert@idx.com) for pointing on it.

=back

=head1 PERFORMANCE

=over 4

=item Processing of XML encoded fragments

C<SOAP::Lite> is based on L<XML::Parser> which is basically wrapper around
James Clark's expat parser. Expat's behavior for parsing XML encoded string
can affect processing messages that have lot of encoded entities, like XML
fragments, encoded as strings. Providing low-level details, parser will call
char() callback for every portion of processed stream, but individually for
every processed entity or newline. It can lead to lot of calls and additional
memory manager expenses even for small messages. By contrast, XML messages
which are encoded as base64Binary, don't have this problem and difference in
processing time can be significant. For XML encoded string that has about 20
lines and 30 tags, number of call could be about 100 instead of one for
the same string encoded as base64Binary.

Since it is parser's feature there is NO fix for this behavior (let me know
if you find one), especially because you need to parse message you already
got (and you cannot control content of this message), however, if your are
in charge for both ends of processing you can switch encoding to base64 on
sender's side. It will definitely work with SOAP::Lite and it B<may> work with
other toolkits/implementations also, but obviously I cannot guarantee that.

If you want to encode specific string as base64, just do
C<< SOAP::Data->type(base64 => $string) >> either on client or on server
side. If you want change behavior for specific instance of SOAP::Lite, you
may subclass C<SOAP::Serializer>, override C<as_string()> method that is
responsible for string encoding (take a look into C<as_base64Binary()>) and
specify B<new> serializer class for your SOAP::Lite object with:

  my $soap = new SOAP::Lite
    serializer => My::Serializer->new,
    ..... other parameters

or on server side:

  my $server = new SOAP::Transport::HTTP::Daemon # or any other server
    serializer => My::Serializer->new,
    ..... other parameters

If you want to change this behavior for B<all> instances of SOAP::Lite, just
substitute C<as_string()> method with C<as_base64Binary()> somewhere in your
code B<after> C<use SOAP::Lite> and B<before> actual processing/sending:

  *SOAP::Serializer::as_string = \&SOAP::XMLSchema2001::Serializer::as_base64Binary;

Be warned that last two methods will affect B<all> strings and convert them
into base64 encoded. It doesn't make any difference for SOAP::Lite, but it
B<may> make a difference for other toolkits.

=back

=head1 BUGS AND LIMITATIONS

=over 4

=item *

No support for multidimensional, partially transmitted and sparse arrays
(however arrays of arrays are supported, as well as any other data structures,
and you can add your own implementation with SOAP::Data).

=item *

Limited support for WSDL schema.

=item *

XML::Parser::Lite relies on Unicode support in Perl and doesn't do entity decoding.

=item *

Limited support for mustUnderstand and Actor attributes.

=back

=head1 PLATFORM SPECIFICS

=over 4

=item MacOS

Information about XML::Parser for MacPerl could be found here:

http://bumppo.net/lists/macperl-modules/1999/07/msg00047.html

Compiled XML::Parser for MacOS could be found here:



( run in 0.651 second using v1.01-cache-2.11-cpan-13bb782fe5a )