| JOAP documentation | Contained in the JOAP distribution. |
JOAP::Proxy - Base class for client-side proxies of JOAP objects
use Net::Jabber qw(Client);
use JOAP::Proxy;
# set up a Net::Jabber connection (your responsibility)
sub jabber_con {
my $user = shift;
my $server = shift;
my $password = shift;
my $port = shift || 5222;
my $resource = shift || 'JOAPProxy';
if (!$user || !$server || !$password) {
return undef;
}
my $con = new Net::Jabber::Client;
my $status = $con->Connect(hostname => $server,
port => $port);
if (!(defined($status))) {
return undef;
}
my @result = $con->AuthSend(username => $user,
password => $password,
resource => $resource);
if ($result[0] ne "ok") {
$con->Disconnect;
return undef;
}
$con->RosterGet();
$con->PresenceSend(priority => 0);
return $con;
}
my $con = jabber_con('me', 'example.com', 'very secret') ||
die("Can't connect.");
# Make that available to all proxy objects
JOAP::Proxy->Connection($con);
This is an abstract base class for local, client-side proxies to remote JOAP objects -- object servers, classes, and instances. It provides some default behavior for subclasses, and contains a class variable for Jabber connectivity, but otherwise it should not be used directly.
All of the classes that proxy for remote JOAP objects are subclasses of JOAP::Proxy. This package defines a lot of common behavior, but almost none of it should be used directly. Consequently, the appropriate methods and such are documented in the subclasses; see below for links to these classes.
There's really only one method in this package that you should care about, and that's the one to set up the Jabber connection used by all the proxies.
This is the Jabber connection used to send and receive messages about proxy information. You should use this method as a mutator in your programs as early as possible, and definitely before using any of the other proxy classes.
The argument $con is a Net::Jabber::Protocol object. It can be a Net::Jabber::Client object or a Net::Jabber::Component object, and possibly even a Net::Jabber::Server object if you're adventurous.
The synopsis above has a good example of setting up the connection. You should avoid setting any callbacks on the connection, or at least setting any that interfere with JOAP's namespace or the 'jabber:iq:rpc' namespace.
This is a translucent, inheritable class data accessor. What that
means is that you can, in theory, set the Connection class
attribute for subclasses of this package, and it will only affect that
subclass. For example:
package FooProxy;
use JOAP::Proxy::Package::Class;
use base qw(JOAP::Proxy::Package::Class);
FooProxy->Address('Foo@bar.example.com'); # say what class this is proxying for
package main;
# set up the connection for all proxies
my $con1 = jabber_con('me', 'example.com', 'very secret') ||
die("Can't connect.");
JOAP::Proxy->Connection($con1);
# set up a different connection just for foo proxies
my $con2 = jabber_con('you', 'example.net', 'also secret') ||
die("Can't connect.");
JOAP::Proxy->Connection($con2);
This is pretty untested, and I wouldn't rely on it too much if I were you. But the possibility is there to use different connections for different proxy packages.
None by default.
You can create a proxy for a remote JOAP object server using JOAP::Proxy::Package::Server (preferred) or JOAP::Proxy::Server.
You can create a proxy for a remote JOAP class using JOAP::Proxy::Package::Class (preferred) or JOAP::Proxy::Class.
You can create a proxy for a remote JOAP instance using JOAP::Proxy::Package::Class (preferred) or JOAP::Proxy::Instance.
JOAP has more general JOAP information, as well as bug-reporting and contact information.
Net::Jabber has more information on setting up, and tearing down, Jabber connections.
Evan Prodromou <evan@prodromou.san-francisco.ca.us>
Copyright (c) 2003, Evan Prodromou <evan@prodromou.san-francisco.ca.us>.
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
| JOAP documentation | Contained in the JOAP distribution. |
# JOAP::Proxy -- Base Class for Things JOAP Clients Use # # Copyright (c) 2003, Evan Prodromou <evan@prodromou.san-francisco.ca.us> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # tag: JOAP client object base class package JOAP::Proxy; use base qw/Exporter Class::Data::Inheritable/; use 5.008; use strict; use warnings; use Net::Jabber qw/Client/; use JOAP; use Error qw(:try); use Symbol; use JOAP::Proxy::Error; our %EXPORT_TAGS = ( 'all' => [ qw// ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw//; our $VERSION = $JOAP::VERSION; our $AUTOLOAD; JOAP::Proxy->mk_classdata('Connection'); sub get { my $proto = shift; my $pkg = ref($proto) || $proto; my $address = shift; my $self = bless({_address => $address}, $pkg); $self->_read(); return $self; } sub refresh { my $self = shift; # XXX: anything else? return $self->_read; } sub save { my $self = shift; # XXX: anything else? return $self->_edit; } sub address { my $self = shift; return $self->{_address}; } sub timestamp { my $self = shift; return $self->{_timestamp}; } sub _set_timestamp { my $self = shift; return $self->{_timestamp} = shift; } sub description { my $self = shift; return $self->{_description}; } sub _set_description { my $self = shift; return $self->{_description} = shift; } sub attributes { my $self = shift; return (@_) ? $self->{_attributes} = shift : $self->{_attributes}; } sub methods { my $self = shift; return (@_) ? $self->{_methods} = shift : $self->{_methods}; } sub _attribute_descriptor { my $self = shift; my $name = shift; return $self->attributes->{$name}; } sub _method_descriptor { my $self = shift; my $name = shift; return $self->methods->{$name}; } sub _described { my $self = shift; return $self->timestamp; } sub _read { my $self = shift; my $con = $self->Connection; if (!$con) { throw JOAP::Proxy::Error::Local("No JOAP proxy connection set."); } if (!$self->_described) { $self->_describe; } my $iq = new Net::Jabber::IQ; $iq->SetTo($self->address); $iq->SetType('get'); $iq->NewQuery($JOAP::NS, 'read'); # XXX: configure to allow reading just some attributes my $resp = $con->SendAndReceiveWithID($iq); if ($resp->GetType eq 'error') { my $code = $resp->GetErrorCode; my $text = $resp->GetError; throw JOAP::Proxy::Error::Remote($text, $code); } my $read = $resp->GetQuery; my @attrs = $read->GetAttribute; foreach my $attr (@attrs) { my $name = $attr->GetName; # XXX: check returned attributes for type my $value = JOAP->decode($attr->GetValue); $self->_set($name, $value); } # FIXME: what should we return? return $resp; } sub _set { my $self = shift; my $name = shift; my $value = shift; $self->{$name} = $value; } sub _edit { my $self = shift; my $con = $self->Connection; if (!$con) { throw JOAP::Proxy::Error::Local("No JOAP proxy connection set."); } if (!$self->_described) { $self->_describe; } my $iq = new Net::Jabber::IQ; $iq->SetTo($self->address); $iq->SetType('set'); my $edit = $iq->NewQuery($JOAP::NS, 'edit'); my $attrs = $self->_default_edit_attrs(); while (my($name, $descriptor) = each %$attrs) { no strict 'refs'; my $loc = $self->$name; use strict 'refs'; my $tval = JOAP->encode($descriptor->{type}, $loc); my $val = $edit->AddAttribute(name => $name)->AddValue; JOAP->copy_value($tval, $val); } my $resp = $con->SendAndReceiveWithID($iq); if ($resp->GetType eq 'error') { throw JOAP::Proxy::Error::Remote($resp->GetError, $resp->GetErrorCode); } return $resp; } sub _default_edit_attrs { my $self = shift; my $attrs = $self->attributes; # find names of writable attributes my @writable = grep { $attrs->{$_}->{writable} } keys %$attrs; # make that into a hash my %write = map {($_, $attrs->{$_})} @writable; # return a reference to that hash return \%write; } sub _describe { my $self = shift; my $con = $self->Connection; if (!$con) { throw JOAP::Proxy::Error::Local("No JOAP proxy connection set."); } my $iq = new Net::Jabber::IQ; $iq->SetTo($self->address); $iq->SetType('get'); $iq->NewQuery($JOAP::NS, 'describe'); my $resp = $con->SendAndReceiveWithID($iq); if ($resp->GetType eq 'error') { throw JOAP::Proxy::Error::Remote($resp->GetError, $resp->GetErrorCode); } my $desc = $resp->GetQuery; # FIXME: handle multiple descriptions if ($desc->DefinedDesc) { $self->_set_description($desc->GetDesc); } my $attrs = {}; my @attrdescs = $desc->GetAttributeDescription; foreach my $attrdesc (@attrdescs) { my $name = $attrdesc->GetName; my $type = $attrdesc->GetType; my $required = $attrdesc->GetRequired || 0; my $writable = $attrdesc->GetWritable || 0; my $allocation = $attrdesc->GetAllocation || 'instance'; my $desc = $attrdesc->GetDesc || ''; $attrs->{$attrdesc->GetName} = {name => $name, type => $type, required => $required, writable => $writable, allocation => $allocation, desc => $desc}; } $self->attributes($attrs); my $meths = {}; my @methdescs = $desc->GetMethodDescription; foreach my $methdesc (@methdescs) { $meths->{$methdesc->GetName} = {name => $methdesc->GetName, returnType => $methdesc->GetReturnType, allocation => $methdesc->GetAllocation, desc => $methdesc->GetDesc}; my $params = []; my @params = $methdesc->GetParams->GetParams; foreach my $param (@params) { push @$params, {name => $param->GetName, type => $param->GetType, desc => $param->GetDesc}; } $meths->{$methdesc->GetName}->{params} = $params; } $self->methods($meths); # save the timestamp $self->_set_timestamp($desc->GetTimestamp); return $resp; } # This allows us to say $self->can('autoloadedmethod'). AUTOLOAD (below) # uses this method to create methods if necessary. sub can { my($self) = shift; my($name) = shift; my($func) = $self->SUPER::can($name); # See if it's findable by standard lookup. if (!$func) { # if not, see if it's something we should make ourselves. if (my $methdesc = $self->_method_descriptor($name)) { $func = $self->_proxy_method($methdesc); } elsif (my $attrdesc = $self->_attribute_descriptor($name)) { $func = $self->_proxy_accessor($attrdesc); } } return $func; } sub _proxy_method { my $self = shift; my $methdesc = shift; my @param_types = map { $_->{type} } @{$methdesc->{params}}; my $param_cnt = scalar(@param_types); my $name = $methdesc->{name}; return sub { my $self = shift; my $con = $self->Connection || throw JOAP::Proxy::Error::Local("Can't call remote method if not connected."); my @args = @_; # XXX: allow named parameters if scalar(@args) == $param_cnt * 2 if (scalar(@args) != $param_cnt) { throw JOAP::Proxy::Error::Local("Wrong number of parameters (need $param_cnt) for method '$name'."); } my $iq = new Net::Jabber::IQ; $iq->SetIQ(to => $self->address, type => 'set'); my $mc = $iq->NewQuery('jabber:iq:rpc')->AddMethodCall; $mc->SetMethodName($name); my $params = $mc->AddParams; my $i; for ($i = 0; $i < $param_cnt; $i++) { my $pv = $params->AddParam->AddValue; my $tv = JOAP->encode($param_types[$i], $args[$i]); JOAP->copy_value($tv, $pv); } my $resp = $con->SendAndReceiveWithID($iq); if ($resp->GetType eq 'error') { throw JOAP::Proxy::Error::Remote($resp->GetError, $resp->GetErrorCode); } my $mr = $resp->GetQuery->GetMethodResponse; if ($mr->DefinedFault) { my $struct = $mr->GetFault->GetValue->GetStruct; my ($code, $text); foreach my $member ($struct->GetMembers()) { if ($member->GetName eq 'faultCode') { $code = JOAP->decode($member->GetValue); } elsif ($member->GetName eq 'faultString'){ $text = JOAP->decode($member->GetValue); } } throw JOAP::Proxy::Error::Fault($text, $code); } else { # FIXME: check return type my @results = map { JOAP->decode($_->GetValue) } $mr->GetParams->GetParams; return @results; } }; } sub _proxy_accessor { my $self = shift; my $descriptor = shift; my $name = $descriptor->{name}; my $writable = $descriptor->{writable}; my $type = $descriptor->{type}; my $func = undef; if ($writable) { $func = sub { my $self = shift; return (@_) ? $self->{$name} = JOAP->coerce($type, shift) : $self->{$name}; }; } else { $func = sub { my $self = shift; if (@_) { throw JOAP::Proxy::Error::Local("Can't modify read-only attribute $name."); } return $self->{$name}; }; } return $func; } sub AUTOLOAD { my ($self) = $_[0]; my ($sub) = $AUTOLOAD; my ($pkg,$name) = ($sub =~ /(.*)::([^:]+)$/); my ($func) = $self->can($name); if ($func) { &$func(@_); } else { throw JOAP::Proxy::Error::Local("No attribute or method '$name'"); } } # skip autoload hoohaw for DESTROY sub DESTROY { } 1; # of these days, Alice __END__