package SAP::BC::XMLRFC; use strict; use SAP::BC; use SAP::BC::Iface; use HTTP::Request; use HTTP::Cookies; use LWP::UserAgent; use XML::Parser; use Data::Dumper; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $AUTOLOAD); require Exporter; @ISA = qw(Exporter); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. @EXPORT_OK = qw ( Iface xmlrfc ); # Global debug flag my $DEBUG = undef; # Valid parameters my $VALID = { SERVER => 1, BC => 1, USERID => 1, PASSWD => 1 }; my $_out = ""; my $_cell = ""; my $_tagre = ""; $VERSION = '0.06'; # Preloaded methods go here. sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = { @_ }; die "Server not supplied !" if ! exists $self->{SERVER}; die "SAP BC USERID not supplied !" if ! exists $self->{USERID}; die "SAP BC Password not supplied (PASSWD) !" if ! exists $self->{PASSWD}; # Validate parameters map { delete $self->{$_} if ! exists $VALID->{$_} } keys %{$self}; # check that the service exists $self->{BC} = new SAP::BC( server => $self->{SERVER}, user => $self->{USERID}, password => $self->{PASSWD} ); # create the object and return it bless ($self, $class); return $self; } # method to dynamically create functions SAP::BC::Iface sub Iface{ my $self = shift; my $service = shift; die "No Service name supplied to lookup " if ! $service; die "Service does not exist - $service " if ! exists $self->{BC}->services->{$service}; my $lookup = "/invoke/sap.rfc/createTemplate"; $self->{BC}->_prime_ua(); my $ua = $self->{BC}->{ua}; # print STDERR "REQ: ".$self->{SERVER}.$lookup."\?\$call\=true\&serverName\=". # $self->{BC}->services->{$service}->{sapsys}. # "\&\$rfcname\_search\=\&groupname=\&\$rfcname\=". # $self->{BC}->services->{$service}->{rfcname}. # "\&table=\&submit\=RFC\-XML" ."\n"; my $req = new HTTP::Request('GET', $self->{SERVER}.$lookup."\?\$call\=true\&serverName\=". $self->{BC}->services->{$service}->{sapsys}. "\&\$rfcname\_search\=\&groupname=\&\$rfcname\=". $self->{BC}->services->{$service}->{rfcname}. "\&table=\&submit\=RFC\-XML" ); $req->authorization_basic($self->{USERID},$self->{PASSWD}); my $res = $ua->request($req); die " Interface lookup call failed: " . $res->message() if !$res->is_success(); my $content = $res->content; die "RFC_SYSTEM_FAILURE in interface lookup" if $content =~ /RFC_ERROR/s; my ( $xml_template ) = $content =~ /^.*xmlData<\/B><\/TD>\s*(.*?)<\/TD>.*$/s; my $p = new XML::Parser( Style => 'Tree', ErrorContext => 3 ); my $r = $p->parse( $xml_template ); my $intrfc = $self->{BC}->services->{$service}->{rfcname}; $intrfc =~ s/\//\_\-/g; die "Interface lookup failed for $service " unless $r->[1]->[8]->[3] eq "rfc:".$intrfc; my $iface = new SAP::BC::Iface( NAME => $service ); # shift over to the interface definition part of the doc $r = $r->[1]->[8]->[4]; my $c = -1; while (my $parmname = $r->[$c+=4]){ # print STDERR " Parm: $parmname \n"; my $parm = $r->[$c + 1]; # determine a table or structure or simple parameter if ( $parm->[3] =~ /\w/){ # we have either a structure or a table if ( $parm->[3] =~ /item/ ){ # we have a table my $struct = SAP::BC::Struc->new( NAME => $parmname ); # add fields my $d = -1; while ( my $fieldname = $parm->[4]->[$d+=4] ){ # fudge for a bad last one ? next unless $fieldname =~ /\w/; $struct->addField( NAME => $fieldname, TYPE => 'chars' ); }; $iface->addTab( NAME => $parmname, STRUCTURE => $struct ); } else { # we have a structure my $struct = SAP::BC::Struc->new( NAME => $parmname ); my $d = -1; while ( my $fieldname = $parm->[$d+=4] ){ # fudge for a bad last one ? next unless $fieldname =~ /\w/; $struct->addField( NAME => $fieldname, TYPE => 'chars' ); }; $iface->addParm( NAME => $parmname, TYPE => 'chars', STRUCTURE => $struct ); }; } else { $iface->addParm( NAME => $parmname, TYPE => 'chars' ); }; }; # print STDERR "Iface: ".Dumper($iface); return $iface; } # Call The Function module sub xmlrfc { my $xml_out = ""; my $intrfc = ""; my $self = shift; my $iface = shift; my $ref = ref($iface); die "this is not an Interface Object!" unless $ref eq "SAP::BC::Iface" and $ref; $self->{BC}->_prime_ua(); my $ua = $self->{BC}->{ua}; my $service = $iface->name(); # print STDERR "The services- $service -: ".Dumper( $self->{BC}->services); $intrfc = $self->{BC}->services->{$service}->{rfcname}; $intrfc =~ s/\//\_\-/g; $service =~ s/\:/\//; my $req = new HTTP::Request('POST', $self->{SERVER}."/invoke/".$service); $req->header('Content-Type' => 'application/x-sap.rfc'); #'Host' => 'my.source.host.net'); $req->authorization_basic($self->{USERID},$self->{PASSWD}); my $start_content = < BC1 BC2 ENDOFSTART my $end_content = < ENDOFEND $xml_out = "\n"; map{ $xml_out.= " <" . $_->name .">"; if (my $s = $_->structure ){ $xml_out.= "\n"; map { $xml_out.= " <" . $_ .">" . $s->Fieldvalue($_) . "<\/" . $_ . ">\n" ; } ( $s->Fields ); $xml_out.= " <\/" . $_->name . ">\n" ; } else { $xml_out.= $_->value . "<\/" . $_->name . ">\n" ; }; } ( $iface->Parms ); map{ my $tab = $_; $xml_out.= " <" . $tab->name . ">\n"; while ( my $row = $tab->nextrow ){ $xml_out .= " \n"; map { $xml_out .= " <$_>$row->{$_}<\/$_>\n" } keys %{$row}; $xml_out .= " <\/item>\n"; }; # map { $xml_out .= " <" . $_ . ">" . "<\/" . $_ . ">\n"; # } ( $tab->structure->Fields ); $xml_out.= " <\/" . $tab->name . ">\n" } ( $iface->Tabs ); $xml_out .= "<\/rfc:".$intrfc.">\n"; # print STDERR "the constructed interface: ".$start_content.$xml_out.$end_content; $req->content($start_content.$xml_out.$end_content); my $res = $ua->request($req); die " RFC-XML call failed: " . $res->as_string() if !$res->is_success(); $xml_out = $res->content; # print $xml_out; die "RFC_SYSTEM_FAILURE in interface lookup".$xml_out if $xml_out =~ /RFC_ERROR/s; my $p = new XML::Parser( Style => 'Tree', ErrorContext => 3 ); # pick properly handled RFC errors my ($faultcode, $faultstring, $faultname) = $xml_out =~ /^.*?\(.*?)\<\/faultcode\>.*? \(.*?)\<\/faultstring\>.*? \(.*?)\<\/name\>.*$/sx; die "RFX-XML call error: ".$faultcode." ".$faultstring." ".$faultname if $faultcode; my $r = $p->parse( $xml_out ); $r = $r->[1]->[4]->[4]; my $c = -1; while (my $parmname = $r->[$c+=4]){ my $parm = $r->[$c + 1]; # is this a table ? if ( $parm->[3] eq 'item' ){ $iface->Tab($parmname)->empty; # process each row my $i = -1; while ($parm->[$i+=4] eq 'item'){ # process each field my $row = $parm->[$i + 1]; my @row = (); my $j = -1; while ( my $field = $row->[$j+=4] ){ push( @row, $row->[$j + 1]->[2] ); }; $iface->Tab($parmname)->addrow(\@row); }; } else { # is it a complex parameter $iface->addParm( SAP::BC::Parms->new( NAME => $parmname, TYPE => 'chars') ); if ( $parm->[3] =~ /\w/ ){ my $struct = SAP::BC::Struc->new( NAME => $parmname ); my $d = -1; while ( my $fieldname = $parm->[$d+=4] ){ # fudge for a bad last one ? next unless $fieldname =~ /\w/; my $field = $parm->[$d + 1]; $struct->addField( NAME => $fieldname, TYPE => 'chars', VALUE => $field->[2]); }; $iface->Parm($parmname)->structure( $struct ); } else { # Simple Parameter $iface->Parm($parmname)->value($parm->[2]); }; }; }; } sub disconnect { my $self = shift; $self->{'BC'}->disconnect(); } # Autoload methods go after =cut, and are processed by the autosplit program. # Below is the stub of documentation for your module. You better edit it! =head1 NAME SAP::BC::XMLRFC - Perl extension for performing RFC Function calls against an SAP R/3 using the Business Connector System. Please refer to the README file found with this distribution. =head1 SYNOPSIS # Setup up a service in the SAP BC server for an RFC-XML based call to RFC_READ_REPORT # called test:ReadReport to make this example work use SAP::BC::XMLRFC; $rfc = new SAP::BC::XMLRFC( ); my $userid = 'testuser'; my $passwd = 'letmein'; my $server="http://my.server.blah:5555"; my $service = 'test:ReadReport'; # build the connecting object my $xmlrfc = new SAP::BC::XMLRFC( SERVER => $server, USERID => $userid, PASSWD => $passwd ); # Discover the interface definition for a function module my $i = $xmlrfc->Iface( $service ); # set a parameter value of the interface $i->Parm('PROGRAM')->value('SAPLGRAP'); # call the BC service with an interface object $xmlrfc->xmlrfc( $i ); print "Name:", $i->Parm('TRDIR')->structure->NAME, "\n"; map {print @{$_}, "\n" } ( $i->Tab('QTAB')->rows ); while ( my $row = $i->Tab('QTAB')->nextrow ){ map { print "$_ = $row->{$_} \n" } keys %{$row}; }; =head1 DESCRIPTION Enabler for XMLRFC calls to SAP vi athe SAP Business Connector =head1 METHODS: my $rfc = new SAP::BC::XMLRFC( SERVER => $server, USERID => $userid, PASSWD => $passwd ); =head1 AUTHOR Piers Harding, saprfc@kogut.demon.co.uk. But Credit must go to all those that have helped. =head1 SEE ALSO perl(1), SAP::BC(3), SAP::BC::XMLRFC(3), SAP::BC::Iface(3) =cut 1;