#!/usr/bin/perl -w # Copyright (C) 2003, Evan Prodromou . # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This file 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 General Public License for more details. # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # tag: proxy class generator use strict; use warnings; use Net::Jabber qw(Client); use Error qw(:try); use JOAP::Proxy::Server; use JOAP::Proxy::Class; use JOAP::Proxy::Error; use File::Spec; use File::Path; use IO::File; use Getopt::Long; use Text::Wrap; # version my $VERSION = $JOAP::VERSION; # These are config options our $jid = undef; our $port = 5222; our $password = undef; our $resource = 'joappxgen'; our $directory = '.'; our $verbose = undef; sub usage; sub jabber_con; sub generate_server_code; sub generate_class_code; sub usage { print <<"END_OF_USAGE"; joappxgen.pl [options] REMOTE PACKAGE Generate JOAP proxy code for JOAP server REMOTE in Perl package PACKAGE Example: joappxgen.pl payroll.example.com Payroll Available options are: --jid=JID Jabber ID to connect with (ex: 'JRandom\@example.com') --port=PORT Port to connect to local server (default: $port) --password=PASS Password to use to connect --resource=RSRC Resource to use to connect (default: '$resource') --directory=DIR Directory for output files (default: '$directory') --config-file=FILE Config file to use (default: ~/.joapperlrc) --help Output this message and exit --version Output version information and exit --verbose Verbose output END_OF_USAGE version(); } sub version { print <<"END_OF_VERSION"; joappxgen.pl $VERSION, Copyright (C) 2003 Evan Prodromou This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. END_OF_VERSION } sub main { my $help = undef; my $version = undef; my $config_file = File::Spec->catfile($ENV{HOME}, ".joapperlrc"); GetOptions("jid=s", \$jid, "port=i", \$port, "password=s", \$password, "resource=s", \$resource, "directory=s", \$directory, "help", \$help, "version", \$version, "verbose", \$verbose, "config-file=s", \$config_file); if ($version) { version; exit(0); } if ($help) { usage; exit(0); } unless (my $return = do $config_file) { warn "Couldn't parse $config_file: $@.\n" if $@; warn "Couldn't do $config_file: $!.\n" unless defined $return; warn "Couldn't run $config_file.\n" unless $return; } my $remote = $ARGV[0]; my $package = $ARGV[1]; if (!$jid || !$port || !$password || !$resource || !$remote || !$directory || !$package) { usage; exit(1); } my $con = jabber_con($jid, $port, $password, $resource); unless ($con) { die("Couldn't connect as $jid (port $port)\n"); } JOAP::Proxy->Connection($con); try { print "Getting info for server $remote\n" if $verbose; my $server = JOAP::Proxy::Server->get($remote); generate_server_code($server, $directory, $package); } catch JOAP::Proxy::Error with { my $err = shift; die("JOAP error " . $err->value . ": " . $err->text . "\n"); }; $con->Disconnect(); } sub jabber_con { my $jid = shift; my $port = shift; my $password = shift; my $resource = shift; my $jidobj = new Net::Jabber::JID($jid); my $user = $jidobj->GetUserID; my $server = $jidobj->GetServer; unless ($user && $server) { return undef; } my $con = new Net::Jabber::Client; my $status = $con->Connect(hostname => $server, port => $port); unless (defined($status)) { return undef; } my @result = $con->AuthSend(username => $user, password => $password, resource => $resource); unless ($result[0] eq 'ok') { $con->Disconnect; return undef; } $con->RosterGet; $con->PresenceSend(priority => 0); return $con; } sub generate_server_code { my $server = shift; my $outdir = shift; my $package = shift; my %classes; foreach my $classname (@{$server->classes}) { print "Getting info for class $classname\n" if $verbose; my $class = JOAP::Proxy::Class->get($classname); my $base = basename($classname); my $class_pkg = pkg_name($package, $base); print "Putting class $classname in package $class_pkg\n" if $verbose; generate_class_code($class, $outdir, $class_pkg); $classes{$base} = $class_pkg; } my $file = output_file($outdir, $package); file_prolog($file, $package, 'Server'); common_metadata($file, $package, $server); classes($file, $package, $server); method_decl($file, $package, $server->methods); attrs_decl($file, $package, $server->attributes); server_methods($file, $package, $server); file_epilog($file, $package); $file->close; }; sub generate_class_code { my $class = shift; my $outdir = shift; my $package = shift; my $file = output_file($outdir, $package); file_prolog($file, $package, 'Class'); common_metadata($file, $package, $class); superclasses($file, $package, $class); method_decl($file, $package, $class->methods); attrs_decl($file, $package, $class->attributes); class_methods($file, $package, $class); file_epilog($file, $package); $file->close; } sub superclasses { my $file = shift; my $package = shift; my $obj = shift; $file->print($package, "->Superclasses([qw(", @{$obj->superclasses}, ")]);\n\n"); } sub classes { my $file = shift; my $package = shift; my $server = shift; $file->print($package, "->Classes([qw(", @{$server->classes}, ")]);\n\n"); } sub common_metadata { my $file = shift; my $package = shift; my $obj = shift; $file->print($package, "->Address('", $obj->address, "');\n\n"); $file->print($package, "->Timestamp('", $obj->timestamp, "');\n\n"); $file->print($package, "->Description(<<'END_OF_DESCRIPTION');\n"); $file->print(wrap('', '', $obj->description)); $file->print("END_OF_DESCRIPTION\n\n"); } sub class_methods { my $file = shift; my $package = shift; my $obj = shift; my $meths = $obj->methods; my $attrs = $obj->attributes; my @class_methods = grep {$meths->{$_}->{allocation} eq 'class'} keys %$meths; my @instance_methods = grep {$meths->{$_}->{allocation} ne 'class'} keys %$meths; my @class_attrs = grep {$attrs->{$_}->{allocation} eq 'class'} keys %$attrs; my @instance_attrs = grep {$attrs->{$_}->{allocation} ne 'class'} keys %$attrs; if (@class_attrs) { $file->print("# class attribute accessors\n\n"); foreach my $name (@class_attrs) { $file->print("*$name = $package->class_accessor('$name');\n"); } $file->print("\n"); } if (@class_methods) { $file->print("# class methods\n\n"); foreach my $name (@class_methods) { $file->print("*$name = $package->class_method('$name');\n"); } $file->print("\n"); } if (@instance_attrs) { $file->print("# instance attribute accessors\n\n"); foreach my $name (@instance_attrs) { $file->print("*$name = $package->instance_accessor('$name');\n"); } $file->print("\n"); } if (@instance_methods) { $file->print("# instance methods\n\n"); foreach my $name (@instance_methods) { $file->print("*$name = $package->instance_method('$name');\n"); } $file->print("\n"); } } sub server_methods { my $file = shift; my $package = shift; my $obj = shift; my $meths = $obj->methods; my $attrs = $obj->attributes; $file->print("\n# server methods\n\n"); while (my ($name, $desc) = each %$meths) { $file->print("*$name = $package->method('$name');\n"); } $file->print("\n# server attribute accessors\n\n"); while (my ($name, $desc) = each %$attrs) { $file->print("*$name = $package->accessor('$name');\n"); } } sub pkg_name { my $package = shift; my $base = shift; return $package . "::" . $base; } sub basename { my $classname = shift; my $jid = new Net::Jabber::JID($classname); return $jid->GetUserID; } sub output_file { my $outdir = shift; my $package = shift; my @dirparts = split(/::/, $package); my $base = pop @dirparts; my $dir = File::Spec->catfile($outdir, @dirparts); unless (-d $dir) { if (-f $dir) { warn "Whomping existing file $dir\n"; unlink $dir; } mkpath($dir); } my $filename = File::Spec->catfile($dir, $base) . ".pm"; # XXX: assumes .pm print "Opening output file $filename for package $package\n" if $verbose; return IO::File->new("> $filename"); } sub method_decl { my $file = shift; my $package = shift; my $meths = shift; $file->print($package, "->Methods({"); while (my ($name, $desc) = each %$meths) { my $desc_wrap = wrap('','', $desc->{desc}); $file->print(<<"END_OF_METHOD_PROLOG"); $name => { desc => <<'END_OF_DESC', $desc_wrap END_OF_DESC returnType => '$desc->{returnType}', allocation => '$desc->{allocation}', params => [ END_OF_METHOD_PROLOG foreach my $paramdesc (@{$desc->{params}}) { my $pdescwrap = wrap('','', $paramdesc->{desc}); $file->print(<<"END_OF_METHOD_PARAM"); { name => '$paramdesc->{name}', type => '$paramdesc->{type}', desc => <<'END_OF_PARAM_DESC' $pdescwrap END_OF_PARAM_DESC }, END_OF_METHOD_PARAM } $file->print(<<"END_OF_METHOD_EPILOG"); ] }, END_OF_METHOD_EPILOG } $file->print("});\n"); } sub attrs_decl { my $file = shift; my $package = shift; my $attrs = shift; $file->print($package, "->Attributes({"); while (my ($name, $desc) = each %$attrs) { my $desc_wrap = wrap('','', $desc->{desc}); $file->print(<<"END_OF_ATTRIBUTE"); $name => { desc => <<'END_OF_DESC', $desc_wrap END_OF_DESC type => '$desc->{type}', allocation => '$desc->{allocation}', required => $desc->{required}, writeable => $desc->{writeable} }, END_OF_ATTRIBUTE } $file->print("});\n"); } sub file_prolog { my $file = shift; my $package = shift; my $type = shift; my $ts = localtime; print $file <<"END_OF_PROLOG"; # Do not edit! Autogenerated file! All will perish! # # created by joappxgen.pl $VERSION at $ts # # More template stuff to go here soon package $package; use 5.008; use strict; use warnings; use JOAP::Proxy::Package::$type; use base qw(JOAP::Proxy::Package::$type); END_OF_PROLOG } sub file_epilog { my $file = shift; my $package = shift; $file->print("1;\n"); } &main();