package NSDL::Request;
# Copyright 2005 Norman Walsh. This work is licensed under a
# Creative Commons License: http://creativecommons.org/licenses/by-nc/2.0/
use XML::LibXML;
use XML::LibXML::XPathContext;
use IO::Scalar;
use NSDL::Response;
use NSDL::UA;
sub new {
my $type = shift;
my $self = {};
$self->{'methods'} = {};
$self->{'parser'} = XML::LibXML->new();
$self->{'credentials'} = undef;
return bless $self, $type;
}
sub auth {
my $self = shift;
my $userid = shift;
my $passwd = shift;
$self->{'credentials'} = [ $userid, $passwd ];
}
sub load {
my $self = shift;
my $nsdl = shift;
my $types = "";
my $nsdlns = 'http://nwalsh.com/xmlns/nsdl#';
my $rngns = 'http://relaxng.org/ns/structure/1.0';
my $xmldoc = $self->{'parser'}->parse_file($nsdl);
my $doc = XML::LibXML::XPathContext->new($xmldoc);
$doc->registerNs('nsdl', $nsdlns);
$doc->registerNs('rng', $rngns);
# find the real root
my $root = $xmldoc->getFirstChild();
while ($root && $root->nodeType() != XML_ELEMENT_NODE) {
$root = $root->getNextSibling();
}
# find the RELAX NG types
my @nodes = $doc->findnodes("nsdl:types", $root);
if (@nodes) {
# Make sure we get the right namespaces in context...
my %nsdecl = ();
my %nscontext = $self->findNsContext($nodes[0]);
foreach my $prefix (keys %nscontext) {
my $ncprefix = $prefix;
chop $ncprefix;
my $decl = "xmlns:$ncprefix";
$decl = "xmlns" if $ncprefix eq '';
$nsdecl{$decl} = $nscontext{$prefix};
}
my $divprefix = "rng";
while (exists $nsdecl{"xmlns:$divprefix"}) {
$divprefix .= "_";
}
$types = "<$divprefix:div xmlns:$divprefix='$rngns'";
foreach my $decl (keys %nsdecl) {
$types .= "\n\t$decl='" . $nsdecl{$decl} . "'";
}
$types .= ">\n";
@nodes = $doc->findnodes("rng:*", $nodes[0]);
foreach my $node (@nodes) {
$types .= $node->toString();
}
$types .= "$divprefix:div>";
}
# now process all the services
@nodes = $doc->findnodes("nsdl:service", $root);
foreach my $svc (@nodes) {
my $name = $svc->getAttribute('name');
my $action = $svc->getAttribute('action');
my $uri = $svc->getAttribute('uri');
my @reqargs = ();
my @optargs = ();
my ($reqnode) = $doc->findnodes("nsdl:request", $svc);
my @params = $doc->findnodes("nsdl:parameter", $reqnode);
foreach my $param (@params) {
my $name = $param->getAttribute('name');
my $type = $param->getAttribute('type');
my $def = $param->getAttribute('default');
my $opt = $param->getAttribute('optional') || "no";
if ($opt eq 'yes') {
push (@optargs, $name, $type, $def);
} else {
push (@reqargs, $name, $type, $def);
}
}
my ($body) = $doc->findnodes("nsdl:body/*", $reqnode);
my $schema = "";
my $SH = new IO::Scalar \$schema;
print $SH "\n";
print $SH "\n";
print $SH "$types\n\n";
print $SH "\n";
print $SH " \n";
for (my $count = 0; $count < $#reqargs; $count += 3) {
print $SH " \n";
}
for (my $count = 0; $count < $#optargs; $count += 3) {
print $SH " \n";
print $SH " \n";
print $SH " \n";
}
print $SH " \n";
print $SH "\n\n";
my @args = (@reqargs, @optargs);
while (@args) {
my $name = shift @args;
my $type = shift @args;
my $def = shift @args;
print $SH "\n";
print $SH " \n";
if ($type =~ /^xsd:(.*)/) {
print $SH " \n";
} elsif ($type eq 'text') {
print $SH " \n";
} else {
print $SH " \n";
}
print $SH " \n";
print $SH "\n\n";
}
print $SH "\n";
my %faults = ();
my ($respnode) = $doc->findnodes("nsdl:response", $svc);
my @faultnodes = $doc->findnodes("nsdl:fault", $respnode);
foreach my $fault (@faultnodes) {
my %nsdecl = $self->findNsContext($fault);
my $name = $fault->getAttribute('name');
my $select = $fault->getAttribute('select');
$faults{$name} = { 'select' => $select,
'nsdecl' => \%nsdecl };
}
my @results = ();
my @resultnodes = $doc->findnodes("nsdl:result", $respnode);
foreach my $result (@resultnodes) {
my %nsdecl = $self->findNsContext($result);
my $select = $result->getAttribute('select');
my $name = $result->getAttribute('name');
if ($#resultnodes > 0 && !defined($name)) {
die "All results must be named if there are multiple\n";
}
my %reshash = ( 'name' => $name,
'select' => $select,
'nsdecl' => \%nsdecl );
push (@results, \%reshash);
}
$self->{'methods'}->{$name} = { 'uri' => $uri,
'action' => $action,
'schema' => $schema,
'faults' => \%faults,
'results' => \@results };
$self->{'methods'}->{$name}->{'body'} = $body
if $body && $action eq 'post';
$self->addMethod(\@reqargs, \@optargs, $name);
}
}
sub findNsContext {
my $self = shift;
my $node = shift;
my %nsmap = ();
while ($node) {
my @nslist = $node->getNamespaces();
foreach my $ns (@nslist) {
my $prefix = $ns->getLocalName();
my $uri = $ns->getData();
$prefix = "" if ! defined $prefix;
if (!exists($nsmap{"$prefix:"})) {
$nsmap{"$prefix:"} = $uri;
}
}
$node = $node->parentNode();
}
return %nsmap;
}
sub addMethod {
my $self = shift;
my $reqargs = shift;
my $optargs = shift;
my $subname = shift;
no strict 'refs';
my @reqargs = @{$reqargs};
my @optargs = @{$optargs};
*$subname = sub {
my $self = shift;
my %param = ();
my $args = $self->check_args($subname, \@reqargs, \@optargs, @_);
if (!ref $args) {
print "Failed: $args\n";
die;
} else {
%param = %{$args};
$self->invoke($subname, %param);
}
};
}
sub check_args {
my $self = shift;
my $name = shift;
my $reqargs = shift;
my $optargs = shift;
my @reqarg = ();
my @optarg = ();
my %params = ();
@reqarg = @{$reqargs} if defined($reqargs);
@optarg = @{$optargs} if defined($optargs);
my $schema = $self->{'methods'}->{$name}->{'schema'};
my $document = "";
my $SH = new IO::Scalar \$document;
print $SH "\n";
my $argnum = 0;
for (my $count = 0; $count < $#reqarg; $count += 3) {
my $arg = $reqarg[$count+2];
$arg = $_[$argnum] if $argnum <= $#_;
return "too few args" if !defined($arg);
my $name = $reqarg[$count];
print $SH "<$name>$arg$name>\n";
$params{$name} = $arg;
$argnum++;
}
my $count = 0;
while ($argnum <= $#_) {
return "too many args" if !defined($optarg[$count]);
my $arg = $optarg[$count+2];
$arg = $_[$argnum] if $argnum <= $#_;
my $name = $optarg[$count];
print $SH "<$name>$arg$name>\n";
$params{$name} = $arg;
$count += 2;
$argnum++;
}
print $SH "\n";
open (F, ">/tmp/p-schema.rng"); print F $schema; close (F);
open (F, ">/tmp/p-schema.xml"); print F $document; close (F);
$schema = XML::LibXML::RelaxNG->new( 'string' => $schema);
my $xmldoc = $self->{'parser'}->parse_string($document);
eval { $schema->validate( $xmldoc ); };
return $@ if $@ ne '';
return \%params;
}
sub invoke {
my $self = shift;
my $name = shift;
my %param = @_;
my $results = undef;
my $action = $self->{'methods'}->{$name}->{'action'};
if ($action eq 'get') {
my $requri = $self->{'methods'}->{$name}->{'uri'};
my $sep = "";
foreach my $p (sort keys %param) {
$requri .= $sep;
$requri .= "$p=";
$requri .= $param{$p};
$sep = "&";
}
my $response = $self->{'methods'}->{$name}->{'response'};
$results = $self->get($requri, $name, $userid, $password);
} elsif ($action eq 'post') {
my $requri = $self->{'methods'}->{$name}->{'uri'};
my $body = $self->{'methods'}->{$name}->{'body'};
my $bodytext = $self->expand($body, %param);
$results = $self->post($requri, $bodytext, $name, $userid, $password);
} else {
die "Don't know how to do $action for $name!\n";
}
if (ref $results eq 'ARRAY') {
my @values = @{$results};
my $results = new NSDL::Response($self->{'content'});
foreach my $reshash (@{$self->{'methods'}->{$name}->{'results'}}) {
my $name = $reshash->{'name'};
my $value = shift @values;
$name = "" if !defined($name);
$results->addValue($name, $value);
}
return $results;
} else {
die "$results\n";
}
}
sub expand {
my $self = shift;
my $node = shift;
my %param = @_;
my $result = "";
if ($node->nodeType() == XML_ELEMENT_NODE) {
$result .= "<" . $node->tagName();
my $attrs = $node->attributes();
for (my $count = 0; $count < $attrs->length(); $count++) {
my $attr = $attrs->item($count);
if (ref $attr eq 'XML::LibXML::Namespace') {
my $prefix = $attr->name();
my $aname = "xmlns";
$aname = "xmlns:$prefix" if defined($prefix);
my $value = $self->subst($attr->value(), %param);
$result .= " $aname='$value'";
} else {
my $name = $attr->name();
my $value = $self->subst($attr->value(), %param);
$result .= " $name='$value'";
}
}
my $child = $node->getFirstChild();
if ($child) {
$result .= ">";
while ($child) {
$result .= $self->expand($child, %param);
$child = $child->getNextSibling();
}
$result .= "" . $node->tagName() . ">";
} else {
$result .= "/>";
}
} elsif ($node->nodeType() == XML_TEXT_NODE) {
$result .= $self->subst($node->getData(), %param);
} elsif ($node->nodeType() == XML_PI_NODE) {
my $value = $self->subst($node->getData(), %param);
$result .= "" . $node->nodeName();
$result .= " $value" if $value ne '';
$result .= "?>";
} elsif ($node->nodeType() == XML_COMMENT_NODE) {
$result .= "";
} else {
die "Unexpected node type!?\n";
}
}
sub subst {
my $self = shift;
local $_ = shift;
my %param = @_;
my $data = "";
while (/\{\$(\S+)\}/) {
$data .= $main::PREMATCH;
my $name = $1;
$_ = $main::POSTMATCH;
$data .= $param{$name} if exists $param{$name};
}
return $data . $_;
}
sub get {
my $self = shift;
my $requri = shift;
my $name = shift;
my $ua = NSDL::UA->new();
$ua->agent("NSDL Dispatcher/0.1");
$ua->env_proxy;
# setup credentials
$ua->credentials(@{$self->{'credentials'}})
if defined $self->{'credentials'};
# Create a request
my $req = HTTP::Request->new('GET' => $requri);
# Pass request to the user agent and get a response back
my $res = $ua->request($req);
# Check the outcome of the response
if ($res->is_success()) {
# FIXME: Danger Will Robinson, this is not thread safe!
$self->{'content'} = $res->content();
return $self->reply($res->content(), $name);
} else {
return "GET failed: " . $res->status_line();
}
}
sub post {
my $self = shift;
my $requri = shift;
my $bodytext = shift;
my $name = shift;
my $userid = shift;
my $password = shift;
open (F, "/tmp/out");
read (F, $_, -s "/tmp/out");
close (F);
return $self->reply($_, $name);
my $ua = NSDL::UA->new();
$ua->agent("NSDL Dispatcher/0.1");
$ua->env_proxy;
# setup credentials
$ua->credentials(@{$self->{'credentials'}})
if defined $self->{'credentials'};
my $res = $ua->request(POST $requri,
Content_Type => 'text/xml',
Content => $bodytext);
# Check the outcome of the response
if ($res->is_success()) {
# FIXME: Danger Will Robinson, this is not thread safe!
$self->{'content'} = $res->content();
return $self->reply($res->content(), $name);
} else {
return "POST failed: " . $res->status_line();
}
}
sub reply {
my $self = shift;
my $content = shift;
my $name = shift;
my $msgdoc = $self->{'parser'}->parse_string($content);
#open (F, ">/tmp/out");
#print F $msgdoc->toString();
#close (F);
my %faults = %{$self->{'methods'}->{$name}->{'faults'}};
foreach my $name (keys %faults) {
my $select = $faults{$name}->{'select'};
my %nsdecl = %{$faults{$name}->{'nsdecl'}};
my $doc = XML::LibXML::XPathContext->new($msgdoc);
foreach my $prefix (keys %nsdecl) {
my $ncprefix = $prefix;
chop $ncprefix;
$doc->registerNs($ncprefix, $nsdecl{$prefix});
}
my @nodes = $doc->findnodes($select);
if (@nodes) {
return $name;
}
}
my @results = ();
foreach my $reshash (@{$self->{'methods'}->{$name}->{'results'}}) {
my $name = $reshash->{'name'};
my $select = $reshash->{'select'};
my %nsdecl = %{$reshash->{'nsdecl'}};
my $doc = XML::LibXML::XPathContext->new($msgdoc);
foreach my $prefix (keys %nsdecl) {
my $ncprefix = $prefix;
chop $ncprefix;
$doc->registerNs($ncprefix, $nsdecl{$prefix});
}
my @res = $doc->findnodes($select);
my @val = ();
foreach my $node (@res) {
push (@val, $self->string_value($node));
}
if (@val) {
if ($#val > 0) {
push (@results, \@val);
} else {
push (@results, $val[0]);
}
} else {
push (@results, '');
}
}
return \@results;
}
sub string_value {
my $self = shift;
my $node = shift;
my $result = "";
if ($node->nodeType == XML_ELEMENT_NODE) {
my $child = $node->getFirstChild();
while ($child) {
$result .= $self->string_value($child);
$child = $child->getNextSibling();
}
} elsif ($node->nodeType == XML_TEXT_NODE) {
$result .= $node->getData();
} elsif ($node->nodeType == XML_ATTRIBUTE_NODE) {
$result .= $node->value();
} else {
# nop;
}
return $result;
}
1;