Initial revision
authorphasefx <phasefx@9efc2488-bf62-4759-914b-345cdb29e865>
Fri, 4 Feb 2005 22:08:15 +0000 (22:08 +0000)
committerphasefx <phasefx@9efc2488-bf62-4759-914b-345cdb29e865>
Fri, 4 Feb 2005 22:08:15 +0000 (22:08 +0000)
git-svn-id: svn://svn.open-ils.org/OpenSRF/trunk@2 9efc2488-bf62-4759-914b-345cdb29e865

80 files changed:
bin/jabber_users_create [new file with mode: 0755]
bin/opensrf_ctl [new file with mode: 0755]
doc/OpenSRF-Messaging-Protocol.html [new file with mode: 0644]
examples/math_bench.pl [new file with mode: 0755]
examples/math_shell.pl [new file with mode: 0755]
examples/math_simple.pl [new file with mode: 0755]
include/opensrf/generic_utils.h [new file with mode: 0644]
include/opensrf/transport_client.h [new file with mode: 0644]
include/opensrf/transport_message.h [new file with mode: 0644]
include/opensrf/transport_session.h [new file with mode: 0644]
include/opensrf/transport_socket.h [new file with mode: 0644]
src/javascript/JSON.js [new file with mode: 0644]
src/javascript/md5.js [new file with mode: 0644]
src/javascript/opensrf_app_session.js [new file with mode: 0644]
src/javascript/opensrf_config.js [new file with mode: 0644]
src/javascript/opensrf_dom_element.js [new file with mode: 0644]
src/javascript/opensrf_domain_object.js [new file with mode: 0644]
src/javascript/opensrf_jabber_transport.js [new file with mode: 0644]
src/javascript/opensrf_msg_stack.js [new file with mode: 0644]
src/javascript/opensrf_transport.js [new file with mode: 0644]
src/javascript/opensrf_utils.js [new file with mode: 0644]
src/libtransport/Makefile [new file with mode: 0644]
src/libtransport/basic_client.c [new file with mode: 0644]
src/libtransport/generic_utils.c [new file with mode: 0644]
src/libtransport/transport_client.c [new file with mode: 0644]
src/libtransport/transport_message.c [new file with mode: 0644]
src/libtransport/transport_session.c [new file with mode: 0644]
src/libtransport/transport_socket.c [new file with mode: 0644]
src/patch/README [new file with mode: 0644]
src/patch/mod_offline.c [new file with mode: 0644]
src/patch/nad.c [new file with mode: 0644]
src/perlmods/JSON.pm [new file with mode: 0644]
src/perlmods/OpenSRF.pm [new file with mode: 0644]
src/perlmods/OpenSRF/AppSession.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application/Client.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application/Demo/Math.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application/Demo/MathDB.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/domainObject.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/domainObjectAttr.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/domainObjectCollection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/param.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/params.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/searchCriteria.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/searchCriterium.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/searchTargetValue.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/userAuth.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsMessage.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsMethod.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsMultiSearch.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsPrimitive.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsResponse.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsSearch.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObjectCollection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/EX.pm [new file with mode: 0644]
src/perlmods/OpenSRF/System.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JInbound.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JMessageWrapper.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JPeerConnection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JabberClient.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Listener.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/PeerHandle.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/Client.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/Inbound.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/MessageWrapper.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/PeerConnection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/UnixServer.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils/Cache.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils/Config.pm [new file with mode: 0755]
src/perlmods/OpenSRF/Utils/LogServer.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils/Logger.pm [new file with mode: 0644]
src/router/Makefile [new file with mode: 0644]
src/router/router.c [new file with mode: 0644]
src/router/router.h [new file with mode: 0644]

diff --git a/bin/jabber_users_create b/bin/jabber_users_create
new file mode 100755 (executable)
index 0000000..16051a2
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/perl -w
+
+# Pulls the jabber users from the oils/jabber config files
+# and populates the mysql db for the jabber server with the users
+
+use DBI;
+use strict;
+use OpenILS::Utils::Config qw( /pines/conf/oils.conf );
+my $config = OpenILS::Utils::Config->current;
+
+if( @ARGV < 2 ) {
+       print "usage: perl jcreate.pl dbhost dbuser dbpass\n";
+       exit;
+}
+
+
+my $host = $ARGV[0];
+my $user       = $ARGV[1];
+my $pass = $ARGV[2];
+
+my $connection = DBI->connect( "DBI:mysql:jabberd2:$host", $user, $pass )
+       or die "Cannot connect to db: $! \n";
+
+my $jpass = $config->transport->auth->password;
+my $realm = $config->transport->server->primary;
+
+# Delete all users
+my $clean = "delete from authreg;";
+my $sth = $connection->prepare( $clean );
+$sth->execute();
+
+my @users = keys %{$config->transport->users};
+
+# Grab each user from the config and push them into mysql
+for my $user (@users) {
+       if( ! $user or $user eq "__id" or $user eq "__sub") { next; }
+       print "Inserting $user:  ";
+
+       my $sql = "insert into authreg (username, realm, password) values " .
+               "('$user', '$realm', '$jpass');";
+
+       print "[$sql]\n"; 
+
+       $sth = $connection->prepare( $sql );
+       $sth->execute();
+
+}
+
+$sth->finish();
+
diff --git a/bin/opensrf_ctl b/bin/opensrf_ctl
new file mode 100755 (executable)
index 0000000..c8469e0
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# 
+#  Simple rc script for controlling the system
+#  Only works on linux because of 'ps' syntax
+#
+
+
+case $1 in 
+       "start")
+               perl -MOpenILS::System -e 'OpenILS::System->bootstrap()' & 
+               sleep 2;
+               $0 status;
+               echo;
+               ;;
+       "stop")
+               PID=$(ps ax | grep "[0-9] System$" | awk '{print $1}');
+               if [ -z $PID ]; then
+                       echo "OpenILS System is not running";
+                       exit;
+               fi
+               echo "Killing System...$PID";
+               kill -s INT $PID;
+               echo "Done";
+               ;;
+       "status")
+               PID=$(ps ax | grep "[0-9] System$" | awk '{print $1}');
+               if [ -z $PID ]; then
+                       echo "OpenILS System is not running";
+                       exit 0;
+               fi
+               echo "OpenILS System is running";
+               exit 1;
+               ;;
+       "restart")
+               $0 stop;
+               $0 start;
+               ;;
+       *)
+               echo "usage: system.sh [start|stop|restart|status]";
+               ;;
+esac
+
+
diff --git a/doc/OpenSRF-Messaging-Protocol.html b/doc/OpenSRF-Messaging-Protocol.html
new file mode 100644 (file)
index 0000000..4cb8f4e
--- /dev/null
@@ -0,0 +1,318 @@
+<html>
+
+       <head>
+
+               <title> OILS Messaging </title>
+
+       </head>
+
+       <body>
+
+
+               <h1> Abstract </h1>
+
+               <p>
+
+               The OILS messaging system works on two different primary layers: the transport layer and the
+               application layer.  The transport layer manages virtual connections between client and server,
+               while the application layer manages user/application level messages.  
+
+               All messages must declare which protocol version they are requesting.  The current protocol level
+               is 1.
+
+               <h1> Transport Layer </h1>
+
+               <p>
+               There are currently three types of messages in the transport layer: <b>CONNECT, STATUS, </b> and
+               <b>DISCONNECT</b>.    
+               
+               <p>
+               <b>STATUS</b> message provide general information to the transport layer are used in different 
+               ways throughout the system.  They are sent primarily by the server in response to client requests.  
+               Each message comes with 
+               a status and statusCode.  The actual status part of the STATUS message is just a helpful message (mostly for debugging).  The 
+               statusCode is an integer representing the exact status this message represents.  The status codes
+               are modeled after HTTP status codes.  Currently used codes consist of the following:
+
+               <b> <pre style="border: solid thin blue; margin: 2% 10% 2% 10%; padding-left: 50px">
+               100     STATUS_CONTINUE
+               200     STATUS_OK       
+               205     STATUS_COMPLETE
+               307     STATUS_REDIRECTED
+               400     STATUS_BADREQUEST
+               403     STATUS_FORBIDDEN
+               404     STATUS_NOTFOUND
+               408     STATUS_TIMEOUT
+               417     STATUS_EXPFAILED
+               </pre> </b>
+
+               <p>
+               This list is likely to change at least a little.
+
+
+               <p>
+               The <b>CONNECT</b> message initiates the virtual connection for a client and expects a <b>STATUS</b>
+               in return.  If the connection is successful, the statusCode for the <b>STATUS</b> message shall be
+               <b>STATUS_OK</b>.  If the authentication fails or if there is not actual authentication information
+               within the message, the statusCode for the returned message shall be <b>STATUS_FORBIDDEN</b>.  
+
+               <p>
+               If at any point the client sends a non-connect message to the server when the client is not connected or the 
+               connection has timed out, the <b>STATUS</b> that is returned shall have statusCode <b>STATUS_EXPFAILED</b>.
+               
+               <p>
+               The <b>DISCONNECT</b> message is sent by the client to the server to end the virtual session.  The server
+               shall not respond to any disconnect messages.
+       
+               
+               <h1> Message Layer </h1>
+
+               <p>
+               There are currently two types of message layer messages: <b>REQUEST</b> and <b>RESULT</b>.  <b>REQUEST</b>
+               messages represent application layer requests made by a client and <b>RESULT</b> messages are the servers 
+               response to such <b>REQUEST</b>'s.
+               
+               <p>
+               By design, all <b>CONNECT</b> and <b>REQUEST</b> messages sent by a client will be acknowledged by one or 
+               more responses from the server.  This is much like the SYN-ACK philosophy of TCP, however different in many 
+               ways.  The only guarantees made by the server are 1. you will know that we received your request and 2. you 
+               will know the final outcome of your request.  It is the responsibility of the actual application to send 
+               the requested application data (e.g. RESULT messages, intermediate STATUS messages).
+               
+               
+               <p>
+               The server responses are matched to client requests by a <b>threadTrace</b>.  A threadTrace is simply a 
+               number and all application layer messages and STATUS messages are required to have one.  (Note that the 
+               threadTrace contained within a STATUS message sent in response to a CONNECT will be ignored).  Currently, 
+               there is no restriction on the number other than it shall be unique within a given virtual connection.  
+               When the server receives a <b>REQUEST</b> message, it extracts the <b>threadTrace</b> from the message 
+               and all responses to that request will contain the same <b>threadTrace</b>.
+               
+               <p>
+               As mentioned above, every <b>CONNECT</b> message will be acknowledged by a single 
+               <b>STATUS</b> message.  <b>REQUEST</b>'s are a little more complex, however.  A <b>REQUEST</b> 
+               will receive one or more <b>RESULT</b>'s if the <b>REQUEST</b> warrants such a response.  A <b>REQUEST</b>
+               may even receive one or more intermediate <b>STATUS</b>'s (e.g. <b>STATUS_CONTINUE</b>).  (Consult the 
+               documentation on the application request the client is requesting for more information on the number and 
+               type of responses to that request).  All <b>REQUEST</b>'s, however, regardless of other response types,
+               shall receieve as the last response a <b>STATUS</b> message with statusCode <b>STATUS_COMPLETE</b>.  This
+               allows the client to wait for REQUEST "completeness" as opposed to waiting on or calculating individual 
+               responses.
+
+
+               <h1> Client Pseudocode </h1>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+send CONNECT
+
+msg = recv()
+
+if ( msg.statusCode == STATUS_OK ) 
+
+       OK. continue
+
+if ( msg.statusCode == STATUS_FORBIDDEN ) 
+
+       handle authentication failure and attempt another connect if requested
+
+while ( more requests ) {
+
+       /* you may send multiple requests before processing any responses.  For the sake
+               of this example, we will only walk through a single client request */
+
+       send REQUEST with threadTrace X 
+
+       while ( response = recv ) { 
+
+               if (  response.threadTrace != X ) 
+
+                       continue/ignore
+
+               if ( response.type == STATUS )
+               
+                       if (  response.statusCode == STATUS_TIMEOUT             or
+                                       response.statusCode == STATUS_REDIRECTED        or
+                                       response.statusCode == STATUS_EXPFAILED)
+
+                               resend the the request with threadTrace X because it was not honored.
+
+                       if ( response.statusCode == STATUS_COMPLETE ) 
+
+                               the request is now complete, nothing more to be done with this request
+                               break out of loop
+       
+               if ( response.typ == RESULT )
+
+                       pass result to the application layer for processing
+
+       } // receiving
+
+} // sending
+
+
+               </pre>
+
+               <br>
+               <h1> Server Pseudocode </h1>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+while( message = recv() ) {
+
+       if( message.type != CONNECT )
+
+               return a STATUS with statusCode STATUS_EXPFAILED
+               start loop over
+
+       if ( message.type == CONNECT and server is unable to authenticate the client )
+
+               return a STATUS with statusCode STATUS_FORBIDDEN
+               start loop over
+
+       if ( message.type == CONNECT and server is able to authenticate user )
+
+               return STATUS with statusCode STATUS_OK and continue
+
+       while ( msg = recv() and virtual session is active ) {
+
+
+               if ( msg.type == REQUEST )
+
+                       Record the threadTrace.  Pass the REQUEST to the application layer for processing.
+                       When the application layer has completed processing, respond to the client
+                       with a STATUS message with statusCode STATUS_COMPLETE and threadTrace matching
+                       the threadTrace of the REQUEST.  Once the final STATUS_COMPLETE message is sent,
+                       the session is over.  Return to outer server loop. 
+
+                       /* Note: during REQUEST processing by the application layer, the application may 
+                               opt to send RESULT and/or STATUS messages to the client.  The server side
+                               transport mechanism is not concerned with these messages.  The server only 
+                               needs to be notified when the REQUEST has been sucessfully completed. */
+
+               if( message.type == DISCONNECT )
+
+                       Virtual session has ended. Return to outer loop.
+
+
+       } // Sessin loop
+
+} // Main server loop
+
+
+
+               </pre>
+
+
+               <br>
+               <h1> XML Examples</h1>
+               <br>
+
+
+               <h2> Protocol Element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="1" name="protocol"/>
+
+               </pre>
+
+               <h2> threadTrace Element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="1" name="threadTrace"/>
+
+               </pre>
+
+               <h2> Type element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:userAuth hashseed="237" secret="89dd8c65300d4af126cf467779ff1820" username="bill"/>
+
+               </pre>
+
+               <h2> CONNECT Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="CONNECT" name="type"/>
+       &lt;oils:userAuth hashseed="237" secret="89dd8c65300d4af126cf467779ff1820" username="bill"/>
+       &lt;oils:domainObjectAttr value="1" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+&lt;/oils:domainObject>
+
+               </pre>
+
+
+               <h2> DISCONNECT Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="DISCONNECT" name="type"/>
+       &lt;oils:domainObjectAttr value="0" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> STATUS Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="STATUS" name="type"/>
+       &lt;oils:domainObjectAttr value="0" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsConnectStatus">
+               &lt;oils:domainObjectAttr value="Connection Successful" name="status"/>
+               &lt;oils:domainObjectAttr value="200" name="statusCode"/>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> REQUEST Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="REQUEST" name="type"/>
+       &lt;oils:domainObjectAttr value="4" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsMethod">
+               &lt;oils:domainObjectAttr value="mult" name="method"/>
+               &lt;oils:params>
+                       &lt;oils:param>1</oils:param>
+                       &lt;oils:param>2</oils:param>
+               &lt;/oils:params>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> RESULT Message </h2>
+               
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="RESULT" name="type"/>
+       &lt;oils:domainObjectAttr value="4" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsResult">
+               &lt;oils:domainObjectAttr value="OK" name="status"/>
+               &lt;oils:domainObjectAttr value="200" name="statusCode"/>
+               &lt;oils:domainObject name="oilsScalar">2&lt;/oils:domainObject>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+               
+
+       </body>
+
+</html>
+
+
diff --git a/examples/math_bench.pl b/examples/math_bench.pl
new file mode 100755 (executable)
index 0000000..30f81af
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::DOM::Element::userAuth;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use Time::HiRes qw/time/;
+use OpenILS::EX qw/:try/;
+
+$| = 1;
+
+# ----------------------------------------------------------------------------------------
+# This is a quick and dirty script to perform benchmarking against the math server.
+# Note: 1 request performs a batch of 4 queries, one for each supported method: add, sub,
+# mult, div.
+# Usage: $ perl math_bench.pl <num_requests>
+# ----------------------------------------------------------------------------------------
+
+
+my $count = $ARGV[0];
+
+unless( $count ) {
+       print "usage: ./math_bench.pl <num_requests>\n";
+       exit;
+}
+
+warn "PID: $$\n";
+
+my $config = OpenILS::Utils::Config->current;
+OpenILS::System->bootstrap_client();
+
+my $session = OpenILS::AppSession->create( 
+               "math", username => 'math_bench', secret => '12345' );
+
+try {
+       if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+} catch OpenILS::EX with {
+       my $e = shift;
+       warn "Connection Failed *\n";
+       die $e;
+}
+
+my @times;
+my %vals = ( add => 3, sub => -1, mult => 2, div => 0.5 );
+
+for my $x (1..100) {
+       if( $x % 10 ) { print ".";}
+       else{ print $x/10; };
+}
+print "\n";
+
+my $c = 0;
+
+for my $scale ( 1..$count ) {
+       for my $mname ( keys %vals ) {
+
+               my $method = OpenILS::DomainObject::oilsMethod->new( method => $mname );
+               $method->params( 1,2 );
+
+               my $req;
+               my $resp;
+               my $starttime;
+               try {
+
+                       $starttime = time();
+                       $req = $session->request( $method );
+                       $resp = $req->recv( timeout => 10 );
+                       push @times, time() - $starttime;
+
+               } catch OpenILS::EX with {
+                       my $e = shift;
+                       die "ERROR\n $e";
+
+               } catch Error with {
+                       my $e = shift;
+                       die "Caught unknown error: $e";
+               };
+
+
+               if( ! $req->complete ) { warn "\nIncomplete\n"; }
+
+
+               if ( $resp ) {
+
+                       my $ret = $resp->content();
+                       if( "$ret" eq $vals{$mname} ) { print "+"; }
+
+                       else { print "*BAD*\n" . $resp->toString(1) . "\n"; }
+
+               } else { print "*NADA*";        }
+
+               $req->finish();
+               $c++;
+
+       }
+       print "\n[$c] \n" unless $scale % 25;
+}
+
+$session->kill_me();
+
+my $total = 0;
+
+$total += $_ for (@times);
+
+$total /= scalar(@times);
+
+print "\n\n\tAverage Round Trip Time: $total Seconds\n";
+
diff --git a/examples/math_shell.pl b/examples/math_shell.pl
new file mode 100755 (executable)
index 0000000..b61157b
--- /dev/null
@@ -0,0 +1,139 @@
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::EX qw/:try/;
+$| = 1;
+
+# ----------------------------------------------------------------------------------------
+# Simple math shell where you can test the transport system.
+# Enter simple, binary equations ony using +, -, *, and /
+# Example: # 1+1
+# Usage: % perl math_shell.pl
+# ----------------------------------------------------------------------------------------
+
+# load the config
+my $config = OpenILS::Utils::Config->current;
+
+# connect to the transport (jabber) server
+OpenILS::System->bootstrap_client();
+
+# build the AppSession object.
+my $session = OpenILS::AppSession->create( 
+       "math", username => 'math_bench', secret => '12345' );
+
+# launch the shell
+print "type 'exit' or 'quit' to leave the shell\n";
+print "# ";
+while( my $request = <> ) {
+
+       chomp $request ;
+
+       # exit loop if user enters 'exit' or 'quit'
+       if( $request =~ /exit/i or $request =~ /quit/i ) { last; }
+
+       # figure out what the user entered
+       my( $a, $mname, $b ) = parse_request( $request );
+
+       if( $a =~ /error/ ) {
+               print "Parse Error. Try again. \nExample # 1+1\n";
+               next;
+       }
+
+
+       try {
+
+               # Connect to the MATH server
+               if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+       } catch OpenILS::EX with {
+               my $e = shift;
+               die "* * Connection Failed with:\n$e";
+       };
+
+       my $method = OpenILS::DomainObject::oilsMethod->new( method => $mname );
+       $method->params( $a, $b );
+
+       my $req;
+       my $resp;
+
+       try {
+               $req = $session->request( $method );
+
+               # we know that this request only has a single reply
+               # if your expecting a 'stream' of results, you can
+               # do: while( $resp = $req->recv( timeout => 10 ) ) {}
+               $resp = $req->recv( timeout => 10 );
+
+       } catch OpenILS::EX with {
+
+               # Any transport layer or server problems will launch an exception
+               my $e = shift;
+               die "ERROR Receiving\n $e";
+
+       } catch Error with {
+
+               # something just died somethere
+               my $e = shift;
+               die "Caught unknown error: $e";
+       };
+
+       if ( $resp ) {
+               # ----------------------------------------------------------------------------------------
+               # $resp is an OpenILS::DomainObject::oilsResponse object. $resp->content() returns whatever 
+               # data the object has.  If the server returns an exception that we're meant to see, then
+               # the data will be an exception object.  In this case, barring any exception, we know that 
+               # the data is an OpenILS::DomainObject::oilsScalar object which has a value() method 
+               # that returns a perl scalar.  For us, that scalar is just a number.
+               # ----------------------------------------------------------------------------------------
+
+               if( UNIVERSAL::isa( $resp, "OpenILS::EX" ) ) {
+                               throw $resp;
+               }
+
+               my $ret = $resp->content();
+               print $ret->value();
+       }
+
+       $req->finish();
+
+       print "\n# ";
+
+}
+
+# disconnect from the MATH server
+$session->kill_me();
+exit;
+
+# ------------------------------------------------------------------------------------
+# parse the user input string
+# returns a list of the form (first param, operation, second param)
+# These operations are what the MATH server recognizes as method names
+# ------------------------------------------------------------------------------------
+sub parse_request {
+       my $string = shift;
+       my $op;
+       my @ops;
+       
+       while( 1 ) {
+
+               @ops = split( /\+/, $string );
+               if( @ops > 1 ) { $op = "add"; last; }
+
+               @ops = split( /\-/, $string );
+               if( @ops > 1 ) { $op = "sub"; last; }
+
+               @ops = split( /\*/, $string );
+               if( @ops > 1 ) { $op = "mult", last; }
+
+               @ops = split( /\//, $string );
+               if( @ops > 1 ) { $op = "div"; last; }
+
+               return ("error");
+       }
+
+       return ($ops[0], $op, $ops[1]);
+}
+
diff --git a/examples/math_simple.pl b/examples/math_simple.pl
new file mode 100755 (executable)
index 0000000..73b015e
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::EX qw/:try/;
+$| = 1;
+
+
+# ----------------------------------------------------------------------------------------
+# This script makes a single query, 1 + 2,  to the the MATH test app and prints the result
+# Usage: % perl math_simple.pl 
+# ----------------------------------------------------------------------------------------
+
+
+# connect to the transport (jabber) server
+OpenILS::System->bootstrap_client();
+
+# build the AppSession object.
+my $session = OpenILS::AppSession->create( 
+       "math", username => 'math_bench', secret => '12345' );
+
+try {
+
+       # Connect to the MATH server
+       if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+} catch OpenILS::EX with {
+       my $e = shift;
+       die "* * Connection Failed with:\n$e";
+};
+
+my $method = OpenILS::DomainObject::oilsMethod->new( method => "add" );
+$method->params( 1, 2 );
+
+my $req;
+my $resp;
+
+try {
+       $req = $session->request( $method );
+
+       # we know that this request only has a single reply
+       # if your expecting a 'stream' of results, you can
+       # do: while( $resp = $req->recv( timeout => 10 ) ) {}
+       $resp = $req->recv( timeout => 10 );
+
+} catch OpenILS::EX with {
+
+       # Any transport layer or server problems will launch an exception
+       my $e = shift;
+       die "ERROR Receiving\n $e";
+
+} catch Error with {
+
+       # something just died somethere
+       my $e = shift;
+       die "Caught unknown error: $e";
+};
+
+if ( $resp ) {
+       # ----------------------------------------------------------------------------------------
+       # $resp is an OpenILS::DomainObject::oilsResponse object. $resp->content() returns whatever 
+       # data the object has.  If the server returns an exception that we're meant to see, then
+       # the data will be an exception object.  In this case, barring any exception, we know that 
+       # the data is an OpenILS::DomainObject::oilsScalar object which has a value() method 
+       # that returns a perl scalar.  For us, that scalar is just a number.
+       # ----------------------------------------------------------------------------------------
+
+       if( UNIVERSAL::isa( $resp, "OpenILS::EX" ) ) {
+                       throw $resp;
+       }
+
+       my $ret = $resp->content();
+       print "Should print 3 => " . $ret->value() . "\n";
+
+} else {
+       die "No Response from Server!\n";
+}
+
+$req->finish();
+
+# disconnect from the MATH server
+$session->kill_me();
+exit;
+
diff --git a/include/opensrf/generic_utils.h b/include/opensrf/generic_utils.h
new file mode 100644 (file)
index 0000000..aa74221
--- /dev/null
@@ -0,0 +1,80 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* libxml stuff for the config reader */
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+#include <libxml/tree.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+
+#ifndef GENERIC_UTILS_H
+#define GENERIC_UTILS_H
+
+
+/** Malloc's, checks for NULL, clears all memory bits and 
+  * returns the pointer
+  * 
+  * @param size How many bytes of memory to allocate
+  */
+inline void* safe_malloc( int size );
+
+/* 10M limit on buffers for overflow protection */
+#define BUFFER_MAX_SIZE 10485760 
+
+// ---------------------------------------------------------------------------------
+// Generic growing buffer. Add data all you want
+// ---------------------------------------------------------------------------------
+struct growing_buffer_struct {
+       char *buf;
+       int n_used;
+       int size;
+};
+typedef struct growing_buffer_struct growing_buffer;
+
+growing_buffer* buffer_init( int initial_num_bytes);
+int buffer_addchar(growing_buffer* gb, char c);
+int buffer_add(growing_buffer* gb, char* c);
+int buffer_reset( growing_buffer* gb);
+char* buffer_data( growing_buffer* gb);
+int buffer_free( growing_buffer* gb );
+
+
+void log_free(); 
+
+// Utility method
+void get_timestamp( char buf_25chars[]);
+
+// ---------------------------------------------------------------------------------
+// Error handling interface.
+// ---------------------------------------------------------------------------------
+
+void fatal_handler( char* message, ...);
+void warning_handler( char* message, ... );
+void info_handler( char* message, ... );
+
+// ---------------------------------------------------------------------------------
+// Config file module
+// ---------------------------------------------------------------------------------
+struct config_reader_struct {
+       xmlDocPtr config_doc;
+       xmlXPathContextPtr xpathCx;
+};
+typedef struct config_reader_struct config_reader;
+config_reader* conf_reader;
+
+void config_reader_init( char* config_file );
+
+void config_reader_free();
+
+// allocastes a char*. FREE me.
+char* config_value( const char* xpath_query, ... );
+
+#endif
diff --git a/include/opensrf/transport_client.h b/include/opensrf/transport_client.h
new file mode 100644 (file)
index 0000000..3d02add
--- /dev/null
@@ -0,0 +1,87 @@
+#include "transport_session.h"
+#include <time.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_CLIENT_H
+#define TRANSPORT_CLIENT_H
+
+#define MESSAGE_LIST_HEAD 1
+#define MESSAGE_LIST_ITEM 2
+
+
+// ---------------------------------------------------------------------------
+// Represents a node in a linked list.  The node holds a pointer to the next
+// node (which is null unless set), a pointer to a transport_message, and
+// and a type variable (which is not really curently necessary).
+// ---------------------------------------------------------------------------
+struct message_list_struct {
+       struct message_list_struct* next;
+       transport_message* message;
+       int type;
+};
+
+typedef struct message_list_struct transport_message_list;
+typedef struct message_list_struct transport_message_node;
+
+// ---------------------------------------------------------------------------
+// Our client struct.  We manage a list of messages and a controlling session
+// ---------------------------------------------------------------------------
+struct transport_client_struct {
+       transport_message_list* m_list;
+       transport_session* session;
+};
+typedef struct transport_client_struct transport_client;
+
+// ---------------------------------------------------------------------------
+// Allocates and initializes and transport_client.  This does no connecting
+// The user must call client_free(client) when finished with the allocated
+// object.
+// ---------------------------------------------------------------------------
+transport_client* client_init( char* server, int port );
+
+// ---------------------------------------------------------------------------
+// Connects to the Jabber server with the provided information. Returns 1 on
+// success, 0 otherwise.
+// ---------------------------------------------------------------------------
+int client_connect( transport_client* client, 
+               char* username, char* password, char* resource, int connect_timeout );
+
+int client_disconnect( transport_client* client );
+
+// ---------------------------------------------------------------------------
+// De-allocates memory associated with a transport_client object.  Users
+// must use this method when finished with a client object.
+// ---------------------------------------------------------------------------
+int client_free( transport_client* client );
+
+// ---------------------------------------------------------------------------
+//  Sends the given message.  The message must at least have the recipient
+// field set.
+// ---------------------------------------------------------------------------
+int client_send_message( transport_client* client, transport_message* msg );
+
+// ---------------------------------------------------------------------------
+// Returns 1 if this client is currently connected to the server, 0 otherwise
+// ---------------------------------------------------------------------------
+int client_connected( transport_client* client );
+
+// ---------------------------------------------------------------------------
+// This is the message handler required by transport_session.  This handler
+// takes all incoming messages and puts them into the back of a linked list
+// of messages.  
+// ---------------------------------------------------------------------------
+void client_message_handler( void* client, transport_message* msg );
+
+// ---------------------------------------------------------------------------
+// If there are any message in the message list, the 'oldest' message is
+// returned.  If not, this function will wait at most 'timeout' seconds 
+// for a message to arrive.  Specifying -1 means that this function will not
+// return unless a message arrives.
+// ---------------------------------------------------------------------------
+transport_message* client_recv( transport_client* client, int timeout );
+
+
+#endif
diff --git a/include/opensrf/transport_message.h b/include/opensrf/transport_message.h
new file mode 100644 (file)
index 0000000..21e3120
--- /dev/null
@@ -0,0 +1,94 @@
+#include "libxml.h"
+
+#include "generic_utils.h"
+
+#include <string.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/debugXML.h>
+#include <libxml/xmlmemory.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_MESSAGE_H
+#define TRANSPORT_MESSAGE_H
+
+
+
+// ---------------------------------------------------------------------------------
+// Jabber message object.
+// ---------------------------------------------------------------------------------
+struct transport_message_struct {
+       char* body;
+       char* subject;
+       char* thread;
+       char* recipient;
+       char* sender;
+       char* router_from;
+       char* router_to;
+       char* router_class;
+       char* router_command;
+       int is_error;
+       char* error_type;
+       int error_code;
+       int broadcast;
+       char* msg_xml; /* the entire message as XML complete with entity encoding */
+};
+typedef struct transport_message_struct transport_message;
+
+// ---------------------------------------------------------------------------------
+// Allocates and returns a transport_message.  All chars are safely re-allocated
+// within this method.
+// Returns NULL on error
+// ---------------------------------------------------------------------------------
+transport_message* message_init( char* body, char* subject, 
+               char* thread, char* recipient, char* sender );
+
+
+void message_set_router_info( transport_message* msg, char* router_from,
+               char* router_to, char* router_class, char* router_command, int broadcast_enabled );
+
+// ---------------------------------------------------------------------------------
+// Formats the Jabber message as XML for encoding. 
+// Returns NULL on error
+// ---------------------------------------------------------------------------------
+char* message_to_xml( const transport_message* msg );
+
+
+// ---------------------------------------------------------------------------------
+// Call this to create the encoded XML for sending on the wire.
+// This is a seperate function so that encoding will not necessarily have
+// to happen on all messages (i.e. typically only occurs outbound messages).
+// ---------------------------------------------------------------------------------
+int message_prepare_xml( transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Deallocates the memory used by the transport_message
+// Returns 0 on error
+// ---------------------------------------------------------------------------------
+int message_free( transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Prepares the shared XML document
+// ---------------------------------------------------------------------------------
+//int message_init_xml();
+
+// ---------------------------------------------------------------------------------
+// Determines the username of a Jabber ID.  This expects a pre-allocated char 
+// array for the return value.
+// ---------------------------------------------------------------------------------
+void jid_get_username( const char* jid, char buf[] );
+
+// ---------------------------------------------------------------------------------
+// Determines the resource of a Jabber ID.  This expects a pre-allocated char 
+// array for the return value.
+// ---------------------------------------------------------------------------------
+void jid_get_resource( const char* jid, char buf[] );
+
+void set_msg_error( transport_message*, char* error_type, int error_code);
+
+#endif
diff --git a/include/opensrf/transport_session.h b/include/opensrf/transport_session.h
new file mode 100644 (file)
index 0000000..b78f20d
--- /dev/null
@@ -0,0 +1,217 @@
+// ---------------------------------------------------------------------------------
+// Manages the Jabber session.  Data is taken from the TCP object and pushed into
+// a SAX push parser as it arrives.  When key Jabber documetn elements are met, 
+// logic ensues.
+// ---------------------------------------------------------------------------------
+#include "libxml.h"
+#include "transport_socket.h"
+#include "transport_message.h"
+#include "generic_utils.h"
+
+#include <string.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+#include <libxml/parser.h>
+#include <libxml/parserInternals.h> /* only for xmlNewInputFromFile() */
+#include <libxml/tree.h>
+#include <libxml/debugXML.h>
+#include <libxml/xmlmemory.h>
+
+#ifndef TRANSPORT_SESSION_H
+#define TRANSPORT_SESSION_H
+
+#define CONNECTING_1 1 /* just starting the connection to Jabber */
+#define CONNECTING_2 2 /* First <stream> packet sent and <stream> packet received from server */
+
+/* Note. these are growing buffers, so all that's necessary is a sane starting point */
+#define JABBER_BODY_BUFSIZE            4096
+#define JABBER_SUBJECT_BUFSIZE 64      
+#define JABBER_THREAD_BUFSIZE          64      
+#define JABBER_JID_BUFSIZE                     64      
+#define JABBER_STATUS_BUFSIZE          16 
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+// ---------------------------------------------------------------------------------
+// Takes data from the socket handler and pushes it directly into the push parser
+// ---------------------------------------------------------------------------------
+void grab_incoming( void * session, char* data );
+
+// ---------------------------------------------------------------------------------
+// Callback for handling the startElement event.  Much of the jabber logic occurs
+// in this and the characterHandler callbacks.
+// Here we check for the various top level jabber elements: body, iq, etc.
+// ---------------------------------------------------------------------------------
+void startElementHandler( 
+               void *session, const xmlChar *name, const xmlChar **atts);
+
+// ---------------------------------------------------------------------------------
+// Callback for handling the endElement event.  Updates the Jabber state machine
+// to let us know the element is over.
+// ---------------------------------------------------------------------------------
+void endElementHandler( void *session, const xmlChar *name);
+
+// ---------------------------------------------------------------------------------
+// This is where we extract XML text content.  In particular, this is useful for
+// extracting Jabber message bodies.
+// ---------------------------------------------------------------------------------
+void characterHandler(
+               void *session, const xmlChar *ch, int len);
+
+void  parseWarningHandler( void *session, const char* msg, ... );
+void  parseErrorHandler( void *session, const char* msg, ... );
+
+// ---------------------------------------------------------------------------------
+// Tells the SAX parser which functions will be used as event callbacks
+// ---------------------------------------------------------------------------------
+static xmlSAXHandler SAXHandlerStruct = {
+   NULL,                                                       /* internalSubset */
+   NULL,                                                       /* isStandalone */
+   NULL,                                                       /* hasInternalSubset */
+   NULL,                                                       /* hasExternalSubset */
+   NULL,                                                       /* resolveEntity */
+   NULL,                                                       /* getEntity */
+   NULL,                                                       /* entityDecl */
+   NULL,                                                       /* notationDecl */
+   NULL,                                                       /* attributeDecl */
+   NULL,                                                       /* elementDecl */
+   NULL,                                                       /* unparsedEntityDecl */
+   NULL,                                                       /* setDocumentLocator */
+   NULL,                                                       /* startDocument */
+   NULL,                                                       /* endDocument */
+       startElementHandler,            /* startElement */
+       endElementHandler,              /* endElement */
+   NULL,                                                       /* reference */
+       characterHandler,                       /* characters */
+   NULL,                                                       /* ignorableWhitespace */
+   NULL,                                                       /* processingInstruction */
+   NULL,                                                       /* comment */
+   parseWarningHandler,                /* xmlParserWarning */
+   parseErrorHandler,          /* xmlParserError */
+   NULL,                                                       /* xmlParserFatalError : unused */
+   NULL,                                                       /* getParameterEntity */
+   NULL,                                                       /* cdataBlock; */
+   NULL,                                                       /* externalSubset; */
+   1,
+   NULL,
+   NULL,                                                       /* startElementNs */
+   NULL,                                                       /* endElementNs */
+       NULL                                                    /* xmlStructuredErrorFunc */
+};
+
+// ---------------------------------------------------------------------------------
+// Our SAX handler pointer.
+// ---------------------------------------------------------------------------------
+static const xmlSAXHandlerPtr SAXHandler = &SAXHandlerStruct;
+
+// ---------------------------------------------------------------------------------
+// Jabber state machine.  This is how we know where we are in the Jabber
+// conversation.
+// ---------------------------------------------------------------------------------
+struct jabber_state_machine_struct {
+       int connected;
+       int connecting;
+       int in_message;
+       int in_message_body;
+       int in_thread;
+       int in_subject;
+       int in_error;
+       int in_message_error;
+       int in_iq;
+       int in_presence;
+       int in_status;
+};
+typedef struct jabber_state_machine_struct jabber_machine;
+
+// ---------------------------------------------------------------------------------
+// Transport session.  This maintains all the various parts of a session
+// ---------------------------------------------------------------------------------
+struct transport_session_struct {
+
+       /* our socket connection */
+       transport_socket* sock_obj;
+       /* our Jabber state machine */
+       jabber_machine* state_machine;
+       /* our SAX push parser context */
+       xmlParserCtxtPtr parser_ctxt;
+
+       /* our text buffers for holding text data */
+       growing_buffer* body_buffer;
+       growing_buffer* subject_buffer;
+       growing_buffer* thread_buffer;
+       growing_buffer* from_buffer;
+       growing_buffer* recipient_buffer;
+       growing_buffer* status_buffer;
+       growing_buffer* message_error_type;
+       int message_error_code;
+
+       /* for OILS extenstions */
+       growing_buffer* router_to_buffer;
+       growing_buffer* router_from_buffer;
+       growing_buffer* router_class_buffer;
+       growing_buffer* router_command_buffer;
+       int router_broadcast;
+
+       /* this can be anything.  It will show up in the 
+               callbacks for your convenience. We will *not*
+          deallocate whatever this is when we're done.  That's
+               your job.
+        */
+       void* user_data;
+
+       /* the Jabber message callback */
+       void (*message_callback) ( void* user_data, transport_message* msg );
+       //void (iq_callback) ( void* user_data, transport_iq_message* iq );
+};
+typedef struct transport_session_struct transport_session;
+
+
+// ------------------------------------------------------------------
+// Allocates and initializes the necessary transport session
+// data structures.
+// ------------------------------------------------------------------
+transport_session* init_transport(  char* server, int port, void* user_data );
+
+// ------------------------------------------------------------------
+// Returns the value of the given XML attribute
+// The xmlChar** construct is commonly returned from SAX event
+// handlers.  Pass that in with the name of the attribute you want
+// to retrieve.
+// ------------------------------------------------------------------
+char* get_xml_attr( const xmlChar** atts, char* attr_name );
+
+// ------------------------------------------------------------------
+// Waits  at most 'timeout' seconds  for data to arrive from the 
+// TCP handler. A timeout of -1 means to wait indefinitely.
+// ------------------------------------------------------------------
+int session_wait( transport_session* session, int timeout );
+
+// ---------------------------------------------------------------------------------
+// Sends the given Jabber message
+// ---------------------------------------------------------------------------------
+int session_send_msg( transport_session* session, transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Returns 1 if this session is connected to the jabber server. 0 otherwise
+// ---------------------------------------------------------------------------------
+int session_connected( transport_session* );
+
+// ------------------------------------------------------------------
+// Deallocates session memory
+// ------------------------------------------------------------------
+int session_free( transport_session* session );
+
+// ------------------------------------------------------------------
+// Connects to the Jabber server.  Waits at most connect_timeout
+// seconds before failing
+// ------------------------------------------------------------------
+int session_connect( transport_session* session, 
+               const char* username, const char* password, const char* resource, int connect_timeout );
+
+int session_disconnect( transport_session* session );
+
+int reset_session_buffers( transport_session* session );
+
+#endif
diff --git a/include/opensrf/transport_socket.h b/include/opensrf/transport_socket.h
new file mode 100644 (file)
index 0000000..65a83cc
--- /dev/null
@@ -0,0 +1,68 @@
+#include "generic_utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <errno.h>
+
+//---------------------------------------------------------------
+// WIN32
+//---------------------------------------------------------------
+#ifdef WIN32
+#include <Windows.h>
+#include <Winsock.h>
+#else
+
+//---------------------------------------------------------------
+// Unix headers
+//---------------------------------------------------------------
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#endif
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_SOCKET_H
+#define TRANSPORT_SOCKET_H
+
+/* how many characters we read from the socket at a time */
+#ifdef _ROUTER
+#define BUFSIZE 412
+#else
+#define BUFSIZE 4096
+#endif
+
+/* we maintain the socket information */
+struct transport_socket_struct {
+       int sock_fd;
+       int connected;
+       char* server;
+       int port;
+       void * user_data;
+       /* user_data may be anything.  it's whatever you wish
+               to see showing up in the callback in addition to
+               the acutal character data*/
+       void (*data_received_callback) (void * user_data, char*);
+};
+typedef struct transport_socket_struct transport_socket;
+
+int tcp_connect( transport_socket* obj );
+int tcp_send( transport_socket* obj, const char* data );
+int tcp_disconnect( transport_socket* obj );
+int tcp_wait( transport_socket* obj, int timeout );
+int tcp_connected( transport_socket* obj );
+
+/* utility methods */
+int set_fl( int fd, int flags );
+int clr_fl( int fd, int flags );
+
+
+#endif
diff --git a/src/javascript/JSON.js b/src/javascript/JSON.js
new file mode 100644 (file)
index 0000000..03ec070
--- /dev/null
@@ -0,0 +1,89 @@
+// in case we run on an implimentation that doesn't have "undefined";
+var undefined;
+
+function Cast (obj, class_constructor) {
+       try {
+               if (eval(class_constructor + '.prototype["class_name"]')) {
+                       var template = eval("new " + class_constructor + "()");
+                       // copy the methods over to 'obj'
+                       for (var m in obj) {
+                               if (typeof(obj[m]) != 'undefined') {
+                                       template[m] = obj[m];
+                               }
+                       }
+                       obj = template;
+               }
+       } catch( E ) {
+               obj['class_name'] = function () { return class_constructor };
+               //dump( super_dump(E) + "\n");
+       } finally {
+               return obj;
+       }
+}
+
+function JSON2js (json) {
+       json = json.replace( /\/\*--\s*S\w*?\s*?\s+\w+\s*--\*\//g, 'Cast(');
+       json = json.replace( /\/\*--\s*E\w*?\s*?\s+(\w+)\s*--\*\//g, ', "$1")');
+       var obj;
+       if (json != '') {
+               eval( 'obj = ' + json );
+       }
+       obj.toString = function () { return js2JSON(this) };
+       return obj;
+}
+
+function js2JSON(arg) {
+var i, o, u, v;
+
+       switch (typeof arg) {
+               case 'object':
+                       if (arg) {
+                               if (arg.constructor == Array) {
+                                       o = '';
+                                       for (i = 0; i < arg.length; ++i) {
+                                               v = js2JSON(arg[i]);
+                                               if (o) {
+                                                       o += ',';
+                                               }
+                                               if (v !== u) {
+                                                       o += v;
+                                               } else {
+                                                       o += 'null,';
+                                               }
+                                       }
+                                       return '[' + o + ']';
+                               } else if (typeof arg.toString != 'undefined') {
+                                       o = '';
+                                       for (i in arg) {
+                                               v = js2JSON(arg[i]);
+                                               if (v !== u) {
+                                                       if (o) {
+                                                               o += ',';
+                                                       }
+                                                       o += js2JSON(i) + ':' + v;
+                                               }
+                                       }
+                                       var obj_start = '{';
+                                       var obj_end = '}';
+                                       try {
+                                               if ( arg.class_name() ) {
+                                                       obj_start = '/*--S ' + arg.class_name() + '--*/{';
+                                                       obj_end   = '}/*--E ' + arg.class_name() + '--*/';
+                                               }
+                                       } catch( E ) {}
+                                       o = obj_start + o + obj_end;
+                                       return o;
+                               } else {
+                                       return;
+                               }
+                       }
+                       return 'null';
+               case 'unknown':
+               case 'undefined':
+               case 'function':
+                       return u;
+               case 'string':
+               default:
+                       return '"' + String(arg).replace(/(["\\])/g, '\\$1') + '"';
+       }
+}
diff --git a/src/javascript/md5.js b/src/javascript/md5.js
new file mode 100644 (file)
index 0000000..46d2aab
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}
diff --git a/src/javascript/opensrf_app_session.js b/src/javascript/opensrf_app_session.js
new file mode 100644 (file)
index 0000000..892a958
--- /dev/null
@@ -0,0 +1,509 @@
+/** @file oils_app_session.js
+  * @brief AppRequest and AppSession.
+  * The AppSession class does most of the communication work and the AppRequest 
+  * contains the top level client API.
+  */
+
+/** The default wait time when a client calls recv. It
+  * may be overrided by passing in a value to the recv method
+  */
+AppRequest.DEF_RECV_TIMEOUT = 10000;
+
+/** Provide a pre-built AppSession object and the payload of the REQUEST
+  * message you wish to send
+  */
+function AppRequest( session, payload ) {
+
+       /** Our controling session */
+       this.session = session;
+
+       /** This requests message thread trace */
+       this.thread_trace = null;
+
+       /** This request REQUEST payload */
+       this.payload = payload;
+
+       /** True if this request has completed is request cycle */
+       this.is_complete = false;
+
+       /** Stores responses received from requests */
+       this.recv_queue = new Array();
+}
+
+/** returns true if this AppRequest has completed its request cycle.  */
+AppRequest.prototype.complete = function() {
+       if( this.is_complete ) { return true; }
+       this.session.queue_wait(0);
+       return this.is_complete;
+}
+
+/** When called on an AppRequest object, its status will be
+  * set to complete regardless of its previous status
+  */
+AppRequest.prototype.set_complete = function() {
+       this.is_complete = true;
+}
+
+/** Returns the current thread trace */
+AppRequest.prototype.get_thread_trace = function() {
+       return this.thread_trace;
+}
+
+/** Pushes some payload onto the recv queue */
+AppRequest.prototype.push_queue = function( payload ) {
+       this.recv_queue.push( payload );
+}
+
+/** Returns the current payload of this request */
+AppRequest.prototype.get_payload = function() {
+       return this.payload;
+}
+
+/** Removes this request from the our AppSession's request bucket 
+  * Call this method when you are finished with a particular request 
+  */
+AppRequest.prototype.finish = function() {
+       this.session.remove_request( this );
+}
+
+
+/** Retrieves the current thread trace from the associated AppSession object,
+  * increments that session's thread trace, sets this AppRequest's thread trace
+  * to the new value.  The request is then sent.
+  */
+AppRequest.prototype.make_request = function() {
+       var tt = this.session.get_thread_trace();
+       this.session.set_thread_trace( ++tt );
+       this.thread_trace = tt;
+       this.session.add_request( this );
+       this.session.send( oilsMessage.REQUEST, tt, this.payload );
+}
+
+/** Checks the receive queue for message payloads.  If any are found, the first 
+  * is returned.  Otherwise, this method will wait at most timeout seconds for
+  * a message to appear in the receive queue.  Once it arrives it is returned.
+  * If no messages arrive in the timeout provided, null is returned.
+
+  * NOTE: timeout is in * milliseconds *
+  */
+
+AppRequest.prototype.recv = function( /*int*/ timeout ) {
+
+
+       if( this.recv_queue.length > 0 ) {
+               return this.recv_queue.shift();
+       }
+
+       //if( this.complete ) { return null; }
+
+       if( timeout == null ) {
+               timeout = AppRequest.DEF_RECV_TIMEOUT;
+       }
+
+       while( timeout > 0 ) {
+
+               var start = new Date().getTime();
+               this.session.queue_wait( timeout );
+
+               if( this.recv_queue.length > 0 ) {
+                       return this.recv_queue.shift();
+               }
+
+               // shortcircuit the call if we're already complete
+               if( this.complete() ) { return null; }
+
+               new Logger().debug( "AppRequest looping in recv " 
+                               + this.get_thread_trace() + " with timeout " + timeout, Logger.DEBUG );
+
+               var end = new Date().getTime();
+               timeout -= ( end - start );
+       }
+
+       return null;
+}
+
+/** Resend this AppRequest's REQUEST message, useful for recovering
+ * from disconnects, etc.
+ */
+AppRequest.prototype.resend = function() {
+
+       new Logger().debug( "Resending msg with thread trace: " 
+                       + this.get_thread_trace(), Logger.DEBUG );
+       this.session.send( oilsMessage.REQUEST, this.get_thread_trace(), this.payload );
+}
+
+
+
+       
+// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
+// AppSession code
+// -----------------------------------------------------------------------------
+
+/** Global cach of AppSession objects */
+AppSession.session_cache = new Array();
+
+// -----------------------------------------------------------------------------
+// Session states
+// -----------------------------------------------------------------------------
+/** True when we're attempting to connect to a remte service */
+AppSession.CONNECTING  = 0; 
+/** True when we have successfully connected to a remote service */
+AppSession.CONNECTED           = 1;
+/** True when we have been disconnected from a remote service */
+AppSession.DISCONNECTED = 2;
+/** The current default method protocol */
+AppSession.PROTOCOL            = 1;
+
+/** Our connection with the outside world */
+AppSession.transport_handle = null;
+
+
+/** Returns the session with the given session id */
+AppSession.find_session = function(session_id) {
+       return AppSession.session_cache[session_id];
+}
+
+/** Adds the given session to the global cache */
+AppSession.push_session = function(session) {
+       AppSession.session_cache[session.get_session_id()] = session;
+}
+
+/** Deletes the session with the given session id from the global cache */
+AppSession.delete_session = function(session_id) {
+       AppSession.session_cache[session_id] = null;
+}
+
+/** Builds a new session.
+  * @param remote_service The remote service we want to make REQUEST's of
+  */
+function AppSession( username, password, remote_service ) {
+
+
+       /** Our logger object */
+       this.logger = new Logger();
+
+       random_num = Math.random() + "";
+       random_num.replace( '.', '' );
+
+       /** Our session id */
+       this.session_id = new Date().getTime() + "" + random_num;
+
+       this.auth = new userAuth( username, password );
+
+       /** Our AppRequest queue */
+       this.request_queue = new Array();
+
+       /** Our connectivity state */
+       this.state = AppSession.DISCONNECTED;
+
+       var config = new Config();
+       
+       /** The remote ID of the service we are communicating with as retrieved 
+         * from the config file
+        */
+       this.orig_remote_id = config.get_value( "remote_service/" + remote_service );
+       if( ! this.orig_remote_id ) { 
+               throw new oils_ex_config( "No remote service id for: " + remote_service );
+       }
+
+       /** The current remote ID of the service we are communicating with */
+       this.remote_id = this.orig_remote_id;
+
+       /** Our current request threadtrace, which is incremented with each 
+         * newly sent AppRequest */
+       this.thread_trace = 0;
+
+       /** Our queue of AppRequest objects */
+       this.req_queue = new Array();
+
+       /** Our queue of AppRequests that are awaiting a resend of their payload */
+       this.resend_queue = new Array();
+
+       // Build the transport_handle if if doesn't already exist
+       if( AppSession.transport_handle == null ) {
+               this.build_transport();
+       }
+
+       AppSession.push_session( this );
+
+}
+
+/** The transport implementation is loaded from the config file and magically
+  * eval'ed into an object.  All connection settings come from the client 
+  * config.
+  * * This should only be called by the AppSession constructor and only when
+  * the transport_handle is null.
+  */
+AppSession.prototype.build_transport = function() {
+
+       var config = new Config();
+       var transport_impl = config.get_value( "transport/transport_impl" );
+       if( ! transport_impl ) {
+               throw new oils_ex_config( "No transport implementation defined in config file" );
+       }
+
+       var username    = config.get_value( "transport/username" );
+       var password    = config.get_value( "transport/password" );
+       var this_host   = config.get_value( "system/hostname" );
+       var resource    = this_host + "_" + new Date().getTime();
+       var server              = config.get_value( "transport/primary" );
+       var port                        = config.get_value( "transport/port" );
+       var tim                 = config.get_value( "transport/connect_timeout" );
+       var timeout             = tim * 1000;
+
+       var eval_string = 
+               "AppSession.transport_handle = new " + transport_impl + "( username, password, resource );";
+
+       eval( eval_string );
+       
+       if( AppSession.transport_handle == null ) {
+               throw new oils_ex_config( "Transport implementation defined in config file is not valid" );
+       }
+
+       if( !AppSession.transport_handle.connect( server, port, timeout ) ) {
+               throw new oils_ex_transport( "Connection attempt to remote service timed out" );
+       }
+
+       if( ! AppSession.transport_handle.connected() ) {
+               throw new oils_ex_transport( "AppSession is unable to connect to the remote service" );
+       }
+}
+
+
+/** Adds the given AppRequest object to this AppSession's request queue */
+AppSession.prototype.add_request = function( req_obj ) {
+       new Logger().debug( "Adding AppRequest: " + req_obj.get_thread_trace(), Logger.DEBUG );
+       this.req_queue[req_obj.get_thread_trace()] = req_obj;
+}
+
+/** Removes the AppRequest object from this AppSession's request queue */
+AppSession.prototype.remove_request = function( req_obj ) {
+       this.req_queue[req_obj.get_thread_trace()] = null;
+}
+
+/** Returns the AppRequest with the given thread_trace */
+AppSession.prototype.get_request = function( thread_trace ) {
+       return this.req_queue[thread_trace];
+}
+
+
+/** Returns this AppSession's session id */
+AppSession.prototype.get_session_id = function() { 
+       return this.session_id; 
+}
+
+/** Resets the remote_id for the transport to the original remote_id retrieved
+  * from the config file
+  */
+AppSession.prototype.reset_remote = function() { 
+       this.remote_id = this.orig_remote_id; 
+}
+
+/** Returns the current message thread trace */
+AppSession.prototype.get_thread_trace = function() {
+       return this.thread_trace;
+}
+
+/** Sets the current thread trace */
+AppSession.prototype.set_thread_trace = function( tt ) {
+       this.thread_trace = tt;
+}
+
+/** Returns the state that this session is in (i.e. CONNECTED) */
+AppSession.prototype.get_state = function() {
+       return this.state;
+}
+
+/** Sets the session state.  The state should be one of the predefined 
+  * session AppSession session states.
+  */
+AppSession.prototype.set_state = function(state) {
+       this.state = state;
+}
+
+/** Returns the current remote_id for this session */
+AppSession.prototype.get_remote_id = function() {
+       return this.remote_id;
+}
+
+/** Sets the current remote_id for this session */
+AppSession.prototype.set_remote_id = function( id ) {
+       this.remote_id = id;
+}
+
+/** Pushes an AppRequest object onto the resend queue */
+AppSession.prototype.push_resend = function( app_request ) {
+       this.resend_queue.push( app_request );
+}
+
+/** Destroys the current session.  This will disconnect from the
+  * remote service, remove all AppRequests from the request
+  * queue, and finally remove this session from the global cache
+  */
+AppSession.prototype.destroy = function() {
+
+       new Logger().debug( "Destroying AppSession: " + this.get_session_id(), Logger.DEBUG );
+
+       // disconnect from the remote service
+       if( this.get_state() != AppSession.DISCONNECTED ) {
+               this.disconnect();
+       }
+       // remove us from the global cache
+       AppSession.delete_session( this.get_session_id() );
+
+       // Remove app request references
+       for( var index in this.req_queue ) {
+               this.req_queue[index] = null;
+       }
+}
+
+/** This forces a resend of all AppRequests that are currently 
+  * in the resend queue
+  */
+AppSession.prototype.flush_resend = function() {
+
+       if( this.resend_queue.length > 0 ) {
+               new Logger().debug( "Resending " 
+                       + this.resend_queue.length + " messages", Logger.INFO );
+       }
+
+       var req = this.resend_queue.shift();
+
+       while( req != null ) {
+               req.resend();
+               req = this.resend_queue.shift();
+       }
+}
+
+/** This method tracks down the AppRequest with the given thread_trace and 
+  * pushes the payload into that AppRequest's recieve queue.
+  */
+AppSession.prototype.push_queue = function( dom_payload, thread_trace ) {
+
+       var req = this.get_request( thread_trace );
+       if( ! req ) {
+               new Logger().debug( "No AppRequest exists for TT: " + thread_trace, Logger.ERROR );
+               return;
+       }
+       req.push_queue( dom_payload );
+}
+
+
+/** Connect to the remote service.  The connect timeout is read from the config.
+  * This method returns null if the connection fails.  It returns a reference
+  * to this AppSession object otherwise.
+  */
+AppSession.prototype.connect = function() {
+
+       if( this.get_state() == AppSession.CONNECTED ) { 
+               return this;
+       }
+
+       var config = new Config();
+       var rem = config.get_value( "transport/connect_timeout" );
+       if( ! rem ) {
+               throw new oils_ex_config( "Unable to retreive timeout value from config" );
+       }
+
+       var remaining = rem * 1000; // milliseconds
+
+       this.reset_remote();
+       this.set_state( AppSession.CONNECTING );
+       this.send( oilsMessage.CONNECT, 0, "" );
+
+       new Logger().debug( "CONNECTING with timeout: " + remaining, Logger.DEBUG );
+
+       while( this.get_state() != AppSession.CONNECTED && remaining > 0 ) {
+
+               var starttime = new Date().getTime();
+               this.queue_wait( remaining );
+               var endtime = new Date().getTime();
+               remaining -= (endtime - starttime);
+       }
+
+       if( ! this.get_state() == AppSession.CONNECTED ) {
+               return null;
+       }
+
+       return this;
+}
+
+/** Disconnects from the remote service */
+AppSession.prototype.disconnect = function() {
+
+       if( this.get_state() == AppSession.DISCONNECTED ) {
+               return;
+       }
+
+       this.send( oilsMessage.DISCONNECT, this.get_thread_trace(), "" );
+       this.set_state( AppSession.DISCONNECTED );
+       this.reset_remote();
+}
+
+
+/** Builds a new message with the given type and thread_trace.  If the message
+  * is a REQUEST, then the payload is added as well.
+  * This method will verify that the session is in the CONNECTED state before
+  * sending any REQUEST's by attempting to do a connect.
+  *
+  * Note: msg_type and thread_trace must be provided.
+  */
+AppSession.prototype.send = function( msg_type, thread_trace, payload ) {
+
+       if( msg_type == null || thread_trace == null ) {
+               throw new oils_ex_args( "Not enough arguments provided to AppSession.send method" );
+       }
+
+       // go ahead and make sure there's nothing new to process
+       this.queue_wait(0);
+
+       var msg;
+       if( msg_type == oilsMessage.CONNECT ) {
+               msg = new oilsMessage( msg_type, AppSession.PROTOCOL, this.auth );
+       } else {
+               msg = new oilsMessage( msg_type, AppSession.PROTOCOL );
+       }
+
+       msg.setThreadTrace( thread_trace );
+
+       if( msg_type == oilsMessage.REQUEST ) {
+               if( ! payload ) {
+                       throw new oils_ex_args( "No payload provided for REQUEST message in AppSession.send" );
+               }
+               msg.add( payload );
+       }
+
+
+       // Make sure we're connected
+       if( (msg_type != oilsMessage.DISCONNECT) && (msg_type != oilsMessage.CONNECT) &&
+                       (this.get_state() != AppSession.CONNECTED) ) {
+               if( ! this.connect() ) {
+                       throw new oils_ex_session( this.get_session_id() + " | Unable to connect to remote service after redirect" );
+               }
+       }
+
+       this.logger.debug( "AppSession sending tt: " 
+                       + thread_trace + " to " + this.get_remote_id() 
+                       + " " +  msg_type , Logger.INFO );
+
+       AppSession.transport_handle.send( this.get_remote_id(), this.get_session_id(), msg.toString(true) );
+
+}
+
+
+/** Waits up to 'timeout' milliseconds for some data to arrive.
+  * Any data that arrives will be process according to its
+  * payload and message type.  This method will return after
+  * any data has arrived.
+  * @param timeout How many milliseconds to wait or data to arrive
+  */
+AppSession.prototype.queue_wait = function( timeout ) {
+       this.flush_resend(); // necessary if running parallel sessions 
+       new Logger().debug( "In queue_wait " + timeout, Logger.DEBUG );
+       var tran_msg = AppSession.transport_handle.process_msg( timeout );
+       this.flush_resend();
+}
+
+
+
diff --git a/src/javascript/opensrf_config.js b/src/javascript/opensrf_config.js
new file mode 100644 (file)
index 0000000..1beef71
--- /dev/null
@@ -0,0 +1,281 @@
+/** @file oils_config.js
+  * Config code and Logger code
+  * The config module is simple.  It parses an xml config file and 
+  * the values from the file are accessed via XPATH queries.
+  */
+
+/** Searches up from Mozilla's chrome directory for the client 
+  * config file and returns the file object
+  */
+function get_config_file() {
+
+       var dirService = Components.classes["@mozilla.org/file/directory_service;1"].
+               getService( Components.interfaces.nsIProperties );
+
+       chromeDir = dirService.get( "AChrom",  Components.interfaces.nsIFile );
+       chromeDir.append("evergreen");
+       chromeDir.append("content");
+       chromeDir.append("conf");
+       chromeDir.append("client_config.xml");
+       return chromeDir;
+
+}
+
+
+/** Constructor. You may provide an optional path to the config file. **/
+function Config( config_file ) {
+
+       if( Config.config != null ) { return Config.config; }
+
+       config_file = get_config_file();
+
+       // ------------------------------------------------------------------
+       // Grab the data from the config file
+       // ------------------------------------------------------------------
+       var data = "";
+       var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileInputStream);
+
+       var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                       .createInstance(Components.interfaces.nsIScriptableInputStream);
+
+       fstream.init(config_file, 1, 0, false);
+       sstream.init(fstream);
+       data += sstream.read(-1);
+
+       sstream.close();
+       fstream.close();
+       
+       
+
+       var DOMParser = new Components.Constructor(
+               "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser" );
+
+       this.config_doc = new DOMParser().parseFromString( data, "text/xml" );
+
+       Config.config = this;
+
+}
+
+/** Returns the value stored in the config file found with the
+  * given xpath expression
+  * E.g.  config.get_value( "/oils_config/dirs/log_dir" );
+  * Note, the 'oils_config' node is the base of the xpath expression,
+  * so something like this will also work:
+  * config.get_value( "dirs/log_dir" );
+  */
+Config.prototype.get_value = function( xpath_query ) {
+
+       var evaluator = Components.classes["@mozilla.org/dom/xpath-evaluator;1"].
+               createInstance( Components.interfaces.nsIDOMXPathEvaluator );
+
+       var xpath_obj = evaluator.evaluate( xpath_query, this.config_doc.documentElement, null, 0, null );
+       if( ! xpath_obj ) { return null; }
+
+       var node = xpath_obj.iterateNext();
+       if( node == null ) {
+               throw new oils_ex_config( "No config option matching " + xpath_query );
+       }
+
+       return node.firstChild.nodeValue;
+}      
+
+
+
+
+
+
+// ------------------------------------------------------------------
+// Logger code.
+// ------------------------------------------------------------------
+/** The global logging object */
+Logger.logger          = null;
+/** No logging level */
+Logger.NONE                    = 0;
+/** Error log level */
+Logger.ERROR           = 1;
+/** Info log level */
+Logger.INFO                    = 2;
+/* Debug log level */
+Logger.DEBUG           = 3;
+
+/** There exists a single logger object that all components share.
+  * Calling var logger = new Logger() will return the same one
+  * with each call.  This is so we only need one file handle for
+  * each type of log file.
+  */
+function Logger() {
+
+       if( Logger.logger != null ) { return Logger.logger }
+
+       var config = new Config();
+       this.log_level = config.get_value( "system/log_level" );
+
+       this.stdout_log = config.get_value( "system/stdout_log" );
+
+       if( ! this.stdout_log || this.stdout_log < 0 || this.stdout_log > 2 ) {
+               throw new oils_ex_config( "stdout_log setting is invalid: " + this.stdout_log + 
+                               ". Should be 0, 1, or 2." );
+       }
+
+       // ------------------------------------------------------------------
+       // Load up all of the log files
+       // ------------------------------------------------------------------
+       var transport_file = config.get_value( "logs/transport" );
+       if( transport_file == null ) {
+               throw new oils_ex_config( "Unable to load transport log file: 'logs/transport'" );
+       }
+
+       var debug_file = config.get_value( "logs/debug" );
+       if( debug_file == null ) {
+               throw new oils_ex_config( "Unable to load debug log file: 'logs/debug'" );
+       }
+       
+       var error_file = config.get_value( "logs/error" );
+       if( error_file == null ) {
+               throw new oils_ex_config( "Unable to load debug log file: 'logs/error'" );
+       }
+
+
+       // ------------------------------------------------------------------
+       // Build the file objects
+       // ------------------------------------------------------------------
+       var transport_file_obj = Logger.get_log_file( transport_file );
+
+       var debug_file_obj = Logger.get_log_file( debug_file );
+
+       var error_file_obj = Logger.get_log_file( error_file );
+
+
+       // ------------------------------------------------------------------
+       // Build all of the file stream objects
+       // ------------------------------------------------------------------
+       this.transport_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileOutputStream);
+
+       this.debug_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileOutputStream);
+
+       this.error_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileOutputStream);
+
+       // ------------------------------------------------------------------
+       // Init all of the streams
+       // use 0x02 | 0x10 to open file for appending.
+       // ------------------------------------------------------------------
+       this.transport_stream.init(transport_file_obj,  0x02 | 0x10 | 0x08, 0664, 0 ); 
+       this.debug_stream.init( debug_file_obj,                 0x02 | 0x10 | 0x08, 0664, 0 ); 
+       this.error_stream.init( error_file_obj,                 0x02 | 0x10 | 0x08, 0664, 0 ); 
+
+       Logger.logger = this;
+
+}
+
+/** Internal.  Returns a XPCOM nsIFile object for the log file we're interested in */
+Logger.get_log_file = function( log_name ) {
+
+       var dirService = Components.classes["@mozilla.org/file/directory_service;1"].
+               getService( Components.interfaces.nsIProperties );
+
+       logFile = dirService.get( "AChrom",  Components.interfaces.nsIFile );
+       logFile.append("evergreen");
+       logFile.append("content");
+       logFile.append("log");
+       logFile.append( log_name );
+
+       if( ! logFile.exists() ) {
+               logFile.create( 0, 0640 );
+       }
+
+       return logFile;
+}
+
+
+
+/** Internal. Builds a log message complete with data, etc. */
+Logger.prototype.build_string = function( message, level ) {
+
+       if( ! (message && level) ) { return null; }
+
+       var lev = "INFO";
+       if( level == Logger.ERROR ) { lev = "ERROR"; }
+       if( level == Logger.DEBUG ) { lev = "DEBUG"; }
+
+       var date                = new Date();
+       var year                = date.getYear();
+       year += 1900;
+
+       var month       = ""+date.getMonth();
+       if(month.length==1) {month="0"+month;}
+       var day         = ""+date.getDate();
+       if(day.length==1) {day="0"+day;}
+       var hour                = ""+date.getHours();
+       if(hour.length== 1){hour="0"+hour;}
+       var min         = ""+date.getMinutes();
+       if(min.length==1){min="0"+min;}
+       var sec         = ""+date.getSeconds();
+       if(sec.length==1){sec="0"+sec;}
+       var mil         = ""+date.getMilliseconds();
+       if(mil.length==1){sec="0"+sec;}
+
+       var date_string = year + "-" + month + "-" + day + " " + 
+               hour + ":" + min + ":" + sec + "." + mil;
+
+       var str_array = message.split('\n');
+       var ret_array = new Array();
+       for( var i in str_array ) {
+               ret_str = "[" + date_string +  "] " + lev + " " + str_array[i] + "\n";
+               ret_array.push( ret_str );
+       }
+
+       var line = "-------------------------\n";
+       ret_array.unshift( line );
+
+       return ret_array;
+}
+
+/** Internal. Does the actual writing */
+Logger.prototype._log = function( data, stream, level ) {
+
+       if( ! data ) { return; }
+       if( ! stream ) { 
+               throw oils_ex_logger( "No file stream open for log message: " + data ); 
+       }
+       if( ! level ) { level = Logger.DEBUG; }
+
+       if( level > this.log_level ) { return; }
+       var str_array = this.build_string( data, level );
+       if( ! str_array ) { return; }
+
+       for( var i in str_array ) {
+               if( this.stdout_log > 0 ) { dump( str_array[i] ); }
+               if( this.stdout_log < 2 ) { stream.write( str_array[i], str_array[i].length ); }
+       }
+
+       // write errors to the error log if they were destined for anywhere else
+       if( level == Logger.ERROR && stream != this.error_stream ) {
+               for( var i in str_array ) {
+                       if( this.stdout_log > 0 ) { dump( str_array[i] ); }
+                       if( this.stdout_log < 2 ) { this.error_stream.write( str_array[i], str_array[i].length ); }
+               }
+       }
+}
+       
+
+
+/** Writes the message to the error log */
+Logger.prototype.error = function( message, level ) {
+       this._log( message, this.error_stream, level );
+}
+
+
+/** Writes to the debug log */
+Logger.prototype.debug = function( message, level ) {
+       this._log( message, this.debug_stream, level );
+}
+
+
+/** Writes to the transport log */
+Logger.prototype.transport = function( message, level ) {
+       this._log( message, this.transport_stream, level );
+}
diff --git a/src/javascript/opensrf_dom_element.js b/src/javascript/opensrf_dom_element.js
new file mode 100644 (file)
index 0000000..2e419e3
--- /dev/null
@@ -0,0 +1,263 @@
+/** @file oils_dom_element.js
+  * -----------------------------------------------------------------------------
+  * This file holds all of the core OILS DOM elements.
+  *  -----------------------------------------------------------------------------
+
+  * -----------------------------------------------------------------------------
+  * Make sure you load md5.js into  your script/html/etc. before loading this 
+  * file
+  * -----------------------------------------------------------------------------
+  * 
+  * -----------------------------------------------------------------------------
+  * 1. Use these if you are running via xpcshell, but not if you are running
+  *    directly from mozilla.
+  * 2. BTW. XMLSerializer doesn't like being run outside of mozilla, so
+  *            toString() fails.
+  * 
+  * var DOMParser = new Components.Constructor(
+  *            "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser" );
+  * 
+  *    var XMLSerializer = new Components.Constructor(
+  *            "@mozilla.org/xmlextras/xmlserializer;1", "nsIDOMSerializer" );
+  *  -----------------------------------------------------------------------------
+  * 
+  * 
+  */
+
+/**
+  *  -----------------------------------------------------------------------------
+  *  DOM - Top level generic class
+  *  -----------------------------------------------------------------------------
+  */
+
+function DOM() { 
+       this.doc = null; 
+       this.root = null;
+       this.element = null;
+}
+
+/** Returns a string representation of the XML doc.  If full_bool
+  * is true, it will return the entire doc and not just the relevant
+  * portions (i.e. our object's element).
+  */
+DOM.prototype.toString = function(full_bool) {
+       if( full_bool ) {
+               return new XMLSerializer().serializeToString(this.doc);
+       } else {
+               return new XMLSerializer().serializeToString(this.element);
+       }
+}
+
+/** Initializes a document and sets this.doc, this.root and this.element */
+DOM.prototype.init = function( elementName ) {
+       
+       this.doc = new DOMParser().parseFromString( 
+               "<?xml version='1.0' encoding='UTF-8'?><oils:root xmlns:oils='http://open-ils.org/namespaces/oils_v1'></oils:root>", "text/xml" );
+       this.root = this.doc.documentElement;
+       this.element = this.doc.createElement( "oils:" + elementName );
+       return this.element;
+}
+
+/** Good for creating objects from raw xml.  This builds a new doc and inserts
+  * the node provided.  So you can build a dummy object, then call replaceNode  
+  * with the new xml to build an object from XML on the fly.
+  */
+DOM.prototype._replaceNode = function( name, new_element ) {
+
+       var node = this.doc.importNode( new_element, true );
+
+       if( node.nodeName != "oils:" + name ) {
+               throw new oils_ex_dom( "Invalid Tag to " + name + "::replaceNode()" );
+       }
+
+       this.doc = new DOMParser().parseFromString( 
+               "<?xml version='1.0' encoding='UTF-8'?><oils:root xmlns:oils='http://open-ils.org/namespaces/oils_v1'></oils:root>", "text/xml" );
+       this.root = this.doc.documentElement;
+
+       this.root.appendChild( node );
+       this.element = this.root.firstChild;
+       return this;
+}
+
+
+
+// -----------------------------------------------------------------------------
+// domainObjectAttr
+// -----------------------------------------------------------------------------
+
+domainObjectAttr.prototype     = new DOM();
+domainObjectAttr.prototype.constructor = domainObjectAttr;
+domainObjectAttr.baseClass     = DOM.prototype.constructor;
+
+/** A domainObjectAttr is a single XML node with 'name' and 'value' attributes */
+function domainObjectAttr( name, value ) {
+
+       var node = this.init( "domainObjectAttr" );
+       if( ! name && value ) { return; }
+       node.setAttribute( "name", name );
+       node.setAttribute( "value", value );
+       this.root.appendChild( node );
+}
+
+
+/** Returns the attribute name */
+domainObjectAttr.prototype.getName = function() {
+       return this.element.getAttribute("name"); 
+}
+
+/** Returns the attribut value. */
+domainObjectAttr.prototype. getValue = function() {
+       return this.element.getAttribute("value"); 
+}
+
+/** Sets the attribute value. */
+domainObjectAttr.prototype. setValue = function( value ) {
+       return this.element.setAttribute("value", value ); 
+}
+
+/** Pass in the this.element component of a domainObjectAttr and this will
+  * replace the old element with the new one 
+  */
+domainObjectAttr.prototype.replaceNode = function( domainObjectAttr_element ) {
+       return this._replaceNode( "domainObjectAttr", domainObjectAttr_element );
+}
+
+// -----------------------------------------------------------------------------
+// userAuth
+// -----------------------------------------------------------------------------
+
+
+userAuth.prototype = new DOM();
+userAuth.prototype.constructor = userAuth;
+userAuth.baseClass = DOM.prototype.constructor;
+
+function userAuth( username, secret ) {
+
+       var node = this.init( "userAuth" );
+       if( !( username && secret) ) { return; }
+       node.setAttribute( "username", username );
+
+       //There is no way to specify the hash seed with the 
+       //md5 utility provided
+       var hash = hex_md5( secret );
+       node.setAttribute( "secret", hash );
+       node.setAttribute( "hashseed", "" ); 
+
+       this.root.appendChild( node );
+}
+
+userAuth.prototype.getUsername = function() {
+       return this.element.getAttribute( "username" );
+}
+
+userAuth.prototype.getSecret = function() {
+       return this.element.getAttribute( "secret" );
+}
+
+userAuth.prototype.getHashseed = function() {
+       return this.element.getAttribute( "hashseed" );
+}
+
+userAuth.prototype.replaceNode = function( userAuth_element ) {
+       return this._replaceNode( "userAuth", userAuth_element );
+}
+
+
+// -----------------------------------------------------------------------------
+// domainObject
+// -----------------------------------------------------------------------------
+
+domainObject.prototype = new DOM(); 
+domainObject.baseClass = DOM.prototype.constructor;
+domainObject.prototype.constructor = domainObject;
+
+/** Holds the XML for a DomainObject (see oils_domain_object.js or the DomainObject class) */
+function domainObject( name ) {
+       this._init_domainObject( name ); 
+}
+
+/** Initializes the domainObject XML node */
+domainObject.prototype._init_domainObject = function( name ) {
+
+       var node = this.init( "domainObject" );
+
+       if( ! name ) { return; }
+       node.setAttribute( "name", name );
+       this.root.appendChild( node ); 
+
+}
+
+/** Pass in any DOM Element or DomainObject and this method will as the
+  * new object as the next child of the root (domainObject) node 
+  */
+domainObject.prototype.add = function( new_DOM ) {
+
+       this.element.appendChild( 
+                       new_DOM.element.cloneNode( true ) );
+}
+
+/** Removes the original domainObject XML node and replaces it with the
+  * one provided.  This is useful for building new domainObject's from
+  * raw xml.  Just create a new one, then call replaceNode with the new XML.
+  */
+domainObject.prototype.replaceNode = function( domainObject_element ) {
+       return this._replaceNode( "domainObject", domainObject_element );
+}
+
+
+
+// -----------------------------------------------------------------------------
+// Param 
+// -----------------------------------------------------------------------------
+
+
+param.prototype = new DOM();
+param.baseClass = DOM.prototype.constructor;
+param.prototype.constructor = param;
+
+function param( value ) {
+       var node = this.init( "param" );
+       node.appendChild( this.doc.createTextNode( value ) );
+       this.root.appendChild( node );
+}
+
+param.prototype.getValue = function() {
+       return this.element.textContent;
+}
+
+param.prototype.replaceNode = function( param_element ) {
+       return this._replaceNode( "param", param_element );
+}
+
+
+// -----------------------------------------------------------------------------
+// domainObjectCollection
+// -----------------------------------------------------------------------------
+
+domainObjectCollection.prototype = new DOM();
+domainObjectCollection.prototype.constructor = 
+       domainObjectCollection;
+domainObjectCollection.baseClass = DOM.prototype.constructor;
+
+function domainObjectCollection( name ) {
+       var node = this.init( "domainObjectCollection" );       
+       this.root.appendChild( node );
+       if(name) { this._initCollection( name ); }
+}
+
+domainObjectCollection.prototype._initCollection = function( name ) {
+       if( name ) {
+               this.element.setAttribute( "name", name );
+       }
+}
+
+domainObjectCollection.prototype.add = function( new_domainObject ) {
+       this.element.appendChild( 
+                       new_domainObject.element.cloneNode( true ) );
+}
+
+domainObjectCollection.prototype.replaceNode = function( new_node ) {
+       return this._replaceNode( "domainObjectCollection", new_node );
+}
+
+
diff --git a/src/javascript/opensrf_domain_object.js b/src/javascript/opensrf_domain_object.js
new file mode 100644 (file)
index 0000000..edbd247
--- /dev/null
@@ -0,0 +1,609 @@
+// -----------------------------------------------------------------------------
+// This houses all of the domain object code.
+// -----------------------------------------------------------------------------
+
+
+
+
+// -----------------------------------------------------------------------------
+// DomainObject 
+
+DomainObject.prototype                                 = new domainObject();
+DomainObject.prototype.constructor     = DomainObject;
+DomainObject.prototype.baseClass               = domainObject.prototype.constructor;
+
+/** Top level DomainObject class.  This most provides convience methods
+  * and a shared superclass
+  */
+function DomainObject( name ) {
+       if( name ) { this._init_domainObject( name ); }
+}
+
+/** Returns the actual element of the given domainObjectAttr. */
+DomainObject.prototype._findAttr = function ( name ) {
+       
+       var nodes = this.element.childNodes;
+
+       if( ! nodes || nodes.length < 1 ) {
+               throw new oils_ex_dom( "Invalid xml object in _findAttr: " + this.toString() );
+       }
+
+       var i=0;
+       var node = nodes.item(i);
+
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObjectAttr" &&
+                               node.getAttribute("name") == name ) {
+                       return node;
+               }
+
+               node = nodes.item(++i);
+       }
+
+       return null;
+}
+
+
+
+
+/** Returns the value stored in the given attribute */
+DomainObject.prototype.getAttr = function ( name ) {
+
+       var node = this._findAttr( name );
+       if( node ) { return node.getAttribute( "value" ); }
+       else { 
+               throw new oils_ex_dom( "getAttr(); Getting nonexistent attribute: " + name ); 
+       }
+}
+
+/** Updates the value held by the given attribute */
+DomainObject.prototype.setAttr = function ( name, value ) {
+
+       var node = this._findAttr( name );
+       if( node ) {
+               node.setAttribute( "value", value );
+       } else { 
+               throw new oils_ex_dom( "setAttr(); Setting nonexistent attribute: " + name ); 
+       }
+}
+
+/** This takes a raw DOM Element node and creates the DomainObject that the node
+  * embodies and returns the object.  All new DomainObjects should be added to
+  * this list if they require this type of dynamic functionality.
+  * NOTE Much of this will be deprecated as move to a more JSON-centric wire protocol
+  */
+DomainObject.newFromNode = function( node ) {
+
+       switch( node.getAttribute("name") ) {
+
+               case "oilsMethod":
+                       return new oilsMethod().replaceNode( node );
+               
+               case "oilsMessage":
+                       return new oilsMessage().replaceNode( node );
+
+               case "oilsResponse":
+                       return new oilsResponse().replaceNode( node );
+
+               case "oilsResult":
+                       return new oilsResult().replaceNode( node );
+
+               case "oilsConnectStatus":
+                       return new oilsConnectStatus().replaceNode( node );
+
+               case "oilsException":
+                       return new oilsException().replaceNode( node );
+
+               case "oilsMethodException":
+                       return new oilsMethodException().replaceNode( node );
+
+               case "oilsScalar":
+                       return new oilsScalar().replaceNode( node );
+
+               case "oilsPair":
+                       return new oilsPair().replaceNode( node );
+
+               case "oilsArray":
+                       return new oilsArray().replaceNode( node );
+
+               case "oilsHash":
+                       return new oilsHash().replaceNode( node );
+
+
+       }
+
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsMethod
+
+oilsMethod.prototype = new DomainObject();
+oilsMethod.prototype.constructor = oilsMethod;
+oilsMethod.prototype.baseClass = DomainObject.prototype.constructor;
+
+/**
+  * oilsMethod Constructor
+  * 
+  * @param method_name The name of the method your are sending
+  * to the remote server
+  * @param params_array A Javascript array of the params (any
+  * Javascript data type)
+  */
+
+function oilsMethod( method_name, params_array ) {
+       this._init_domainObject( "oilsMethod" );
+       this.add( new domainObjectAttr( "method", method_name ) );
+
+       if( params_array ) {
+               var params = this.doc.createElement( "oils:params" );
+               this.element.appendChild( params ); 
+               params.appendChild( this.doc.createTextNode( 
+                               js2JSON( params_array ) ) ); 
+       }
+}
+
+
+/** Locates the params node of this method */
+oilsMethod.prototype._getParamsNode = function() {
+
+       var nodes = this.element.childNodes;
+       if( ! nodes || nodes.length < 1 ) { return null; }
+       var x=0;
+       var node = nodes.item(x);
+       while( node != null ) {
+               if( node.nodeName == "oils:params" ) {
+                       return node;
+               }
+               node = nodes.item(++x);
+       }
+
+       return null;
+}
+
+
+/** Returns an array of param objects  */
+oilsMethod.prototype.getParams = function() {
+       var node = this._getParamsNode();
+       if(node != null ) { return JSON2js( node->textContent ); }
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsMessage
+
+// -----------------------------------------------------------------------------
+// oilsMessage message types
+// -----------------------------------------------------------------------------
+/** CONNECT Message type */
+oilsMessage.CONNECT            = 'CONNECT';
+/** DISCONNECT Message type */
+oilsMessage.DISCONNECT = 'DISCONNECT';
+/** STATUS Message type */
+oilsMessage.STATUS             = 'STATUS';
+/** REQUEST Message type */
+oilsMessage.REQUEST            = 'REQUEST';
+/** RESULT Message type */
+oilsMessage.RESULT             = 'RESULT';
+
+
+oilsMessage.prototype = new DomainObject();
+oilsMessage.prototype.constructor = oilsMessage;
+oilsMessage.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Core XML object for message passing */
+function oilsMessage( type, protocol, user_auth ) {
+
+       if( !( type && protocol) ) { 
+               type = oilsMessage.CONNECT; 
+               protocol = "1";
+       }
+
+       if( ! ( type == oilsMessage.CONNECT             ||
+                               type == oilsMessage.DISCONNECT  ||
+                               type == oilsMessage.STATUS                      ||
+                               type == oilsMessage.REQUEST             ||
+                               type == oilsMessage.RESULT      ) ) {
+               throw new oils_ex_message( "Attempt to create oilsMessage with incorrect type: " + type );
+       }
+
+       this._init_domainObject( "oilsMessage" );
+
+       this.add( new domainObjectAttr( "type", type ) );
+       this.add( new domainObjectAttr( "threadTrace", 0 ) );
+       this.add( new domainObjectAttr( "protocol", protocol ) );       
+
+       if( user_auth ) { this.add( user_auth ); }
+
+}
+
+/** Builds a new oilsMessage from raw xml.
+  * Checks are taken to make sure the xml is supposed to be an oilsMessage.  
+  * If not, an oils_ex_dom exception is throw.
+  */
+oilsMessage.newFromXML = function( xml ) {
+
+       try {
+
+               if( ! xml || xml == "" ) { throw 1; }
+
+               var doc = new DOMParser().parseFromString( xml, "text/xml" );
+               if( ! doc ) { throw 1; }
+
+               var root = doc.documentElement;
+               if( ! root ) { throw 1; }
+
+               // -----------------------------------------------------------------------------
+               // There are two options here.  One is that we were provided the full message
+               // xml (i.e. contains the <oils:root> tag.  The other option is that we were
+               // provided just the message xml portion (top level element is the 
+               // <oils:domainObject>
+               // -----------------------------------------------------------------------------
+               
+               var element;
+               if( root.nodeName == "oils:root"  ) { 
+
+                       element = root.firstChild;
+                       if( ! element ) { throw 1; }
+
+               } else { 
+
+                       if( root.nodeName == "oils:domainObject" ) { 
+                               element = root;
+
+                       } else { throw 1; }
+
+               }
+
+
+               if( element.nodeName != "oils:domainObject" ) { throw 1; } 
+               if( element.getAttribute( "name" ) != "oilsMessage" ) { throw 1; }
+
+               return new oilsMessage().replaceNode( element );
+
+       } catch( E ) {
+
+               if( E && E.message ) {
+                       throw new oils_ex_dom( "Bogus XML for creating oilsMessage: " + E.message + "\n" + xml );
+
+               } else {
+                       throw new oils_ex_dom( "Bogus XML for creating oilsMessage:\n" + xml );
+               }
+       }
+
+       return msg;
+}
+
+
+
+/** Adds a copy of the given DomainObject to the message */
+oilsMessage.prototype.addPayload = function( new_DomainObject ) {
+       this.add( new_DomainObject );
+}
+
+
+/** Returns the top level DomainObject contained in this message
+  *
+  * Note:  The object retuturned will be the actual object, not just a 
+  * generic DomainObject - e.g. oilsException.
+  */
+oilsMessage.prototype.getPayload = function() {
+       
+       var nodes = this.element.childNodes;
+       var x=0;
+       var node = nodes.item(0);
+
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObject" ) {
+
+                       new Logger().debug( "Building oilsMessage payload from\n" + 
+                               new XMLSerializer().serializeToString( node ), Logger.DEBUG );
+                       return DomainObject.newFromNode( node );
+               }
+               node = nodes.item(++x);
+       }
+
+       return null;
+}
+
+oilsMessage.prototype.getUserAuth = function() {
+
+       var nodes = this.element.getElementsByTagName( "oils:userAuth" );
+       if( ! nodes ) { return null; }
+
+       var auth_elem = nodes.item(0); // should only be one
+       if ( ! auth_elem ) { return null; }
+
+       return new userAuth().replaceNode( auth_elem );
+
+}
+
+/** Returns the type of the message */
+oilsMessage.prototype.getType = function() {
+       return this.getAttr( "type" );
+}
+
+/** Returns the message protocol */
+oilsMessage.prototype.getProtocol = function() {
+       return this.getAttr( "protocol" );
+}
+
+/** Returns the threadTrace */
+oilsMessage.prototype.getThreadTrace = function() {
+       return this.getAttr( "threadTrace" );
+}
+
+/** Sets the thread trace - MUST be an integer */
+oilsMessage.prototype.setThreadTrace = function( trace ) {
+       this.setAttr( "threadTrace", trace );
+}
+
+/** Increments the thread trace by 1 */
+oilsMessage.prototype.updateThreadTrace = function() {
+       var tt = this.getThreadTrace();
+       return this.setThreadTrace( ++tt );
+}
+
+
+
+
+
+// -----------------------------------------------------------------------------
+// oilsResponse
+
+
+// -----------------------------------------------------------------------------
+// Response codes
+// -----------------------------------------------------------------------------
+
+/**
+  * Below are the various response statuses
+  */
+oilsResponse.STATUS_CONTINUE                                           = 100
+
+oilsResponse.STATUS_OK                                                         = 200
+oilsResponse.STATUS_ACCEPTED                                           = 202
+oilsResponse.STATUS_COMPLETE                                           = 205
+
+oilsResponse.STATUS_REDIRECTED                                 = 307
+
+oilsResponse.STATUS_BADREQUEST                                 = 400
+oilsResponse.STATUS_UNAUTHORIZED                                       = 401
+oilsResponse.STATUS_FORBIDDEN                                          = 403
+oilsResponse.STATUS_NOTFOUND                                           = 404
+oilsResponse.STATUS_NOTALLOWED                                 = 405
+oilsResponse.STATUS_TIMEOUT                                            = 408
+oilsResponse.STATUS_EXPFAILED                                          = 417
+oilsResponse.STATUS_INTERNALSERVERERROR                = 500
+oilsResponse.STATUS_NOTIMPLEMENTED                             = 501
+oilsResponse.STATUS_VERSIONNOTSUPPORTED                = 505
+
+
+oilsResponse.prototype = new DomainObject();
+oilsResponse.prototype.constructor = oilsResponse;
+oilsResponse.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Constructor.  status is the text describing the message status and
+  * statusCode must correspond to one of the oilsResponse status code numbers
+  */
+function oilsResponse( name, status, statusCode ) {
+       if( name && status && statusCode ) {
+               this._initResponse( name, status, statusCode );
+       }
+}
+
+/** Initializes the reponse XML */
+oilsResponse.prototype._initResponse = function( name, status, statusCode ) {
+
+       this._init_domainObject( name );
+       this.add( new domainObjectAttr( "status", status ));
+       this.add( new domainObjectAttr( "statusCode", statusCode ) );
+}
+
+
+
+/** Returns the status of the response */
+oilsResponse.prototype.getStatus = function() {
+       return this.getAttr( "status" );
+}
+
+/** Returns the statusCode of the response */
+oilsResponse.prototype.getStatusCode = function() {
+       return this.getAttr("statusCode");
+}
+
+
+oilsConnectStatus.prototype = new oilsResponse();
+oilsConnectStatus.prototype.constructor = oilsConnectStatus;
+oilsConnectStatus.prototype.baseClass = oilsResponse.prototype.constructor;
+
+/** Constructor.  These give us info on our connection attempts **/
+function oilsConnectStatus( status, statusCode ) {
+       this._initResponse( "oilsConnectStatus", status, statusCode );
+}
+
+
+oilsResult.prototype = new oilsResponse();
+oilsResult.prototype.constructor = oilsResult;
+oilsResult.prototype.baseClass = oilsResponse.prototype.constructor;
+
+
+/** Constructor.  These usually carry REQUEST responses */
+function oilsResult( status, statusCode ) {
+       if( status && statusCode ) {
+               this._initResponse( "oilsResult", status, statusCode );
+       }
+}
+
+/** Returns the top level DomainObject within this result message.
+  * Note:  The object retuturned will be the actual object, not just a 
+  * generic DomainObject - e.g. oilsException.
+  */
+oilsResult.prototype.getContent = function() {
+
+       var nodes = this.element.childNodes;
+       if( ! nodes || nodes.length < 1 ) {
+               throw new oils_ex_dom("Content node for oilsResult is invalid\n" + this.toString() );
+       }
+       var x = 0;
+       var node = nodes.item(x);
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObject"                                        ||
+                               node.nodeName == "oils:domainObjectCollection" ) {
+
+                       return DomainObject.newFromNode( node );
+               }
+               node = nodes.item(++x);
+       }
+
+       throw new oils_ex_dom("Content node for oilsResult is invalid\n" + this.toString() );
+}
+
+
+oilsException.prototype = new oilsResponse();
+oilsException.prototype.constructor = oilsException;
+oilsException.prototype.baseClass = oilsResponse.prototype.constructor;
+
+/** Top level exception */
+function oilsException( status, statusCode ) {
+       if( status && statusCode ) {
+               this._initResponse( "oilsException", status, statusCode );
+       }
+}
+
+
+oilsMethodException.prototype = new oilsException();
+oilsMethodException.prototype.constructor = oilsMethodException;
+oilsMethodException.prototype.baseClass = oilsException.prototype.constructor;
+
+/** Method exception */
+function oilsMethodException( status, statusCode ) {
+       if( status && statusCode ) {
+               this._initResponse( "oilsMethodException", status, statusCode );
+       }
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsScalar
+
+
+oilsScalar.prototype = new DomainObject();
+oilsScalar.prototype.constructor = oilsScalar;
+oilsScalar.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Contains a single JSON item as its text content */
+function oilsScalar( value ) {
+       this._init_domainObject( "oilsScalar" );
+       this.element.appendChild( this.doc.createTextNode( value ) );
+}
+
+/** Returns the contained item as a Javascript object */
+oilsScalar.prototype.getValue = function() {
+       return JSON2js( this.element.textContent );
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsPair
+
+
+oilsPair.prototype = new DomainObject()
+oilsPair.prototype.constructor = oilsPair;
+oilsPair.prototype.baseClass = DomainObject.prototype.constructor;
+
+function oilsPair( key, value ) {
+
+       this._init_domainObject( "oilsPair" );
+       this.element.appendChild( this.doc.createTextNode( value ) );
+       this.element.setAttribute( "key", key );
+}
+
+oilsPair.prototype.getKey = function() {
+       return this.element.getAttribute( "key" );
+}
+
+oilsPair.prototype.getValue = function() {
+       return this.element.textContent;
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsArray - is a domainObjectCollection that stores a list of domainObject's
+// or domainObjectCollections.
+// -----------------------------------------------------------------------------
+
+oilsArray.prototype = new domainObjectCollection()
+oilsArray.prototype.constructor = oilsArray;
+oilsArray.prototype.baseClass = domainObjectCollection.prototype.constructor;
+
+function oilsArray( obj_list ) {
+       this._initCollection( "oilsArray" );
+       if(obj_list) { this.initArray( obj_list ); }
+}
+
+// -----------------------------------------------------------------------------
+// Adds the array of objects to the array, these should be actual objects, not
+// DOM nodes/elements, etc.
+// -----------------------------------------------------------------------------
+oilsArray.prototype.initArray = function( obj_list ) {
+       if( obj_array != null && obj_array.length > 0 ) {
+               for( var i= 0; i!= obj_array.length; i++ ) {
+                       this.add( obj_array[i] );
+               }
+       }
+}
+
+// -----------------------------------------------------------------------------
+// Returns an array of DomainObjects or domainObjectCollections.  The objects
+// returned will already be 'cast' into the correct object. i.e. you will not
+// receieve an array of DomainObjects, but instead an array of oilsScalar's or
+// an array of oilsHashe's, etc.
+// -----------------------------------------------------------------------------
+oilsArray.prototype.getObjects = function() {
+
+       var obj_list = new Array();
+       var nodes = this.element.childNodes;
+       var i=0;
+       var node = nodes.item(i);
+
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObject"                                        ||
+                               node.nodeName == "oils:domainObjectCollection" ) {
+                       obj_list[i++] = DomainObject.newFromNode( node );
+               }
+               node = nodes.item(i);
+       }
+
+       return obj_list;
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsHash - an oilsHash is an oilsArray except it only stores oilsPair's
+// -----------------------------------------------------------------------------
+
+oilsHash.prototype = new oilsArray();
+oilsHash.prototype.constructor = oilsHash;
+oilsHash.prototype.baseClass = oilsArray.prototype.constructor;
+
+function oilsHash( pair_array ) {
+       this._initCollection("oilsHash");
+       if(pair_array) { this.initArray( pair_array ); }
+}
+
+// -----------------------------------------------------------------------------
+// Returns the array of oilsPairs objects that this hash contains
+// -----------------------------------------------------------------------------
+oilsHash.prototype.getPairs = function() {
+       return this.getObjects();
+}
+
+
diff --git a/src/javascript/opensrf_jabber_transport.js b/src/javascript/opensrf_jabber_transport.js
new file mode 100644 (file)
index 0000000..fbd6dac
--- /dev/null
@@ -0,0 +1,418 @@
+// ------------------------------------------------------------------
+//             Houses the jabber transport code
+//
+// 1. jabber_connection - high level jabber component
+// 2. jabber_message - message class
+// 3. jabber_socket - socket handling code (shouldn't have to 
+//             use this class directly)
+//
+// Requires oils_utils.js
+// ------------------------------------------------------------------
+
+
+
+
+
+// ------------------------------------------------------------------
+// JABBER_CONNECTION
+// High level transport code
+
+// ------------------------------------------------------------------
+// Constructor
+// ------------------------------------------------------------------
+jabber_connection.prototype = new transport_connection();
+jabber_connection.prototype.constructor = jabber_connection;
+jabber_connection.baseClass = transport_connection.prototype.constructor;
+
+/** Initializes a jabber_connection object */
+function jabber_connection( username, password, resource ) {
+
+       this.username           = username;
+       this.password           = password;
+       this.resource           = resource;
+       this.socket                     = new jabber_socket();
+
+       this.host                       = "";
+
+}
+
+/** Connects to the Jabber server.  'timeout' is the connect timeout
+  * in milliseconds 
+ */
+jabber_connection.prototype.connect = function( host, port, timeout ) {
+       this.host = host;
+       return this.socket.connect( 
+                       this.username, this.password, this.resource, host, port, timeout );
+}
+
+/** Sends a message to 'recipient' with the provided message 
+  * thread and body 
+  */
+jabber_connection.prototype.send = function( recipient, thread, body ) {
+       var jid = this.username+"@"+this.host+"/"+this.resource;
+       var msg = new jabber_message( jid, recipient, thread, body );
+       return this.socket.tcp_send( msg.to_string() );
+}
+
+/** This method will wait at most 'timeout' milliseconds
+  * for a Jabber message to arrive.  If one arrives
+  * it is returned to the caller, other it returns null
+  */
+jabber_connection.prototype.recv = function( timeout ) {
+       return this.socket.recv( timeout );
+}
+
+/** Disconnects from the jabber server */
+jabber_connection.prototype.disconnect = function() {
+       return this.socket.disconnect();
+}
+
+/** Returns true if we are currently connected to the 
+  * Jabber server
+  */
+jabber_connection.prototype.connected = function() {
+       return this.socket.connected();
+}
+
+
+
+// ------------------------------------------------------------------
+// JABBER_MESSAGE
+// High level message handling code
+       
+
+jabber_message.prototype = new transport_message();
+jabber_message.prototype.constructor = jabber_message;
+jabber_message.prototype.baseClass = transport_message.prototype.constructor;
+
+/** Builds a jabber_message object */
+function jabber_message( sender, recipient, thread, body ) {
+
+       if( sender == null || recipient == null || recipient.length < 1 ) { return; }
+
+       this.doc = new DOMParser().parseFromString("<message></message>", "text/xml");
+       this.root = this.doc.documentElement;
+       this.root.setAttribute( "from", sender );
+       this.root.setAttribute( "to", recipient );
+
+       var body_node = this.doc.createElement("body");
+       body_node.appendChild( this.doc.createTextNode( body ) );
+
+       var thread_node = this.doc.createElement("thread");
+       thread_node.appendChild( this.doc.createTextNode( thread ) );
+
+       this.root.appendChild( body_node );
+       this.root.appendChild( thread_node );
+
+}
+
+/** Builds a new message from raw xml.
+  * If the message is a Jabber error message, then msg.is_error_msg
+  * is set to true;
+  */
+jabber_message.prototype.from_xml = function( xml ) {
+       var msg = new jabber_message();
+       msg.doc = new DOMParser().parseFromString( xml, "text/xml" );
+       msg.root = msg.doc.documentElement;
+
+       if( msg.root.getAttribute( "type" ) == "error" ) {
+               msg.is_error_msg = true;
+       } else {
+               this.is_error_msg = false;
+       }
+
+       return msg;
+}
+
+/** Returns the 'from' field of the message */
+jabber_message.prototype.get_sender = function() {
+       return this.root.getAttribute( "from" );
+}
+
+/** Returns the jabber thread */
+jabber_message.prototype.get_thread = function() {
+       var nodes = this.root.getElementsByTagName( "thread" );
+       var thread_node = nodes.item(0);
+       return thread_node.firstChild.nodeValue;
+}
+
+/** Returns the message body */
+jabber_message.prototype.get_body = function() {
+       var nodes = this.root.getElementsByTagName( "body" );
+       var body_node = nodes.item(0);
+       new Logger().transport( "Get Body returning:\n" + body_node.textContent, Logger.DEBUG );
+       return body_node.textContent;
+}
+       
+/** Returns the message as a whole as an XML string */
+jabber_message.prototype.to_string = function() {
+   return new XMLSerializer().serializeToString(this.root);
+}
+
+
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_SOCKET
+
+/** Initializes a new jabber_socket object */
+function jabber_socket() {
+
+       this.is_connected       = false;
+       this.outstream          = "";
+       this.instream           = "";
+       this.buffer                     = "";
+       this.socket                     = "";
+
+}
+
+/** Connects to the jabber server */
+jabber_socket.prototype.connect = 
+       function( username, password, resource,  host, port, timeout ) {
+
+       var starttime = new Date().getTime();
+
+       // there has to be at least some kind of timeout
+       if( ! timeout || timeout < 100 ) { timeout = 1000; }
+
+       try {
+
+               this.xpcom_init( host, port );
+               this.tcp_send( "<stream:stream to='"+host
+                               +"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>" );
+
+               if( !this.tcp_recv( timeout ) ) {  throw 1; }
+
+       } catch( E ) {
+               throw new oils_ex_transport( "Could not open a socket to the transport server\n" 
+                               + "Server: " + host + " Port: " + port  );
+       }
+
+       // Send the auth packet
+       this.tcp_send( "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>" 
+                       + username + "</username><password>" + password + 
+                       "</password><resource>" + resource + "</resource></query></iq>" );
+
+       var cur = new Date().getTime();
+       var remaining = timeout - ( cur - starttime );
+       this.tcp_recv( remaining );
+
+       if( ! this.connected() ) {
+               throw new oils_ex_transport( "Connection to transport server timed out" );
+       }
+
+       return true;
+
+
+}
+
+
+/** Sets up all of the xpcom components */
+jabber_socket.prototype.xpcom_init = function( host, port ) {
+
+       var transportService =
+               Components.classes["@mozilla.org/network/socket-transport-service;1"]
+               .getService(Components.interfaces.nsISocketTransportService);
+
+       this.transport = transportService.createTransport( null, 0, host, port, null);
+
+       // ------------------------------------------------------------------
+       // Build the stream objects
+       // ------------------------------------------------------------------
+       this.outstream = this.transport.openOutputStream(0,0,0);
+       
+       var stream = this.transport.openInputStream(0,0,0);
+
+       this.instream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                       .createInstance(Components.interfaces.nsIScriptableInputStream);
+
+       this.instream.init(stream);
+
+}
+
+/** Send data to the TCP pipe */
+jabber_socket.prototype.tcp_send = function( data ) {
+       new Logger().transport( "Sending Data: \n" + data, Logger.INFO );
+       this.outstream.write(data,data.length);
+}
+
+
+/** Accepts data coming directly from the socket.  If we're not
+  * connected, we pass it off to procecc_connect().  Otherwise,
+  * this method adds the data to the local buffer.
+  */
+jabber_socket.prototype.process_data = function( data ) {
+
+       new Logger().transport( "Received TCP data: " + data, Logger.DEBUG );
+
+       if( ! this.connected() ) {
+               this.process_connect( data );
+               return;
+       } 
+
+       this.buffer += data;
+
+}
+
+/** Processes connect data to verify we are logged in correctly */
+jabber_socket.prototype.process_connect = function( data ) {
+
+       var reg = /type=["\']result["\']/;
+       var err = /error/;
+
+       if( reg.exec( data ) ) {
+               this.is_connected = true;
+       } else {
+               if( err.exec( data ) ) {
+                       //throw new oils_ex_transport( "Server returned: \n" + data );
+                       throw new oils_ex_jabber_auth( "Server returned: \n" + data );
+                       // Throw exception, return something...
+               }
+       }
+}
+
+/** Waits up to at most 'timeout' milliseconds for data to arrive 
+  * in the TCP buffer.  If there is at least one byte of data 
+  * in the buffer, then all of the data that is in the buffer is sent 
+  * to the process_data method for processing and the method returns.  
+  */
+jabber_socket.prototype.tcp_recv = function( timeout ) {
+
+       var count = this.instream.available();
+       var did_receive = false;
+
+       // ------------------------------------------------------------------
+       // If there is any data in the tcp buffer, process it and return
+       // ------------------------------------------------------------------
+       if( count > 0 ) { 
+
+               did_receive = true;
+               while( count > 0 ) { 
+                       this.process_data( this.instream.read( count ) );
+                       count = this.instream.available();
+               }
+
+       } else { 
+
+               // ------------------------------------------------------------------
+               // Do the timeout dance
+               // ------------------------------------------------------------------
+
+               // ------------------------------------------------------------------
+               // If there is no data in the buffer, wait up to timeout seconds
+               // for some data to arrive.  Once it arrives, suck it all out
+               // and send it on for processing
+               // ------------------------------------------------------------------
+
+               var now, then;
+               now = new Date().getTime();
+               then = now;
+
+               // ------------------------------------------------------------------
+               // Loop and poll for data every 50 ms.
+               // ------------------------------------------------------------------
+               while( ((now-then) <= timeout) && count <= 0 ) { 
+                       sleep(50);
+                       count = this.instream.available();
+                       now = new Date().getTime();
+               }
+
+               // ------------------------------------------------------------------
+               // if we finally get some data, process it.
+               // ------------------------------------------------------------------
+               if( count > 0 ) {
+
+                       did_receive = true;
+                       while( count > 0 ) { // pull in all of the data there is
+                               this.process_data( this.instream.read( count ) );
+                               count = this.instream.available();
+                       }
+               }
+       }
+
+       return did_receive;
+
+}
+
+/** If a message is already sitting in the queue, it is returned.  
+  * If not, this method waits till at most 'timeout' milliseconds 
+  * for a full jabber message to arrive and then returns that.
+  * If none ever arrives, returns null.
+  */
+jabber_socket.prototype.recv = function( timeout ) {
+
+       var now, then;
+       now = new Date().getTime();
+       then = now;
+
+       while( ((now-then) <= timeout) ) {
+               
+               if( this.buffer.length == 0 ) {
+                       if( ! this.tcp_recv( timeout ) ) {
+                               return null;
+                       }
+               }
+
+               //new Logger().transport( "\n\nTCP Buffer Before: \n" + this.buffer, Logger.DEBUG );
+
+               var buf = this.buffer;
+               this.buffer = "";
+
+               buf = buf.replace( /\n/g, '' ); // remove pesky newlines
+
+               var reg = /<message.*?>.*?<\/message>/;
+               var iqr = /<iq.*?>.*?<\/iq>/;
+               var out = reg.exec(buf);
+
+               if( out ) { 
+
+                       var msg_xml = out[0];
+                       this.buffer = buf.substring( msg_xml.length, buf.length );
+                       new Logger().transport( "Building Jabber message\n\n" + msg_xml, Logger.DEBUG );
+                       var jab_msg = new jabber_message().from_xml( msg_xml );
+                       if( jab_msg.is_error_msg ) {
+                               new Logger().transport( "Received Jabber error message \n\n" + msg_xml, Logger.ERROR );
+                       } 
+
+                       return jab_msg;
+
+
+               } else { 
+
+                       out = iqr.exec(buf);
+
+                       if( out ) {
+                               var msg_xml = out[0];
+                               this.buffer = buf.substring( msg_xml.length, buf.length );
+                               process_iq_data( msg_xml );
+                               return;
+
+                       }
+               }
+
+               now = new Date().getTime();
+       }
+
+       return null;
+}
+
+jabber_socket.prototype.process_iq_data = function( data ) {
+       new Logger().transport( "IQ Packet received... Not Implemented\n" + data, Logger.ERROR );
+}
+
+/** Disconnects from the jabber server and closes down shop */
+jabber_socket.prototype.disconnect = function() {
+       this.tcp_send( "</stream:stream>" );
+       this.instream.close();
+       this.outstream.close();
+}
+
+/** True if connected */
+jabber_socket.prototype.connected = function() {
+       return this.is_connected;
+}
+
+
+
+
+
diff --git a/src/javascript/opensrf_msg_stack.js b/src/javascript/opensrf_msg_stack.js
new file mode 100644 (file)
index 0000000..51d5fd8
--- /dev/null
@@ -0,0 +1,203 @@
+// -----------------------------------------------------------------------------
+// Message stack code.
+// -----------------------------------------------------------------------------
+
+
+
+// -----------------------------------------------------------------------------
+// These just have to be defined for the 'static' methods to work
+// -----------------------------------------------------------------------------
+function Transport() {}
+function Message() {}
+function Application() {}
+
+/** Transport handler.  
+  * Takes a transport_message as parameter
+  * Parses the incoming message data and builds one or more oilsMessage objects
+  * from the XML.  Each message is passed in turn to the Message.handler
+  * method.
+  */
+Transport.handler = function( msg ) {
+
+       if( msg.is_error_msg ) {
+               throw new oils_ex_session( "Receved error message from jabber server for recipient: " + msg.get_sender() );
+               return;
+       }
+
+       var remote_id   = msg.get_sender();
+       var session_id = msg.get_thread();
+       var body                        = msg.get_body();
+
+       var session             = AppSession.find_session( session_id );
+
+       if( ! session ) {
+               new Logger().debug( "No AppSession found with id: " + session_id );
+               return;
+       }
+
+       session.set_remote_id( remote_id );
+
+       var nodelist; // oilsMessage nodes
+
+
+       // -----------------------------------------------------------------------------
+       // Parse the incoming XML
+       // -----------------------------------------------------------------------------
+       try {
+
+               var doc = new DOMParser().parseFromString( body, "text/xml" );
+               nodelist = doc.documentElement.getElementsByTagName( "oils:domainObject" );
+
+               if( ! nodelist || nodelist.length < 1 ) {
+                       nodelist = doc.documentElement.getElementsByTagName( "domainObject" );
+                       if( ! nodelist || nodelist.length < 1 ) { throw 1; }
+               }
+
+       } catch( E ) {
+
+               var str = "Error parsing incoming message document";
+
+               if( E ) { throw new oils_ex_dom( str + "\n" + E.message + "\n" + body ); 
+               } else { throw new oils_ex_dom( str + "\n" + body ); }
+
+       }
+       
+       // -----------------------------------------------------------------------------
+       // Pass the messages up the chain.
+       // -----------------------------------------------------------------------------
+       try {
+
+               var i = 0;
+               var node = nodelist.item(i); // a single oilsMessage
+
+               while( node != null ) {
+
+                       if( node.getAttribute("name") != "oilsMessage" ) {
+                               node = nodelist.item(++i);
+                               continue;
+                       }
+                               
+                       var oils_msg = new oilsMessage().replaceNode( node );  
+
+
+                       // -----------------------------------------------------------------------------
+                       // this is really inefficient compared to the above line of code,
+                       // however, this resolves some namespace oddities in DOMParser - 
+                       // namely, DOMParser puts dummy namesapaces in "a0" when, I'm assuming, it 
+                       // can't find the definition for the namesapace included.
+                       // -----------------------------------------------------------------------------
+                       //      var oils_msg = oilsMessage.newFromXML( new XMLSerializer().serializeToString( node ) );
+
+                       new Logger().transport( "Transport passing up:\n" + oils_msg.toString(true), Logger.INFO );
+
+                       // Pass the message off to the message layer
+                       Message.handler( session, oils_msg );
+                       node = nodelist.item(++i);
+               }
+
+       } catch( E ) {
+
+               var str = "Processing Error";
+
+               if( E ) { throw new oils_ex_session( str + "\n" + E.message + "\n" + body ); } 
+               else { throw new oils_ex_session( str + "\n" + body ); }
+       }
+}
+
+/** Checks to see what type of message has arrived.  If it is a 'STATUS' message,
+  * the appropriate transport layer actions are taken.  Otherwise (RESULT), the
+  * message is passed to the Application.handler method.
+  */
+Message.handler = function( session, msg ) {
+
+       var msg_type                                    = msg.getType();
+       var domain_object_payload       = msg.getPayload();
+       var tt                                                  = msg.getThreadTrace();
+
+       var code = domain_object_payload.getStatusCode();
+
+       new Logger().debug( "Message.handler received " + msg_type + " from " +
+                       session.get_remote_id() + " with thread_trace " + tt + " and status " + code, Logger.INFO );
+       new Logger().debug( "Message.handler received:\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+       if( msg_type == oilsMessage.STATUS ) {
+
+               switch( code ) {
+
+                       case  oilsResponse.STATUS_OK + "": {
+                               session.set_state( AppSession.CONNECTED );
+                               new Logger().debug( " * Connected Successfully: " + tt, Logger.INFO );
+                               return;
+                       }
+
+                       case oilsResponse.STATUS_TIMEOUT + "": {
+                               return Message.reset_session( session, tt, "Disconnected because of timeout" );
+                       }
+
+                       case oilsResponse.STATUS_REDIRECTED + "": {
+                               return Message.reset_session( session, tt, "Disconnected because of redirect" );
+                       }
+
+                       case oilsResponse.STATUS_EXPFAILED + "": {
+                               return Message.reset_session( session, tt, "Disconnected because of mangled session" );
+                       }
+
+                       case oilsResponse.STATUS_NOTALLOWED + "": {
+                               new Logger().debug( "Method Not Allowed", Logger.ERROR );
+                               session.destroy();
+                               break; // we want the exception to be thrown below
+                       }
+
+                       case oilsResponse.STATUS_CONTINUE +"": {
+                               return;
+                       }
+
+                       case oilsResponse.STATUS_COMPLETE + "": {
+                               var req = session.get_request(tt);
+                               if( req ) { req.set_complete(); }
+                               new Logger().debug( " * Request completed: " + tt, Logger.INFO );
+                               return;
+                       }
+
+                       default: { break; } 
+               }
+
+       }
+
+       // throw any exceptions received from the server
+       if( domain_object_payload instanceof oilsException ) {
+               throw new oils_ex_session( domain_object_payload.getStatus() );
+       }
+
+       new Logger().debug( "Message Layer passing up:\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+       Application.handler( session, domain_object_payload, tt );
+
+}
+
+/** Utility method for cleaning up a session.  Sets state to disconnected.
+  * resets the remoted_id, pushes the current app_request onto the resend
+  * queue. Logs a message.
+  */
+Message.reset_session = function( session, thread_trace, message ) {
+       session.set_state( AppSession.DISCONNECTED );
+       session.reset_remote();
+       var req = session.get_request( thread_trace );
+       if( req && !req.complete ) { session.push_resend( req ); }
+       new Logger().debug( " * " + message + " : " + thread_trace, Logger.INFO );
+}
+
+
+/** Pushes all incoming messages onto the session message queue. **/
+Application.handler = function( session, domain_object_payload, thread_trace ) {
+
+       new Logger().debug( "Application Pushing onto queue: " 
+                       + thread_trace + "\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+       session.push_queue( domain_object_payload, thread_trace );
+}
+
+
+       
+
+       
diff --git a/src/javascript/opensrf_transport.js b/src/javascript/opensrf_transport.js
new file mode 100644 (file)
index 0000000..a0d846d
--- /dev/null
@@ -0,0 +1,64 @@
+/** @file oils_transport.js
+  * Houses the top level transport 'abstract' classes
+  * You can think of this like a header file which provides the 
+  * interface that any transport code must implement
+  */
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_CONNECTION
+
+/** Constructor */
+function transport_connection( username, password, resource ) { }
+
+/** Connects to the transport host */
+transport_connection.prototype.connect = function(  host, /*int*/ port, /*int*/ timeout ) {}
+
+/** Sends a new message to recipient, with session_id and body */
+transport_connection.prototype.send = function( recipient, session_id, body ) {}
+
+
+/** Returns a transport_message.  This function will return 
+  * immediately if there is a message available.  Otherwise, it will
+  * wait at most 'timeout' seconds for one to arrive.  Returns
+  * null if a message does not arrivae in time.
+
+  * 'timeout' is specified in milliseconds
+  */
+transport_connection.prototype.recv = function( /*int*/ timeout ) {}
+
+/** This method calls recv and then passes the contents on to the
+  * message processing stack.
+  */
+transport_connection.prototype.process_msg = function( /*int*/ timeout ) {
+       var msg = this.recv( timeout );
+       if( msg ) { Transport.handler( msg ); }
+}
+
+/** Disconnects from the transpot host */
+transport_connection.prototype.disconnect = function() {}
+
+/** Returns true if this connection instance is currently connected
+  * to the transport host.
+  */
+transport_connection.prototype.connected = function() {}
+
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_MESSAGE
+       
+
+/** Constructor */
+function transport_message( sender, recipient, session_id, body ) {}
+
+/** Returns the sender of the message */
+transport_message.prototype.get_sender = function() {}
+
+/** Returns the session id */
+transport_message.prototype.get_session = function() {}
+
+/** Returns the message body */
+transport_message.prototype.get_body = function() {}
+       
+
diff --git a/src/javascript/opensrf_utils.js b/src/javascript/opensrf_utils.js
new file mode 100644 (file)
index 0000000..d348fd5
--- /dev/null
@@ -0,0 +1,117 @@
+// ------------------------------------------------------------------
+// Houses utility functions
+// ------------------------------------------------------------------
+
+/** Prints to console.  If alert_bool = true, displays a popup as well */
+function _debug( message, alert_bool ) {
+
+       dump( "\n" + new Date() + "\n--------------------------------\n" + 
+                       message + "\n-----------------------------------\n" );
+       if( alert_bool == true ) { alert( message ) };
+
+}
+
+
+/** Provides millisec sleep times, enjoy... */
+function sleep(gap){ 
+
+       var threadService = Components.classes["@mozilla.org/thread;1"].
+               getService(Components.interfaces.nsIThread);
+
+       var th = threadService.currentThread;
+       th.sleep(gap);
+}
+
+
+
+/** Top level exception classe */
+function oils_ex() {}
+
+/** Initializes an exception */
+oils_ex.prototype.init_ex = function( name, message ) {
+       if( !(name && message) ) { return; }
+       this.name = name;
+       this.message = name + " : " + message;
+       new Logger().debug( "***\n" + this.message + "\n***", Logger.ERROR );
+}
+
+/** Returns a string representation of an exception */
+oils_ex.prototype.toString = function() {
+       return this.message;
+}
+
+
+oils_ex_transport.prototype = new oils_ex();
+oils_ex_transport.prototype.constructor = oils_ex_transport;
+oils_ex_transport.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when the transport connection has problems*/
+function oils_ex_transport( message ) {
+       this.init_ex( "Transport Exception", message );
+}
+
+
+oils_ex_transport_auth.prototype = new oils_ex_transport(); 
+oils_ex_transport_auth.prototype.constructor = oils_ex_transport_auth;
+oils_ex_transport_auth.baseClass = oils_ex_transport.prototype.constructor;
+
+/** Thrown when the initial transport connection fails */
+function oils_ex_transport_auth( message ) {
+       this.init_ex( "Transport Authentication Error", message );
+}
+
+oils_ex_dom.prototype = new oils_ex(); 
+oils_ex_dom.prototype.constructor = oils_ex_dom;
+oils_ex_dom.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when there is an XML problem */
+function oils_ex_dom( message ) {
+       this.init_ex( "DOM Error", message );
+}
+
+oils_ex_message.prototype = new oils_ex();
+oils_ex_message.prototype.constructor = oils_ex_message;
+oils_ex_message.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when there is a message problem */
+function oils_ex_message( message ) {
+       this.init_ex( "OILS Message Layer Error", message ) ;
+}
+
+oils_ex_config.prototype = new oils_ex();
+oils_ex_config.prototype.constructor = oils_ex_config;
+oils_ex_config.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when values cannot be retrieved from the config file */
+function oils_ex_config( message ) {
+       this.init_ex( "OILS Config Exception", message );
+}
+
+oils_ex_logger.prototype = new oils_ex();
+oils_ex_logger.prototype.constructor = oils_ex_logger;
+oils_ex_logger.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown where there are logging problems */
+function oils_ex_logger( message ) {
+       this.init_ex( "OILS Logger Exception", message );
+}
+
+
+oils_ex_args.prototype = new oils_ex();
+oils_ex_args.prototype.constructor = oils_ex_args;
+oils_ex_args.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when when a method does not receive all of the required arguments */
+function oils_ex_args( message ) {
+       this.init_ex( "Method Argument Exception", message );
+}
+
+
+oils_ex_session.prototype = new oils_ex();
+oils_ex_session.prototype.constructor = oils_ex_session;
+oils_ex_session.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown where there is a session processing problem */
+function oils_ex_session( message ) {
+       this.init_ex( "Session Exception", message );
+}
diff --git a/src/libtransport/Makefile b/src/libtransport/Makefile
new file mode 100644 (file)
index 0000000..9a5c0b5
--- /dev/null
@@ -0,0 +1,31 @@
+# set this shell variable prior to calling make to run with malloc_check enabled
+#MALLOC_CHECK_=1 # XXX debug only
+
+CC = gcc
+LIB_DIR=../../lib
+CC_OPTS = -Wall -O2 -I /usr/include/libxml2 -I /usr/include/libxml2/libxml -I ../../include -I ../../../../cc/libxml2-2.6.16
+LD_OPTS = -lxml2
+EXE_LD_OPTS = -L $(LIB_DIR) -lxml2 -ltransport 
+LIB_SOURCES = generic_utils.c transport_socket.c transport_session.c transport_message.c transport_client.c
+
+TARGETS=generic_utils.o transport_socket.o transport_message.o transport_session.o transport_client.o 
+
+all: router basic_client
+
+basic_client: lib
+       $(CC) $(CC_OPTS) $(EXE_LD_OPTS) basic_client.c -o $@
+
+# --- Libs -----------------------------------------------
+       
+lib: 
+       $(CC) -c $(CC_OPTS)     $(LIB_SOURCES)  
+       $(CC) -shared -W1 $(LD_OPTS) $(TARGETS) -o $(LIB_DIR)/libtransport.so
+
+
+# The router is compiled as a static binary because of some 
+# necessary #defines that would break the library
+router: 
+       $(CC) $(LD_OPTS) -D_ROUTER $(CC_OPTS)   $(LIB_SOURCES) transport_router.c -o $@ 
+
+clean:
+       /bin/rm -f *.o ../../lib/libtransport.so router basic_client
diff --git a/src/libtransport/basic_client.c b/src/libtransport/basic_client.c
new file mode 100644 (file)
index 0000000..61cb9e6
--- /dev/null
@@ -0,0 +1,78 @@
+#include "transport_client.h"
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+/**
+  * Simple jabber client
+  */
+
+
+
+
+/* connects and registers with the router */
+int main( int argc, char** argv ) {
+
+       if( argc < 5 ) {
+               fatal_handler( "Usage: %s <username> <host> <resource> <recipient> \n", argv[0] );
+               return 99;
+       }
+
+       transport_message* send;
+       transport_client* client = client_init( argv[2], 5222 );
+
+       // try to connect, allow 15 second connect timeout 
+       if( client_connect( client, argv[1], "asdfjkjk", argv[3], 15 ) ) 
+               info_handler("Connected...\n");
+        else  
+               fatal_handler( "NOT Connected...\n" ); 
+       
+       if( fork() ) {
+
+               fprintf(stderr, "Listener: %d\n", getpid() );   
+               char buf[300];
+               memset(buf, 0, 300);
+               printf("=> ");
+
+               while( fgets( buf, 299, stdin) ) {
+
+                       // remove newline
+                       buf[strlen(buf)-1] = '\0';
+
+                       if( strcmp(buf, "exit")==0) { 
+                               client_free( client );  
+                               break; 
+                       }
+
+                       send = message_init( buf, "", "123454321", argv[4], NULL );
+                       client_send_message( client, send );
+                       message_free( send );
+                       printf("\n=> ");
+                       memset(buf, 0, 300);
+               }
+               return 0;
+
+       } else {
+
+               fprintf(stderr, "Sender: %d\n", getpid() );     
+
+               transport_message* recv;
+               while( (recv=client_recv( client, -1)) ) {
+                       if( recv->is_error )
+                               fprintf( stderr, "\nReceived Error\t: ------------------\nFrom:\t\t"
+                                       "%s\nRouterFrom:\t%s\nBody:\t\t%s\nType %s\nCode %d\n=> ", recv->sender, recv->router_from, recv->body, recv->error_type, recv->error_code );
+                       else
+                               fprintf( stderr, "\nReceived\t: ------------------\nFrom:\t\t"
+                                       "%s\nRouterFrom:\t%s\nBody:\t\t%s\n=> ", recv->sender, recv->router_from, recv->body );
+
+                       message_free( recv );
+               }
+
+       }
+       return 0;
+
+}
+
+
+
+
diff --git a/src/libtransport/generic_utils.c b/src/libtransport/generic_utils.c
new file mode 100644 (file)
index 0000000..ba0b284
--- /dev/null
@@ -0,0 +1,392 @@
+#include "generic_utils.h"
+#include <stdio.h>
+#include "pthread.h"
+
+int _init_log();
+
+int balance = 0;
+
+#define LOG_ERROR 1
+#define LOG_WARNING 2
+#define LOG_INFO 3
+
+void get_timestamp( char buf_25chars[]) {
+       time_t epoch = time(NULL);      
+       char* localtime = strdup( ctime( &epoch ) );
+       strcpy( buf_25chars, localtime );
+       buf_25chars[ strlen(localtime)-1] = '\0'; // remove newline
+       free(localtime);
+}
+
+
+inline void* safe_malloc( int size ) {
+       void* ptr = (void*) malloc( size );
+       if( ptr == NULL ) 
+               fatal_handler("safe_malloc(): Out of Memory" );
+       memset( ptr, 0, size );
+       return ptr;
+}
+
+// ---------------------------------------------------------------------------------
+// Here we define how we want to handle various error levels.
+// ---------------------------------------------------------------------------------
+
+
+static FILE* log_file = NULL;
+static int log_level = -1;
+pthread_mutex_t mutex;
+
+void log_free() { if( log_file != NULL ) fclose(log_file ); }
+
+void fatal_handler( char* msg, ... ) {
+               
+       char buf[25];
+       memset( buf, 0, 25 );
+       get_timestamp( buf );
+       pid_t  pid = getpid();
+       va_list args;
+
+       if( _init_log() ) {
+
+               if( log_level < LOG_ERROR )
+                       return;
+
+               pthread_mutex_lock( &(mutex) );
+               fprintf( log_file, "[%s %d] [%s] ", buf, pid, "ERR " );
+       
+               va_start(args, msg);
+               vfprintf(log_file, msg, args);
+               va_end(args);
+       
+               fprintf(log_file, "\n");
+               fflush( log_file );
+               pthread_mutex_unlock( &(mutex) );
+
+       }
+       
+       /* also log to stderr  for ERRORS*/
+       fprintf( stderr, "[%s %d] [%s] ", buf, pid, "ERR " );
+       va_start(args, msg);
+       vfprintf(stderr, msg, args);
+       va_end(args);
+       fprintf( stderr, "\n" );
+
+       exit(99);
+}
+
+void warning_handler( char* msg, ... ) {
+
+       char buf[25];
+       memset( buf, 0, 25 );
+       get_timestamp( buf );
+       pid_t  pid = getpid();
+       va_list args;
+       
+       if( _init_log() ) {
+
+               if( log_level < LOG_WARNING )
+                       return;
+
+               pthread_mutex_lock( &(mutex) );
+               fprintf( log_file, "[%s %d] [%s] ", buf, pid, "WARN" );
+       
+               va_start(args, msg);
+               vfprintf(log_file, msg, args);
+               va_end(args);
+       
+               fprintf(log_file, "\n");
+               fflush( log_file );
+               pthread_mutex_unlock( &(mutex) );
+
+       } else {
+
+               fprintf( stderr, "[%s %d] [%s] ", buf, pid, "WARN" );
+               va_start(args, msg);
+               vfprintf(stderr, msg, args);
+               va_end(args);
+               fprintf( stderr, "\n" );
+       }
+
+}
+
+void info_handler( char* msg, ... ) {
+
+       char buf[25];
+       memset( buf, 0, 25 );
+       get_timestamp( buf );
+       pid_t  pid = getpid();
+       va_list args;
+
+       if( _init_log() ) {
+
+               if( log_level < LOG_INFO )
+                       return;
+               pthread_mutex_lock( &(mutex) );
+               fprintf( log_file, "[%s %d] [%s] ", buf, pid, "INFO" );
+
+               va_start(args, msg);
+               vfprintf(log_file, msg, args);
+               va_end(args);
+       
+               fprintf(log_file, "\n");
+               fflush( log_file );
+               pthread_mutex_unlock( &(mutex) );
+
+       } else {
+
+               fprintf( stderr, "[%s %d] [%s] ", buf, pid, "INFO" );
+               va_start(args, msg);
+               vfprintf(stderr, msg, args);
+               va_end(args);
+               fprintf( stderr, "\n" );
+               fflush(stderr);
+
+       }
+}
+
+
+int _init_log() {
+
+       if( log_level != -1 )
+               return 1;
+
+
+       pthread_mutex_init( &(mutex), NULL );
+
+       /* load the log level setting if we haven't already */
+
+       if( conf_reader == NULL ) {
+               return 0;
+               //fprintf( stderr, "No config file specified" );
+       }
+
+       char* log_level_str = config_value( "//router/log/level");
+       if( log_level_str == NULL ) {
+       //      fprintf( stderr, "No log level specified" );
+               return 0;
+       }
+       log_level = atoi(log_level_str);
+       free(log_level_str);
+
+       /* see if we have a log file yet */
+       char* f = config_value("//router/log/file");
+
+       if( f == NULL ) {
+               // fprintf( stderr, "No log file specified" );
+               return 0;
+       }
+
+       log_file = fopen( f, "a" );
+       if( log_file == NULL ) {
+               fprintf( stderr, "Unable to open log file %s for appending\n", f );
+               return 0;
+       }
+       free(f);
+       return 1;
+                               
+}
+
+
+
+// ---------------------------------------------------------------------------------
+// Flesh out a ubiqitous growing string buffer
+// ---------------------------------------------------------------------------------
+
+growing_buffer* buffer_init(int num_initial_bytes) {
+
+       if( num_initial_bytes > BUFFER_MAX_SIZE ) {
+               return NULL;
+       }
+
+
+       size_t len = sizeof(growing_buffer);
+
+       growing_buffer* gb = (growing_buffer*) safe_malloc(len);
+
+       gb->n_used = 0;/* nothing stored so far */
+       gb->size = num_initial_bytes;
+       gb->buf = (char *) safe_malloc(gb->size + 1);
+
+       return gb;
+}
+
+int buffer_add(growing_buffer* gb, char* data) {
+
+
+       if( ! gb || ! data  ) { return 0; }
+       int data_len = strlen( data );
+
+       if( data_len == 0 ) { return 0; }
+       int total_len = data_len + gb->n_used;
+
+       while( total_len >= gb->size ) {
+               gb->size *= 2;
+       }
+
+       if( gb->size > BUFFER_MAX_SIZE ) {
+               warning_handler( "Buffer reached MAX_SIZE of %d", BUFFER_MAX_SIZE );
+               buffer_free( gb );
+               return 0;
+       }
+
+       char* new_data = (char*) safe_malloc( gb->size );
+
+       strcpy( new_data, gb->buf );
+       free( gb->buf );
+       gb->buf = new_data;
+
+       strcat( gb->buf, data );
+       gb->n_used = total_len;
+       return total_len;
+}
+
+
+int buffer_reset( growing_buffer *gb){
+       if( gb == NULL ) { return 0; }
+       if( gb->buf == NULL ) { return 0; }
+       memset( gb->buf, 0, gb->size );
+       gb->n_used = 0;
+       return 1;
+}
+
+int buffer_free( growing_buffer* gb ) {
+       if( gb == NULL ) 
+               return 0;
+       free( gb->buf );
+       free( gb );
+       return 1;
+}
+
+char* buffer_data( growing_buffer *gb) {
+       return strdup( gb->buf );
+}
+
+
+
+
+
+// ---------------------------------------------------------------------------------
+// Config module
+// ---------------------------------------------------------------------------------
+
+
+// ---------------------------------------------------------------------------------
+// Allocate and build the conf_reader.  This only has to happen once in a given
+// system.  Repeated calls are ignored.
+// ---------------------------------------------------------------------------------
+void config_reader_init( char* config_file ) {
+       if( conf_reader == NULL ) {
+
+               if( config_file == NULL || strlen(config_file) == 0 ) {
+                       fatal_handler( "config_reader_init(): No config file specified" );
+                       return;
+               }
+
+               size_t len = sizeof( config_reader );
+               conf_reader = (config_reader*) safe_malloc( len );
+
+               conf_reader->config_doc = xmlParseFile( config_file ); 
+               conf_reader->xpathCx = xmlXPathNewContext( conf_reader->config_doc );
+               if( conf_reader->xpathCx == NULL ) {
+                       fatal_handler( "config_reader_init(): Unable to create xpath context");
+                       return;
+               }
+       }
+}
+
+char* config_value( const char* xp_query, ... ) {
+
+       if( conf_reader == NULL || xp_query == NULL ) {
+               fatal_handler( "config_value(): NULL param(s)" );
+               return NULL;
+       }
+
+       int slen = strlen(xp_query) + 512;/* this is unsafe ... */
+       char xpath_query[ slen ]; 
+       memset( xpath_query, 0, slen );
+       va_list va_args;
+       va_start(va_args, xp_query);
+       vsprintf(xpath_query, xp_query, va_args);
+       va_end(va_args);
+
+
+       char* val;
+       int len = strlen(xpath_query) + 100;
+       char alert_buffer[len];
+       memset( alert_buffer, 0, len );
+
+       // build the xpath object
+       xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression( BAD_CAST xpath_query, conf_reader->xpathCx );
+
+       if( xpathObj == NULL ) {
+               sprintf( alert_buffer, "Could not build xpath object: %s", xpath_query );
+               fatal_handler( alert_buffer );
+               return NULL;
+       }
+
+
+       if( xpathObj->type == XPATH_NODESET ) {
+
+               // ----------------------------------------------------------------------------
+               // Grab nodeset from xpath query, then first node, then first text node and 
+               // finaly the text node's value
+               // ----------------------------------------------------------------------------
+               xmlNodeSet* node_list = xpathObj->nodesetval;
+               if( node_list == NULL ) {
+                       sprintf( alert_buffer, "Could not build xpath object: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+               if( node_list->nodeNr == 0 ) {
+                       sprintf( alert_buffer, "Config XPATH query  returned 0 results: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+
+               xmlNodePtr element_node = *(node_list)->nodeTab;
+               if( element_node == NULL ) {
+                       sprintf( alert_buffer, "Config XPATH query  returned 0 results: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+               xmlNodePtr text_node = element_node->children;
+               if( text_node == NULL ) {
+                       sprintf( alert_buffer, "Config variable has no value: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+               val = text_node->content;
+               if( val == NULL ) {
+                       sprintf( alert_buffer, "Config variable has no value: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+
+       } else { 
+               sprintf( alert_buffer, "Xpath evaluation failed: %s", xpath_query );
+               warning_handler(alert_buffer);
+               return NULL;
+       }
+
+       char* value = strdup(val);
+       if( value == NULL ) { fatal_handler( "config_value(): Out of Memory!" ); }
+
+       // Free XPATH structures
+       if( xpathObj != NULL ) xmlXPathFreeObject( xpathObj );
+
+       return value;
+}
+
+
+void config_reader_free() {
+       if( conf_reader == NULL ) { return; }
+       xmlXPathFreeContext( conf_reader->xpathCx );
+       xmlFreeDoc( conf_reader->config_doc );
+       free( conf_reader );
+       conf_reader = NULL;
+}
diff --git a/src/libtransport/transport_client.c b/src/libtransport/transport_client.c
new file mode 100644 (file)
index 0000000..40b2c36
--- /dev/null
@@ -0,0 +1,212 @@
+#include "transport_client.h"
+
+
+//int main( int argc, char** argv );
+
+/*
+int main( int argc, char** argv ) {
+
+       transport_message* recv;
+       transport_message* send;
+
+       transport_client* client = client_init( "spacely.georgialibraries.org", 5222 );
+
+       // try to connect, allow 15 second connect timeout 
+       if( client_connect( client, "admin", "asdfjkjk", "system", 15 ) ) {
+               printf("Connected...\n");
+       } else { 
+               printf( "NOT Connected...\n" ); exit(99); 
+       }
+
+       while( (recv = client_recv( client, -1 )) ) {
+
+               if( recv->body ) {
+                       int len = strlen(recv->body);
+                       char buf[len + 20];
+                       memset( buf, 0, len + 20); 
+                       sprintf( buf, "Echoing...%s", recv->body );
+                       send = message_init( buf, "Echoing Stuff", "12345", recv->sender, "" );
+               } else {
+                       send = message_init( " * ECHOING * ", "Echoing Stuff", "12345", recv->sender, "" );
+               }
+
+               if( send == NULL ) { printf("something's wrong"); }
+               client_send_message( client, send );
+                               
+               message_free( send );
+               message_free( recv );
+       }
+
+       printf( "ended recv loop\n" );
+
+       return 0;
+
+}
+*/
+
+
+transport_client* client_init( char* server, int port ) {
+
+       if(server == NULL) return NULL;
+
+       /* build and clear the client object */
+       size_t c_size = sizeof( transport_client);
+       transport_client* client = (transport_client*) safe_malloc( c_size );
+
+       /* build and clear the message list */
+       size_t l_size = sizeof( transport_message_list );
+       client->m_list = (transport_message_list*) safe_malloc( l_size );
+
+       client->m_list->type = MESSAGE_LIST_HEAD;
+       client->session = init_transport( server, port, client );
+
+
+       if(client->session == NULL) {
+               fatal_handler( "client_init(): Out of Memory"); 
+               return NULL;
+       }
+       client->session->message_callback = client_message_handler;
+
+       return client;
+}
+
+int client_connect( transport_client* client, 
+               char* username, char* password, char* resource, int connect_timeout ) {
+       if(client == NULL) return 0; 
+       return session_connect( client->session, username, password, resource, connect_timeout );
+}
+
+int client_disconnect( transport_client* client ) {
+       if( client == NULL ) { return 0; }
+       return session_disconnect( client->session );
+}
+
+int client_connected( transport_client* client ) {
+       if(client == NULL) return 0;
+       return client->session->state_machine->connected;
+}
+
+int client_send_message( transport_client* client, transport_message* msg ) {
+       if(client == NULL) return 0;
+       return session_send_msg( client->session, msg );
+}
+
+
+transport_message* client_recv( transport_client* client, int timeout ) {
+       if( client == NULL ) { return NULL; }
+
+       transport_message_node* node;
+       transport_message* msg;
+
+
+       /* see if there are any message in the messages queue */
+       if( client->m_list->next != NULL ) {
+               /* pop off the first one... */
+               node = client->m_list->next;
+               client->m_list->next = node->next;
+               msg = node->message;
+               free( node );
+               return msg;
+       }
+
+       if( timeout == -1 ) {  /* wait potentially forever for data to arrive */
+
+               while( client->m_list->next == NULL ) {
+                       if( ! session_wait( client->session, -1 ) ) {
+                               return NULL;
+                       }
+               }
+
+       } else { /* wait at most timeout seconds */
+
+       
+               /* if not, loop up to 'timeout' seconds waiting for data to arrive */
+               time_t start = time(NULL);      
+               time_t remaining = (time_t) timeout;
+
+               int counter = 0;
+
+               int wait_ret;
+               while( client->m_list->next == NULL && remaining >= 0 ) {
+
+                       if( ! (wait_ret= session_wait( client->session, remaining)) ) 
+                               return NULL;
+
+                       ++counter;
+
+#ifdef _ROUTER
+                       // session_wait returns -1 if there is no more data and we're a router
+                       if( remaining == 0 && wait_ret == -1 ) {
+                               break;
+                       }
+#else
+                       if( remaining == 0 ) // or infinite loop
+                               break;
+#endif
+
+                       remaining -= (int) (time(NULL) - start);
+               }
+
+               info_handler("It took %d reads to grab this messag", counter);
+       }
+
+       /* again, see if there are any messages in the message queue */
+       if( client->m_list->next != NULL ) {
+               /* pop off the first one... */
+               node = client->m_list->next;
+               client->m_list->next = node->next;
+               msg = node->message;
+               free( node );
+               return msg;
+       } else {
+               return NULL;
+       }
+}
+
+/* throw the message into the message queue */
+void client_message_handler( void* client, transport_message* msg ){
+
+       if(client == NULL) return;
+       if(msg == NULL) return; 
+
+       transport_client* cli = (transport_client*) client;
+
+       size_t len = sizeof(transport_message_node);
+       transport_message_node* node = 
+               (transport_message_node*) safe_malloc(len);
+       node->type = MESSAGE_LIST_ITEM;
+       node->message = msg;
+
+
+       /* find the last node and put this onto the end */
+       transport_message_node* tail = cli->m_list;
+       transport_message_node* current = tail->next;
+
+       while( current != NULL ) {
+               tail = current;
+               current = current->next;
+       }
+       tail->next = node;
+}
+
+
+int client_free( transport_client* client ){
+       if(client == NULL) return 0; 
+
+       session_free( client->session );
+       transport_message_node* current = client->m_list->next;
+       transport_message_node* next;
+
+       /* deallocate the list of messages */
+       while( current != NULL ) {
+               next = current->next;
+               message_free( current->message );
+               free(current);
+               current = next;
+       }
+
+       free( client->m_list );
+       free( client );
+       return 1;
+}
+
diff --git a/src/libtransport/transport_message.c b/src/libtransport/transport_message.c
new file mode 100644 (file)
index 0000000..78ebb1b
--- /dev/null
@@ -0,0 +1,230 @@
+#include "transport_message.h"
+
+
+// ---------------------------------------------------------------------------------
+// Allocates and initializes a new transport_message
+// ---------------------------------------------------------------------------------
+transport_message* message_init( char* body, 
+               char* subject, char* thread, char* recipient, char* sender ) {
+
+       transport_message* msg = 
+               (transport_message*) safe_malloc( sizeof(transport_message) );
+
+       if( body                                        == NULL ) { body                        = ""; }
+       if( thread                              == NULL ) { thread              = ""; }
+       if( subject                             == NULL ) { subject             = ""; }
+       if( sender                              == NULL ) { sender              = ""; }
+       if( recipient                   ==      NULL ) { recipient      = ""; }
+
+       msg->body                               = strdup(body);
+       msg->thread                             = strdup(thread);
+       msg->subject                    = strdup(subject);
+       msg->recipient                  = strdup(recipient);
+       msg->sender                             = strdup(sender);
+
+       if(     msg->body               == NULL || msg->thread                          == NULL ||
+                       msg->subject    == NULL || msg->recipient                       == NULL ||
+                       msg->sender             == NULL ) {
+
+               fatal_handler( "message_init(): Out of Memory" );
+               return NULL;
+       }
+
+       return msg;
+}
+
+void message_set_router_info( transport_message* msg, char* router_from,
+               char* router_to, char* router_class, char* router_command, int broadcast_enabled ) {
+
+       msg->router_from                = strdup(router_from);
+       msg->router_to                  = strdup(router_to);
+       msg->router_class               = strdup(router_class);
+       msg->router_command     = strdup(router_command);
+       msg->broadcast = broadcast_enabled;
+
+       if( msg->router_from == NULL || msg->router_to == NULL ||
+                       msg->router_class == NULL || msg->router_command == NULL ) 
+               fatal_handler( "message_set_router_info(): Out of Memory" );
+
+       return;
+}
+
+
+
+/* encodes the message for traversal */
+int message_prepare_xml( transport_message* msg ) {
+       if( msg->msg_xml != NULL ) { return 1; }
+       msg->msg_xml = message_to_xml( msg );
+       return 1;
+}
+
+
+// ---------------------------------------------------------------------------------
+//
+// ---------------------------------------------------------------------------------
+int message_free( transport_message* msg ){
+       if( msg == NULL ) { return 0; }
+
+       free(msg->body); 
+       free(msg->thread);
+       free(msg->subject);
+       free(msg->recipient);
+       free(msg->sender);
+       free(msg->router_from);
+       free(msg->router_to);
+       free(msg->router_class);
+       free(msg->router_command);
+       if( msg->error_type != NULL ) free(msg->error_type);
+       if( msg->msg_xml != NULL ) free(msg->msg_xml);
+       free(msg);
+       return 1;
+}
+       
+// ---------------------------------------------------------------------------------
+// Allocates a char* holding the XML representation of this jabber message
+// ---------------------------------------------------------------------------------
+char* message_to_xml( const transport_message* msg ) {
+
+       int                     bufsize;
+       xmlChar*                xmlbuf;
+       char*                   encoded_body;
+
+       xmlNodePtr      message_node;
+       xmlNodePtr      body_node;
+       xmlNodePtr      thread_node;
+       xmlNodePtr      subject_node;
+       xmlNodePtr      error_node;
+       
+       xmlDocPtr       doc;
+
+       xmlKeepBlanksDefault(0);
+
+       if( ! msg ) { 
+               warning_handler( "Passing NULL message to message_to_xml()"); 
+               return 0; 
+       }
+
+       doc = xmlReadDoc( BAD_CAST "<message/>", NULL, NULL, XML_PARSE_NSCLEAN );
+       message_node = xmlDocGetRootElement(doc);
+
+       if( msg->is_error ) {
+               error_node = xmlNewChild(message_node, NULL, BAD_CAST "error" , NULL );
+               xmlAddChild( message_node, error_node );
+               xmlNewProp( error_node, BAD_CAST "type", BAD_CAST msg->error_type );
+               char code_buf[16];
+               memset( code_buf, 0, 16);
+               sprintf(code_buf, "%d", msg->error_code );
+               xmlNewProp( error_node, BAD_CAST "code", BAD_CAST code_buf  );
+       }
+       
+
+       /* set from and to */
+       xmlNewProp( message_node, BAD_CAST "to", BAD_CAST msg->recipient );
+       xmlNewProp( message_node, BAD_CAST "from", BAD_CAST msg->sender );
+       xmlNewProp( message_node, BAD_CAST "router_from", BAD_CAST msg->router_from );
+       xmlNewProp( message_node, BAD_CAST "router_to", BAD_CAST msg->router_to );
+       xmlNewProp( message_node, BAD_CAST "router_class", BAD_CAST msg->router_class );
+       xmlNewProp( message_node, BAD_CAST "router_command", BAD_CAST msg->router_command );
+
+       if( msg->broadcast )
+               xmlNewProp( message_node, BAD_CAST "broadcast", BAD_CAST "1" );
+
+       /* Now add nodes where appropriate */
+       char* body                              = msg->body;
+       char* subject                   = msg->subject;
+       char* thread                    = msg->thread; 
+
+       if( thread && strlen(thread) > 0 ) {
+               thread_node = xmlNewChild(message_node, NULL, (xmlChar*) "thread", (xmlChar*) thread );
+               xmlAddChild( message_node, thread_node ); 
+       }
+
+       if( subject && strlen(subject) > 0 ) {
+               subject_node = xmlNewChild(message_node, NULL, (xmlChar*) "subject", (xmlChar*) subject );
+               xmlAddChild( message_node, subject_node ); 
+       }
+
+       if( body && strlen(body) > 0 ) {
+               body_node = xmlNewChild(message_node, NULL, (xmlChar*) "body", (xmlChar*) body );
+               xmlAddChild( message_node, body_node ); 
+       }
+
+
+       xmlDocDumpFormatMemory( doc, &xmlbuf, &bufsize, 0 );
+       encoded_body = strdup( (char*) xmlbuf );
+
+       if( encoded_body == NULL ) 
+               fatal_handler("message_to_xml(): Out of Memory");
+
+       xmlFree(xmlbuf);
+       xmlFreeDoc( doc );
+       xmlCleanupParser();
+
+
+       /*** remove the XML declaration */
+
+       int len = strlen(encoded_body);
+       char tmp[len];
+       memset( tmp, 0, len );
+       int i;
+       int found_at = 0;
+
+       /* when we reach the first >, take everything after it */
+       for( i = 0; i!= len; i++ ) {
+               if( encoded_body[i] == 62) { /* ascii > */
+
+                       /* found_at holds the starting index of the rest of the doc*/
+                       found_at = i + 1; 
+                       break;
+               }
+       }
+
+       if( found_at ) {
+               /* move the shortened doc into the tmp buffer */
+               strncpy( tmp, encoded_body + found_at, len - found_at );
+               /* move the tmp buffer back into the allocated space */
+               memset( encoded_body, 0, len );
+               strcpy( encoded_body, tmp );
+       }
+
+       return encoded_body;
+}
+
+
+
+void jid_get_username( const char* jid, char buf[] ) {
+
+       if( jid == NULL ) { return; }
+
+       /* find the @ and return whatever is in front of it */
+       int len = strlen( jid );
+       int i;
+       for( i = 0; i != len; i++ ) {
+               if( jid[i] == 64 ) { /*ascii @*/
+                       strncpy( buf, jid, i );
+                       return;
+               }
+       }
+}
+
+
+void jid_get_resource( const char* jid, char buf[])  {
+       if( jid == NULL ) { return; }
+       int len = strlen( jid );
+       int i;
+       for( i = 0; i!= len; i++ ) {
+               if( jid[i] == 47 ) { /* ascii / */
+                       strncpy( buf, jid + i + 1, len - (i+1) );
+               }
+       }
+}
+
+void set_msg_error( transport_message* msg, char* type, int err_code ) {
+
+       if( type != NULL && strlen( type ) > 0 ) {
+               msg->error_type = safe_malloc( strlen(type)+1); 
+               strcpy( msg->error_type, type );
+               msg->error_code = err_code;
+       }
+       msg->is_error = 1;
+}
diff --git a/src/libtransport/transport_session.c b/src/libtransport/transport_session.c
new file mode 100644 (file)
index 0000000..71b0768
--- /dev/null
@@ -0,0 +1,507 @@
+#include "transport_session.h"
+
+
+
+// ---------------------------------------------------------------------------------
+// returns a built and allocated transport_session object.
+// This codes does no network activity, only memory initilization
+// ---------------------------------------------------------------------------------
+transport_session* init_transport(  char* server, int port, void* user_data ) {
+
+       /* create the session struct */
+       transport_session* session = 
+               (transport_session*) safe_malloc( sizeof(transport_session) );
+
+       session->user_data = user_data;
+
+       /* initialize the data buffers */
+       session->body_buffer                    = buffer_init( JABBER_BODY_BUFSIZE );
+       session->subject_buffer         = buffer_init( JABBER_SUBJECT_BUFSIZE );
+       session->thread_buffer          = buffer_init( JABBER_THREAD_BUFSIZE );
+       session->from_buffer                    = buffer_init( JABBER_JID_BUFSIZE );
+       session->status_buffer          = buffer_init( JABBER_STATUS_BUFSIZE );
+       session->recipient_buffer       = buffer_init( JABBER_JID_BUFSIZE );
+       session->message_error_type = buffer_init( JABBER_JID_BUFSIZE );
+
+       /* for OILS extensions */
+       session->router_to_buffer               = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_from_buffer     = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_class_buffer    = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_command_buffer  = buffer_init( JABBER_JID_BUFSIZE );
+
+
+
+       if(     session->body_buffer            == NULL || session->subject_buffer       == NULL        ||
+                       session->thread_buffer  == NULL || session->from_buffer          == NULL        ||
+                       session->status_buffer  == NULL || session->recipient_buffer == NULL ||
+                       session->router_to_buffer       == NULL || session->router_from_buffer   == NULL ||
+                       session->router_class_buffer == NULL || session->router_command_buffer == NULL ) { 
+
+               fatal_handler( "init_transport(): buffer_init returned NULL" );
+               return 0;
+       }
+
+
+       /* initialize the jabber state machine */
+       session->state_machine = (jabber_machine*) safe_malloc( sizeof(jabber_machine) );
+
+       /* initialize the sax push parser */
+       session->parser_ctxt = xmlCreatePushParserCtxt(SAXHandler, session, "", 0, NULL);
+
+       /* initialize the transport_socket structure */
+       session->sock_obj = (transport_socket*) safe_malloc( sizeof(transport_socket) );
+
+       //int serv_size = strlen( server );
+       session->sock_obj->server = server;
+       session->sock_obj->port = port;
+       session->sock_obj->data_received_callback = &grab_incoming;
+
+       /* this will be handed back to us in callbacks */
+       session->sock_obj->user_data = session; 
+
+       return session;
+}
+
+/* XXX FREE THE BUFFERS */
+int session_free( transport_session* session ) {
+       if( ! session ) { return 0; }
+
+       if( session->sock_obj ) 
+               free( session->sock_obj );
+
+       if( session->state_machine ) free( session->state_machine );
+       if( session->parser_ctxt) {
+               xmlCleanupCharEncodingHandlers();
+               xmlFreeDoc( session->parser_ctxt->myDoc );
+               xmlFreeParserCtxt(session->parser_ctxt);
+       }
+       xmlCleanupParser();
+
+       buffer_free(session->body_buffer);
+       buffer_free(session->subject_buffer);
+       buffer_free(session->thread_buffer);
+       buffer_free(session->from_buffer);
+       buffer_free(session->recipient_buffer);
+       buffer_free(session->status_buffer);
+       buffer_free(session->message_error_type);
+       buffer_free(session->router_to_buffer);
+       buffer_free(session->router_from_buffer);
+       buffer_free(session->router_class_buffer);
+       buffer_free(session->router_command_buffer);
+
+
+       free( session );
+       return 1;
+}
+
+
+int session_wait( transport_session* session, int timeout ) {
+       if( ! session || ! session->sock_obj ) {
+               return 0;
+       }
+       int ret =  tcp_wait( session->sock_obj, timeout );
+       if( ! ret ) {
+               session->state_machine->connected = 0;
+       }
+       return ret;
+}
+
+int session_send_msg( 
+               transport_session* session, transport_message* msg ) {
+
+       if( ! session ) { return 0; }
+
+       if( ! session->state_machine->connected ) {
+               warning_handler("State machine is not connected in send_msg()");
+               return 0;
+       }
+
+       message_prepare_xml( msg );
+       tcp_send( session->sock_obj, msg->msg_xml );
+
+       return 1;
+
+}
+
+
+/* connects to server and connects to jabber */
+int session_connect( transport_session* session, 
+               const char* username, const char* password, const char* resource, int connect_timeout ) {
+
+       int size1 = 0;
+       int size2 = 0;
+
+       if( ! session ) { 
+               warning_handler( "session is null in connect" );
+               return 0; 
+       }
+
+
+       /*
+       session->state_machine->connected = 
+               tcp_connected( session->sock_obj );
+
+       if( session->state_machine->connected ) {
+               return 1;
+       }
+       */
+
+
+       char* server = session->sock_obj->server;
+
+       if( ! session->sock_obj ) {
+               return 0;
+       }
+
+       if( ! session->sock_obj->connected ) {
+               if( ! tcp_connect( session->sock_obj ))
+                       return 0;
+       }
+
+       /* the first Jabber connect stanza */
+       size1 = 100 + strlen( server );
+       char stanza1[ size1 ]; 
+       memset( stanza1, 0, size1 );
+       sprintf( stanza1, 
+                       "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>",
+                       server );
+       
+       /* the second jabber connect stanza including login info*/
+       /* currently, we only support plain text login */
+       size2 = 150 + strlen( username ) + strlen(password) + strlen(resource);
+       char stanza2[ size2 ];
+       memset( stanza2, 0, size2 );
+
+       sprintf( stanza2, 
+                       "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>%s</username><password>%s</password><resource>%s</resource></query></iq>",
+                       username, password, resource );
+
+       /* send the first stanze */
+       session->state_machine->connecting = CONNECTING_1;
+       if( ! tcp_send( session->sock_obj, stanza1 ) ) {
+               warning_handler("error sending");
+               return 0;
+       }
+
+       /* wait for reply */
+       tcp_wait( session->sock_obj, connect_timeout ); /* make the timeout smarter XXX */
+
+       /* server acknowledges our existence, now see if we can login */
+       if( session->state_machine->connecting == CONNECTING_2 ) {
+               if( ! tcp_send( session->sock_obj, stanza2 )  ) {
+                       warning_handler("error sending");
+                       return 0;
+               }
+       }
+
+       /* wait for reply */
+       tcp_wait( session->sock_obj, connect_timeout );
+
+
+       if( session->state_machine->connected ) {
+               /* yar! */
+               return 1;
+       }
+
+       return 0;
+}
+
+// ---------------------------------------------------------------------------------
+// TCP data callback. Shove the data into the push parser.
+// ---------------------------------------------------------------------------------
+void grab_incoming( void * session, char* data ) {
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+       xmlParseChunk(ses->parser_ctxt, data, strlen(data), 0);
+}
+
+
+void startElementHandler(
+       void *session, const xmlChar *name, const xmlChar **atts) {
+
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+
+       
+       if( strcmp( name, "message" ) == 0 ) {
+               ses->state_machine->in_message = 1;
+               buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
+               buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
+               buffer_add( ses->router_from_buffer, get_xml_attr( atts, "router_from" ) );
+               buffer_add( ses->router_to_buffer, get_xml_attr( atts, "router_to" ) );
+               buffer_add( ses->router_class_buffer, get_xml_attr( atts, "router_class" ) );
+               buffer_add( ses->router_command_buffer, get_xml_attr( atts, "router_command" ) );
+               char* broadcast = get_xml_attr( atts, "broadcast" );
+               if( broadcast )
+                       ses->router_broadcast = atoi( broadcast );
+
+               return;
+       }
+
+       if( ses->state_machine->in_message ) {
+
+               if( strcmp( name, "body" ) == 0 ) {
+                       ses->state_machine->in_message_body = 1;
+                       return;
+               }
+       
+               if( strcmp( name, "subject" ) == 0 ) {
+                       ses->state_machine->in_subject = 1;
+                       return;
+               }
+       
+               if( strcmp( name, "thread" ) == 0 ) {
+                       ses->state_machine->in_thread = 1;
+                       return;
+               }
+
+       }
+
+       if( strcmp( name, "presence" ) == 0 ) {
+               ses->state_machine->in_presence = 1;
+               buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
+               buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
+               return;
+       }
+
+       if( strcmp( name, "status" ) == 0 ) {
+               ses->state_machine->in_status = 1;
+               return;
+       }
+
+
+       if( strcmp( name, "stream:error" ) == 0 ) {
+               ses->state_machine->in_error = 1;
+               warning_handler( "Received <stream:error> message from Jabber server" );
+               return;
+       }
+
+
+       /* first server response from a connect attempt */
+       if( strcmp( name, "stream:stream" ) == 0 ) {
+               if( ses->state_machine->connecting == CONNECTING_1 ) {
+                       ses->state_machine->connecting = CONNECTING_2;
+               }
+       }
+
+       if( strcmp( name, "error" ) == 0 ) {
+               ses->state_machine->in_message_error = 1;
+               buffer_add( ses->message_error_type, get_xml_attr( atts, "type" ) );
+               ses->message_error_code = atoi( get_xml_attr( atts, "code" ) );
+               warning_handler( "Received <error> message" );
+               return;
+       }
+
+       if( strcmp( name, "iq" ) == 0 ) {
+               ses->state_machine->in_iq = 1;
+
+               if( strcmp( get_xml_attr(atts, "type"), "result") == 0 
+                               && ses->state_machine->connecting == CONNECTING_2 ) {
+                       ses->state_machine->connected = 1;
+                       ses->state_machine->connecting = 0;
+                       return;
+               }
+
+               if( strcmp( get_xml_attr(atts, "type"), "error") == 0 ) {
+                       warning_handler( "Error connecting to jabber" );
+                       return;
+               }
+       }
+}
+
+char* get_xml_attr( const xmlChar** atts, char* attr_name ) {
+       int i;
+       if (atts != NULL) {
+               for(i = 0;(atts[i] != NULL);i++) {
+                       if( strcmp( atts[i++], attr_name ) == 0 ) {
+                               if( atts[i] != NULL ) {
+                                       return (char*) atts[i];
+                               }
+                       }
+               }
+       }
+       return NULL;
+}
+
+
+// ------------------------------------------------------------------
+// See which tags are ending
+// ------------------------------------------------------------------
+void endElementHandler( void *session, const xmlChar *name) {
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+
+       if( strcmp( name, "message" ) == 0 ) {
+
+               /* pass off the message info the callback */
+               if( ses->message_callback ) {
+
+                       /* here it's ok to pass in the raw buffers because
+                               message_init allocates new space for the chars 
+                               passed in */
+                       transport_message* msg =  message_init( 
+                               ses->body_buffer->buf, 
+                               ses->subject_buffer->buf,
+                               ses->thread_buffer->buf, 
+                               ses->recipient_buffer->buf, 
+                               ses->from_buffer->buf );
+
+                       message_set_router_info( msg, 
+                               ses->router_from_buffer->buf, 
+                               ses->router_to_buffer->buf, 
+                               ses->router_class_buffer->buf,
+                               ses->router_command_buffer->buf,
+                               ses->router_broadcast );
+
+                       if( ses->message_error_type->n_used > 0 ) {
+                               set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
+                       }
+
+                       if( msg == NULL ) { return; }
+                       ses->message_callback( ses->user_data, msg );
+               }
+
+               ses->state_machine->in_message = 0;
+               reset_session_buffers( session );
+               return;
+       }
+       
+       if( strcmp( name, "body" ) == 0 ) {
+               ses->state_machine->in_message_body = 0;
+               return;
+       }
+
+       if( strcmp( name, "subject" ) == 0 ) {
+               ses->state_machine->in_subject = 0;
+               return;
+       }
+
+       if( strcmp( name, "thread" ) == 0 ) {
+               ses->state_machine->in_thread = 0;
+               return;
+       }
+       
+       if( strcmp( name, "iq" ) == 0 ) {
+               ses->state_machine->in_iq = 0;
+               if( ses->message_error_code > 0 ) {
+                       warning_handler( "Error in IQ packet: code %d",  ses->message_error_code );
+                       warning_handler( "Error 401 means not authorized" );
+               }
+               reset_session_buffers( session );
+               return;
+       }
+
+       if( strcmp( name, "presence" ) == 0 ) {
+               ses->state_machine->in_presence = 0;
+               /*
+               if( ses->presence_callback ) {
+                       // call the callback with the status, etc.
+               }
+               */
+               reset_session_buffers( session );
+               return;
+       }
+
+       if( strcmp( name, "status" ) == 0 ) {
+               ses->state_machine->in_status = 0;
+               return;
+       }
+
+       if( strcmp( name, "error" ) == 0 ) {
+               ses->state_machine->in_message_error = 0;
+               return;
+       }
+
+       if( strcmp( name, "error:error" ) == 0 ) {
+               ses->state_machine->in_error = 0;
+               return;
+       }
+}
+
+int reset_session_buffers( transport_session* ses ) {
+       buffer_reset( ses->body_buffer );
+       buffer_reset( ses->subject_buffer );
+       buffer_reset( ses->thread_buffer );
+       buffer_reset( ses->from_buffer );
+       buffer_reset( ses->recipient_buffer );
+       buffer_reset( ses->router_from_buffer );
+       buffer_reset( ses->router_to_buffer );
+       buffer_reset( ses->router_class_buffer );
+       buffer_reset( ses->router_command_buffer );
+       buffer_reset( ses->message_error_type );
+
+       return 1;
+}
+
+// ------------------------------------------------------------------
+// takes data out of the body of the message and pushes it into
+// the appropriate buffer
+// ------------------------------------------------------------------
+void characterHandler(
+               void *session, const xmlChar *ch, int len) {
+
+       char data[len+1];
+       memset( data, 0, len+1 );
+       strncpy( data, (char*) ch, len );
+       data[len] = 0;
+
+       //printf( "Handling characters: %s\n", data );
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+
+       /* set the various message parts */
+       if( ses->state_machine->in_message ) {
+
+               if( ses->state_machine->in_message_body ) {
+                       buffer_add( ses->body_buffer, data );
+               }
+
+               if( ses->state_machine->in_subject ) {
+                       buffer_add( ses->subject_buffer, data );
+               }
+
+               if( ses->state_machine->in_thread ) {
+                       buffer_add( ses->thread_buffer, data );
+               }
+       }
+
+       /* set the presence status */
+       if( ses->state_machine->in_presence && ses->state_machine->in_status ) {
+               buffer_add( ses->status_buffer, data );
+       }
+
+       if( ses->state_machine->in_error ) {
+               /* for now... */
+               warning_handler( "ERROR Xml fragment: %s\n", ch );
+       }
+
+}
+
+/* XXX change to warning handlers */
+void  parseWarningHandler( void *session, const char* msg, ... ) {
+
+       va_list args;
+       va_start(args, msg);
+       fprintf(stdout, "WARNING");
+       vfprintf(stdout, msg, args);
+       va_end(args);
+       fprintf(stderr, "XML WARNING: %s\n", msg ); 
+}
+
+void  parseErrorHandler( void *session, const char* msg, ... ){
+
+       va_list args;
+       va_start(args, msg);
+       fprintf(stdout, "ERROR");
+       vfprintf(stdout, msg, args);
+       va_end(args);
+       fprintf(stderr, "XML ERROR: %s\n", msg ); 
+
+}
+
+int session_disconnect( transport_session* session ) {
+       if( session == NULL ) { return 0; }
+       tcp_send( session->sock_obj, "</stream:stream>");
+       return tcp_disconnect( session->sock_obj );
+}
+
diff --git a/src/libtransport/transport_socket.c b/src/libtransport/transport_socket.c
new file mode 100644 (file)
index 0000000..6a9446c
--- /dev/null
@@ -0,0 +1,289 @@
+#include "transport_socket.h"
+
+
+/*
+int main( char* argc, char** argv ) {
+
+       transport_socket sock_obj;
+       sock_obj.port = 5222;
+       sock_obj.server = "10.0.0.4";
+       sock_obj.data_received_callback = &print_stuff;
+
+       printf("connecting...\n");
+       if( (tcp_connect( &sock_obj )) < 0 ) {
+               printf( "error connecting" );
+       }
+
+       printf("sending...\n");
+       if( tcp_send( &sock_obj, "<stream>\n" ) < 0 ) {
+               printf( "error sending" );
+       }
+       
+       printf("waiting...\n");
+       if( tcp_wait( &sock_obj, 15 ) < 0 ) {
+               printf( "error receiving" );
+       }
+
+       printf("disconnecting...\n");
+       tcp_disconnect( &sock_obj );
+
+}
+*/
+
+
+// returns the socket fd, -1 on error
+int tcp_connect( transport_socket* sock_obj ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "connect(): null sock_obj" );
+               return -1;
+       }
+       struct sockaddr_in remoteAddr, localAddr;
+       struct hostent *hptr;
+       int sock_fd;
+
+       #ifdef WIN32
+       WSADATA data;
+       char bfr;
+       if( WSAStartup(MAKEWORD(1,1), &data) ) {
+               fatal_handler( "somethin's broke with windows socket startup" );
+               return -1;
+       }
+       #endif
+
+
+
+       // ------------------------------------------------------------------
+       // Create the socket
+       // ------------------------------------------------------------------
+       if( (sock_fd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) {
+               fatal_handler( "tcp_connect(): Cannot create socket" );
+               return -1;
+       }
+
+       // ------------------------------------------------------------------
+       // Get the hostname
+       // ------------------------------------------------------------------
+       if( (hptr = gethostbyname( sock_obj->server ) ) == NULL ) {
+               fatal_handler( "tcp_connect(): Unknown Host" );
+               return -1;
+       }
+
+       // ------------------------------------------------------------------
+       // Construct server info struct
+       // ------------------------------------------------------------------
+       memset( &remoteAddr, 0, sizeof(remoteAddr));
+       remoteAddr.sin_family = AF_INET;
+       remoteAddr.sin_port = htons( sock_obj->port );
+       memcpy( (char*) &remoteAddr.sin_addr.s_addr,
+                       hptr->h_addr_list[0], hptr->h_length );
+
+       // ------------------------------------------------------------------
+       // Construct local info struct
+       // ------------------------------------------------------------------
+       memset( &localAddr, 0, sizeof( localAddr ) );
+       localAddr.sin_family = AF_INET;
+       localAddr.sin_addr.s_addr = htonl( INADDR_ANY );
+       localAddr.sin_port = htons(0);
+
+       // ------------------------------------------------------------------
+       // Bind to a local port
+       // ------------------------------------------------------------------
+       if( bind( sock_fd, (struct sockaddr *) &localAddr, sizeof( localAddr ) ) < 0 ) {
+               fatal_handler( "tcp_connect(): Cannot bind to local port" );
+               return -1;
+       }
+
+       // ------------------------------------------------------------------
+       // Connect to server
+       // ------------------------------------------------------------------
+       if( connect( sock_fd, (struct sockaddr*) &remoteAddr, sizeof( struct sockaddr_in ) ) < 0 ) {
+               fatal_handler( "tcp_connect(): Cannot connect to server %s", sock_obj->server );
+               return -1;
+       }
+
+       sock_obj->sock_fd = sock_fd;
+       sock_obj->connected = 1;
+       return sock_fd;
+
+}
+
+
+int tcp_send( transport_socket* sock_obj, const char* data ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "tcp_send(): null sock_obj" );
+               return 0;
+       }
+
+       //fprintf( stderr, "TCP Sending: \n%s\n", data );
+
+       // ------------------------------------------------------------------
+       // Send the data down the TCP pipe
+       // ------------------------------------------------------------------
+       if( send( sock_obj->sock_fd, data, strlen(data), 0 ) < 0 ) {
+               fatal_handler( "tcp_send(): Error sending data" );
+               return 0;
+       }
+       return 1;
+}
+
+
+int tcp_disconnect( transport_socket* sock_obj ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "tcp_disconnect(): null sock_obj" );
+               return -1;
+       }
+
+       if( close( sock_obj->sock_fd ) == -1 ) {
+
+               // ------------------------------------------------------------------
+               // Not really worth throwing an exception for... should be logged.
+               // ------------------------------------------------------------------
+               warning_handler( "tcp_disconnect(): Error closing socket" );
+               return -1;
+       } 
+
+       return 0;
+}
+
+// ------------------------------------------------------------------
+// And now for the gory C socket code.
+// Returns 0 on failure, 1 otherwise
+// ------------------------------------------------------------------
+int tcp_wait( transport_socket* sock_obj, int timeout ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "tcp_wait(): null sock_obj" );
+               return 0;
+       }
+
+       int n = 0; 
+       int retval = 0;
+       char buf[BUFSIZE];
+       int sock_fd = sock_obj->sock_fd;
+
+
+       fd_set read_set;
+
+       FD_ZERO( &read_set );
+       FD_SET( sock_fd, &read_set );
+
+       // ------------------------------------------------------------------
+       // Build the timeval struct
+       // ------------------------------------------------------------------
+       struct timeval tv;
+       tv.tv_sec = timeout;
+       tv.tv_usec = 0;
+
+       if( timeout == -1 ) {  
+
+               // ------------------------------------------------------------------
+               // If timeout is -1, there is no timeout passed to the call to select
+               // ------------------------------------------------------------------
+               if( (retval = select( sock_fd + 1 , &read_set, NULL, NULL, NULL)) == -1 ) {
+                       warning_handler( "Call to select failed" );
+                       return 0;
+               }
+
+       } else if( timeout != 0 ) { /* timeout of 0 means don't block */
+
+               if( (retval = select( sock_fd + 1 , &read_set, NULL, NULL, &tv)) == -1 ) {
+                       warning_handler( "Call to select failed" );
+                       return 0;
+               }
+       }
+
+       memset( &buf, 0, BUFSIZE );
+
+       if( set_fl( sock_fd, O_NONBLOCK ) < 0 ) 
+               return 0;
+
+#ifdef _ROUTER // just read one buffer full of data
+
+       n = recv(sock_fd, buf, BUFSIZE-1, 0);
+       sock_obj->data_received_callback( sock_obj->user_data, buf );
+       if( n == 0 )
+               n = -1;
+
+#else // read everything we can
+
+       while( (n = recv(sock_fd, buf, BUFSIZE-1, 0) ) > 0 ) {
+               //printf("\nReceived:  %s\n", buf);
+               sock_obj->data_received_callback( sock_obj->user_data, buf );
+               memset( &buf, 0, BUFSIZE );
+       }
+
+#endif
+
+       if( clr_fl( sock_fd, O_NONBLOCK ) < 0 ) {
+               return 0;
+       }
+
+       if( n < 0 ) { 
+               if( errno != EAGAIN ) { 
+                       warning_handler( " * Error reading socket with errno %d", errno );
+                       return 0;
+               }
+       }
+
+#ifdef _ROUTER
+       return n;
+#else
+       return 1;
+#endif
+
+}
+
+int set_fl( int fd, int flags ) {
+       
+       int val;
+
+       if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) {
+               fatal_handler("fcntl F_GETFL error");
+               return -1;
+       }
+
+       val |= flags;
+
+       if( fcntl( fd, F_SETFL, val ) < 0 ) {
+               fatal_handler( "fcntl F_SETFL error" );
+               return -1;
+       }
+       return 0;
+}
+       
+int clr_fl( int fd, int flags ) {
+       
+       int val;
+
+       if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) {
+               fatal_handler("fcntl F_GETFL error" );
+               return -1;
+       }
+
+       val &= ~flags;
+
+       if( fcntl( fd, F_SETFL, val ) < 0 ) {
+               fatal_handler( "fcntl F_SETFL error" );
+               return -1;
+       }
+       return 0;
+}
+       
+
+/*
+int tcp_connected( transport_socket* obj ) {
+
+       int ret;
+       if( ! obj->sock_fd ) { return 0; }
+
+       ret = read( obj->sock_fd  , NULL,0 );
+       if( ret <= 0 ) {
+               return 0;
+       }
+       return 1;
+}
+*/
+
diff --git a/src/patch/README b/src/patch/README
new file mode 100644 (file)
index 0000000..3865013
--- /dev/null
@@ -0,0 +1,8 @@
+Patch                                          Source                          Location                                                                                Purpose
+------------------------------------------------------------------------------------------------------
+
+mod_offline.c                  jabberd-2.0s4           jabberd-2.0s4/sm/mod_offline.c          Disables offline storage 
+------------------------------------------------------------------------------------------------------
+
+nad.c                                          jabberd-2.0s4           jabberd-2.0s4/util/nad.c                                Fixes segfault in a branch of code that we don't use 
+
diff --git a/src/patch/mod_offline.c b/src/patch/mod_offline.c
new file mode 100644 (file)
index 0000000..4234cd7
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * jabberd - Jabber Open Source Server
+ * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
+ *                    Ryan Eatmon, Robert Norris
+ *
+ * This program 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
+ */
+
+
+/** ! ! !  Patched version to disable offline storage for jabberd-2.0s4 ! ! ! */
+
+#include "sm.h"
+
+/** @file sm/mod_offline.c
+  * @brief offline storage
+  * @author Robert Norris
+  * $Date$
+  * $Revision$
+  */
+
+typedef struct _mod_offline_st {
+    int dropmessages;
+    int dropsubscriptions;
+} *mod_offline_t;
+
+static mod_ret_t _offline_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) {
+    st_ret_t ret;
+    os_t os;
+    os_object_t o;
+    os_type_t ot;
+    nad_t nad;
+    pkt_t queued;
+    int ns, elem, attr;
+    char cttl[15], cstamp[18];
+    time_t ttl, stamp;
+
+    /* if they're becoming available for the first time */
+    if(pkt->type == pkt_PRESENCE && pkt->to == NULL && sess->user->top == NULL) {
+
+        ret = storage_get(pkt->sm->st, "queue", jid_user(sess->jid), NULL, &os);
+        if(ret != st_SUCCESS) {
+            log_debug(ZONE, "storage_get returned %d", ret);
+            return mod_PASS;
+        }
+        
+        if(os_iter_first(os))
+            do {
+                o = os_iter_object(os);
+
+                if(os_object_get(o, "xml", (void **) &nad, &ot)) {
+                    queued = pkt_new(pkt->sm, nad_copy(nad));
+                    if(queued == NULL) {
+                        log_debug(ZONE, "invalid queued packet, not delivering");
+                    } else {
+                        /* check expiry as necessary */
+                        if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 &&
+                           (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                           (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) {
+                            snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                            ttl = atoi(cttl);
+
+                            /* it should have a x:delay stamp, because we stamp everything we store */
+                            if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 &&
+                               (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                               (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) {
+                                snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                                stamp = datetime_in(cstamp);
+
+                                if(stamp + ttl <= time(NULL)) {
+                                    log_debug(ZONE, "queued packet has expired, dropping");
+                                    pkt_free(queued);
+                                    continue;
+                                }
+                            }
+                        }
+
+                        log_debug(ZONE, "delivering queued packet to %s", jid_full(sess->jid));
+                        pkt_sess(queued, sess);
+                    }
+                }
+            } while(os_iter_next(os));
+
+        os_free(os);
+
+        /* drop the spool */
+        storage_delete(pkt->sm->st, "queue", jid_user(sess->jid), NULL);
+    }
+
+    /* pass it so that other modules and mod_presence can get it */
+    return mod_PASS;
+}
+
+static mod_ret_t _offline_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) {
+    mod_offline_t offline = (mod_offline_t) mi->mod->private;
+    int ns, elem, attr;
+    os_t os;
+    os_object_t o;
+    pkt_t event;
+
+    /* send messages and s10ns to the top session */
+    if(user->top != NULL && (pkt->type & pkt_MESSAGE || pkt->type & pkt_S10N)) {
+        pkt_sess(pkt, user->top);
+        return mod_HANDLED;
+    }
+
+    /* save messages and s10ns for later */
+    if((pkt->type & pkt_MESSAGE && !offline->dropmessages) ||
+       (pkt->type & pkt_S10N && !offline->dropsubscriptions)) {
+        log_debug(ZONE, "saving message for later");
+
+        pkt_delay(pkt, time(NULL), user->sm->id);
+
+        /* new object */
+        os = os_new();
+        o = os_object_new(os);
+
+        os_object_put(o, "xml", pkt->nad, os_type_NAD);
+
+        /* store it */
+        switch(storage_put(user->sm->st, "queue", jid_user(user->jid), os)) {
+            case st_FAILED:
+                os_free(os);
+                return -stanza_err_INTERNAL_SERVER_ERROR;
+
+            case st_NOTIMPL:
+                os_free(os);
+                return -stanza_err_SERVICE_UNAVAILABLE;     /* xmpp-im 9.5#4 */
+
+            default:
+                os_free(os);
+
+                /* send offline events if they asked for it */
+                if((ns = nad_find_scoped_namespace(pkt->nad, uri_EVENT, NULL)) >= 0 &&
+                   (elem = nad_find_elem(pkt->nad, 1, ns, "x", 1)) >= 0 &&
+                   nad_find_elem(pkt->nad, elem, ns, "offline", 1) >= 0) {
+
+                    event = pkt_create(user->sm, "message", NULL, jid_full(pkt->from), jid_full(pkt->to));
+
+                    attr = nad_find_attr(pkt->nad, 1, -1, "type", NULL);
+                    if(attr >= 0)
+                        nad_set_attr(event->nad, 1, -1, "type", NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr));
+
+                    ns = nad_add_namespace(event->nad, uri_EVENT, NULL);
+                    nad_append_elem(event->nad, ns, "x", 2);
+                    nad_append_elem(event->nad, ns, "offline", 3);
+
+                    nad_append_elem(event->nad, ns, "id", 3);
+                    attr = nad_find_attr(pkt->nad, 1, -1, "id", NULL);
+                    if(attr >= 0)
+                        nad_append_cdata(event->nad, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr), 4);
+
+                    pkt_router(event);
+                }
+
+                pkt_free(pkt);
+                return mod_HANDLED;
+        }
+    }
+
+    return mod_PASS;
+}
+
+static void _offline_user_delete(mod_instance_t mi, jid_t jid) {
+    os_t os;
+    os_object_t o;
+    os_type_t ot;
+    nad_t nad;
+    pkt_t queued;
+    int ns, elem, attr;
+    char cttl[15], cstamp[18];
+    time_t ttl, stamp;
+
+    log_debug(ZONE, "deleting queue for %s", jid_user(jid));
+
+    /* bounce the queue */
+    if(storage_get(mi->mod->mm->sm->st, "queue", jid_user(jid), NULL, &os) == st_SUCCESS) {
+        if(os_iter_first(os))
+            do {
+                o = os_iter_object(os);
+
+                if(os_object_get(o, "xml", (void **) &nad, &ot)) {
+                    queued = pkt_new(mi->mod->mm->sm, nad);
+                    if(queued == NULL) {
+                        log_debug(ZONE, "invalid queued packet, not delivering");
+                    } else {
+                        /* check expiry as necessary */
+                        if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 &&
+                           (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                           (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) {
+                            snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                            ttl = atoi(cttl);
+
+                            /* it should have a x:delay stamp, because we stamp everything we store */
+                            if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 &&
+                               (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                               (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) {
+                                snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                                stamp = datetime_in(cstamp);
+
+                                if(stamp + ttl <= time(NULL)) {
+                                    log_debug(ZONE, "queued packet has expired, dropping");
+                                    pkt_free(queued);
+                                    continue;
+                                }
+                            }
+                        }
+
+                        log_debug(ZONE, "bouncing queued packet from %s", jid_full(queued->from));
+                        pkt_router(pkt_error(queued, stanza_err_ITEM_NOT_FOUND));
+                    }
+                }
+            } while(os_iter_next(os));
+
+        os_free(os);
+    }
+    
+    storage_delete(mi->sm->st, "queue", jid_user(jid), NULL);
+}
+
+static void _offline_free(module_t mod) {
+    mod_offline_t offline = (mod_offline_t) mod->private;
+
+    free(offline);
+}
+
+int offline_init(mod_instance_t mi, char *arg) {
+    module_t mod = mi->mod;
+    char *configval;
+    mod_offline_t offline;
+    int dropmessages = 0;
+    int dropsubscriptions = 0;
+
+    if(mod->init) return 0;
+
+    configval = config_get_one(mod->mm->sm->config, "offline.dropmessages", 0);
+    if (configval != NULL)
+        dropmessages = 1;
+    configval = config_get_one(mod->mm->sm->config, "offline.dropsubscriptions", 0);
+    if (configval != NULL)
+        dropsubscriptions = 1;
+
+    offline = (mod_offline_t) malloc(sizeof(struct _mod_offline_st));
+    offline->dropmessages = dropmessages;
+    offline->dropsubscriptions = dropsubscriptions;
+
+    mod->private = offline;
+
+    mod->in_sess = _offline_in_sess;
+    mod->pkt_user = _offline_pkt_user;
+    mod->user_delete = _offline_user_delete;
+    mod->free = _offline_free;
+
+    return 0;
+}
diff --git a/src/patch/nad.c b/src/patch/nad.c
new file mode 100644 (file)
index 0000000..d9914c4
--- /dev/null
@@ -0,0 +1,1155 @@
+/*
+ * jabberd - Jabber Open Source Server
+ * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
+ *                    Ryan Eatmon, Robert Norris
+ *
+ * This program 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
+ */
+
+
+/** ! ! ! Patched version to fix segfault issue for jabberd-2.0s4 ! ! ! */
+
+/**
+ * !!! Things to do (after 2.0)
+ *
+ * - make nad_find_scoped_namespace() take an element index, and only search
+ *   the scope on that element (currently, it searchs all elements from
+ *   end to start, which isn't really correct, though it works in most cases
+ *
+ * - new functions:
+ *     * insert one nad (or part thereof) into another nad
+ *     * clear a part of a nad (like xmlnode_hide)
+ *
+ * - audit use of depth array and parent (see j2 bug #792)
+ */
+
+#include "util.h"
+
+#ifdef HAVE_EXPAT
+#include "expat/expat.h"
+#endif
+
+/* define NAD_DEBUG to get pointer tracking - great for weird bugs that you can't reproduce */
+#ifdef NAD_DEBUG
+
+static xht _nad_alloc_tracked = NULL;
+static xht _nad_free_tracked = NULL;
+
+static void _nad_ptr_check(const char *func, nad_t nad) {
+    char loc[24];
+    snprintf(loc, sizeof(loc), "%x", (int) nad);
+
+    if(xhash_get(_nad_alloc_tracked, loc) == NULL) {
+        fprintf(stderr, ">>> NAD OP %s: 0x%x not allocated!\n", func, (int) nad);
+        abort();
+    }
+
+    if(xhash_get(_nad_free_tracked, loc) != NULL) {
+        fprintf(stderr, ">>> NAD OP %s: 0x%x previously freed!\n", func, (int) nad);
+        abort();
+    }
+
+    fprintf(stderr, ">>> NAD OP %s: 0x%x\n", func, (int) nad);
+}
+#else
+#define _nad_ptr_check(func,nad)
+#endif
+
+#define BLOCKSIZE 1024
+
+/** internal: do and return the math and ensure it gets realloc'd */
+int _nad_realloc(void **oblocks, int len)
+{
+    void *nblocks;
+    int nlen;
+
+    /* round up to standard block sizes */
+    nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE;
+
+    /* keep trying till we get it */
+    while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1);
+    *oblocks = nblocks;
+    return nlen;
+}
+
+/** this is the safety check used to make sure there's always enough mem */
+#define NAD_SAFE(blocks, size, len) if((size) > len) len = _nad_realloc((void**)&(blocks),(size));
+
+/** internal: append some cdata and return the index to it */
+int _nad_cdata(nad_t nad, const char *cdata, int len)
+{
+    NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen);
+
+    memcpy(nad->cdata + nad->ccur, cdata, len);
+    nad->ccur += len;
+    return nad->ccur - len;
+}
+
+/** internal: create a new attr on any given elem */
+int _nad_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen)
+{
+    int attr;
+
+    /* make sure there's mem for us */
+    NAD_SAFE(nad->attrs, (nad->acur + 1) * sizeof(struct nad_attr_st), nad->alen);
+
+    attr = nad->acur;
+    nad->acur++;
+    nad->attrs[attr].next = nad->elems[elem].attr;
+    nad->elems[elem].attr = attr;
+    nad->attrs[attr].lname = strlen(name);
+    nad->attrs[attr].iname = _nad_cdata(nad,name,nad->attrs[attr].lname);
+    if(vallen > 0)
+        nad->attrs[attr].lval = vallen;
+    else
+        nad->attrs[attr].lval = strlen(val);
+    nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval);    
+    nad->attrs[attr].my_ns = ns;
+
+    return attr;
+}
+
+/** create a new cache, simple pointer to a list of nads */
+nad_cache_t nad_cache_new(void)
+{
+    nad_cache_t cache;
+    while((cache = malloc(sizeof(nad_cache_t))) == NULL) sleep(1);
+    *cache = NULL;
+
+#ifdef NAD_DEBUG
+    if(_nad_alloc_tracked == NULL) _nad_alloc_tracked = xhash_new(501);
+    if(_nad_free_tracked == NULL) _nad_free_tracked = xhash_new(501);
+#endif
+    
+    return cache;
+}
+
+
+/** free the cache and any nads in it */
+void nad_cache_free(nad_cache_t cache)
+{
+    nad_t cur;
+    while((cur = *cache) != NULL)
+    {
+        *cache = cur->next;
+        free(cur->elems);
+        free(cur->attrs);
+        free(cur->nss);
+        free(cur->cdata);
+        free(cur->depths);
+        free(cur);
+    }
+    free(cache);
+}
+
+/** get the next nad from the cache, or create some */
+nad_t nad_new(nad_cache_t cache)
+{
+    nad_t nad;
+
+#ifndef NAD_DEBUG
+    /* If cache==NULL, then this NAD is not in a cache */
+
+    if ((cache!=NULL) && (*cache != NULL))
+    {
+        nad = *cache;
+        *cache = nad->next;
+        nad->ccur = nad->ecur = nad->acur = nad->ncur = 0;
+        nad->scope = -1;
+        nad->cache = cache;
+        nad->next = NULL;
+        return nad;
+    }
+#endif
+
+    while((nad = malloc(sizeof(struct nad_st))) == NULL) sleep(1);
+    memset(nad,0,sizeof(struct nad_st));
+
+    nad->scope = -1;
+    nad->cache = cache;
+
+#ifdef NAD_DEBUG
+    {
+    char loc[24];
+    snprintf(loc, sizeof(loc), "%x", (int) nad);
+    xhash_put(_nad_alloc_tracked, pstrdup(xhash_pool(_nad_alloc_tracked), loc), (void *) 1);
+    }
+    _nad_ptr_check(__func__, nad);
+#endif
+
+    return nad;
+}
+
+nad_t nad_copy(nad_t nad)
+{
+    nad_t copy;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(nad == NULL) return NULL;
+
+    /* create a new nad not participating in a cache */
+    copy = nad_new(NULL);
+
+    /* if it's not large enough, make bigger */
+    NAD_SAFE(copy->elems, nad->elen, copy->elen);
+    NAD_SAFE(copy->attrs, nad->alen, copy->alen);
+    NAD_SAFE(copy->nss, nad->nlen, copy->nlen);
+    NAD_SAFE(copy->cdata, nad->clen, copy->clen);
+
+    /* copy all data */
+    memcpy(copy->elems, nad->elems, nad->elen);
+    memcpy(copy->attrs, nad->attrs, nad->alen);
+    memcpy(copy->nss, nad->nss, nad->nlen);
+    memcpy(copy->cdata, nad->cdata, nad->clen);
+
+    /* sync data */
+    copy->ecur = nad->ecur;
+    copy->acur = nad->acur;
+    copy->ncur = nad->ncur;
+    copy->ccur = nad->ccur;
+
+    copy->scope = nad->scope;
+
+    return copy;
+}
+
+/** free nad, or plug nad back in the cache */
+void nad_free(nad_t nad)
+{
+    if(nad == NULL) return;
+
+#ifdef NAD_DEBUG
+    _nad_ptr_check(__func__, nad);
+    {
+    char loc[24];
+    snprintf(loc, sizeof(loc), "%x", (int) nad);
+    xhash_zap(_nad_alloc_tracked, loc);
+    xhash_put(_nad_free_tracked, pstrdup(xhash_pool(_nad_free_tracked), loc), (void *) nad);
+    }
+#else
+    /* If nad->cache != NULL, then put back into cache, otherwise this nad is not in a cache */
+
+    if (nad->cache != NULL) {
+       nad->next = *(nad->cache);
+       *(nad->cache) = nad;
+       return;
+    } 
+#endif
+
+    /* Free nad */
+    free(nad->elems);
+    free(nad->attrs);
+    free(nad->cdata);
+    free(nad->nss);
+    free(nad->depths);
+}
+
+/** locate the next elem at a given depth with an optional matching name */
+int nad_find_elem(nad_t nad, int elem, int ns, const char *name, int depth)
+{
+    int my_ns;
+    int lname = 0;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure there are valid args */
+    if(elem >= nad->ecur || name == NULL) return -1;
+
+    /* set up args for searching */
+    depth = nad->elems[elem].depth + depth;
+    if(name != NULL) lname = strlen(name);
+
+    /* search */
+    for(elem++;elem < nad->ecur;elem++)
+    {
+        /* if we hit one with a depth less than ours, then we don't have the
+         * same parent anymore, bail */
+        if(nad->elems[elem].depth < depth)
+            return -1;
+
+        if(nad->elems[elem].depth == depth && (lname <= 0 || (lname == nad->elems[elem].lname && strncmp(name,nad->cdata + nad->elems[elem].iname, lname) == 0)) &&
+          (ns < 0 || ((my_ns = nad->elems[elem].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0)))
+            return elem;
+    }
+
+    return -1;
+}
+
+/** get a matching attr on this elem, both name and optional val */
+int nad_find_attr(nad_t nad, int elem, int ns, const char *name, const char *val)
+{
+    int attr, my_ns;
+    int lname, lval = 0;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure there are valid args */
+    if(elem >= nad->ecur || name == NULL) return -1;
+
+    attr = nad->elems[elem].attr;
+    lname = strlen(name);
+    if(val != NULL) lval = strlen(val);
+
+    while(attr >= 0)
+    {
+        /* hefty, match name and if a val, also match that */
+        if(lname == nad->attrs[attr].lname && strncmp(name,nad->cdata + nad->attrs[attr].iname, lname) == 0 && 
+          (lval <= 0 || (lval == nad->attrs[attr].lval && strncmp(val,nad->cdata + nad->attrs[attr].ival, lval) == 0)) &&
+          (ns < 0 || ((my_ns = nad->attrs[attr].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0)))
+            return attr;
+        attr = nad->attrs[attr].next;
+    }
+    return -1;
+}
+
+/** get a matching ns on this elem, both uri and optional prefix */
+int nad_find_namespace(nad_t nad, int elem, const char *uri, const char *prefix)
+{
+    int check, ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(uri == NULL)
+        return -1;
+
+    /* work backwards through our parents, looking for our namespace on each one.
+     * if we find it, link it. if not, the namespace is undeclared - for now, just drop it */
+    check = elem;
+    while(check >= 0)
+    {
+        ns = nad->elems[check].ns;
+        while(ns >= 0)
+        {
+            if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 && (prefix == NULL || (nad->nss[ns].iprefix >= 0 && strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0)))
+                return ns;
+            ns = nad->nss[ns].next;
+        }
+        check = nad->elems[check].parent;
+    }
+
+    return -1;
+}
+
+/** find a namespace in scope */
+int nad_find_scoped_namespace(nad_t nad, const char *uri, const char *prefix)
+{
+    int ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(uri == NULL)
+        return -1;
+
+    for(ns = 0; ns < nad->ncur; ns++)
+    {
+        if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 &&
+           (prefix == NULL ||
+             (nad->nss[ns].iprefix >= 0 &&
+              strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0)))
+            return ns;
+    }
+
+    return -1;
+}
+
+/** create, update, or zap any matching attr on this elem */
+void nad_set_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen)
+{
+    int attr;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* find one to replace first */
+    if((attr = nad_find_attr(nad, elem, ns, name, NULL)) < 0)
+    {
+        /* only create new if there's a value to store */
+        if(val != NULL)
+            _nad_attr(nad, elem, ns, name, val, vallen);
+        return;
+    }
+
+    /* got matching, update value or zap */
+    if(val == NULL)
+    {
+        nad->attrs[attr].lval = nad->attrs[attr].lname = 0;
+    }else{
+        if(vallen > 0)
+            nad->attrs[attr].lval = vallen;
+        else
+            nad->attrs[attr].lval = strlen(val);
+        nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval);
+    }
+
+}
+
+/** shove in a new child elem after the given one */
+int nad_insert_elem(nad_t nad, int parent, int ns, const char *name, const char *cdata)
+{
+    int elem = parent + 1;
+
+    _nad_ptr_check(__func__, nad);
+
+    NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+    /* relocate all the rest of the elems (unless we're at the end already) */
+    if(nad->ecur != elem)
+        memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st));
+    nad->ecur++;
+
+    /* set up req'd parts of new elem */
+    nad->elems[elem].parent = parent;
+    nad->elems[elem].lname = strlen(name);
+    nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+    nad->elems[elem].attr = -1;
+    nad->elems[elem].ns = nad->scope; nad->scope = -1;
+    nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+    nad->elems[elem].my_ns = ns;
+
+    /* add cdata if given */
+    if(cdata != NULL)
+    {
+        nad->elems[elem].lcdata = strlen(cdata);
+        nad->elems[elem].icdata = _nad_cdata(nad,cdata,nad->elems[elem].lcdata);
+    }else{
+        nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+    }
+
+    /* parent/child */
+    nad->elems[elem].depth = nad->elems[parent].depth + 1;
+
+    return elem;
+}
+
+/** wrap an element with another element */
+void nad_wrap_elem(nad_t nad, int elem, int ns, const char *name)
+{
+    int cur;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(elem >= nad->ecur) return;
+
+    NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+    /* relocate all the rest of the elems after us */
+    memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st));
+    nad->ecur++;
+
+    /* set up req'd parts of new elem */
+    nad->elems[elem].lname = strlen(name);
+    nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+    nad->elems[elem].attr = -1;
+    nad->elems[elem].ns = nad->scope; nad->scope = -1;
+    nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+    nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+    nad->elems[elem].my_ns = ns;
+
+    /* raise the bar on all the children */
+    nad->elems[elem+1].depth++;
+    for(cur = elem + 2; cur < nad->ecur && nad->elems[cur].depth > nad->elems[elem].depth; cur++) nad->elems[cur].depth++;
+
+    /* relink the parents */
+    nad->elems[elem].parent = nad->elems[elem + 1].parent;
+    nad->elems[elem + 1].parent = elem;
+}
+
+/** create a new elem on the list */
+int nad_append_elem(nad_t nad, int ns, const char *name, int depth)
+{
+    int elem;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure there's mem for us */
+    NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+    elem = nad->ecur;
+    nad->ecur++;
+    nad->elems[elem].lname = strlen(name);
+    nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+    nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+    nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+    nad->elems[elem].attr = -1;
+    nad->elems[elem].ns = nad->scope; nad->scope = -1;
+    nad->elems[elem].depth = depth;
+    nad->elems[elem].my_ns = ns;
+
+    /* make sure there's mem in the depth array, then track us */
+    NAD_SAFE(nad->depths, (depth + 1) * sizeof(int), nad->dlen);
+    nad->depths[depth] = elem;
+
+    /* our parent is the previous guy in the depth array */
+    if(depth <= 0)
+        nad->elems[elem].parent = -1;
+    else
+        nad->elems[elem].parent = nad->depths[depth - 1];
+
+    return elem;
+}
+
+/** attach new attr to the last elem */
+int nad_append_attr(nad_t nad, int ns, const char *name, const char *val)
+{
+    _nad_ptr_check(__func__, nad);
+
+    return _nad_attr(nad, nad->ecur - 1, ns, name, val, 0);
+}
+
+/** append new cdata to the last elem */
+void nad_append_cdata(nad_t nad, const char *cdata, int len, int depth)
+{
+    int elem = nad->ecur - 1;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure this cdata is the child of the last elem to append */
+    if(nad->elems[elem].depth == depth - 1)
+    {
+        if(nad->elems[elem].icdata == 0)
+            nad->elems[elem].icdata = nad->ccur;
+        _nad_cdata(nad,cdata,len);
+        nad->elems[elem].lcdata += len;
+        return;
+    }
+
+    /* otherwise, pin the cdata on the tail of the last element at this depth */
+    elem = nad->depths[depth];
+    if(nad->elems[elem].itail == 0)
+        nad->elems[elem].itail = nad->ccur;
+    _nad_cdata(nad,cdata,len);
+    nad->elems[elem].ltail += len;
+}
+
+/** bring a new namespace into scope */
+int nad_add_namespace(nad_t nad, const char *uri, const char *prefix)
+{
+    int ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* only add it if its not already in scope */
+    ns = nad_find_scoped_namespace(nad, uri, NULL);
+    if(ns >= 0)
+        return ns;
+
+    /* make sure there's mem for us */
+    NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen);
+
+    ns = nad->ncur;
+    nad->ncur++;
+    nad->nss[ns].next = nad->scope;
+    nad->scope = ns;
+
+    nad->nss[ns].luri = strlen(uri);
+    nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri);
+    if(prefix != NULL)
+    {
+        nad->nss[ns].lprefix = strlen(prefix);
+        nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix);    
+    }
+    else
+        nad->nss[ns].iprefix = -1;
+
+    return ns;
+}
+
+/** declare a namespace on an already-existing element */
+int nad_append_namespace(nad_t nad, int elem, const char *uri, const char *prefix) {
+    int ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* see if its already scoped on this element */
+    ns = nad_find_namespace(nad, elem, uri, NULL);
+    if(ns >= 0)
+        return ns;
+
+    /* make some room */
+    NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen);
+
+    ns = nad->ncur;
+    nad->ncur++;
+    nad->nss[ns].next = nad->elems[elem].ns;
+    nad->elems[elem].ns = ns;
+
+    nad->nss[ns].luri = strlen(uri);
+    nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri);
+    if(prefix != NULL)
+    {
+        nad->nss[ns].lprefix = strlen(prefix);
+        nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix);    
+    }
+    else
+        nad->nss[ns].iprefix = -1;
+
+    return ns;
+}
+
+void _nad_escape(nad_t nad, int data, int len, int flag)
+{
+    char *c;
+    int ic;
+
+    if(len <= 0) return;
+
+    /* first, if told, find and escape ' */
+    while(flag >= 3 && (c = memchr(nad->cdata + data,'\'',len)) != NULL)
+    {
+        /* get offset */
+        ic = c - nad->cdata;
+
+        /* cute, eh?  handle other data before this normally */
+        _nad_escape(nad, data, ic - data, 2);
+
+        /* ensure enough space, and add our escaped &apos; */
+        NAD_SAFE(nad->cdata, nad->ccur + 6, nad->clen);
+        memcpy(nad->cdata + nad->ccur, "&apos;", 6);
+        nad->ccur += 6;
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* next look for < */
+    while(flag >= 2 && (c = memchr(nad->cdata + data,'<',len)) != NULL)
+    {
+        ic = c - nad->cdata;
+        _nad_escape(nad, data, ic - data, 1);
+
+        /* ensure enough space, and add our escaped &lt; */
+        NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+        memcpy(nad->cdata + nad->ccur, "&lt;", 4);
+        nad->ccur += 4;
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* check for ]]>, we need to escape the > */
+       /* WE DID THIS  (add the '0' as the first test to the while loop 
+               because the loops dies 3 lines in... (and we don't reall
+               need this)) ...
+        */
+    while( 0 && flag >= 1 && (c = memchr(nad->cdata + data, '>', len)) != NULL)
+    {
+        ic = c - nad->cdata;
+
+        _nad_escape(nad, data, ic - data, 0);
+
+        /* check for the sequence */
+
+        if( c >= nad->cdata + 2 && c[-1] == ']' && c[-2] == ']')
+        {
+            /* ensure enough space, and add our escaped &gt; */
+            NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+            memcpy(nad->cdata + nad->ccur, "&gt;", 4);
+            nad->ccur += 4;
+        }
+
+        /* otherwise, just plug the > in as-is */
+        else
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+            *(nad->cdata + nad->ccur) = '>';
+            nad->ccur++;
+        }
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* if & is found, escape it */
+    while((c = memchr(nad->cdata + data,'&',len)) != NULL)
+    {
+        ic = c - nad->cdata;
+
+        /* ensure enough space */
+        NAD_SAFE(nad->cdata, nad->ccur + 5 + (ic - data), nad->clen);
+
+        /* handle normal data */
+        memcpy(nad->cdata + nad->ccur, nad->cdata + data, (ic - data));
+        nad->ccur += (ic - data);
+
+        /* append escaped &lt; */
+        memcpy(nad->cdata + nad->ccur, "&amp;", 5);
+        nad->ccur += 5;
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* nothing exciting, just append normal cdata */
+    NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen);
+    memcpy(nad->cdata + nad->ccur, nad->cdata + data, len);
+    nad->ccur += len;
+}
+
+/** internal recursive printing function */
+int _nad_lp0(nad_t nad, int elem)
+{
+    int attr;
+    int ndepth;
+    int ns;
+
+    /* there's a lot of code in here, but don't let that scare you, it's just duplication in order to be a bit more efficient cpu-wise */
+
+    /* this whole thing is in a big loop for processing siblings */
+    while(elem != nad->ecur)
+    {
+
+    /* make enough space for the opening element */
+    ns = nad->elems[elem].my_ns;
+    if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+    {
+        NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + nad->nss[ns].lprefix + 2, nad->clen);
+    } else {
+        NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + 1, nad->clen);
+    }
+
+    /* opening tag */
+    *(nad->cdata + nad->ccur++) = '<';
+
+    /* add the prefix if necessary */
+    if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+    {
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+        nad->ccur += nad->nss[ns].lprefix;
+        *(nad->cdata + nad->ccur++) = ':';
+    }
+    
+    /* copy in the name */
+    memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+    nad->ccur += nad->elems[elem].lname;
+
+    /* add the namespaces */
+    for(ns = nad->elems[elem].ns; ns >= 0; ns = nad->nss[ns].next)
+    {
+        /* never explicitly declare the implicit xml namespace */
+        if(nad->nss[ns].luri == strlen(uri_XML) && strncmp(uri_XML, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri) == 0)
+            continue;
+
+        /* make space */
+        if(nad->nss[ns].iprefix >= 0)
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + nad->nss[ns].lprefix + 10, nad->clen);
+        } else {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + 9, nad->clen);
+        }
+
+        /* start */
+        memcpy(nad->cdata + nad->ccur, " xmlns", 6);
+        nad->ccur += 6;
+
+        /* prefix if necessary */
+        if(nad->nss[ns].iprefix >= 0)
+        {
+            *(nad->cdata + nad->ccur++) = ':';
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+            nad->ccur += nad->nss[ns].lprefix;
+        }
+
+        *(nad->cdata + nad->ccur++) = '=';
+        *(nad->cdata + nad->ccur++) = '\'';
+
+        /* uri */
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri);
+        nad->ccur += nad->nss[ns].luri;
+
+        *(nad->cdata + nad->ccur++) = '\'';
+    }
+
+    for(attr = nad->elems[elem].attr; attr >= 0; attr = nad->attrs[attr].next)
+    {
+        if(nad->attrs[attr].lname <= 0) continue;
+
+        /* make enough space for the wrapper part */
+        ns = nad->attrs[attr].my_ns;
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + nad->nss[ns].lprefix + 4, nad->clen);
+        } else {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + 3, nad->clen);
+        }
+
+        *(nad->cdata + nad->ccur++) = ' ';
+
+        /* add the prefix if necessary */
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+            nad->ccur += nad->nss[ns].lprefix;
+            *(nad->cdata + nad->ccur++) = ':';
+        }
+    
+        /* copy in the name parts */
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->attrs[attr].iname, nad->attrs[attr].lname);
+        nad->ccur += nad->attrs[attr].lname;
+        *(nad->cdata + nad->ccur++) = '=';
+        *(nad->cdata + nad->ccur++) = '\'';
+
+        /* copy in the escaped value */
+        _nad_escape(nad, nad->attrs[attr].ival, nad->attrs[attr].lval, 3);
+
+        /* make enough space for the closing quote and add it */
+        NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+        *(nad->cdata + nad->ccur++) = '\'';
+    }
+
+    /* figure out what's next */
+    if(elem+1 == nad->ecur)
+        ndepth = -1;
+    else
+        ndepth = nad->elems[elem+1].depth;
+
+    /* handle based on if there are children, update nelem after done */
+    if(ndepth <= nad->elems[elem].depth)
+    {
+        /* make sure there's enough for what we could need */
+        NAD_SAFE(nad->cdata, nad->ccur + 2, nad->clen);
+        if(nad->elems[elem].lcdata == 0)
+        {
+            memcpy(nad->cdata + nad->ccur, "/>", 2);
+            nad->ccur += 2;
+        }else{
+            *(nad->cdata + nad->ccur++) = '>';
+
+            /* copy in escaped cdata */
+            _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2);
+
+            /* make room */
+            ns = nad->elems[elem].my_ns;
+            if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+            {
+                NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen);
+            } else {
+                NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen);
+            }
+
+            /* close tag */
+            memcpy(nad->cdata + nad->ccur, "</", 2);
+            nad->ccur += 2;
+    
+            /* add the prefix if necessary */
+            if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+            {
+                memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+                nad->ccur += nad->nss[ns].lprefix;
+                *(nad->cdata + nad->ccur++) = ':';
+            }
+    
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+            nad->ccur += nad->elems[elem].lname;
+            *(nad->cdata + nad->ccur++) = '>';
+        }
+
+        /* always try to append the tail */
+        _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2);
+
+        /* if no siblings either, bail */
+        if(ndepth < nad->elems[elem].depth)
+            return elem+1;
+
+        /* next sibling */
+        elem++;
+    }else{
+        int nelem;
+        /* process any children */
+
+        /* close ourself and append any cdata first */
+        NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+        *(nad->cdata + nad->ccur++) = '>';
+        _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2);
+
+        /* process children */
+        nelem = _nad_lp0(nad,elem+1);
+
+        /* close and tail up */
+        ns = nad->elems[elem].my_ns;
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen);
+        } else {
+            NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen);
+        }
+        memcpy(nad->cdata + nad->ccur, "</", 2);
+        nad->ccur += 2;
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+            nad->ccur += nad->nss[ns].lprefix;
+            *(nad->cdata + nad->ccur++) = ':';
+        }
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+        nad->ccur += nad->elems[elem].lname;
+        *(nad->cdata + nad->ccur++) = '>';
+        _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2);
+
+        /* if the next element is not our sibling, we're done */
+        if(nelem < nad->ecur && nad->elems[nelem].depth < nad->elems[elem].depth)
+            return nelem;
+
+        /* for next sibling in while loop */
+        elem = nelem;
+    }
+
+    /* here's the end of that big while loop */
+    }
+
+    return elem;
+}
+
+void nad_print(nad_t nad, int elem, char **xml, int *len)
+{
+    int ixml = nad->ccur;
+
+    _nad_ptr_check(__func__, nad);
+
+    _nad_lp0(nad,elem);
+    *len = nad->ccur - ixml;
+    *xml = nad->cdata + ixml;
+}
+
+/**
+ * nads serialize to a buffer of this form:
+ *
+ * [buflen][ecur][acur][ncur][ccur][elems][attrs][nss][cdata]
+ *
+ * nothing is done with endianness or word length, so the nad must be
+ * serialized and deserialized on the same platform
+ *
+ * buflen is not actually used by deserialize(), but is provided as a
+ * convenience to the application so it knows how many bytes to read before
+ * passing them in to deserialize()
+ *
+ * the depths array is not stored, so after deserialization
+ * nad_append_elem() and nad_append_cdata() will not work. this is rarely
+ * a problem
+ */
+
+void nad_serialize(nad_t nad, char **buf, int *len) {
+    char *pos;
+
+    _nad_ptr_check(__func__, nad);
+
+    *len = sizeof(int) * 5 + /* 4 ints in nad_t, plus one for len */
+           sizeof(struct nad_elem_st) * nad->ecur +
+           sizeof(struct nad_attr_st) * nad->acur +
+           sizeof(struct nad_ns_st) * nad->ncur +
+           sizeof(char) * nad->ccur;
+
+    *buf = (char *) malloc(*len);
+    pos = *buf;
+
+    * (int *) pos = *len;       pos += sizeof(int);
+    * (int *) pos = nad->ecur;  pos += sizeof(int);
+    * (int *) pos = nad->acur;  pos += sizeof(int);
+    * (int *) pos = nad->ncur;  pos += sizeof(int);
+    * (int *) pos = nad->ccur;  pos += sizeof(int);
+
+    memcpy(pos, nad->elems, sizeof(struct nad_elem_st) * nad->ecur);    pos += sizeof(struct nad_elem_st) * nad->ecur;
+    memcpy(pos, nad->attrs, sizeof(struct nad_attr_st) * nad->acur);    pos += sizeof(struct nad_attr_st) * nad->acur;
+    memcpy(pos, nad->nss, sizeof(struct nad_ns_st) * nad->ncur);        pos += sizeof(struct nad_ns_st) * nad->ncur;
+    memcpy(pos, nad->cdata, sizeof(char) * nad->ccur);
+}
+
+nad_t nad_deserialize(nad_cache_t cache, const char *buf) {
+    nad_t nad = nad_new(cache);
+    const char *pos = buf + sizeof(int);  /* skip len */
+
+    _nad_ptr_check(__func__, nad);
+
+    nad->ecur = * (int *) pos; pos += sizeof(int);
+    nad->acur = * (int *) pos; pos += sizeof(int);
+    nad->ncur = * (int *) pos; pos += sizeof(int);
+    nad->ccur = * (int *) pos; pos += sizeof(int);
+    nad->elen = nad->ecur;
+    nad->alen = nad->acur;
+    nad->nlen = nad->ncur;
+    nad->clen = nad->ccur;
+
+    if(nad->ecur > 0)
+    {
+        nad->elems = (struct nad_elem_st *) malloc(sizeof(struct nad_elem_st) * nad->ecur);
+        memcpy(nad->elems, pos, sizeof(struct nad_elem_st) * nad->ecur);
+        pos += sizeof(struct nad_elem_st) * nad->ecur;
+    }
+
+    if(nad->acur > 0)
+    {
+        nad->attrs = (struct nad_attr_st *) malloc(sizeof(struct nad_attr_st) * nad->acur);
+        memcpy(nad->attrs, pos, sizeof(struct nad_attr_st) * nad->acur);
+        pos += sizeof(struct nad_attr_st) * nad->acur;
+    }
+
+    if(nad->ncur > 0)
+    {
+        nad->nss = (struct nad_ns_st *) malloc(sizeof(struct nad_ns_st) * nad->ncur);
+        memcpy(nad->nss, pos, sizeof(struct nad_ns_st) * nad->ncur);
+        pos += sizeof(struct nad_ns_st) * nad->ncur;
+    }
+
+    if(nad->ccur > 0)
+    {
+        nad->cdata = (char *) malloc(sizeof(char) * nad->ccur);
+        memcpy(nad->cdata, pos, sizeof(char) * nad->ccur);
+    }
+
+    return nad;
+}
+
+#ifdef HAVE_EXPAT
+
+/** parse a buffer into a nad */
+
+struct build_data {
+    nad_t               nad;
+    int                 depth;
+};
+
+static void _nad_parse_element_start(void *arg, const char *name, const char **atts) {
+    struct build_data *bd = (struct build_data *) arg;
+    char buf[1024];
+    char *uri, *elem, *prefix;
+    const char **attr;
+    int ns;
+
+    /* make a copy */
+    strncpy(buf, name, 1024);
+    buf[1023] = '\0';
+
+    /* expat gives us:
+         prefixed namespaced elem: uri|elem|prefix
+          default namespaced elem: uri|elem
+               un-namespaced elem: elem
+     */
+
+    /* extract all the bits */
+    uri = buf;
+    elem = strchr(uri, '|');
+    if(elem != NULL) {
+        *elem = '\0';
+        elem++;
+        prefix = strchr(elem, '|');
+        if(prefix != NULL) {
+            *prefix = '\0';
+            prefix++;
+        }
+        ns = nad_add_namespace(bd->nad, uri, prefix);
+    } else {
+        /* un-namespaced, just take it as-is */
+        uri = NULL;
+        elem = buf;
+        prefix = NULL;
+        ns = -1;
+    }
+
+    /* add it */
+    nad_append_elem(bd->nad, ns, elem, bd->depth);
+
+    /* now the attributes, one at a time */
+    attr = atts;
+    while(attr[0] != NULL) {
+
+        /* make a copy */
+        strncpy(buf, attr[0], 1024);
+        buf[1023] = '\0';
+
+        /* extract all the bits */
+        uri = buf;
+        elem = strchr(uri, '|');
+        if(elem != NULL) {
+            *elem = '\0';
+            elem++;
+            prefix = strchr(elem, '|');
+            if(prefix != NULL) {
+                *prefix = '\0';
+                prefix++;
+            }
+            ns = nad_add_namespace(bd->nad, uri, prefix);
+        } else {
+            /* un-namespaced, just take it as-is */
+            uri = NULL;
+            elem = buf;
+            prefix = NULL;
+            ns = -1;
+        }
+
+        /* add it */
+        nad_append_attr(bd->nad, ns, elem, (char *) attr[1]);
+
+        attr += 2;
+    }
+
+    bd->depth++;
+}
+
+static void _nad_parse_element_end(void *arg, const char *name) {
+    struct build_data *bd = (struct build_data *) arg;
+
+    bd->depth--;
+}
+
+static void _nad_parse_cdata(void *arg, const char *str, int len) {
+    struct build_data *bd = (struct build_data *) arg;
+
+    /* go */
+    nad_append_cdata(bd->nad, (char *) str, len, bd->depth);
+}
+
+static void _nad_parse_namespace_start(void *arg, const char *prefix, const char *uri) {
+    struct build_data *bd = (struct build_data *) arg;
+
+    nad_add_namespace(bd->nad, (char *) uri, (char *) prefix);
+}
+
+nad_t nad_parse(nad_cache_t cache, const char *buf, int len) {
+    struct build_data bd;
+    XML_Parser p;
+
+    if(len == 0)
+        len = strlen(buf);
+
+    p = XML_ParserCreateNS(NULL, '|');
+    if(p == NULL)
+        return NULL;
+
+    bd.nad = nad_new(cache);
+    bd.depth = 0;
+
+    XML_SetUserData(p, (void *) &bd);
+    XML_SetElementHandler(p, _nad_parse_element_start, _nad_parse_element_end);
+    XML_SetCharacterDataHandler(p, _nad_parse_cdata);
+    XML_SetStartNamespaceDeclHandler(p, _nad_parse_namespace_start);
+
+    if(!XML_Parse(p, buf, len, 1)) {
+        XML_ParserFree(p);
+        nad_free(bd.nad);
+        return NULL;
+    }
+
+    XML_ParserFree(p);
+
+    if(bd.depth != 0)
+        return NULL;
+
+    return bd.nad;
+}
+
+#endif
diff --git a/src/perlmods/JSON.pm b/src/perlmods/JSON.pm
new file mode 100644 (file)
index 0000000..18454b5
--- /dev/null
@@ -0,0 +1,235 @@
+package JSON::number;
+sub new {
+       my $class = shift;
+       my $x = shift || $class;
+       return bless \$x => __PACKAGE__;
+}
+use overload ( '""' => \&toString );
+use overload ( '0+' => sub { ${$_[0]} } );
+
+sub toString { defined($_[1]) ? ${$_[1]} : ${$_[0]} }
+
+package JSON::bool::true;
+sub new { return bless {} => __PACKAGE__ }
+use overload ( '""' => \&toString );
+use overload ( 'bool' => sub { 1 } );
+use overload ( '0+' => sub { 1 } );
+
+sub toString { 'true' }
+
+package JSON::bool::false;
+sub new { return bless {} => __PACKAGE__ }
+use overload ( '""' => \&toString );
+use overload ( 'bool' => sub { 0 } );
+use overload ( '0+' => sub { 0 } );
+
+sub toString { 'false' }
+
+package JSON;
+use vars qw/%_class_map/;
+
+sub register_class_hint {
+       my $class = shift;
+       my %args = @_;
+
+       $_class_map{$args{hint}} = \%args;
+       $_class_map{$args{name}} = \%args;
+}
+
+sub JSON2perl {
+       my ($class, $json) = @_;
+       $json ||= $class;
+
+       $json =~ s/\/\/.+$//gmo; # remove C++ comments
+       $json =~ s/(?<!\\)\$/\\\$/gmo; # fixup $ for later
+       $json =~ s/(?<!\\)\@/\\\@/gmo; # fixup @ for later
+
+       my @casts;
+       my $casting_depth = 0;
+       my $current_cast;
+       my $output = '';
+       while ($json =~ s/^\s* ( 
+                                  {                            | # start object
+                                  \[                           | # start array
+                                  -?\d+\.?\d*                  | # number literal
+                                  "(?:(?:\\[\"])|[^\"])+"      | # string literal
+                                  (?:\/\*.+?\*\/)              | # C comment
+                                  true                         | # bool true
+                                  false                        | # bool false
+                                  null                         | # undef()
+                                  :                            | # object key-value sep
+                                  ,                            | # list sep
+                                  \]                           | # array end
+                                  }                              # object end
+                               )
+                        \s*//sox) {
+               my $element = $1;
+
+               if ($element eq 'null') {
+                       $output .= ' undef() ';
+                       next;
+               } elsif ($element =~ /^\/\*--\s*S\w*?\s+(\w+)\s*--\*\/$/) {
+                       my $hint = $1;
+                       if (exists $_class_map{$hint}) {
+                               $casts[$casting_depth] = $hint;
+                               $output .= ' bless(';
+                       }
+                       next;
+               } elsif ($element =~ /^\/\*/) {
+                       next;
+               } elsif ($element =~ /^\d/) {
+                       $output .= "do { JSON::number::new($element) }";
+                       next;
+               } elsif ($element eq '{' or $element eq '[') {
+                       $casting_depth++;
+               } elsif ($element eq '}' or $element eq ']') {
+                       $casting_depth--;
+                       my $hint = $casts[$casting_depth];
+                       $casts[$casting_depth] = undef;
+                       if (defined $hint and exists $_class_map{$hint}) {
+                               $output .= $element . ',"'. $_class_map{$hint}{name} . '")';
+                               next;
+                       }
+               } elsif ($element eq ':') {
+                       $output .= ' => ';
+                       next;
+               } elsif ($element eq 'true') {
+                       $output .= 'bless( {}, "JSON::bool::true")';
+                       next;
+               } elsif ($element eq 'false') {
+                       $output .= 'bless( {}, "JSON::bool::false")';
+                       next;
+               }
+               
+               $output .= $element;
+       }
+
+       return eval $output;
+}
+
+sub perl2JSON {
+       my ($class, $perl) = @_;
+       $perl ||= $class;
+
+       my $output = '';
+       if (!defined($perl)) {
+               $output = 'null';
+       } elsif (ref($perl) and ref($perl) =~ /^JSON/) {
+               $output .= $perl;
+       } elsif ( ref($perl) && exists($_class_map{ref($perl)}) ) {
+               $output .= '/*--S '.$_class_map{ref($perl)}{hint}.'--*/';
+               if (lc($_class_map{ref($perl)}{type}) eq 'hash') {
+                       my %hash =  %$perl;
+                       $output .= perl2JSON(\%hash);
+               } elsif (lc($_class_map{ref($perl)}{type}) eq 'array') {
+                       my @array =  @$perl;
+                       $output .= perl2JSON(\@array);
+               }
+               $output .= '/*--E '.$_class_map{ref($perl)}{hint}.'--*/';
+       } elsif (ref($perl) and ref($perl) =~ /HASH/) {
+               $output .= '{';
+               my $c = 0;
+               for my $key (sort keys %$perl) {
+                       $output .= ',' if ($c); 
+                       
+                       $output .= perl2JSON($key).':'.perl2JSON($$perl{$key});
+                       $c++;
+               }
+               $output .= '}';
+       } elsif (ref($perl) and ref($perl) =~ /ARRAY/) {
+               $output .= '[';
+               my $c = 0;
+               for my $part (@$perl) {
+                       $output .= ',' if ($c); 
+                       
+                       $output .= perl2JSON($part);
+                       $c++;
+               }
+               $output .= ']';
+       } else {
+               $perl =~ s/\\/\\\\/sgo;
+               $perl =~ s/"/\\"/sgo;
+               $perl =~ s/\t/\\t/sgo;
+               $perl =~ s/\f/\\f/sgo;
+               $perl =~ s/\r/\\r/sgo;
+               $perl =~ s/\n/\\n/sgo;
+               $output = '"'.$perl.'"';
+       }
+
+       return $output;
+}
+
+my $depth = 0;
+sub perl2prettyJSON {
+       my ($class, $perl, $nospace) = @_;
+       $perl ||= $class;
+
+       my $output = '';
+       if (!defined($perl)) {
+               $output = 'null';
+       } elsif (ref($perl) and ref($perl) =~ /^JSON/) {
+               $output .= $perl;
+       } elsif ( ref($perl) && exists($_class_map{ref($perl)}) ) {
+               $depth++;
+               $output .= "\n";
+               $output .= "   "x$depth;
+               $output .= '/*--S '.$_class_map{ref($perl)}{hint}."--*/ ";
+               if (lc($_class_map{ref($perl)}{type}) eq 'hash') {
+                       my %hash =  %$perl;
+                       $output .= perl2prettyJSON(\%hash,undef,1);
+               } elsif (lc($_class_map{ref($perl)}{type}) eq 'array') {
+                       my @array =  @$perl;
+                       $output .= perl2prettyJSON(\@array,undef,1);
+               }
+               #$output .= "   "x$depth;
+               $output .= ' /*--E '.$_class_map{ref($perl)}{hint}.'--*/';
+               $depth--;
+       } elsif (ref($perl) and ref($perl) =~ /HASH/) {
+               #$depth++;
+               $output .= "   "x$depth unless ($nospace);
+               $output .= "{\n";
+               my $c = 0;
+               $depth++;
+               for my $key (sort keys %$perl) {
+                       $output .= ",\n" if ($c); 
+                       
+                       $output .= perl2prettyJSON($key)." : ".perl2prettyJSON($$perl{$key}, undef, 1);
+                       $c++;
+               }
+               $depth--;
+               $output .= "\n";
+               $output .= "   "x$depth;
+               $output .= '}';
+               #$depth--;
+       } elsif (ref($perl) and ref($perl) =~ /ARRAY/) {
+               #$depth++;
+               $output .= "   "x$depth unless ($nospace);
+               $output .= "[\n";
+               my $c = 0;
+               $depth++;
+               for my $part (@$perl) {
+                       $output .= ",\n" if ($c); 
+                       
+                       $output .= perl2prettyJSON($part);
+                       $c++;
+               }
+               $depth--;
+               $output .= "\n";
+               $output .= "   "x$depth;
+               $output .= "]";
+               #$depth--;
+       } else {
+               $perl =~ s/\\/\\\\/sgo;
+               $perl =~ s/"/\\"/sgo;
+               $perl =~ s/\t/\\t/sgo;
+               $perl =~ s/\f/\\f/sgo;
+               $perl =~ s/\r/\\r/sgo;
+               $perl =~ s/\n/\\n/sgo;
+               $output .= "   "x$depth unless($nospace);
+               $output .= '"'.$perl.'"';
+       }
+
+       return $output;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF.pm b/src/perlmods/OpenSRF.pm
new file mode 100644 (file)
index 0000000..18d157a
--- /dev/null
@@ -0,0 +1,75 @@
+package OpenILS;
+use strict;
+use Error;
+use vars qw/$VERSION $AUTOLOAD/;
+$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
+
+=head1 OpenILS
+
+=cut
+
+=head2 Overview
+
+ Top level class for OpenILS perl modules.
+
+=cut
+
+# Exception base classes
+#use Exception::Class
+#      ( OpenILSException => { fields => [ 'errno' ] });
+#push @Exception::Class::ISA, 'Error';
+
+=head3 AUTOLOAD()
+
+ Traps methods calls for methods that have not been defined so they
+ don't propogate up the class hierarchy.
+
+=cut
+sub AUTOLOAD {
+       my $self = shift;
+       my $type = ref($self) || $self;
+       my $name = $AUTOLOAD;
+       my $otype = ref $self;
+       
+       my ($package, $filename, $line) = caller;
+       my ($package1, $filename1, $line1) = caller(1);
+       my ($package2, $filename2, $line2) = caller(2);
+       my ($package3, $filename3, $line3) = caller(3);
+       my ($package4, $filename4, $line4) = caller(4);
+       my ($package5, $filename5, $line5) = caller(5);
+       $name =~ s/.*://;   # strip fully-qualified portion
+       warn <<"        WARN";
+****
+** ${name}() isn't there.  Please create me somewhere (like in $type)!
+** Error at $package ($filename), line $line
+** Call Stack (5 deep):
+**     $package1 ($filename1), line $line1
+**     $package2 ($filename2), line $line2
+**     $package3 ($filename3), line $line3
+**     $package4 ($filename4), line $line4
+**     $package5 ($filename5), line $line5
+** Object type was $otype
+****
+       WARN
+}
+
+
+
+=head3 alert_abstract()
+
+ This method is called by abstract methods to ensure that
+ the process dies when an undefined abstract method is called
+
+=cut
+sub alert_abstract() {
+       my $c = shift;
+       my $class = ref( $c ) || $c;
+       my ($file, $line, $method) = (caller(1))[1..3];
+       die " * Call to abstract method $method at $file, line $line";
+}
+
+sub class {
+       return scalar(caller);
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/AppSession.pm b/src/perlmods/OpenSRF/AppSession.pm
new file mode 100644 (file)
index 0000000..0fa3d45
--- /dev/null
@@ -0,0 +1,827 @@
+package OpenSRF::AppSession;
+use OpenSRF::DOM;
+use OpenSRF::DOM::Element::userAuth;
+use OpenSRF::DomainObject::oilsMessage;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Transport::PeerHandle;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX;
+use OpenSRF;
+use Exporter;
+use base qw/Exporter OpenSRF/;
+use Time::HiRes qw( time usleep );
+use warnings;
+use strict;
+
+our @EXPORT_OK = qw/CONNECTING INIT_CONNECTED CONNECTED DISCONNECTED CLIENT SERVER/;
+our %EXPORT_TAGS = ( state => [ qw/CONNECTING INIT_CONNECTED CONNECTED DISCONNECTED/ ],
+                endpoint => [ qw/CLIENT SERVER/ ],
+);
+
+my $logger = "OpenSRF::Utils::Logger";
+
+our %_CACHE;
+our @_CLIENT_CACHE;
+our @_RESEND_QUEUE;
+
+sub kill_client_session_cache {
+       for my $session ( @_CLIENT_CACHE ) {
+               $session->kill_me;
+       }
+}
+
+sub CONNECTING { return 3 };
+sub INIT_CONNECTED { return 4 };
+sub CONNECTED { return 1 };
+sub DISCONNECTED { return 2 };
+
+sub CLIENT { return 2 };
+sub SERVER { return 1 };
+
+sub find {
+       return undef unless (defined $_[1]);
+       return $_CACHE{$_[1]} if (exists($_CACHE{$_[1]}));
+}
+
+sub find_client {
+       my( $self, $app ) = @_;
+       $logger->debug( "Client Cache contains: " .scalar(@_CLIENT_CACHE), INTERNAL );
+       my ($client) = grep { $_->[0] eq $app and $_->[1] == 1 } @_CLIENT_CACHE;
+       $client->[1] = 0;
+       return $client->[2];
+}
+
+sub transport_connected {
+       my $self = shift;
+       if( ! exists $self->{peer_handle} || 
+                                       ! $self->{peer_handle} ) {
+               return 0;
+       }
+       return $self->{peer_handle}->tcp_connected();
+}
+
+# ----------------------------------------------------------------------------
+# Clears the transport buffers
+# call this if you are not through with the sesssion, but you want 
+# to have a clean slate.  You shouldn't have to call this if
+# you are correctly 'recv'ing all of the data from a request.
+# however, if you don't want all of the data, this will
+# slough off any excess
+#  * * Note: This will delete data for all sessions using this transport
+# handle.  For example, all client sessions use the same handle.
+# ----------------------------------------------------------------------------
+sub buffer_reset {
+
+       my $self = shift;
+       if( ! exists $self->{peer_handle} || ! $self->{peer_handle} ) { 
+               return 0;
+       }
+       $self->{peer_handle}->buffer_reset();
+}
+
+
+sub client_cache {
+       my $self = shift;
+       push @_CLIENT_CACHE, [ $self->app, 1, $self ];
+}
+
+# when any incoming data is received, this method is called.
+sub server_build {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $sess_id = shift;
+       my $remote_id = shift;
+       my $service = shift;
+
+       return undef unless ($sess_id and $remote_id and $service);
+
+       my $conf = OpenSRF::Utils::Config->current;
+       
+       if( ! $conf ) { 
+               OpenSRF::EX::Config->throw( "No suitable config found" ); 
+       }
+
+       if ( my $thingy = $class->find($sess_id) ) {
+               $thingy->remote_id( $remote_id );
+               $logger->debug( "AppSession returning existing session $sess_id", DEBUG );
+               return $thingy;
+       } else {
+               $logger->debug( "AppSession building new server session $sess_id", DEBUG );
+       }
+
+       if( $service eq "client" ) {
+               #throw OpenSRF::EX::PANIC ("Attempting to build a client session as a server" .
+               #       " Session ID [$sess_id], remote_id [$remote_id]");
+               $logger->debug("Attempting to build a client session as ".
+                               "a server Session ID [$sess_id], remote_id [$remote_id]", ERROR );
+       }
+
+
+       my $max_requests = $conf->$service->max_requests;
+       $logger->debug( "Max Requests for $service is $max_requests", INTERNAL );# if $max_requests;
+
+       $logger->transport( "AppSession creating new session: $sess_id", INTERNAL );
+
+       my $self = bless { recv_queue  => [],
+                          request_queue  => [],
+                          requests  => 0,
+                          endpoint    => SERVER,
+                          state       => CONNECTING, 
+                          session_id  => $sess_id,
+                          remote_id    => $remote_id,
+                               peer_handle => OpenSRF::Transport::PeerHandle->retrieve($service),
+                               max_requests => $max_requests,
+                               session_threadTrace => 0,
+                               service => $service,
+                        } => $class;
+
+       return $_CACHE{$sess_id} = $self;
+}
+
+sub service { return shift()->{service}; }
+
+sub continue_request {
+       my $self = shift;
+       $self->{'requests'}++;
+       return $self->{'requests'} <= $self->{'max_requests'} ? 1 : 0;
+}
+
+sub last_sent_payload {
+       my( $self, $payload ) = @_;
+       if( $payload ) {
+               return $self->{'last_sent_payload'} = $payload;
+       }
+       return $self->{'last_sent_payload'};
+}
+
+sub last_sent_type {
+       my( $self, $type ) = @_;
+       if( $type ) {
+               return $self->{'last_sent_type'} = $type;
+       }
+       return $self->{'last_sent_type'};
+}
+
+# When we're a client and we want to connect to a remote service
+# create( $app, username => $user, secret => $passwd );
+#    OR
+# create( $app, sysname => $user, secret => $shared_secret );
+sub create {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $app = shift;
+       my %auth_args = @_;
+
+
+       unless ( $app &&
+                exists($auth_args{secret}) &&
+                ( exists($auth_args{username}) ||
+                  exists($auth_args{sysname}) ) ) {
+               throw OpenSRF::EX::User ( 'Insufficient authentication information for session creation');
+       }
+
+       if( my $thingy = OpenSRF::AppSession->find_client( $app ) ) {
+                       $logger->debug( "AppSession returning existing client session for $app", DEBUG );
+                       return $thingy;
+       } else {
+               $logger->debug( "AppSession creating new client session for $app", DEBUG );
+       }
+
+
+       
+       my $auth = OpenSRF::DOM::Element::userAuth->new( %auth_args );
+
+       my $conf = OpenSRF::Utils::Config->current;
+
+       if( ! $conf ) { 
+               OpenSRF::EX::Config->throw( "No suitable config found" ); 
+       }
+
+       my $sess_id = time . rand( $$ );
+       while ( $class->find($sess_id) ) {
+               $sess_id = time . rand( $$ );
+       }
+
+       my $r_id = $conf->$app->transport_target ||
+                       die("No remote id for $app!");
+
+       my $self = bless { app_name    => $app,
+                          client_auth => $auth,
+                          #recv_queue  => [],
+                          request_queue  => [],
+                          endpoint    => CLIENT,
+                          state       => DISCONNECTED,#since we're init'ing
+                          session_id  => $sess_id,
+                          remote_id   => $r_id,
+                          orig_remote_id   => $r_id,
+                               # peer_handle => OpenSRF::Transport::PeerHandle->retrieve($app),
+                               peer_handle => OpenSRF::Transport::PeerHandle->retrieve("client"),
+                               session_threadTrace => 0,
+                        } => $class;
+
+       $self->client_cache();
+       $_CACHE{$sess_id} = $self;
+       return $self->find_client( $app );
+}
+
+sub app {
+       return shift()->{app_name};
+}
+
+sub reset {
+       my $self = shift;
+       $self->remote_id($$self{orig_remote_id});
+}
+
+# 'connect' can be used as a constructor if called as a class method,
+# or used to connect a session that has disconnectd if called against
+# an existing session that seems to be disconnected, or was just built
+# using 'create' above.
+
+# connect( $app, username => $user, secret => $passwd );
+#    OR
+# connect( $app, sysname => $user, secret => $shared_secret );
+
+# --- Returns undef if the connect attempt times out.
+# --- Returns the OpenSRF::EX object if one is returned by the server
+# --- Returns self if connected
+sub connect {
+       my $self = shift;
+       my $class = ref($self) || $self;
+
+       return $self if ( ref( $self ) and  $self->state && $self->state == CONNECTED  );
+
+       my $app = shift;
+
+       $self = $class->create($app, @_) if (!ref($self));
+       return undef unless ($self);
+
+
+       $self->reset;
+       $self->state(CONNECTING);
+       $self->send('CONNECT', "");
+
+       my $time_remaining = OpenSRF::Utils::Config->current->client->connect_timeout;
+
+       while ( $self->state != CONNECTED  and $time_remaining > 0 ) {
+               my $starttime = time;
+               $self->queue_wait($time_remaining);
+               my $endtime = time;
+               $time_remaining -= ($endtime - $starttime);
+       }
+
+       return undef unless($self->state == CONNECTED);
+
+       return $self;
+}
+
+sub finish {
+       my $self = shift;
+       #$self->disconnect if ($self->endpoint == CLIENT);
+       for my $ses ( @_CLIENT_CACHE ) {
+               if ($ses->[2]->session_id eq $self->session_id) {
+                       $ses->[1] = 1;
+               }
+       }
+}
+
+sub kill_me {
+       my $self = shift;
+       if( ! $self->session_id ) { return 0; }
+       $self->disconnect;
+       $logger->transport( "AppSession killing self: " . $self->session_id(), DEBUG );
+       my @a;
+       for my $ses ( @_CLIENT_CACHE ) {
+               push @a, $ses 
+                       if ($ses->[2]->session_id ne $self->session_id);
+       }
+       @_CLIENT_CACHE = @a;
+       delete $_CACHE{$self->session_id};
+       delete($$self{$_}) for (keys %$self);
+}
+
+sub disconnect {
+       my $self = shift;
+       unless( $self->state == DISCONNECTED ) {
+               $self->send('DISCONNECT', "") if ($self->endpoint == CLIENT);;
+               $self->state( DISCONNECTED ); 
+       }
+       $self->reset;
+}
+
+sub request {
+       my $self = shift;
+       my $meth = shift;
+
+       my $method;
+       if (!ref $meth) {
+               $method = new OpenSRF::DomainObject::oilsMethod ( method => $meth );
+       } else {
+               $method = $meth;
+       }
+       
+       $method->params( @_ );
+
+       $self->send('REQUEST',$method);
+}
+
+
+sub send {
+       my $self = shift;
+       my @payload_list = @_; # this is a Domain Object
+
+       $logger->debug( "In send", INTERNAL );
+       
+       my $tT;
+
+       if( @payload_list % 2 ) { $tT = pop @payload_list; }
+
+       if( ! @payload_list ) {
+               $logger->debug( "payload_list param is incomplete in AppSession::send()", ERROR );
+               return undef; 
+       }
+
+       my $doc = OpenSRF::DOM->createDocument();
+
+       $logger->debug( "In send2", INTERNAL );
+
+       my $disconnect = 0;
+       my $connecting = 0;
+
+       while( @payload_list ) {
+
+               my ($msg_type, $payload) = ( shift(@payload_list), shift(@payload_list) ); 
+
+               if ($msg_type eq 'DISCONNECT' ) {
+                       $disconnect++;
+                       if( $self->state == DISCONNECTED) {
+                               next;
+                       }
+               }
+
+               if( $msg_type eq "CONNECT" ) { $connecting++; }
+
+
+               if( $payload ) {
+                       $logger->debug( "Payload is ".$payload->toString, INTERNAL );
+               }
+       
+       
+               my $msg = OpenSRF::DomainObject::oilsMessage->new();
+               $logger->debug( "AppSession after creating oilsMessage $msg", INTERNAL );
+               
+               $msg->type($msg_type);
+               $logger->debug( "AppSession after adding type" . $msg->toString(), INTERNAL );
+       
+               $msg->userAuth($self->client_auth) if ($self->endpoint == CLIENT && $msg_type eq 'CONNECT');
+       
+               no warnings;
+               $msg->threadTrace( $tT || int($self->session_threadTrace) || int($self->last_threadTrace) );
+               use warnings;
+       
+               if ($msg->type eq 'REQUEST') {
+                       if ( !defined($tT) || $self->last_threadTrace != $tT ) {
+                               $msg->update_threadTrace;
+                               $self->session_threadTrace( $msg->threadTrace );
+                               $tT = $self->session_threadTrace;
+                               OpenSRF::AppRequest->new($self, $payload);
+                       }
+               }
+       
+               $msg->protocol(1);
+               $msg->payload($payload) if $payload;
+       
+               $doc->documentElement->appendChild( $msg );
+
+       
+               $logger->debug( "AppSession sending ".$msg->type." to ".$self->remote_id.
+                       " with threadTrace [".$msg->threadTrace."]", INFO );
+
+       }
+       
+       if ($self->endpoint == CLIENT and ! $disconnect) {
+               $self->queue_wait(0);
+               unless ($self->state == CONNECTED || ($self->state == CONNECTING && $connecting )) {
+                       my $v = $self->connect();
+                       if( ! $v ) {
+                               $logger->debug( "Unable to connect to remote service in AppSession::send()", ERROR );
+                               return undef;
+                       }
+                       if( $v and $v->class->isa( "OpenSRF::EX" ) ) {
+                               return $v;
+                       }
+               }
+       } 
+
+       $logger->debug( "AppSession sending doc: " . $doc->toString(), INTERNAL );
+
+
+       $self->{peer_handle}->send( 
+                                       to     => $self->remote_id,
+                                  thread => $self->session_id,
+                                  body   => $doc->toString );
+
+       return $self->app_request( $tT );
+}
+
+sub app_request {
+       my $self = shift;
+       my $tT = shift;
+       
+       return undef unless (defined $tT);
+       my ($req) = grep { $_->threadTrace == $tT } @{ $self->{request_queue} };
+
+       return $req;
+}
+
+sub remove_app_request {
+       my $self = shift;
+       my $req = shift;
+       
+       my @list = grep { $_->threadTrace != $req->threadTrace } @{ $self->{request_queue} };
+
+       $self->{request_queue} = \@list;
+}
+
+sub endpoint {
+       return $_[0]->{endpoint};
+}
+
+
+sub session_id {
+       my $self = shift;
+       return $self->{session_id};
+}
+
+sub push_queue {
+       my $self = shift;
+       my $resp = shift;
+       my $req = $self->app_request($resp->[1]);
+       return $req->push_queue( $resp->[0] ) if ($req);
+       push @{ $self->{recv_queue} }, $resp->[0];
+}
+
+sub last_threadTrace {
+       my $self = shift;
+       my $new_last_threadTrace = shift;
+
+       my $old_last_threadTrace = $self->{last_threadTrace};
+       if (defined $new_last_threadTrace) {
+               $self->{last_threadTrace} = $new_last_threadTrace;
+               return $new_last_threadTrace unless ($old_last_threadTrace);
+       }
+
+       return $old_last_threadTrace;
+}
+
+sub session_threadTrace {
+       my $self = shift;
+       my $new_last_threadTrace = shift;
+
+       my $old_last_threadTrace = $self->{session_threadTrace};
+       if (defined $new_last_threadTrace) {
+               $self->{session_threadTrace} = $new_last_threadTrace;
+               return $new_last_threadTrace unless ($old_last_threadTrace);
+       }
+
+       return $old_last_threadTrace;
+}
+
+sub last_message_type {
+       my $self = shift;
+       my $new_last_message_type = shift;
+
+       my $old_last_message_type = $self->{last_message_type};
+       if (defined $new_last_message_type) {
+               $self->{last_message_type} = $new_last_message_type;
+               return $new_last_message_type unless ($old_last_message_type);
+       }
+
+       return $old_last_message_type;
+}
+
+sub last_message_protocol {
+       my $self = shift;
+       my $new_last_message_protocol = shift;
+
+       my $old_last_message_protocol = $self->{last_message_protocol};
+       if (defined $new_last_message_protocol) {
+               $self->{last_message_protocol} = $new_last_message_protocol;
+               return $new_last_message_protocol unless ($old_last_message_protocol);
+       }
+
+       return $old_last_message_protocol;
+}
+
+sub remote_id {
+       my $self = shift;
+       my $new_remote_id = shift;
+
+       my $old_remote_id = $self->{remote_id};
+       if (defined $new_remote_id) {
+               $self->{remote_id} = $new_remote_id;
+               return $new_remote_id unless ($old_remote_id);
+       }
+
+       return $old_remote_id;
+}
+
+sub client_auth {
+       my $self = shift;
+       my $new_ua = shift;
+
+       my $old_ua = $self->{client_auth};
+       if (defined $new_ua) {
+               $self->{client_auth} = $new_ua;
+               return $new_ua unless ($old_ua);
+       }
+
+       return $old_ua->cloneNode(1);
+}
+
+sub state {
+       my $self = shift;
+       my $new_state = shift;
+
+       my $old_state = $self->{state};
+       if (defined $new_state) {
+               $self->{state} = $new_state;
+               return $new_state unless ($old_state);
+       }
+
+       return $old_state;
+}
+
+sub DESTROY {
+       my $self = shift;
+       delete $$self{$_} for keys %$self;
+       return undef;
+}
+
+sub recv {
+       my $self = shift;
+       my @proto_args = @_;
+       my %args;
+
+       if ( @proto_args ) {
+               if ( !(@proto_args % 2) ) {
+                       %args = @proto_args;
+               } elsif (@proto_args == 1) {
+                       %args = ( timeout => @proto_args );
+               }
+       }
+
+       #$logger->debug( ref($self). " recv_queue before wait: " . $self->_print_queue(), INTERNAL );
+
+       if( exists( $args{timeout} ) ) {
+               $args{timeout} = int($args{timeout});
+       } else {
+               $args{timeout} = 10;
+       }
+
+       #$args{timeout} = 0 if ($self->complete);
+
+       $logger->debug( ref($self) ."->recv with timeout " . $args{timeout}, INTERNAL );
+
+       $args{count} ||= 1;
+
+       my $avail = @{ $self->{recv_queue} };
+       my $time_remaining = $args{timeout};
+
+       while ( $avail < $args{count} and $time_remaining > 0 ) {
+               last if $self->complete;
+               my $starttime = time;
+               $self->queue_wait($time_remaining);
+               my $endtime = time;
+               $time_remaining -= ($endtime - $starttime);
+               $avail = @{ $self->{recv_queue} };
+       }
+
+       #$logger->debug( ref($self)." queue after wait: " . $self->_print_queue(), INTERNAL );
+               
+       my @list;
+       while ( my $msg = shift @{ $self->{recv_queue} } ) {
+               push @list, $msg;
+               last if (scalar(@list) >= $args{count});
+       }
+
+#      $self->{recv_queue} = [@unlist, @{ $self->{recv_queue} }];
+       $logger->debug( "Number of matched responses: " . @list, DEBUG );
+
+       $self->queue_wait(0); # check for statuses
+       
+       return $list[0] unless (wantarray);
+       return @list;
+}
+
+sub push_resend {
+       my $self = shift;
+       push @OpenSRF::AppSession::_RESEND_QUEUE, @_;
+}
+
+sub flush_resend {
+       my $self = shift;
+       $logger->debug( "Resending..." . @_RESEND_QUEUE, DEBUG );
+       while ( my $req = shift @OpenSRF::AppSession::_RESEND_QUEUE ) {
+               $req->resend;
+       }
+}
+
+
+sub queue_wait {
+       my $self = shift;
+       if( ! $self->{peer_handle} ) { return 0; }
+       my $timeout = shift || 0;
+       $logger->debug( "Calling queue_wait($timeout)" , DEBUG );
+       $logger->debug( "Timestamp before process($timeout) : " . $logger->format_time(), INTERNAL );
+       my $o = $self->{peer_handle}->process($timeout);
+       $logger->debug( "Timestamp after  process($timeout) : " . $logger->format_time(), INTERNAL );
+       $self->flush_resend;
+       return $o;
+}
+
+sub _print_queue {
+       my( $self ) = @_;
+       my $string = "";
+       foreach my $msg ( @{$self->{recv_queue}} ) {
+               $string = $string . $msg->toString(1) . "\n";
+       }
+       return $string;
+}
+
+sub status {
+       my $self = shift;
+       $self->send( 'STATUS', @_ );
+}
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::AppRequest;
+use base qw/OpenSRF::AppSession/;
+use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $session = shift;
+       my $threadTrace = $session->session_threadTrace || $session->last_threadTrace;
+       my $payload = shift;
+       
+       my $self = {    session     => $session,
+                       threadTrace => $threadTrace,
+                       payload     => $payload,
+                       complete    => 0,
+                       recv_queue  => [],
+       };
+
+       bless $self => $class;
+
+       push @{ $self->session->{request_queue} }, $self;
+
+       return $self;
+}
+
+sub queue_size {
+       my $size = @{$_[0]->{recv_queue}};
+       return $size;
+}
+       
+sub send {
+       shift()->session->send(@_);
+}
+
+sub finish {
+       my $self = shift;
+       $self->session->remove_app_request($self);
+       delete($$self{$_}) for (keys %$self);
+}
+
+sub session {
+       return shift()->{session};
+}
+
+sub complete {
+       my $self = shift;
+       my $complete = shift;
+       return $self->{complete} if ($self->{complete});
+       if (defined $complete) {
+               $self->{complete} = $complete;
+       } else {
+               $self->session->queue_wait(0);
+       }
+       return $self->{complete};
+}
+
+sub wait_complete {
+       my $self = shift;
+       my $timeout = shift || 1;
+       my $time_remaining = $timeout;
+
+       while ( ! $self->complete  and $time_remaining > 0 ) {
+               my $starttime = time;
+               $self->queue_wait($time_remaining);
+               my $endtime = time;
+               $time_remaining -= ($endtime - $starttime);
+       }
+
+       return $self->complete;
+}
+
+sub threadTrace {
+       return shift()->{threadTrace};
+}
+
+sub push_queue {
+       my $self = shift;
+       my $resp = shift;
+       push @{ $self->{recv_queue} }, $resp;
+       OpenSRF::Utils::Logger->debug( "AppRequest pushed ".$resp->toString(), INTERNAL );
+}
+
+sub queue_wait {
+       my $self = shift;
+       OpenSRF::Utils::Logger->debug( "Calling queue_wait(@_)", DEBUG );
+       return $self->session->queue_wait(@_)
+}
+
+sub payload { return shift()->{payload}; }
+
+sub resend {
+       my $self = shift;
+       OpenSRF::Utils::Logger->debug(
+               "I'm resending the request for threadTrace ". $self->threadTrace, DEBUG);
+       OpenSRF::Utils::Logger->debug($self->payload->toString,INTERNAL);
+       return $self->session->send('REQUEST', $self->payload, $self->threadTrace );
+}
+
+sub status {
+       my $self = shift;
+       my $msg = shift;
+       $self->session->send( 'STATUS',$msg, $self->threadTrace );
+}
+
+sub respond {
+       my $self = shift;
+       my $msg = shift;
+
+       my $response;
+       if (!ref($msg) || ($msg->can('getAttribute') && $msg->getAttribute('name') !~ /oilsResult/)) {
+               $response = new OpenSRF::DomainObject::oilsResult;
+               $response->content($msg);
+       } else {
+               $response = $msg;
+       }
+
+       $self->session->send('RESULT', $response, $self->threadTrace);
+}
+
+sub respond_complete {
+       my $self = shift;
+       my $msg = shift;
+
+       my $response;
+       if (!ref($msg) || ($msg->can('getAttribute') && $msg->getAttribute('name') !~ /oilsResult/)) {
+               $response = new OpenSRF::DomainObject::oilsResult;
+               $response->content($msg);
+       } else {
+               $response = $msg;
+       }
+
+       my $stat = OpenSRF::DomainObject::oilsConnectStatus->new(
+               statusCode => STATUS_COMPLETE(),
+               status => 'Request Complete' );
+
+       $self->session->send( 'RESULT' => $response, 'STATUS' => $stat, $self->threadTrace);
+
+}
+
+
+1;
+
+
+__END__
+
+[client]
+interval:connect_timeout = 2 seconds
+
+[servers]
+subsection:StorageServer = Storage_config
+
+[Storage_config]
+#transport = xmlrpc
+#transport_target = http://open-ils.org/RPC2/services/Storage
+
+transport = jabber
+transport_target = Storage@open-ils.org/SERVICE_RECEIVER
+method_class = OpenSRF::App::Storage::PGStore;
+
+
+
+
+
diff --git a/src/perlmods/OpenSRF/Application.pm b/src/perlmods/OpenSRF/Application.pm
new file mode 100644 (file)
index 0000000..13f80dc
--- /dev/null
@@ -0,0 +1,222 @@
+package OpenSRF::Application;
+use base qw/OpenSRF/;
+use OpenSRF::AppSession;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Utils::Logger qw/:level/;
+use Time::HiRes qw/time/;
+use vars qw/$_app $log/;
+use OpenSRF::EX qw/:try/;
+use strict;
+use warnings;
+
+$log = 'OpenSRF::Utils::Logger';
+
+our $in_request = 0;
+our @pending_requests;
+
+sub application_implementation {
+       my $self = shift;
+       my $app = shift;
+
+       if (defined $app) {
+               $_app = $app;
+               eval "use $_app;";
+               if( $@ ) {
+                       $log->error( "Error loading application_implementation: $app -> $@", ERROR);
+               }
+
+       }
+
+       return $_app;
+}
+
+sub handler {
+       my ($self, $session, $app_msg) = @_;
+
+       $log->debug( "In Application::handler()", DEBUG );
+
+       my $app = $self->application_implementation;
+
+       if( $app ) {
+               $log->debug( "Application is $app", DEBUG);
+       }
+       $log->debug( "Message is ".$app_msg->toString(1), INTERNAL);
+
+
+       if ($session->last_message_type eq 'REQUEST') {
+               $log->debug( "We got a REQUEST: ". $app_msg->method, INFO );
+
+               my $method_name = $app_msg->method;
+               $log->debug( " * Looking up $method_name inside $app", DEBUG);
+
+               my $method_proto = $session->last_message_protocol;
+               $log->debug( " * Method API Level [$method_proto]", DEBUG);
+
+               my $coderef = $app->method_lookup( $method_name, $method_proto );
+
+               unless ($coderef) {
+                       $session->status( OpenSRF::DomainObject::oilsMethodException->new() );
+                       return 1;
+               }
+
+               #if ( $session->client_auth->username || $session->client_auth->userid ) {
+               #       unless ( $coderef->is_action ) {
+               #               $session->status(
+               #                       OpenSRF::DomainObject::oilsMethodException->new(
+               #                                       statusCode => STATUS_NOTALLOWED(),
+               #                                       status => "User cannot use [$method_name]" ) );
+               #               return 1;
+               #       }
+               #}
+                       
+
+               $log->debug( " (we got coderef $coderef", DEBUG);
+
+               unless ($session->continue_request) {
+                       $session->status(
+                               OpenSRF::DomainObject::oilsConnectStatus->new(
+                                               statusCode => STATUS_REDIRECTED(),
+                                               status => 'Disconnect on max requests' ) );
+                       $session->kill_me;
+                       return 1;
+               }
+
+               if (ref $coderef) {
+                       my @args = $app_msg->params;
+                       my $appreq = OpenSRF::AppRequest->new( $session );
+
+                       $log->debug( "in_request = $in_request : [" . $appreq->threadTrace."]", DEBUG );
+                       if( $in_request ) {
+                               $log->debug( "Pushing onto pending requests: " . $appreq->threadTrace, DEBUG );
+                               push @pending_requests, [ $appreq, \@args, $coderef ]; 
+                               return 1;
+                       }
+
+
+                       $in_request++;
+
+                       $log->debug( "Executing coderef for {$method_name -> ".join(', ', @args)."}", INTERNAL );
+
+                       my $resp;
+                       try {
+                               my $start = time();
+                               $resp = $coderef->run( $appreq, @args); 
+                               my $time = sprintf '%.3f', time() - $start;
+                               $log->debug( "Method duration for {$method_name -> ".join(', ', @args)."}:  ". $time, DEBUG );
+                               if( ref( $resp ) ) {
+                                       $log->debug( "Calling respond_complete: ". $resp->toString(), INTERNAL );
+                                       $appreq->respond_complete( $resp );
+                               } else {
+                                       $appreq->status( OpenSRF::DomainObject::oilsConnectStatus->new(
+                                                               statusCode => STATUS_COMPLETE(),
+                                                               status => 'Request Complete' ) );
+                               }
+                       } catch Error with {
+                               my $e = shift;
+                               $e = $e->{-text} || $e->message if (ref $e);
+                               my $sess_id = $session->session_id;
+                               $session->status(
+                                       OpenSRF::DomainObject::oilsMethodException->new(
+                                                       statusCode      => STATUS_INTERNALSERVERERROR(),
+                                                       status          => " *** Call to [$method_name] failed for session ".
+                                                                          "[$sess_id], thread trace [".$appreq->threadTrace."]:\n".$e
+                                       )
+                               );
+                       };
+
+
+
+                       # ----------------------------------------------
+
+
+                       # XXX may need this later
+                       # $_->[1] = 1 for (@OpenSRF::AppSession::_CLIENT_CACHE);
+
+                       $in_request--;
+
+                       $log->debug( "Pending Requests: " . scalar(@pending_requests), INTERNAL );
+
+                       # cycle through queued requests
+                       while( my $aref = shift @pending_requests ) {
+                               $in_request++;
+                               my $resp;
+                               try {
+                                       my $start = time;
+                                       my $response = $aref->[2]->run( $aref->[0], @{$aref->[1]} );
+                                       my $time = sprintf '%.3f', time - $start;
+                                       $log->debug( "Method duration for {[".$aref->[2]->name." -> ".join(', ',@{$aref->[1]}).'}:  '.$time, DEBUG );
+
+                                       $appreq = $aref->[0];   
+                                       if( ref( $response ) ) {
+                                               $log->debug( "Calling respond_complete: ". $response->toString(), INTERNAL );
+                                               $appreq->respond_complete( $response );
+                                       } else {
+                                               $appreq->status( OpenSRF::DomainObject::oilsConnectStatus->new(
+                                                                       statusCode => STATUS_COMPLETE(),
+                                                                       status => 'Request Complete' ) );
+                                       }
+                                       $log->debug( "Executed: " . $appreq->threadTrace, DEBUG );
+                               } catch Error with {
+                                       my $e = shift;
+                                       $session->status(
+                                               OpenSRF::DomainObject::oilsMethodException->new(
+                                                               statusCode => STATUS_INTERNALSERVERERROR(),
+                                                               status => "Call to [".$aref->[2]->name."] faild:  ".$e->{-text}
+                                               )
+                                       );
+                               };
+                               $in_request--;
+                       }
+
+                       return 1;
+               } 
+               my $res = OpenSRF::DomainObject::oilsMethodException->new;
+               $session->send('ERROR', $res);
+               $session->kill_me;
+               return 1;
+
+       } else {
+               $log->debug( "Pushing ". $app_msg->toString ." onto queue", INTERNAL );
+               $session->push_queue([ $app_msg, $session->last_threadTrace ]);
+       }
+
+       $session->last_message_type('');
+       $session->last_message_protocol('');
+
+       return 1;
+}
+
+sub method_lookup {
+       my $self = shift;
+       my $method = shift;
+       my $proto = shift;
+
+       my $class = ref($self) || $self;
+
+       $log->debug("Looking up [$method] in [$self]", INTERNAL);
+       
+       my $obj = bless {} => $self;
+       if (my $coderef = $self->can("${method}_${proto}")) {
+               $$obj{code} = $coderef;
+               $$obj{name} = "${method}_${proto}";
+               return $obj;
+       }
+       return undef;
+}
+
+sub run {
+       my $self = shift;
+       $self->{code}->(@_);
+}
+
+sub is_action {
+       my $self = shift;
+       if (my $can = $self->can($self->{name} . '_action')) {
+               return $can->();
+       }
+       return 0;
+}
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Application/Client.pm b/src/perlmods/OpenSRF/Application/Client.pm
new file mode 100644 (file)
index 0000000..fc0665a
--- /dev/null
@@ -0,0 +1,6 @@
+package OpenSRF::App::Client;
+use base 'OpenSRF';
+use OpenSRF::Utils::Logger qw/:level/;
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Application/Demo/Math.pm b/src/perlmods/OpenSRF/Application/Demo/Math.pm
new file mode 100644 (file)
index 0000000..be5b46a
--- /dev/null
@@ -0,0 +1,125 @@
+package OpenSRF::Application::Demo::Math;
+use base qw/OpenSRF::Application/;
+use OpenSRF::Application;
+use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::DomainObject::oilsResponse;
+use OpenSRF::EX qw/:try/;
+use strict;
+use warnings;
+
+sub DESTROY{}
+
+our $log = 'OpenSRF::Utils::Logger';
+
+#sub method_lookup {
+#
+#      my( $class, $method_name, $method_proto ) = @_;
+#
+#      if( $method_name eq "add" ) {
+#              return \&add;
+#      }
+
+#      if( $method_name eq "sub" ) {
+#              return \&sub;
+#      }
+#
+#      if( $method_name eq "mult" ) {
+#              return \&mult;
+#      }
+#
+#      if( $method_name eq "div" ) {
+#              return \&div;
+#      }
+
+#      return undef;
+#
+#}
+
+sub send_request {
+
+       my $method_name = shift;
+       my @params = @_;
+
+       $log->debug( "Creating a client environment", DEBUG );
+       my $session = OpenSRF::AppSession->create( 
+                       "dbmath", sysname => 'math', secret => '12345' );
+
+       $log->debug( "Sending request to math server", INTERNAL );
+       
+       my $method = OpenSRF::DomainObject::oilsMethod->new( method => $method_name );
+       
+       $method->params( @params );
+       
+
+       my $req; 
+       my $resp;
+               
+
+       try {
+
+               for my $nn (0..1) {
+                       my $vv = $session->connect();
+                       if($vv) { last; }
+                       if( $nn and !$vv ) {
+                               throw OpenSRF::EX::CRITICAL ("DBMath connect attempt timed out");
+                       }
+               }
+
+               $req = $session->request( $method );
+               $resp = $req->recv(10); 
+
+       } catch OpenSRF::DomainObject::oilsAuthException with { 
+               my $e = shift;
+               $e->throw();
+       }; 
+
+       if ( defined($resp) and $resp and $resp->class->isa('OpenSRF::DomainObject::oilsResult') ){ 
+
+               $log->debug( "Math server returned " . $resp->toString(1), INTERNAL );
+               $req->finish;
+               $session->finish;
+               return $resp;
+
+       } else {
+
+               if( $resp ) { $log->debug( "Math received \n".$resp->toString(), ERROR ); }
+               else{ $log->debug( "Math received empty value", ERROR ); }
+               $req->finish;
+               $session->finish;
+               throw OpenSRF::EX::ERROR ("Did not receive expected data from MathDB");
+
+       }
+}
+
+
+sub add_1_action { 1 };
+sub add_1 {
+
+       my $client = shift;
+       my @args = @_;
+       return send_request( "add", @args );
+}
+
+sub sub_1_action { 1 };
+sub sub_1 {
+       my $client = shift;
+       my @args = @_;
+       return send_request( "sub", @args );
+}
+
+sub mult_1_action { 1 };
+sub mult_1 {
+       my $client = shift;
+       my @args = @_;
+       return send_request( "mult", @args );
+}
+
+sub div_1_action { 1 };
+sub div_1 {
+       my $client = shift;
+       my @args = @_;
+       return send_request( "div", @args );
+}
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Application/Demo/MathDB.pm b/src/perlmods/OpenSRF/Application/Demo/MathDB.pm
new file mode 100644 (file)
index 0000000..0b40256
--- /dev/null
@@ -0,0 +1,94 @@
+package OpenSRF::Application::Demo::MathDB;
+use base qw/OpenSRF::Application/;
+use OpenSRF::Application;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::DomainObject::oilsPrimitive;
+use OpenSRF::Utils::Logger qw/:level/;
+use strict;
+use warnings;
+sub DESTROY{}
+our $log = 'OpenSRF::Utils::Logger';
+
+#sub method_lookup {
+#
+#      my( $class, $method_name, $method_proto ) = @_;
+#
+#      if( $method_name eq "add" ) {
+#              return \&add;
+#      }
+#
+#      if( $method_name eq "sub" ) {
+#              return \&sub;
+#      }
+#
+#      if( $method_name eq "mult" ) {
+#              return \&mult;
+#      }
+#
+#      if( $method_name eq "div" ) {
+#              return \&div;
+#      }
+#
+#      return undef;
+#
+#}
+
+sub add_1 {
+       my $client = shift;
+       my @args = @_;
+       $log->debug("Adding @args", INTERNAL);
+       $log->debug("AppRequest is $client", INTERNAL);
+       my $n1 = shift; my $n2 = shift;
+       $n1 =~ s/\s+//; $n2 =~ s/\s+//;
+       my $a = $n1 + $n2;
+       my $result = new OpenSRF::DomainObject::oilsResult;
+       $result->content( OpenSRF::DomainObject::oilsScalar->new($a) );
+       return $result;
+       $client->respond($result);
+       return 1;
+}
+sub sub_1 {
+       my $client = shift;
+       my @args = @_;
+       $log->debug("Subbing @args", INTERNAL);
+       $log->debug("AppRequest is $client", INTERNAL);
+       my $n1 = shift; my $n2 = shift;
+       $n1 =~ s/\s+//; $n2 =~ s/\s+//;
+       my $a = $n1 - $n2;
+       my $result = new OpenSRF::DomainObject::oilsResult;
+       $result->content( OpenSRF::DomainObject::oilsScalar->new($a) );
+       return $result;
+       $client->respond($result);
+       return 1;
+}
+
+sub mult_1 {
+       my $client = shift;
+       my @args = @_;
+       $log->debug("Multiplying @args", INTERNAL);
+       $log->debug("AppRequest is $client", INTERNAL);
+       my $n1 = shift; my $n2 = shift;
+       $n1 =~ s/\s+//; $n2 =~ s/\s+//;
+       my $a = $n1 * $n2;
+       my $result = new OpenSRF::DomainObject::oilsResult;
+       $result->content( OpenSRF::DomainObject::oilsScalar->new($a) );
+#      $client->respond($result);
+       return $result;
+}
+
+sub div_1 {
+       my $client = shift;
+       my @args = @_;
+       $log->debug("Dividing @args", INTERNAL);
+       $log->debug("AppRequest is $client", INTERNAL);
+       my $n1 = shift; my $n2 = shift;
+       $n1 =~ s/\s+//; $n2 =~ s/\s+//;
+       my $a = $n1 / $n2;
+       my $result = new OpenSRF::DomainObject::oilsResult;
+       $result->content( OpenSRF::DomainObject::oilsScalar->new($a) );
+       return $result;
+       $client->respond($result);
+       return 1;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM.pm b/src/perlmods/OpenSRF/DOM.pm
new file mode 100644 (file)
index 0000000..7e23d9c
--- /dev/null
@@ -0,0 +1,289 @@
+use XML::LibXML;
+use OpenSRF::Utils::Logger qw(:level);
+
+package XML::LibXML::Element;
+use OpenSRF::EX;
+
+sub AUTOLOAD {
+       my $self = shift;
+       (my $name = $AUTOLOAD) =~ s/.*://;   # strip fully-qualified portion
+
+       ### Check for recursion
+       my $calling_method = (caller(1))[3];
+       my @info = caller(1);
+
+       if( @info ) {
+               if ($info[0] =~ /AUTOLOAD/) { @info = caller(2); }
+       }
+       unless( @info ) { @info = caller(); }
+       if( $calling_method  and $calling_method eq "XML::LibXML::Element::AUTOLOAD" ) {
+               throw OpenSRF::EX::PANIC ( "RECURSION! Caller [ @info ] | Object [ ".ref($self)." ]\n ** Trying to call $name", ERROR );
+       }
+       ### Check for recursion
+       
+       #OpenSRF::Utils::Logger->debug( "Autoloading method for DOM: $AUTOLOAD on ".$self->toString, INTERNAL );
+
+       my $new_node = OpenSRF::DOM::upcast($self);
+       OpenSRF::Utils::Logger->debug( "Autoloaded to: ".ref($new_node), INTERNAL );
+
+       return $new_node->$name(@_);
+}
+
+
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM;
+use base qw/XML::LibXML OpenSRF/;
+
+our %_NAMESPACE_MAP = (
+       'http://open-ils.org/xml/namespaces/oils_v1' => 'oils',
+);
+
+our $_one_true_parser;
+
+sub new {
+       my $self = shift;
+       return $_one_true_parser if (defined $_one_true_parser);
+       $_one_true_parser = $self->SUPER::new(@_);
+       $_one_true_parser->keep_blanks(0);
+       $XML::LibXML::skipXMLDeclaration = 0;
+       return $_one_true_parser = $self->SUPER::new(@_);
+}
+
+sub createDocument {
+       my $self = shift;
+
+       # DOM API: createDocument(namespaceURI, qualifiedName, doctype?)
+       my $doc = XML::LibXML::Document->new("1.0", "UTF-8");
+       my $el = $doc->createElement('root');
+
+       $el->setNamespace('http://open-ils.org/xml/namespaces/oils_v1', 'oils', 1);
+       $doc->setDocumentElement($el);
+
+       return $doc;
+}
+
+my %_loaded_classes;
+sub upcast {
+       my $node = shift;
+       return undef unless $node;
+
+       my ($ns,$tag) = split ':' => $node->nodeName;
+
+       return $node unless ($ns eq 'oils');
+
+       my $class = "OpenSRF::DOM::Element::$tag";
+       unless (exists $_loaded_classes{$class}) {
+               eval "use $class;";
+               $_loaded_classes{$class} = 1;
+       }
+       if ($@) {
+               OpenSRF::Utils::Logger->error("Couldn't use $class! $@");
+       }
+
+       #OpenSRF::Utils::Logger->debug("Upcasting ".$node->toString." to $class", INTERNAL);
+
+       return bless $node => $class;
+}
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Node;
+use base 'XML::LibXML::Node';
+
+sub new {
+       my $class = shift;
+       return bless $class->SUPER::new(@_) => $class;
+}
+
+sub childNodes {
+       my $self = shift;
+       my @children = $self->_childNodes();
+       return wantarray ? @children : OpenSRF::DOM::NodeList->new( @children );
+}
+
+sub attributes {
+       my $self = shift;
+       my @attr = $self->_attributes();
+       return wantarray ? @attr : OpenSRF::DOM::NamedNodeMap->new( @attr );
+}
+
+sub findnodes {
+       my ($node, $xpath) = @_;
+       my @nodes = $node->_findnodes($xpath);
+       if (wantarray) {
+               return @nodes;
+       } else {
+               return OpenSRF::DOM::NodeList->new(@nodes);
+       }
+}
+
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::NamedNodeMap;
+use base 'XML::LibXML::NamedNodeMap';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::NodeList;
+use base 'XML::LibXML::NodeList';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Element;
+use base 'XML::LibXML::Element';
+
+sub new {
+       my $class = shift;
+
+       # magically create the element (tag) name, or build a blank element
+       (my $name = $class) =~ s/^OpenSRF::DOM::Element:://;
+       if ($name) {
+               $name = "oils:$name";
+       } else {
+               undef $name;
+       }
+
+       my $self = $class->SUPER::new($name);
+
+       my %attrs = @_;
+       for my $aname (keys %attrs) {
+               $self->setAttribute($aname, $attrs{$aname});
+       }
+
+       return $self;
+}
+
+sub getElementsByTagName {
+    my ( $node , $name ) = @_;
+        my $xpath = "descendant::$name";
+    my @nodes = $node->_findnodes($xpath);
+        return wantarray ? @nodes : OpenSRF::DOM::NodeList->new(@nodes);
+}
+
+sub  getElementsByTagNameNS {
+    my ( $node, $nsURI, $name ) = @_;
+    my $xpath = "descendant::*[local-name()='$name' and namespace-uri()='$nsURI']";
+    my @nodes = $node->_findnodes($xpath);
+    return wantarray ? @nodes : OpenSRF::DOM::NodeList->new(@nodes);
+}
+
+sub getElementsByLocalName {
+    my ( $node,$name ) = @_;
+    my $xpath = "descendant::*[local-name()='$name']";
+    my @nodes = $node->_findnodes($xpath);
+    return wantarray ? @nodes : OpenSRF::DOM::NodeList->new(@nodes);
+}
+
+sub getChildrenByLocalName {
+    my ( $node,$name ) = @_;
+    my $xpath = "./*[local-name()='$name']";
+    my @nodes = $node->_findnodes($xpath);
+    return @nodes;
+}
+
+sub getChildrenByTagName {
+    my ( $node, $name ) = @_;
+    my @nodes = grep { $_->nodeName eq $name } $node->childNodes();
+    return @nodes;
+}
+
+sub getChildrenByTagNameNS {
+    my ( $node, $nsURI, $name ) = @_;
+    my $xpath = "*[local-name()='$name' and namespace-uri()='$nsURI']";
+    my @nodes = $node->_findnodes($xpath);
+    return @nodes;
+}
+
+sub appendWellBalancedChunk {
+    my ( $self, $chunk ) = @_;
+
+    my $local_parser = OpenSRF::DOM->new();
+    my $frag = $local_parser->parse_xml_chunk( $chunk );
+
+    $self->appendChild( $frag );
+}
+
+package OpenSRF::DOM::Element::root;
+use base 'OpenSRF::DOM::Element';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Text;
+use base 'XML::LibXML::Text';
+
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Comment;
+use base 'XML::LibXML::Comment';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::CDATASection;
+use base 'XML::LibXML::CDATASection';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Document;
+use base 'XML::LibXML::Document';
+
+sub empty {
+       my $self = shift;
+       return undef unless (ref($self));
+       $self->documentElement->removeChild($_) for $self->documentElement->childNodes;
+       return $self;
+}
+
+sub new {
+       my $class = shift;
+       return bless $class->SUPER::new(@_) => $class;
+}
+
+sub getElementsByTagName {
+       my ( $doc , $name ) = @_;
+       my $xpath = "descendant-or-self::node()/$name";
+       my @nodes = $doc->_findnodes($xpath);
+       return wantarray ? @nodes : OpenSRF::DOM::NodeList->new(@nodes);
+}
+
+sub  getElementsByTagNameNS {
+       my ( $doc, $nsURI, $name ) = @_;
+       my $xpath = "descendant-or-self::*[local-name()='$name' and namespace-uri()='$nsURI']";
+       my @nodes = $doc->_findnodes($xpath);
+       return wantarray ? @nodes : OpenSRF::DOM::NodeList->new(@nodes);
+}
+
+sub getElementsByLocalName {
+       my ( $doc,$name ) = @_;
+       my $xpath = "descendant-or-self::*[local-name()='$name']";
+       my @nodes = $doc->_findnodes($xpath);
+       return wantarray ? @nodes : OpenSRF::DOM::NodeList->new(@nodes);
+}
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::DocumentFragment;
+use base 'XML::LibXML::DocumentFragment';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Attr;
+use base 'XML::LibXML::Attr';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Dtd;
+use base 'XML::LibXML::Dtd';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::PI;
+use base 'XML::LibXML::PI';
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Namespace;
+use base 'XML::LibXML::Namespace';
+
+sub isEqualNode {
+       my ( $self, $ref ) = @_;
+       if ( $ref->isa("XML::LibXML::Namespace") ) {
+               return $self->_isEqual($ref);
+       }
+       return 0;
+}
+
+#--------------------------------------------------------------------------------
+package OpenSRF::DOM::Schema;
+use base 'XML::LibXML::Schema';
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/domainObject.pm b/src/perlmods/OpenSRF/DOM/Element/domainObject.pm
new file mode 100644 (file)
index 0000000..4d9fd2a
--- /dev/null
@@ -0,0 +1,114 @@
+package OpenSRF::DOM::Element::domainObject;
+use strict; use warnings;
+use base 'OpenSRF::DOM::Element';
+use OpenSRF::DOM;
+use OpenSRF::DOM::Element::domainObjectAttr;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX;
+use Carp;
+#use OpenSRF::DomainObject::oilsPrimitive;
+#use OpenSRF::DomainObject::oilsResponse;
+use vars qw($AUTOLOAD);
+
+sub AUTOLOAD {
+       my $self = shift;
+       (my $name = $AUTOLOAD) =~ s/.*://;   # strip fully-qualified portion
+
+       return class($self) if ($name eq 'class');
+       if ($self->can($name)) {
+               return $self->$name(@_);
+       }
+
+       if (1) {
+               ### Check for recursion
+               my $calling_method = (caller(1))[3];
+               my @info = caller(1);
+
+               if( @info ) {
+                       if ($info[0] =~ /AUTOLOAD/) { @info = caller(2); }
+               }
+               unless( @info ) { @info = caller(); }
+
+               if( $calling_method  and $calling_method eq "OpenSRF::DOM::Element::domainObject::AUTOLOAD" ) {
+                       warn Carp::cluck;
+                       throw OpenSRF::EX::PANIC ( "RECURSION! Caller [ @info[0..2] ] | Object [ ".ref($self)." ]\n ** Trying to call $name", ERROR );
+               }
+               ### Check for recursion
+       }
+
+        my @args = @_;
+       my $meth = class($self).'::'.$name;
+
+       try {
+               return $self->$meth(@args);
+       } catch Error with {
+               my $e = shift;
+               OpenSRF::Utils::Logger->error( $@ . $e);
+               die $@;
+       };
+
+
+       my $node = OpenSRF::DOM::Element::domainObject::upcast($self);
+       OpenSRF::Utils::Logger->debug( "Autoloaded to: ".ref($node), INTERNAL );
+
+       return $node->$name(@_);
+}
+
+sub downcast {
+       my $obj = shift;
+       return bless $obj => 'XML::LibXML::Element';
+}
+
+sub upcast {
+       my $self = shift;
+       return bless $self => class($self);
+}
+
+sub new {
+       my $class = shift;
+       my $type = shift;
+       my $obj = $class->SUPER::new( name => $type );
+       while (@_) {
+               my ($attr,$val) = (shift,shift);
+               last unless ($attr and $val);
+               $obj->addAttr( $attr, $val );
+               #$obj->appendChild( OpenSRF::DOM::Element::domainObjectAttr->new($attr, $val) );
+       }
+       return $obj;
+}
+
+sub class {
+       my $self = shift;
+       return 'OpenSRF::DomainObject::'.$self->getAttribute('name');
+}
+
+sub base_type {
+       my $self = shift;
+        return $self->getAttribute('name');
+}
+
+sub addAttr {
+       my $self = shift;
+       $self->appendChild( $_ ) for OpenSRF::DOM::Element::domainObjectAttr->new(@_);
+       return $self;
+}
+
+sub attrNode {
+       my $self = shift;
+       my $type = shift;
+       return (grep { $_->getAttribute('name') eq $type } $self->getChildrenByTagName("oils:domainObjectAttr"))[0];
+}
+
+sub attrHash {
+       my $self = shift;
+       my %attrs = map { ( $_->getAttribute('name') => $_->getAttribute('value') ) } $self->getChildrenByTagName('oils:domainObjectAttr');
+
+       return \%attrs;
+}
+
+sub attrValue {
+       my $self = shift;
+       return $self->attrHash->{shift};
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/domainObjectAttr.pm b/src/perlmods/OpenSRF/DOM/Element/domainObjectAttr.pm
new file mode 100644 (file)
index 0000000..fbdc28e
--- /dev/null
@@ -0,0 +1,15 @@
+package OpenSRF::DOM::Element::domainObjectAttr;
+use base 'OpenSRF::DOM::Element';
+
+sub new {
+       my $class = shift;
+       my @nodes;
+       while (@_) {
+               my ($name,$val) = (shift,shift);
+               push @nodes, $class->SUPER::new(name => $name, value => $val);
+       }
+       return @nodes if (wantarray);
+       return $nodes[0];
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/domainObjectCollection.pm b/src/perlmods/OpenSRF/DOM/Element/domainObjectCollection.pm
new file mode 100644 (file)
index 0000000..264c435
--- /dev/null
@@ -0,0 +1,116 @@
+package OpenSRF::DOM::Element::domainObjectCollection;
+use base 'OpenSRF::DOM::Element';
+use OpenSRF::DOM::Element::domainObjectAttr;
+use OpenSRF::EX;
+
+sub AUTOLOAD {
+       my $self = CORE::shift;
+       (my $name = $AUTOLOAD) =~ s/.*://;   # strip fully-qualified portion
+
+       return  class($self) if ($name eq 'class');
+
+       my @args = @_;
+       my $meth = class($self).'::'.$name;
+
+       ### Check for recursion
+       my $calling_method = (caller(1))[3];
+       my @info = caller(1);
+
+       if( @info ) {
+               if ($info[0] =~ /AUTOLOAD/) { @info = caller(2); }
+       }
+       unless( @info ) { @info = caller(); }
+               if( $calling_method  and $calling_method eq "OpenSRF::DOM::Element::domainObjectCollection::AUTOLOAD" ) {
+               throw OpenSRF::EX::PANIC ( "RECURSION! Caller [ @info ] | Object [ ".ref($self)." ]\n ** Trying to call $name", ERROR );
+       }
+       ### Check for recursion
+
+       try {
+               return $self->$meth(@args);;
+       } catch Error with {
+               my $e = shift;
+               OpenSRF::Utils::Logger->error( $@ . $e);
+               die $@;
+       };
+
+       return upcast($self)->$name(@_);
+}
+
+sub downcast {
+       my $obj = CORE::shift;
+       return bless $obj => 'XML::LibXML::Element';
+}
+
+sub upcast {
+       my $self = CORE::shift;
+       return bless $self => class($self);
+}
+
+sub new {
+       my $class = CORE::shift;
+       my $type = CORE::shift;
+       my $obj = $class->SUPER::new( name => $type );
+       while ( my $val = shift) {
+               throw OpenSRF::EX::NotADomainObject
+                       if (ref $val and $val->nodeName !~ /^oils:domainObject/o);
+               $obj->appendChild( $val );
+       }
+       return $obj;
+}
+
+sub class {
+       my $self = shift;
+       return 'OpenSRF::DomainObjectCollection::'.$self->getAttribute('name');
+}
+
+sub base_type {
+       my $self = shift;
+       return $self->getAttribute('name');
+}
+
+sub pop { 
+       my $self = CORE::shift;
+       return $self->removeChild( $self->lastChild )->upcast;
+}
+
+sub push { 
+       my $self = CORE::shift;
+       my @args = @_;
+       for my $node (@args) {
+               #throw OpenSRF::EX::NotADomainObject ( "$_ must be a oils:domainOjbect*, it's a ".$_->nodeName )
+               #       unless ($_->nodeName =~ /^oils:domainObject/o);
+               
+               unless ($node->nodeName =~ /^oils:domainObject/o) {
+                       $node = OpenSRF::DomainObject::oilsScalar->new($node);
+               }
+
+               $self->appendChild( $node );
+       }
+}
+
+sub shift { 
+       my $self = CORE::shift;
+       return $self->removeChild( $self->firstChild )->upcast;
+}
+
+sub unshift { 
+       my $self = CORE::shift;
+       my @args = @_;
+       for (reverse @args) {
+               throw OpenSRF::EX::NotADomainObject
+                       unless ($_->nodeName =~ /^oils:domainObject/o);
+               $self->insertBefore( $_, $self->firstChild );
+       }
+}
+
+sub first {
+       my $self = CORE::shift;
+       return $self->firstChild->upcast;
+}
+
+sub list {
+       my $self = CORE::shift;
+       return map {(bless($_ => 'OpenSRF::DomainObject::'.$_->getAttribute('name')))} $self->childNodes;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/param.pm b/src/perlmods/OpenSRF/DOM/Element/param.pm
new file mode 100644 (file)
index 0000000..44993ef
--- /dev/null
@@ -0,0 +1,4 @@
+package OpenSRF::DOM::Element::param;
+use base 'OpenSRF::DOM::Element';
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/params.pm b/src/perlmods/OpenSRF/DOM/Element/params.pm
new file mode 100644 (file)
index 0000000..ee3755a
--- /dev/null
@@ -0,0 +1,4 @@
+package OpenSRF::DOM::Element::params;
+use base 'OpenSRF::DOM::Element';
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/searchCriteria.pm b/src/perlmods/OpenSRF/DOM/Element/searchCriteria.pm
new file mode 100644 (file)
index 0000000..c8b1eb3
--- /dev/null
@@ -0,0 +1,48 @@
+package OpenSRF::DOM::Element::searchCriteria;
+use base 'OpenSRF::DOM::Element';
+use OpenSRF::DOM;
+use OpenSRF::DOM::Element::searchCriterium;
+
+sub new {
+       my $self = shift;
+       my $class = ref($self) || $self;
+
+       if (@_ == 3 and !ref($_[1])) {
+               my @crit = @_;
+               @_ = ('AND', \@crit);
+       }
+
+       my ($joiner,@crits) = @_;
+
+       unless (@crits) {
+               push @crits, $joiner;
+               $joiner = 'AND';
+       }
+
+       my $collection = $class->SUPER::new(joiner => $joiner);
+
+       for my $crit (@crits) {
+               if (ref($crit) and ref($crit) =~ /ARRAY/) {
+                       if (ref($$crit[1])) {
+                               $collection->appendChild( $class->new(@$crit) );
+                       } else {
+                               $collection->appendChild( OpenSRF::DOM::Element::searchCriterium->new( @$crit ) );
+                       }
+               } else {
+                       $collection->appendChild($crit);
+               }
+       }
+       return $collection;
+}
+
+sub toSQL {
+       my $self = shift;
+
+       my @parts = ();
+       for my $kid ($self->childNodes) {
+               push @parts, $kid->toSQL;
+       }
+       return '(' . join(' '.$self->getAttribute('joiner').' ', @parts) . ')';
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/searchCriterium.pm b/src/perlmods/OpenSRF/DOM/Element/searchCriterium.pm
new file mode 100644 (file)
index 0000000..9bfa3b4
--- /dev/null
@@ -0,0 +1,116 @@
+package OpenSRF::DOM::Element::searchCriterium;
+use base 'OpenSRF::DOM::Element';
+use OpenSRF::DOM::Element::searchTargetValue;
+
+sub new {
+       my $class = shift;
+       my @nodes;
+       my @args = @_;
+       while (scalar(@args)) {
+               my ($attr,$cmp,$val) = (shift(@args),shift(@args),shift(@args),shift(@args));
+               push @nodes, $class->SUPER::new(property => $attr, comparison => $cmp);
+               $nodes[-1]->appendChild( $_ ) for OpenSRF::DOM::Element::searchTargetValue->new($val);
+       }
+       return @nodes if (wantarray);
+       return $nodes[0];
+}
+
+sub toSQL {
+       my $self = shift;
+       my %args = @_;
+
+       my $column = $self->getAttribute('property');
+       my $cmp = lc($self->getAttribute('comparison'));
+
+       my $value = [ map { ($_->getAttribute('value')) } $self->childNodes ];
+
+       if ($cmp eq '=' || $cmp eq '==' || $cmp eq 'eq' || $cmp eq 'is') {
+               $cmp = '=';
+               if (!$value || lc($value) eq 'null') {
+                       $cmp = 'IS';
+                       $value = 'NULL';
+               }
+               ($value = $value->[0]) =~ s/'/''/gmso;
+               $value =~ s/\\/\\\\/gmso;
+               $value = "'$value'" unless $args{no_quote};
+       } elsif ($cmp eq '>' || $cmp eq 'gt' || $cmp eq 'over' || $cmp eq 'after') {
+               $cmp = '>';
+               if (!$value || lc($value) eq 'null') {
+                       warn "Can not compare NULL";
+               }
+               ($value = $value->[0]) =~ s/'/''/gmso;
+               $value =~ s/\\/\\\\/gmso;
+               $value = "'$value'" unless $args{no_quote};
+       } elsif ($cmp eq '<' || $cmp eq 'lt' || $cmp eq 'under' || $cmp eq 'before') {
+               $cmp = '<';
+               if (!$value || lc($value) eq 'null') {
+                       warn "Can not compare NULL";
+               }
+               ($value = $value->[0]) =~ s/'/''/gmso;
+               $value =~ s/\\/\\\\/gmso;
+               $value = "'$value'" unless $args{no_quote};
+       } elsif ($cmp eq '!=' || $cmp eq '<>' || $cmp eq 'ne' || $cmp eq 'not') {
+               $cmp = '<>';
+               if (!$value || lc($value) eq 'null') {
+                       $cmp = 'IS NOT';
+                       $value = 'NULL';
+               }
+               ($value = $value->[0]) =~ s/'/''/gmso;
+               $value =~ s/\\/\\\\/gmso;
+               $value = "'$value'" unless $args{no_quote};
+       } elsif (lc($cmp) eq 'fts' || $cmp eq 'tsearch' || $cmp eq '@@') {
+               $cmp = '@@';
+               if (!$value || lc($value) eq 'null') {
+                       warn "Can not compare NULL";
+               }
+               ($value = $value->[0]) =~ s/'/''/gmso;
+               $value =~ s/\\/\\\\/gmso;
+               $value = "to_tsquery('$value')";
+       } elsif ($cmp eq 'like' || $cmp eq 'contains' || $cmp eq 'has') {
+               $cmp = 'LIKE';
+               if (!$value || lc($value) eq 'null') {
+                       warn "Can not compare NULL";
+               }
+               ($value = $value->[0]) =~ s/'/''/gmso;
+               $value =~ s/\\/\\\\/gmso;
+               $value =~ s/%/\\%/gmso;
+               $value =~ s/_/\\_/gmso;
+               $value = "'\%$value\%'";
+       } elsif ($cmp eq 'between') {
+               $cmp = 'BETWEEN';
+               if (!ref($value) || lc($value) eq 'null') {
+                       warn "Can not check 'betweenness' of NULL";
+               }
+               if (ref($value) and ref($value) =~ /ARRAY/o) {
+                       $value = "(\$text\$$$value[0]\$text\$ AND \$text\$$$value[-1]\$text\$)";
+               }
+       } elsif ($cmp eq 'not between') {
+               $cmp = 'NOT BETWEEN';
+               if (!ref($value) || lc($value) eq 'null') {
+                       warn "Can not check 'betweenness' of NULL";
+               }
+               if (ref($value) and ref($value) =~ /ARRAY/o) {
+                       $value = "(\$text\$$$value[0]\$text\$ AND \$text\$$$value[-1]\$text\$)";
+               }
+       } elsif ($cmp eq 'in' || $cmp eq 'any' || $cmp eq 'some') {
+               $cmp = 'IN';
+               if (!ref($value) || lc($value) eq 'null') {
+                       warn "Can not check 'inness' of NULL";
+               }
+               if (ref($value) and ref($value) =~ /ARRAY/o) {
+                       $value = '($text$'.join('$text$,$text$', @$value).'$text$)';
+               }
+       } elsif ($cmp eq 'not in' || $cmp eq 'not any' || $cmp eq 'not some') {
+               $cmp = 'NOT IN';
+               if (!ref($value) || lc($value) eq 'null') {
+                       warn "Can not check 'inness' of NULL";
+               }
+               if (ref($value) and ref($value) =~ /ARRAY/o) {
+                       $value = '($text$'.join('$text$,$text$', @$value).'$text$)';
+               }
+       }
+
+       return join(' ', ($column, $cmp, $value));
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/searchTargetValue.pm b/src/perlmods/OpenSRF/DOM/Element/searchTargetValue.pm
new file mode 100644 (file)
index 0000000..7e6d3c8
--- /dev/null
@@ -0,0 +1,23 @@
+package OpenSRF::DOM::Element::searchTargetValue;
+use base 'OpenSRF::DOM::Element';
+
+sub new {
+       my $self = shift;
+       my $class = ref($self) || $self;
+       my @args = @_;
+
+       my @values = ();
+       for my $val (@args) {
+               next unless ($val);
+               if (ref($val)) {
+                       push @values, $class->new(@$val);
+               } else {
+                       push @values, $class->SUPER::new( value => $val );
+               }
+               
+       }
+       return $values[0] if (!wantarray);
+       return @values;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DOM/Element/userAuth.pm b/src/perlmods/OpenSRF/DOM/Element/userAuth.pm
new file mode 100644 (file)
index 0000000..1072017
--- /dev/null
@@ -0,0 +1,178 @@
+package OpenSRF::DOM::Element::userAuth;
+use OpenSRF::DOM;
+use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::Utils::Config;
+use Digest::MD5 qw/md5_hex/;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse;
+use OpenSRF::App::Auth;
+use OpenSRF::EX qw/:try/;
+use OpenSRF::Utils::Cache;
+
+use base 'OpenSRF::DOM::Element';
+
+my $log = 'OpenSRF::Utils::Logger';
+
+=head1 NAME
+
+OpenSRF::DOM::Element::userAuth
+
+=over 4
+
+User authentication data structure for use in oilsMessage objects.
+
+=back
+
+=head1 SYNOPSIS
+
+ use OpenSRF::DOM::Element::userAuth;
+
+ %auth_structure = ( userid   => '0123456789', secret => 'junko' );
+ %auth_structure = ( username => 'miker',      secret => 'junko' );
+
+ my $auth = OpenSRF::DOM::Element::userAuth->new( %auth_structure );
+
+...
+
+ my %server_auth = ( sysname   => 'OPACServer',
+                     secret    => 'deadbeefdeadbeef' );
+
+ my $auth = OpenSRF::DOM::Element::userAuth->new( %server_auth );
+
+=cut
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my %args = @_;
+
+       $args{hashseed} ||= int( rand( $$ ) );
+
+       $args{secret} = md5_hex($args{secret});
+       $args{secret} = md5_hex($args{hashseed}. $args{secret});
+
+       return $class->SUPER::new( %args );
+}
+
+sub username {
+       my $self = shift;
+       return $self->getAttribute('username');
+}
+
+sub userid {
+       my $self = shift;
+       return $self->getAttribute('userid');
+}
+
+sub sysname {
+       my $self = shift;
+       return $self->getAttribute('sysname');
+}
+
+sub secret {
+       my $self = shift;
+       return $self->getAttribute('secret');
+}
+
+sub hashseed {
+       my $self = shift;
+       return $self->getAttribute('hashseed');
+}
+
+sub authenticate {
+       my $self = shift;
+       my $session = shift;
+       my $u = $self->username ||
+               $self->userid ||
+               $self->sysname;
+       $log->debug("Authenticating user [$u]",INFO);
+
+
+       # We need to make sure that we are not the auth server.  If we are,
+       # we don't want to send a request to ourselves.  Instead just call
+       # the local auth method.
+       my @params = ( $u, $self->secret, $self->hashseed );
+       my $res;
+
+       # ------------------------------
+       # See if we can auth with the cache first
+       $log->debug( "Attempting cache auth...", INTERNAL );
+       my $cache = OpenSRF::Utils::Cache->current("user"); 
+       my $value = $cache->get( $u );
+
+       if( $value  and $value eq $self->secret ) {
+               $log->debug( "User $u is cached and authenticated", INTERNAL );
+               return 1;
+       }
+       # ------------------------------
+
+       if( $session->service eq "auth" ) {
+               $log->debug( "We are AUTH. calling local auth", DEBUG ); 
+               my $meth = OpenSRF::App::Auth->method_lookup('authenticate', 1);
+               $log->debug("Meth ref is $meth", INTERNAL);
+               $res = $meth->run( 1, @params );
+
+       } else { 
+               $log->debug( "Calling AUTH server", DEBUG );    
+               $res = _request_remote_auth( $session, @params ); 
+       }
+
+
+       if( $res and $res->class->isa('OpenSRF::DomainObject::oilsResult') and 
+                       $res->content and ($res->content->value eq "yes") ) {
+
+               $log->debug( "User $u is authenticated", DEBUG );
+               $log->debug( "Adding $u to cache", INTERNAL );
+
+               # Add to the cache ------------------------------
+               $cache->set( $u, $self->secret ); 
+
+               return 1;
+
+       } else { 
+               return 0; 
+       }
+       
+} 
+
+sub _request_remote_auth {
+
+       my $server_session = shift;
+       my @params = @_;
+
+       my $service = $server_session->service;
+
+       my @server_auth = (sysname => OpenSRF::Utils::Config->current->$service->sysname,
+                          secret  => OpenSRF::Utils::Config->current->$service->secret );
+
+       my $session = OpenSRF::AppSession->create( "auth", @server_auth ); 
+
+       $log->debug( "Sending request to auth server", INTERNAL );
+       
+       my $req; my $res;
+
+       try {
+
+               if( ! $session->connect() ) {
+                       throw OpenSRF::EX::CRITICAL ("Cannot communicate with auth server");
+               }
+               $req = $session->request( authenticate => @params );
+               $req->wait_complete( OpenSRF::Utils::Config->current->client->connect_timeout );
+               $res = $req->recv(); 
+
+       } catch OpenSRF::DomainObject::oilsAuthException with {
+               return 0;
+
+       } finally {
+               $req->finish() if $req;
+               $session->finish() if $session;
+       };
+
+       return $res;
+
+}
+
+
+
+1;
diff --git a/src/perlmods/OpenSRF/DomainObject.pm b/src/perlmods/OpenSRF/DomainObject.pm
new file mode 100644 (file)
index 0000000..9e7d960
--- /dev/null
@@ -0,0 +1,90 @@
+package OpenSRF::DomainObject;
+use base 'OpenSRF::DOM::Element::domainObject';
+use OpenSRF::DOM;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::DomainObject::oilsPrimitive;
+my $logger = "OpenSRF::Utils::Logger";
+
+=head1 NAME
+
+OpenSRF::DomainObject
+
+=head1 SYNOPSIS
+
+OpenSRF::DomainObject is an abstract base class.  It
+should not be used directly.  See C<OpenSRF::DomainObject::*>
+for details.
+
+=cut
+
+my $tmp_doc;
+
+sub object_castor {
+       my $self = shift;
+       my $node = shift;
+
+       return unless (defined $node);
+
+       if (ref($node) eq 'HASH') {
+               return new OpenSRF::DomainObject::oilsHash (%$node);
+       } elsif (ref($node) eq 'ARRAY') {
+               return new OpenSRF::DomainObject::oilsArray (@$node);
+       }
+
+       return $node;
+}
+
+sub native_castor {
+       my $self = shift;
+       my $node = shift;
+
+       return unless (defined $node);
+
+       if ($node->nodeType == 3) {
+               return $node->nodeValue;
+       } elsif ($node->nodeName =~ /domainObject/o) {
+               return $node->tie_me if ($node->class->can('tie_me'));
+       }
+       return $node;
+}
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       (my $type = $class) =~ s/^.+://o;
+
+       $tmp_doc ||= OpenSRF::DOM->createDocument;
+       my $dO = OpenSRF::DOM::Element::domainObject->new( $type, @_ );
+
+       $tmp_doc->documentElement->appendChild($dO);
+
+       return $dO;
+}
+
+sub _attr_get_set {
+       my $self = shift;
+       my $part = shift;
+
+       $logger->debug( "DomainObject:_attr_get_set: ". $self->toString, INTERNAL );
+
+       my $node = $self->attrNode($part);
+
+       $logger->debug( "DomainObject:_attr_get_set " . $node->toString(), INTERNAL ) if ($node);
+
+
+       if (defined(my $new_value = shift)) {
+               if (defined $node) {
+                       my $old_val = $node->getAttribute( "value" );
+                       $node->setAttribute(value => $new_value);
+                       return $old_val;
+               } else {
+                       $self->addAttr( $part => $new_value );
+                       return $new_value;
+               }
+       } elsif ( $node ) {
+               return $node->getAttribute( "value" );
+       }
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DomainObject/oilsMessage.pm b/src/perlmods/OpenSRF/DomainObject/oilsMessage.pm
new file mode 100644 (file)
index 0000000..b7343e6
--- /dev/null
@@ -0,0 +1,344 @@
+package OpenILS::DomainObject::oilsMessage;
+use base 'OpenILS::DomainObject';
+use OpenILS::AppSession;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use OpenILS::Utils::Logger qw/:level/;
+use warnings; use strict;
+use OpenILS::EX qw/:try/;
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsMessage
+
+=head1
+
+use OpenILS::DomainObject::oilsMessage;
+
+my $msg = OpenILS::DomainObject::oilsMessage->new( type => 'CONNECT' );
+
+$msg->userAuth( $userAuth_element );
+
+$msg->payload( $domain_object );
+
+=head1 ABSTRACT
+
+OpenILS::DomainObject::oilsMessage is used internally to wrap data sent
+between client and server.  It provides the structure needed to authenticate
+session data, and also provides the logic needed to unwrap session data and 
+pass this information along to the Application Layer.
+
+=cut
+
+my $log = 'OpenILS::Utils::Logger';
+
+=head1 METHODS
+
+=head2 OpenILS::DomainObject::oilsMessage->type( [$new_type] )
+
+=over 4
+
+Used to specify the type of message.  One of
+B<CONNECT, REQUEST, RESULT, STATUS, ERROR, or DISCONNECT>.
+
+=back
+
+=cut
+
+sub type {
+       my $self = shift;
+       return $self->_attr_get_set( type => shift );
+}
+
+=head2 OpenILS::DomainObject::oilsMessage->protocol( [$new_protocol_number] )
+
+=over 4
+
+Used to specify the protocol of message.  Currently, only protocol C<1> is
+supported.  This will be used to check that messages are well-formed, and as
+a hint to the Application as to which version of a method should fulfill a
+REQUEST message.
+
+=back
+
+=cut
+
+sub protocol {
+       my $self = shift;
+       return $self->_attr_get_set( protocol => shift );
+}
+
+=head2 OpenILS::DomainObject::oilsMessage->threadTrace( [$new_threadTrace] );
+
+=over 4
+
+Sets or gets the current message sequence identifier, or thread trace number,
+for a message.  Useful as a debugging aid, but that's about it.
+
+=back
+
+=cut
+
+sub threadTrace {
+       my $self = shift;
+       return $self->_attr_get_set( threadTrace => shift );
+}
+
+=head2 OpenILS::DomainObject::oilsMessage->update_threadTrace
+
+=over 4
+
+Increments the threadTrace component of a message.  This is automatic when
+using the normal session processing stack.
+
+=back
+
+=cut
+
+sub update_threadTrace {
+       my $self = shift;
+       my $tT = $self->threadTrace;
+
+       $tT ||= 0;
+       $tT++;
+
+       $log->debug("Setting threadTrace to $tT",DEBUG);
+
+       $self->threadTrace($tT);
+
+       return $tT;
+}
+
+=head2 OpenILS::DomainObject::oilsMessage->payload( [$new_payload] )
+
+=over 4
+
+Sets or gets the payload of a message.  This should be exactly one object
+of (sub)type domainObject or domainObjectCollection.
+
+=back
+
+=cut
+
+sub payload {
+       my $self = shift;
+       my $new_pl = shift;
+
+       my ($payload) = $self->getChildrenByTagName('oils:domainObjectCollection') ||
+                               $self->getChildrenByTagName('oils:domainObject');
+       if ($new_pl) {
+               $payload = $self->removeChild($payload) if ($payload);
+               $self->appendChild($new_pl);
+               return $new_pl unless ($payload);
+       }
+
+       return OpenILS::DOM::upcast($payload)->upcast if ($payload);
+}
+
+=head2 OpenILS::DomainObject::oilsMessage->userAuth( [$new_userAuth_element] )
+
+=over 4
+
+Sets or gets the userAuth element for this message.  This is used internally by the
+session object.
+
+=back
+
+=cut
+
+sub userAuth {
+       my $self = shift;
+       my $new_ua = shift;
+
+       my ($ua) = $self->getChildrenByTagName('oils:userAuth');
+       if ($new_ua) {
+               $ua = $self->removeChild($ua) if ($ua);
+               $self->appendChild($new_ua);
+               return $new_ua unless ($ua);
+       }
+
+       return $ua;
+}
+
+=head2 OpenILS::DomainObject::oilsMessage->handler( $session_id )
+
+=over 4
+
+Used by the message processing stack to set session state information from the current
+message, and then sends control (via the payload) to the Application layer.
+
+=back
+
+=cut
+
+sub handler {
+       my $self = shift;
+       my $session = shift;
+
+       my $mtype = $self->type;
+       my $protocol = $self->protocol || 1;;
+       my $tT = $self->threadTrace;
+
+       $session->last_message_type($mtype);
+       $session->last_message_protocol($protocol);
+       $session->last_threadTrace($tT);
+
+       $log->debug(" Received protocol => [$protocol], MType => [$mtype], ".
+                       "from [".$session->remote_id."], threadTrace[".$self->threadTrace."]", INFO);
+       $log->debug("endpoint => [".$session->endpoint."]", DEBUG);
+       $log->debug("OpenILS::AppSession->SERVER => [".$session->SERVER()."]", DEBUG);
+
+       $log->debug("Before ALL", DEBUG);
+
+       my $val;
+       if ( $session->endpoint == $session->SERVER() ) {
+               $val = $self->do_server( $session, $mtype, $protocol, $tT );
+
+       } elsif ($session->endpoint == $session->CLIENT()) {
+               $val = $self->do_client( $session, $mtype, $protocol, $tT );
+       }
+
+       if( $val ) {
+               return OpenILS::Application->handler($session, $self->payload);
+       }
+
+       return 1;
+
+}
+
+
+
+# handle server side message processing
+
+# !!! Returning 0 means that we don't want to pass ourselves up to the message layer !!!
+sub do_server {
+       my( $self, $session, $mtype, $protocol, $tT ) = @_;
+
+       # A Server should never receive STATUS messages.  If so, we drop them.
+       # This is to keep STATUS's from dead client sessions from creating new server
+       # sessions which send mangled session exceptions to backends for messages 
+       # that they are not aware of any more.
+       if( $mtype eq 'STATUS' ) { return 0; }
+
+       
+       if ($mtype eq 'DISCONNECT') {
+               $session->state( $session->DISCONNECTED );
+               $session->kill_me;
+               return 0;
+       }
+
+       if ($session->state == $session->CONNECTING()) {
+
+               # the transport layer thinks this is a new connection. is it?
+               unless ($mtype eq 'CONNECT') {
+                       $log->error("Connection seems to be mangled: Got $mtype instead of CONNECT");
+
+                       my $res = OpenILS::DomainObject::oilsBrokenSession->new(
+                                       status => "Connection seems to be mangled: Got $mtype instead of CONNECT",
+                       );
+
+                       $session->status($res);
+                       $session->kill_me;
+                       return 0;
+
+               }
+               
+               #unless ($self->userAuth ) {
+               #       $log->debug( "No Authentication information was provided with the initial packet", ERROR );
+               #       my $res = OpenILS::DomainObject::oilsConnectException->new(
+               #                       status => "No Authentication info was provided with initial message" );
+               #       $session->status($res);
+               #       $session->kill_me;
+               #       return 0;
+               #}
+
+               #unless( $self->userAuth->authenticate( $session ) ) {
+               #       my $res = OpenILS::DomainObject::oilsAuthException->new(
+               #               status => "Authentication Failed for " . $self->userAuth->getAttribute('username') );
+               #       $session->status($res) if $res;
+               #       $session->kill_me;
+               #       return 0;
+               #}
+
+               #$session->client_auth( $self->userAuth );
+
+               $log->debug("We're a server and the user is authenticated",DEBUG);
+
+               my $res = OpenILS::DomainObject::oilsConnectStatus->new;
+               $session->status($res);
+               $session->state( $session->CONNECTED );
+
+               return 0;
+       }
+
+
+       $log->debug("Passing to Application::handler()", INFO);
+       $log->debug($self->toString(1), DEBUG);
+
+       return 1;
+
+}
+
+
+# Handle client side message processing. Return 1 when the the message should be pushed
+# up to the application layer.  return 0 otherwise.
+sub do_client {
+
+       my( $self, $session , $mtype, $protocol, $tT) = @_;
+
+
+       if ($mtype eq 'STATUS') {
+
+               if ($self->payload->statusCode == STATUS_OK) {
+                       $session->state($session->CONNECTED);
+                       $log->debug("We connected successfully to ".$session->app, INFO);
+                       return 0;
+               }
+
+               if ($self->payload->statusCode == STATUS_TIMEOUT) {
+                       $session->state( $session->DISCONNECTED );
+                       $session->reset;
+                       $session->push_resend( $session->app_request($self->threadTrace) );
+                       $log->debug("Disconnected because of timeout", WARN);
+                       return 0;
+
+               } elsif ($self->payload->statusCode == STATUS_REDIRECTED) {
+                       $session->state( $session->DISCONNECTED );
+                       $session->reset;
+                       $session->push_resend( $session->app_request($self->threadTrace) );
+                       $log->debug("Disconnected because of redirect", WARN);
+                       return 0;
+
+               } elsif ($self->payload->statusCode == STATUS_EXPFAILED) {
+                       $session->state( $session->DISCONNECTED );
+                       $log->debug("Disconnected because of mangled session", WARN);
+                       $session->reset;
+                       $session->push_resend( $session->app_request($self->threadTrace) );
+                       return 0;
+
+               } elsif ($self->payload->statusCode == STATUS_CONTINUE) {
+                       return 0;
+
+               } elsif ($self->payload->statusCode == STATUS_COMPLETE) {
+                       my $req = $session->app_request($self->threadTrace);
+                       $req->complete(1) if ($req);
+                       return 0;
+               }
+
+               # add more STATUS handling code here (as 'elsif's), for Message layer status stuff
+
+       } elsif ($session->state == $session->CONNECTING()) {
+               # This should be changed to check the type of response (is it a connectException?, etc.)
+       }
+
+       if( $self->payload->class->isa( "OpenILS::EX" ) ) { 
+               $self->payload->throw();
+       }
+
+       $log->debug("Passing to OpenILS::Application::handler()\n" . $self->payload->toString(1), INTERNAL);
+       $log->debug("oilsMessage passing to Application: " . $self->type." : ".$session->remote_id, INFO );
+
+       return 1;
+
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DomainObject/oilsMethod.pm b/src/perlmods/OpenSRF/DomainObject/oilsMethod.pm
new file mode 100644 (file)
index 0000000..6db472b
--- /dev/null
@@ -0,0 +1,100 @@
+package OpenILS::DomainObject::oilsMethod;
+use OpenILS::DOM::Element::params;
+use OpenILS::DOM::Element::param;
+use JSON;
+use base 'OpenILS::DomainObject';
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsMethod
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsMethod;
+
+my $method = OpenILS::DomainObject::oilsMethod->new( method => 'search' );
+
+$method->return_type( 'mods' );
+
+$method->params( 'title:harry potter' );
+
+$client->send( 'REQUEST', $method );
+
+=head1 METHODS
+
+=head2 OpenILS::DomainObject::oilsMethod->method( [$new_method_name] )
+
+=over 4
+
+Sets or gets the method name that will be called on the server.  As above,
+this can be specified as a build attribute as well as added to a prebuilt
+oilsMethod object.
+
+=back
+
+=cut
+
+sub method {
+       my $self = shift;
+       return $self->_attr_get_set( method => shift );
+}
+
+=head2 OpenILS::DomainObject::oilsMethod->return_type( [$new_return_type] )
+
+=over 4
+
+Sets or gets the return type for this method call.  This can also be supplied as
+a build attribute.
+
+This option does not require that the server return the type you request.  It is
+used as a suggestion when more than one return type or format is possible.
+
+=back
+
+=cut
+
+
+sub return_type {
+       my $self = shift;
+       return $self->_attr_get_set( return_type => shift );
+}
+
+=head2 OpenILS::DomainObject::oilsMethod->params( [@new_params] )
+
+=over 4
+
+Sets or gets the parameters for this method call.  Just pass in either text
+parameters, or DOM nodes of any type.
+
+=back
+
+=cut
+
+
+sub params {
+       my $self = shift;
+       my @args = @_;
+
+       my ($old_params) = $self->getChildrenByTagName('oils:params');
+
+       my $params;
+       if (@args) {
+
+               $self->removeChild($old_params) if ($old_params);
+
+               my $params = OpenILS::DOM::Element::params->new;
+               $self->appendChild($params);
+               $params->appendTextNode( JSON->perl2JSON( \@args );
+
+               $old_params = $params unless ($old_params);
+       }
+
+       if ($old_params) {
+               $params = JSON->JSON2perl( $old_params->textContent );
+               return @$params;
+       }
+
+       return @args;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DomainObject/oilsMultiSearch.pm b/src/perlmods/OpenSRF/DomainObject/oilsMultiSearch.pm
new file mode 100644 (file)
index 0000000..340b1b7
--- /dev/null
@@ -0,0 +1,186 @@
+package OpenILS::DomainObjectCollection::oilsMultiSearch;
+use OpenILS::DomainObjectCollection;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::DomainObject::oilsSearch;
+use OpenILS::DOM::Element::searchCriteria;
+use OpenILS::DOM::Element::searchCriterium;
+use base 'OpenILS::DomainObjectCollection::oilsHash';
+
+sub new {
+       my $class = shift;
+       my %args = @_;
+
+       $class = ref($class) || $class;
+
+       my $self = $class->SUPER::new;
+
+       tie my %hash, 'OpenILS::DomainObjectCollection::oilsHash', $self;
+
+       $self->set( bind_count  => 1 );
+       $self->set( searches    => new OpenILS::DomainObjectCollection::oilsHash );
+       $self->set( relators    => new OpenILS::DomainObjectCollection::oilsArray );
+       $self->set( fields      => new OpenILS::DomainObjectCollection::oilsArray );
+       $self->set( group_by    => new OpenILS::DomainObjectCollection::oilsArray );
+       $self->set( order_by    => new OpenILS::DomainObjectCollection::oilsArray );
+       
+       return $self;
+}
+
+sub add_subsearch {
+       my $self = shift;
+       my $alias = shift;
+       my $search = shift;
+       my $relator = shift;
+       
+       $search = OpenILS::DomainObject::oilsSearch->new($search) if (ref($search) eq 'ARRAY');
+
+       $self->searches->set( $alias => $search );
+       
+       if ($self->searches->size > 1) {
+               throw OpenILS::EX::InvalidArg ('You need to pass a relator searchCriterium')
+                       unless (defined $relator);
+       }
+
+       $relator = OpenILS::DOM::Element::searchCriterium->new( @$relator )
+               if (ref($relator) eq 'ARRAY');
+
+       $self->relators->push( $relator ) if (defined $relator);
+
+       return $self;
+}
+
+sub relators {
+       return $_[0]->_accessor('relators');
+}
+
+sub searches {
+       return $_[0]->_accessor('searches');
+}
+
+sub fields {
+       my $self = shift;
+       my @parts = @_;
+       if (@parts) {
+               $self->set( fields => OpenILS::DomainObjectCollection::oilsArray->new(@_) );
+       }
+       return $self->_accessor('fields')->list;
+}
+
+sub format {
+       $_[0]->set( format => $_[1] ) if (defined $_[1]);
+       return $_[0]->_accessor('format');
+}
+
+sub limit {
+       $_[0]->set( limit => $_[1] ) if (defined $_[1]);
+       return $_[0]->_accessor('limit');
+}
+
+sub offset {
+       $_[0]->set( offset => $_[1] ) if (defined $_[1]);
+       return $_[0]->_accessor('offset');
+}
+
+sub chunk_key {
+       $_[0]->set( chunk_key => $_[1] ) if (defined $_[1]);
+       return $_[0]->_accessor('chunk_key');
+}
+
+sub order_by {
+       my $self = shift;
+       my @parts = @_;
+       if (@parts) {
+               $self->set( order_by => OpenILS::DomainObjectCollection::oilsArray->new(@_) );
+       }
+       return $self->_accessor('order_by')->list;
+}
+
+sub group_by {
+       my $self = shift;
+       my @parts = @_;
+       if (@parts) {
+               $self->set( group_by => OpenILS::DomainObjectCollection::oilsArray->new(@_) );
+       }
+       return $self->_accessor('group_by')->list;
+}
+
+sub SQL_select_list {
+       my $self = shift;
+
+       if (my $sql = $self->_accessor('sql_select_list')) {
+               return $sql;
+       }
+
+       $self->set( sql_select_list => 'SELECT '.join(', ', $self->fields) ) if defined($self->fields);
+       return $self->_accessor('sql_select_list');
+}
+
+sub SQL_group_by {
+       my $self = shift;
+
+       if (my $sql = $self->_accessor('sql_group_by')) {
+               return $sql;
+       }
+
+       $self->set( sql_group_by => 'GROUP BY '.join(', ', $self->group_by) ) if defined($self->group_by);
+       return $self->_accessor('sql_group_by');
+}
+
+sub SQL_order_by {
+       my $self = shift;
+
+       if (my $sql = $self->_accessor('sql_order_by')) {
+               return $sql;
+       }
+
+       $self->set( sql_order_by => 'ORDER BY '.join(', ', $self->order_by) ) if defined($self->order_by);
+       return $self->_accessor('sql_order_by');
+}
+
+sub SQL_offset {
+       my $self = shift;
+
+       if (my $sql = $self->_accessor('sql_offset')) {
+               return $sql;
+       }
+
+       $self->set( sql_offset => 'OFFSET '.$self->offset ) if defined($self->offset);
+       return $self->_accessor('sql_offset');
+}
+
+sub SQL_limit {
+       my $self = shift;
+
+       if (my $sql = $self->_accessor('sql_limit')) {
+               return $sql;
+       }
+
+       $self->set( sql_limit => 'LIMIT '.$self->limit ) if defined($self->limit);
+       return $self->_accessor('sql_limit');
+}
+
+sub toSQL {
+       my $self = shift;
+
+       my $SQL = $self->SQL_select_list.' FROM ';
+
+       my @subselects;
+       for my $search ( $self->searches->keys ) {
+               push @subselects, '('.$self->searches->_accessor($search)->toSQL.') '.$search;
+       }
+       $SQL .= join(', ', @subselects).' WHERE ';
+
+       my @relators;
+       for my $rel ( $self->relators->list ) {
+               push @relators, $rel->value->toSQL( no_quote => 1 );
+       }
+       $SQL .= join(' AND ', @relators).' ';
+       $SQL .= join ' ', ($self->SQL_group_by, $self->SQL_order_by, $self->SQL_limit, $self->SQL_offset);
+
+       return $SQL;
+}
+
+#this is just to allow DomainObject to "upcast" nicely
+package OpenILS::DomainObject::oilsMultiSearch;
+use base OpenILS::DomainObjectCollection::oilsMultiSearch;
+1;
diff --git a/src/perlmods/OpenSRF/DomainObject/oilsPrimitive.pm b/src/perlmods/OpenSRF/DomainObject/oilsPrimitive.pm
new file mode 100644 (file)
index 0000000..0f34624
--- /dev/null
@@ -0,0 +1,623 @@
+package OpenILS::DomainObject::oilsScalar;
+use base 'OpenILS::DomainObject';
+use OpenILS::DomainObject;
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsScalar
+
+=head1 SYNOPSIS
+
+  use OpenILS::DomainObject::oilsScalar;
+
+  my $text = OpenILS::DomainObject::oilsScalar->new( 'a string or number' );
+  $text->value( 'replacement value' );
+  print "$text"; # stringify
+
+   ...
+
+  $text->value( 1 );
+  if( $text ) { # boolify
+
+   ...
+
+  $text->value( rand() * 1000 );
+  print 10 + $text; # numify
+
+    Or, using the TIE interface:
+
+  my $scalar;
+  my $real_object = tie($scalar, 'OpenILS::DomainObject::oilsScalar', "a string to store...");
+
+  $scalar = "a new string";
+  print $scalar . "\n";
+  print $real_object->toString . "\n";
+
+=head1 METHODS
+
+=head2 OpenILS::DomainObject::oilsScalar->value( [$new_value] )
+
+=over 4
+
+Sets or gets the value of the scalar.  As above, this can be specified
+as a build attribute as well as added to a prebuilt oilsScalar object.
+
+=back
+
+=cut
+
+use overload '""'   => sub { return ''.$_[0]->value };
+use overload '0+'   => sub { return int($_[0]->value) };
+use overload '<=>'   => sub { return int($_[0]->value) <=> $_[1] };
+use overload 'bool' => sub { return 1 if ($_[0]->value); return 0 };
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $value = shift;
+
+       return $value
+               if (    defined $value and
+                       ref $value and $value->can('base_type') and
+                       UNIVERSAL::isa($value->class, __PACKAGE__) and
+                       !scalar(@_)
+               );
+
+       my $self = $class->SUPER::new;
+
+       if (ref($value) and ref($value) eq 'SCALAR') {
+               $self->value($$value);
+               tie( $$value, ref($self->upcast), $self);
+       } else {
+               $self->value($value) if (defined $value);
+       }
+
+       return $self;
+}
+
+sub TIESCALAR {
+       return CORE::shift()->new(@_);
+}
+
+sub value {
+       my $self = shift;
+       my $value = shift;
+
+       if ( defined $value ) {
+               $self->removeChild($_) for ($self->childNodes);
+               if (ref($value) && $value->isa('XML::LibXML::Node')) {
+                       #throw OpenILS::EX::NotADomainObject
+                       #       unless ($value->nodeName =~ /^oils:domainObject/o);
+                       $self->appendChild($value);
+               } elsif (defined $value) {
+                       $self->appendText( ''.$value );
+               }
+
+               return $value
+       } else {
+               $value = $self->firstChild;
+               if ($value) {
+                       if ($value->nodeType == 3) {
+                               return $value->textContent;
+                       } else {
+                               return $value;
+                       }
+               }
+               return undef;
+       }
+}
+
+sub FETCH { $_[0]->value }
+sub STORE { $_[0]->value($_[1]) }
+
+package OpenILS::DomainObject::oilsPair;
+use base 'OpenILS::DomainObject::oilsScalar';
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsPair
+
+=head1 SYNOPSIS
+
+  use OpenILS::DomainObject::oilsPair;
+
+  my $pair = OpenILS::DomainObject::oilsPair->new( 'key_for_pair' => 'a string or number' );
+
+  $pair->key( 'replacement key' );
+  $pair->value( 'replacement value' );
+
+  print "$pair"; # stringify 'value'
+
+   ...
+
+  $pair->value( 1 );
+
+  if( $pair ) { # boolify
+
+   ...
+
+  $pair->value( rand() * 1000 );
+
+  print 10 + $pair; # numify 'value'
+
+=head1 ABSTRACT
+
+This class impliments a "named pair" object.  This is the basis for
+hash-type domain objects.
+
+=head1 METHODS
+
+=head2 OpenILS::DomainObject::oilsPair->value( [$new_value] )
+
+=over 4
+
+Sets or gets the value of the pair.  As above, this can be specified
+as a build attribute as well as added to a prebuilt oilsPair object.
+
+=back
+
+=head2 OpenILS::DomainObject::oilsPair->key( [$new_key] )
+
+=over 4
+
+Sets or gets the key of the pair.  As above, this can be specified
+as a build attribute as well as added to a prebuilt oilsPair object.
+This must be a perlish scalar; any string or number that is valid as the 
+attribute on an XML node will work.
+
+=back
+
+=cut
+
+use overload '""'   => sub { return ''.$_[0]->value };
+use overload '0+'   => sub { return int($_[0]->value) };
+use overload 'bool' => sub { return 1 if ($_[0]->value); return 0 };
+
+sub new {
+       my $class = shift;
+       my ($key, $value) = @_;
+
+       my $self = $class->SUPER::new($value);
+       $self->setAttribute( key => $key);
+
+       return $self;
+}
+
+sub key {
+       my $self = shift;
+       my $key = shift;
+
+       $self->setAttribute( key => $key) if ($key);
+       return $self->getAttribute( 'key' );
+}
+
+package OpenILS::DomainObjectCollection::oilsArray;
+use base qw/OpenILS::DomainObjectCollection Tie::Array/;
+use OpenILS::DomainObjectCollection;
+
+=head1 NAME
+
+OpenILS::DomainObjectCollection::oilsArray
+
+=head1 SYNOPSIS
+
+  use OpenILS::DomainObject::oilsPrimitive;
+
+  my $collection = OpenILS::DomainObjectCollection::oilsArray->new( $domain_object, $another_domain_object, ...);
+
+  $collection->push( 'appended value' );
+  $collection->unshift( 'prepended vaule' );
+  my $first = $collection->shift;
+  my $last = $collection->pop;
+
+   ...
+
+  my @values = $collection->list;
+
+    Or, using the TIE interface:
+
+  my @array;
+  my $real_object = tie(@array, 'OpenILS::DomainObjectCollection::oilsArray', $domain, $objects, 'to', $store);
+
+      or to tie an existing $collection object
+
+  my @array;
+  tie(@array, 'OpenILS::DomainObjectCollection::oilsArray', $collection);
+
+      or even....
+
+  my @array;
+  tie(@array, ref($collection), $collection);
+
+
+  $array[2] = $DomainObject; # replaces 'to' (which is now an OpenILS::DomainObject::oilsScalar) above
+  delete( $array[3] ); # removes '$store' above.
+  my $size = scalar( @array );
+
+  print $real_object->toString;
+
+=head1 ABSTRACT
+
+This package impliments array-like domain objects.  A full tie interface
+is also provided.  If elements are passed in as strings (or numbers) they
+are turned into oilsScalar objects.  Any simple scalar or Domain Object may
+be stored in the array.
+
+=head1 METHODS
+
+=head2 OpenILS::DomainObjectCollection::oilsArray->list()
+
+=over 4
+
+Returns the array of 'OpenILS::DomainObject's that this collection contains.
+
+=back
+
+=cut
+
+sub tie_me {
+       my $class = shift;
+       $class = ref($class) || $class;
+       my $node = shift;
+       my @array;
+       tie @array, $class, $node;
+       return \@array;
+}
+
+# an existing DomainObjectCollection::oilsArray can now be tied
+sub TIEARRAY {
+       return CORE::shift()->new(@_);
+}
+
+sub new {
+       my $class = CORE::shift;
+       $class = ref($class) || $class;
+
+       my $first = CORE::shift;
+
+       return $first
+               if (    defined $first and
+                       ref $first and $first->can('base_type') and
+                       UNIVERSAL::isa($first->class, __PACKAGE__) and
+                       !scalar(@_)
+               );
+
+       my $self = $class->SUPER::new;
+
+       my @args = @_;
+       if (ref($first) and ref($first) eq 'ARRAY') {
+               push @args, @$first;
+               tie( @$first, ref($self->upcast), $self);
+       } else {
+               unshift @args, $first if (defined $first);
+       }
+
+       $self->STORE($self->FETCHSIZE, $_) for (@args);
+       return $self;
+}
+
+sub STORE {
+       my $self = CORE::shift;
+       my ($index, $value) = @_;
+
+       $value = OpenILS::DomainObject::oilsScalar->new($value)
+               unless ( ref $value and $value->nodeName =~ /^oils:domainObject/o );
+
+       $self->_expand($index) unless ($self->EXISTS($index));
+
+       ($self->childNodes)[$index]->replaceNode( $value );
+
+       return $value->upcast;
+}
+
+sub push {
+       my $self = CORE::shift;
+       my @values = @_;
+       $self->STORE($self->FETCHSIZE, $_) for (@values);
+}
+
+sub pop {
+       my $self = CORE::shift;
+       my $node = $self->SUPER::pop;
+       if ($node) {
+               if ($node->base_type eq 'oilsScalar') {
+                       return $node->value;
+               }
+               return $node->upcast;
+       }
+}
+
+sub unshift {
+       my $self = CORE::shift;
+       my @values = @_;
+       $self->insertBefore($self->firstChild, $_ ) for (reverse @values);
+}
+
+sub shift {
+       my $self = CORE::shift;
+       my $node = $self->SUPER::shift;
+       if ($node) {
+               if ($node->base_type eq 'oilsScalar') {
+                       return $node->value;
+               }
+               return $node->upcast;
+       }
+}
+
+sub FETCH {
+       my $self = CORE::shift;
+       my $index = CORE::shift;
+       my $node =  ($self->childNodes)[$index]->upcast;
+       if ($node) {
+               if ($node->base_type eq 'oilsScalar') {
+                       return $node->value;
+               }
+               return $node->upcast;
+       }
+}
+
+sub size {
+       my $self = CORE::shift;
+       scalar($self->FETCHSIZE)
+}
+
+sub FETCHSIZE {
+       my $self = CORE::shift;
+       my @a = $self->childNodes;
+       return scalar(@a);
+}
+
+sub _expand {
+       my $self = CORE::shift;
+       my $count = CORE::shift;
+       my $size = $self->FETCHSIZE;
+       for ($size..$count) {
+               $self->SUPER::push( new OpenILS::DomainObject::oilsScalar );
+       }
+}
+
+sub STORESIZE {
+       my $self = CORE::shift;
+       my $count = CORE::shift;
+       my $size = $self->FETCHSIZE - 1;
+
+       if (defined $count and $count != $size) {
+               if ($size < $count) {
+                       $self->_expand($count);
+                       $size = $self->FETCHSIZE - 1;
+               } else {
+                       while ($size > $count) {
+                               $self->SUPER::pop;
+                               $size = $self->FETCHSIZE - 1;
+                       }
+               }
+       }
+
+       return $size
+}
+
+sub EXISTS {
+       my $self = CORE::shift;
+       my $index = CORE::shift;
+       return $self->FETCHSIZE > abs($index) ? 1 : 0;
+}
+
+sub CLEAR {
+       my $self = CORE::shift;
+       $self->STORESIZE(0);
+       return $self;
+}
+
+sub DELETE {
+       my $self = CORE::shift;
+       my $index = CORE::shift;
+       return $self->removeChild( ($self->childNodes)[$index] );
+}
+
+package OpenILS::DomainObjectCollection::oilsHash;
+use base qw/OpenILS::DomainObjectCollection Tie::Hash/;
+
+=head1 NAME
+
+OpenILS::DomainObjectCollection::oilsHash
+
+=head1 SYNOPSIS
+
+  use OpenILS::DomainObject::oilsPrimitive;
+
+  my $collection = OpenILS::DomainObjectCollection::oilsHash->new( key1 => $domain_object, key2 => $another_domain_object, ...);
+
+  $collection->set( key =>'value' );
+  my $value = $collection->find( $key );
+  my $dead_value = $collection->remove( $key );
+  my @keys = $collection->keys;
+  my @values = $collection->values;
+
+    Or, using the TIE interface:
+
+  my %hash;
+  my $real_object = tie(%hash, 'OpenILS::DomainObjectCollection::oilsHash', domain => $objects, to => $store);
+
+      or to tie an existing $collection object
+
+  my %hash;
+  tie(%hash, 'OpenILS::DomainObjectCollection::oilsHash', $collection);
+
+      or even....
+
+  my %hash;
+  tie(%hash, ref($collection), $collection);
+
+      or perhaps ...
+
+  my $content = $session->recv->content; # eh? EH?!?!
+  tie(my %hash, ref($content), $content);
+
+  $hash{domain} = $DomainObject; # replaces value for key 'domain' above
+  delete( $hash{to} ); # removes 'to => $store' above.
+  for my $key ( keys %hash ) {
+    ... do stuff ...
+  }
+
+  print $real_object->toString;
+
+=head1 ABSTRACT
+
+This package impliments hash-like domain objects.  A full tie interface
+is also provided.  If elements are passed in as strings (or numbers) they
+are turned into oilsScalar objects.  Any simple scalar or Domain Object may
+be stored in the hash.
+
+=back
+
+=cut
+
+sub tie_me {
+       my $class = shift;
+       $class = ref($class) || $class;
+       my $node = shift;
+       my %hash;
+       tie %hash, $class, $node;
+       return %hash;
+}
+
+
+sub keys {
+       my $self = shift;
+       return map { $_->key } $self->childNodes;
+}
+
+sub values {
+       my $self = shift;
+       return map { $_->value } $self->childNodes;
+}
+
+# an existing DomainObjectCollection::oilsHash can now be tied
+sub TIEHASH {
+       return shift()->new(@_);
+}
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+       my $first = shift;
+       
+       return $first
+               if (    defined $first and
+                       ref $first and $first->can('base_type') and
+                       UNIVERSAL::isa($first->class, __PACKAGE__) and
+                       !scalar(@_)
+               );
+       
+       my $self = $class->SUPER::new;
+
+       my @args = @_;
+       if (ref($first) and ref($first) eq 'HASH') {
+               push @args, %$first;
+               tie( %$first, ref($self->upcast), $self);
+       } else {
+               unshift @args, $first if (defined $first);
+       }
+
+       my %arg_hash = @args;
+       while ( my ($key, $value) = each(%arg_hash) ) {
+               $self->STORE($key => $value);
+       }
+       return $self;
+}
+
+sub STORE {
+       shift()->set(@_);
+}
+
+sub set {
+       my $self = shift;
+       my ($key, $value) = @_;
+
+       my $node = $self->find_node($key);
+
+       return $node->value( $value ) if (defined $node);
+       return $self->appendChild( OpenILS::DomainObject::oilsPair->new($key => $value) );
+}
+
+sub _accessor {
+       my $self = shift;
+       my $key = shift;
+       my $node = find_node($self, $key);
+       return $node->value if ($node);
+}       
+
+sub find_node {
+       my $self = shift;
+       my $key = shift;
+       return ($self->findnodes("oils:domainObject[\@name=\"oilsPair\" and \@key=\"$key\"]", $self))[0];
+}
+
+sub find {
+       my $self = shift;
+       my $key = shift;
+       my $node = $self->find_node($key);
+       my $value = $node->value if (defined $node);
+       return $value;
+}
+
+sub size {
+       my $self = CORE::shift;
+       my @a = $self->childNodes;
+       return scalar(@a);
+}
+
+sub FETCH {
+       my $self = shift;
+       my $key = shift;
+       return $self->find($key);
+}
+
+sub EXISTS {
+       my $self = shift;
+       my $key = shift;
+       return $self->find_node($key);
+}
+
+sub CLEAR {
+       my $self = shift;
+       $self->removeChild for ($self->childNodes);
+       return $self;
+}
+
+sub DELETE {
+       shift()->remove(@_);
+}
+
+sub remove {
+       my $self = shift;
+       my $key = shift;
+       return $self->removeChild( $self->find_node($key) );
+}
+
+sub FIRSTKEY {
+       my $self = shift;
+       return $self->firstChild->key;
+}
+
+sub NEXTKEY {
+       my $self = shift;
+       my $key = shift;
+       my ($prev_node) = $self->find_node($key);
+       my $last_node = $self->lastChild;
+
+       if ($last_node and $last_node->key eq $prev_node->key) {
+               return undef;
+       } else {
+               return $prev_node->nextSibling->key;
+       }
+}
+
+package OpenILS::DomainObject::oilsHash;
+use base qw/OpenILS::DomainObjectCollection::oilsHash/;
+
+package OpenILS::DomainObject::oilsArray;
+use base qw/OpenILS::DomainObjectCollection::oilsArray/;
+
+1;
diff --git a/src/perlmods/OpenSRF/DomainObject/oilsResponse.pm b/src/perlmods/OpenSRF/DomainObject/oilsResponse.pm
new file mode 100644 (file)
index 0000000..70aa894
--- /dev/null
@@ -0,0 +1,406 @@
+package OpenILS::DomainObject::oilsResponse;
+use vars qw/@EXPORT_OK %EXPORT_TAGS/;
+use Exporter;
+use JSON;
+use base qw/OpenILS::DomainObject Exporter/;
+use OpenILS::Utils::Logger qw/:level/;
+
+BEGIN {
+@EXPORT_OK = qw/STATUS_CONTINUE STATUS_OK STATUS_ACCEPTED
+                                       STATUS_BADREQUEST STATUS_UNAUTHORIZED STATUS_FORBIDDEN
+                                       STATUS_NOTFOUND STATUS_NOTALLOWED STATUS_TIMEOUT
+                                       STATUS_INTERNALSERVERERROR STATUS_NOTIMPLEMENTED
+                                       STATUS_VERSIONNOTSUPPORTED STATUS_REDIRECTED 
+                                       STATUS_EXPFAILED STATUS_COMPLETE/;
+
+%EXPORT_TAGS = (
+       status => [ qw/STATUS_CONTINUE STATUS_OK STATUS_ACCEPTED
+                                       STATUS_BADREQUEST STATUS_UNAUTHORIZED STATUS_FORBIDDEN
+                                       STATUS_NOTFOUND STATUS_NOTALLOWED STATUS_TIMEOUT
+                                       STATUS_INTERNALSERVERERROR STATUS_NOTIMPLEMENTED
+                                       STATUS_VERSIONNOTSUPPORTED STATUS_REDIRECTED 
+                                       STATUS_EXPFAILED STATUS_COMPLETE/ ],
+);
+
+}
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsResponse
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+
+my $resp = OpenILS::DomainObject::oilsResponse->new;
+
+$resp->status( 'a status message' );
+
+$resp->statusCode( STATUS_CONTINUE );
+
+$client->respond( $resp );
+
+=head1 ABSTRACT
+
+OpenILS::DomainObject::oilsResponse implements the base class for all Application
+layer messages send between the client and server.
+
+=cut
+
+sub STATUS_CONTINUE            { return 100 }
+
+sub STATUS_OK                          { return 200 }
+sub STATUS_ACCEPTED            { return 202 }
+sub STATUS_COMPLETE            { return 205 }
+
+sub STATUS_REDIRECTED  { return 307 }
+
+sub STATUS_BADREQUEST  { return 400 }
+sub STATUS_UNAUTHORIZED        { return 401 }
+sub STATUS_FORBIDDEN           { return 403 }
+sub STATUS_NOTFOUND            { return 404 }
+sub STATUS_NOTALLOWED  { return 405 }
+sub STATUS_TIMEOUT             { return 408 }
+sub STATUS_EXPFAILED           { return 417 }
+
+sub STATUS_INTERNALSERVERERROR { return 500 }
+sub STATUS_NOTIMPLEMENTED                      { return 501 }
+sub STATUS_VERSIONNOTSUPPORTED { return 505 }
+
+my $log = 'OpenILS::Utils::Logger';
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $default_status = eval "\$${class}::status";
+       my $default_statusCode = eval "\$${class}::statusCode";
+
+       my %args = (    status => $default_status,
+                       statusCode => $default_statusCode,
+                       @_ );
+       
+       return $class->SUPER::new( %args );
+}
+
+sub status {
+       my $self = shift;
+       return $self->_attr_get_set( status => shift );
+}
+
+sub statusCode {
+       my $self = shift;
+       return $self->_attr_get_set( statusCode => shift );
+}
+
+
+#-------------------------------------------------------------------------------
+
+
+
+package OpenILS::DomainObject::oilsStatus;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use base 'OpenILS::DomainObject::oilsResponse';
+use vars qw/$status $statusCode/;
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsException
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsResponse;
+
+...
+
+# something happens.
+
+$client->status( OpenILS::DomainObject::oilsStatus->new );
+
+=head1 ABSTRACT
+
+The base class for Status messages sent between client and server.  This
+is implemented on top of the C<OpenILS::DomainObject::oilsResponse> class, and 
+sets the default B<status> to C<Status> and B<statusCode> to C<STATUS_OK>.
+
+=cut
+
+$status = 'Status';
+$statusCode = STATUS_OK;
+
+package OpenILS::DomainObject::oilsConnectStatus;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use base 'OpenILS::DomainObject::oilsStatus';
+use vars qw/$status $statusCode/;
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsConnectStatus
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsResponse;
+
+...
+
+# something happens.
+
+$client->status( new OpenILS::DomainObject::oilsConnectStatus );
+
+=head1 ABSTRACT
+
+The class for Stati relating to the connection status of a session.  This
+is implemented on top of the C<OpenILS::DomainObject::oilsStatus> class, and 
+sets the default B<status> to C<Connection Successful> and B<statusCode> to C<STATUS_OK>.
+
+=head1 SEE ALSO
+
+B<OpenILS::DomainObject::oilsStatus>
+
+=cut
+
+$status = 'Connection Successful';
+$statusCode = STATUS_OK;
+
+1;
+
+
+
+#-------------------------------------------------------------------------------
+
+
+
+package OpenILS::DomainObject::oilsResult;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use OpenILS::DomainObject::oilsPrimitive;
+use base 'OpenILS::DomainObject::oilsResponse';
+use vars qw/$status $statusCode/;
+
+
+$status = 'OK';
+$statusCode = STATUS_OK;
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsResult
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsResponse;
+
+ .... do stuff, create $object ...
+
+my $res = OpenILS::DomainObject::oilsResult->new;
+
+$res->content($object)
+
+$session->respond( $res );
+
+=head1 ABSTRACT
+
+This is the base class for encapuslating RESULT messages send from the server
+to a client.  It is a subclass of B<OpenILS::DomainObject::oilsResponse>, and
+sets B<status> to C<OK> and B<statusCode> to C<STATUS_OK>.
+
+=head1 METHODS
+
+=head2 OpenILS::DomainObject::oilsMessage->content( [$new_content] )
+
+=over 4
+
+Sets or gets the content of the response.  This should be exactly one object
+of (sub)type domainObject or domainObjectCollection.
+
+=back
+
+=cut
+
+sub content {
+        my $self = shift;
+       my $new_content = shift;
+
+       my ($content) = $self->getChildrenByTagName('oils:domainObject');
+
+       if ($new_content) {
+               $new_content = OpenILS::DomainObject::oilsScalar->new( JSON->perl2JSON( $new_content ) );
+
+               $self->removeChild($content) if ($content);
+               $self->appendChild($new_content);
+       }
+
+
+       $new_content = $content if ($content);
+
+       return JSON->JSON2perl($new_content->textContent) if $new_content;
+}
+
+=head1 SEE ALSO
+
+B<OpenILS::DomainObject::oilsResponse>
+
+=cut
+
+1;
+
+
+
+#-------------------------------------------------------------------------------
+
+
+
+package OpenILS::DomainObject::oilsException;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use OpenILS::EX;
+use base qw/OpenILS::EX OpenILS::DomainObject::oilsResponse/;
+use vars qw/$status $statusCode/;
+use Error;
+
+sub message {
+       my $self = shift;
+       return '<' . $self->statusCode . '>  ' . $self->status;
+}
+
+sub new {
+       my $class = shift;
+       return $class->OpenILS::DomainObject::oilsResponse::new( @_ );
+}
+
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsException
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsResponse;
+
+...
+
+# something breaks.
+
+$client->send( 'ERROR', OpenILS::DomainObject::oilsException->new( status => "ARRRRRRG!" ) );
+
+=head1 ABSTRACT
+
+The base class for Exception messages sent between client and server.  This
+is implemented on top of the C<OpenILS::DomainObject::oilsResponse> class, and 
+sets the default B<status> to C<Exception occured> and B<statusCode> to C<STATUS_BADREQUEST>.
+
+=cut
+
+$status = 'Exception occured';
+$statusCode = STATUS_INTERNALSERVERERROR;
+
+package OpenILS::DomainObject::oilsConnectException;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use OpenILS::EX;
+use base qw/OpenILS::DomainObject::oilsException OpenILS::EX::ERROR/;
+use vars qw/$status $statusCode/;
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsConnectException
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsResponse;
+
+...
+
+# something breaks while connecting.
+
+$client->send( 'ERROR', new OpenILS::DomainObject::oilsConnectException );
+
+=head1 ABSTRACT
+
+The class for Exceptions that occur durring the B<CONNECT> phase of a session.  This
+is implemented on top of the C<OpenILS::DomainObject::oilsException> class, and 
+sets the default B<status> to C<Connect Request Failed> and B<statusCode> to C<STATUS_FORBIDDEN>.
+
+=head1 SEE ALSO
+
+B<OpenILS::DomainObject::oilsException>
+
+=cut
+
+
+$status = 'Connect Request Failed';
+$statusCode = STATUS_FORBIDDEN;
+
+package OpenILS::DomainObject::oilsMethodException;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use base 'OpenILS::DomainObject::oilsException';
+use vars qw/$status $statusCode/;
+
+=head1 NAME
+
+OpenILS::DomainObject::oilsMehtodException
+
+=head1 SYNOPSIS
+
+use OpenILS::DomainObject::oilsResponse;
+
+...
+
+# something breaks while looking up or starting
+# a method call.
+
+$client->send( 'ERROR', new OpenILS::DomainObject::oilsMethodException );
+
+=head1 ABSTRACT
+
+The class for Exceptions that occur durring the B<CONNECT> phase of a session.  This
+is implemented on top of the C<OpenILS::DomainObject::oilsException> class, and 
+sets the default B<status> to C<Connect Request Failed> and B<statusCode> to C<STATUS_NOTFOUND>.
+
+=head1 SEE ALSO
+
+B<OpenILS::DomainObject::oilsException>
+
+=cut
+
+
+$status = 'Method not found';
+$statusCode = STATUS_NOTFOUND;
+
+# -------------------------------------------
+
+package OpenILS::DomainObject::oilsServerError;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use base 'OpenILS::DomainObject::oilsException';
+use vars qw/$status $statusCode/;
+
+$status = 'Internal Server Error';
+$statusCode = STATUS_INTERNALSERVERERROR;
+
+# -------------------------------------------
+
+
+
+
+
+package OpenILS::DomainObject::oilsBrokenSession;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use OpenILS::EX;
+use base qw/OpenILS::DomainObject::oilsException OpenILS::EX::ERROR/;
+use vars qw/$status $statusCode/;
+$status = "Request on Disconnected Session";
+$statusCode = STATUS_EXPFAILED;
+
+package OpenILS::DomainObject::oilsXMLParseError;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use OpenILS::EX;
+use base qw/OpenILS::DomainObject::oilsException OpenILS::EX::ERROR/;
+use vars qw/$status $statusCode/;
+$status = "XML Parse Error";
+$statusCode = STATUS_EXPFAILED;
+
+package OpenILS::DomainObject::oilsAuthException;
+use OpenILS::DomainObject::oilsResponse qw/:status/;
+use OpenILS::EX;
+use base qw/OpenILS::DomainObject::oilsException OpenILS::EX::ERROR/;
+use vars qw/$status $statusCode/;
+$status = "Authentication Failure";
+$statusCode = STATUS_FORBIDDEN;
+
+1;
diff --git a/src/perlmods/OpenSRF/DomainObject/oilsSearch.pm b/src/perlmods/OpenSRF/DomainObject/oilsSearch.pm
new file mode 100644 (file)
index 0000000..2785da3
--- /dev/null
@@ -0,0 +1,106 @@
+package OpenILS::DomainObject::oilsSearch;
+use OpenILS::DomainObject;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::DOM::Element::searchCriteria;
+use base 'OpenILS::DomainObject';
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       unshift @_, 'table' if (@_ == 1);
+       my %args = @_;
+
+       my $self = $class->SUPER::new;
+       
+       for my $part ( keys %args ) {
+               if ($part ne 'criteria') {
+                       $self->$part( $args{$part} );
+                       next;
+               }
+               $self->criteria( OpenILS::DOM::Element::searchCriteria->new( @{$args{$part}} ) );
+       }
+       return $self;
+}
+
+sub format {
+       my $self = shift;
+       return $self->_attr_get_set( format => shift );
+}
+
+sub table {
+       my $self = shift;
+       return $self->_attr_get_set( table => shift );
+}
+
+sub fields {
+       my $self = shift;
+       my $new_fields_ref = shift;
+
+       my ($old_fields) = $self->getChildrenByTagName("oils:domainObjectCollection");
+       
+       if ($new_fields_ref) {
+               my $do = OpenILS::DomainObjectCollection::oilsArray->new( @$new_fields_ref );
+               if (defined $old_fields) {
+                       $old_fields->replaceNode($do);
+               } else {
+                       $self->appendChild($do);
+                       return $do->list;
+               }
+       }
+
+       return $old_fields->list if ($old_fields);
+}
+
+sub limit {
+       my $self = shift;
+       return $self->_attr_get_set( limit => shift );
+}
+
+sub offset {
+       my $self = shift;
+       return $self->_attr_get_set( offset => shift );
+}
+
+sub group_by {
+       my $self = shift;
+       return $self->_attr_get_set( group_by => shift );
+}
+
+sub criteria {
+       my $self = shift;
+       my $new_crit = shift;
+
+       if (@_) {
+               unshift @_, $new_crit;
+               $new_crit = OpenILS::DOM::Element::searchCriteria->new(@_);
+       }
+
+       my ($old_crit) = $self->getChildrenByTagName("oils:searchCriteria");
+       
+       if (defined $new_crit) {
+               if (defined $old_crit) {
+                       $old_crit->replaceNode($new_crit);
+               } else {
+                       $self->appendChild($new_crit);
+                       return $new_crit;
+               }
+       }
+
+       return $old_crit;
+}
+
+sub toSQL {
+       my $self = shift;
+
+       my $SQL  = 'SELECT    ' . join(',', $self->fields);
+          $SQL .= '  FROM    ' . $self->table;
+          $SQL .= '  WHERE   ' . $self->criteria->toSQL if ($self->criteria);
+          $SQL .= ' GROUP BY ' . $self->group_by if ($self->group_by);
+          $SQL .= '  LIMIT   ' . $self->limit if ($self->limit);
+          $SQL .= '  OFFSET  ' . $self->offset if ($self->offset);
+       
+       return $SQL;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/DomainObjectCollection.pm b/src/perlmods/OpenSRF/DomainObjectCollection.pm
new file mode 100644 (file)
index 0000000..7049af7
--- /dev/null
@@ -0,0 +1,35 @@
+package OpenSRF::DomainObjectCollection;
+use base 'OpenSRF::DOM::Element::domainObjectCollection';
+use OpenSRF::DOM;
+use OpenSRF::Utils::Logger qw(:level);
+my $logger = "OpenSRF::Utils::Logger";
+
+=head1 NAME
+
+OpenSRF::DomainObjectCollection
+
+=head1 SYNOPSIS
+
+OpenSRF::DomainObjectCollection is an abstract base class.  It
+should not be used directly.  See C<OpenSRF::DomainObjectCollection::*>
+for details.
+
+=cut
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my @args = shift;
+
+       (my $type = $class) =~ s/^.+://o;
+
+       my $doc = OpenSRF::DOM->createDocument;
+       my $dO = OpenSRF::DOM::Element::domainObjectCollection->new( $type, @args );
+
+       $doc->documentElement->appendChild($dO);
+
+       return $dO;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/EX.pm b/src/perlmods/OpenSRF/EX.pm
new file mode 100644 (file)
index 0000000..7b2cfb0
--- /dev/null
@@ -0,0 +1,251 @@
+package OpenSRF::EX;
+use Error qw(:try);
+use base qw( OpenSRF Error );
+use OpenSRF::Utils::Logger;
+
+my $log = "OpenSRF::Utils::Logger";
+$Error::Debug = 1;
+
+sub new {
+       my( $class, $message ) = @_;
+       $class = ref( $class ) || $class;
+       my $self = {};
+       $self->{'msg'} = ${$class . '::ex_msg_header'} ." \n$message";
+       return bless( $self, $class );
+}      
+
+sub message() { return $_[0]->{'msg'}; }
+
+sub DESTROY{}
+
+
+=head1 OpenSRF::EX
+
+Top level exception.  This class logs an exception when it is thrown.  Exception subclasses
+should subclass one of OpenSRF::EX::INFO, NOTICE, WARN, ERROR, CRITICAL, and PANIC and provide
+a new() method that takes a message and a message() method that returns that message.
+
+=cut
+
+=head2 Synopsis
+
+
+       throw OpenSRF::EX::Jabber ("I Am Dying");
+
+       OpenSRF::EX::InvalidArg->throw( "Another way" );
+
+       my $je = OpenSRF::EX::Jabber->new( "I Cannot Connect" );
+       $je->throw();
+
+
+       See OpenSRF/EX.pm for example subclasses.
+
+=cut
+
+# Log myself and throw myself
+
+#sub message() { shift->alert_abstract(); }
+
+#sub new() { shift->alert_abstract(); }
+
+sub throw() {
+
+       my $self = shift;
+
+       if( ! ref( $self ) || scalar( @_ ) ) {
+               $self = $self->new( @_ );
+       }
+
+       if(             $self->class->isa( "OpenSRF::EX::INFO" )        ||
+                               $self->class->isa( "OpenSRF::EX::NOTICE" ) ||
+                               $self->class->isa( "OpenSRF::EX::WARN" ) ) {
+
+               $log->debug( $self->stringify(), $log->DEBUG );
+       }
+
+       else{ $log->debug( $self->stringify(), $log->ERROR ); }
+       
+       $self->SUPER::throw;
+}
+
+
+sub stringify() {
+
+       my $self = shift;
+       my $ctime = localtime();
+       my( $package, $file, $line) = get_caller();
+       my $name = ref( $self );
+       my $msg = $self->message();
+
+       $msg =~ s/^/Mess: /mg;
+
+       return "  * ! EXCEPTION ! * \nTYPE: $name\n$msg\n".
+               "Loc.: $line $package \nLoc.: $file \nTime: $ctime\n";
+}
+
+
+# --- determine the originating caller of this exception
+sub get_caller() {
+
+       $package = caller();
+       my $x = 0;
+       while( $package->isa( "Error" ) || $package =~ /^Error::/ ) { 
+               $package = caller( ++$x );
+       }
+       return (caller($x));
+}
+
+
+
+
+# -------------------------------------------------------------------
+# -------------------------------------------------------------------
+
+# Top level exception subclasses defining the different exception
+# levels.
+
+# -------------------------------------------------------------------
+
+package OpenSRF::EX::INFO;
+use base qw(OpenSRF::EX);
+our $ex_msg_header = "System INFO";
+
+# -------------------------------------------------------------------
+
+package OpenSRF::EX::NOTICE;
+use base qw(OpenSRF::EX);
+our $ex_msg_header = "System NOTICE";
+
+# -------------------------------------------------------------------
+
+package OpenSRF::EX::WARN;
+use base qw(OpenSRF::EX);
+our $ex_msg_header = "System WARNING";
+
+# -------------------------------------------------------------------
+
+package OpenSRF::EX::ERROR;
+use base qw(OpenSRF::EX);
+our $ex_msg_header = "System ERROR";
+
+# -------------------------------------------------------------------
+
+package OpenSRF::EX::CRITICAL;
+use base qw(OpenSRF::EX);
+our $ex_msg_header = "System CRITICAL";
+
+# -------------------------------------------------------------------
+
+package OpenSRF::EX::PANIC;
+use base qw(OpenSRF::EX);
+our $ex_msg_header = "System PANIC";
+
+# -------------------------------------------------------------------
+# -------------------------------------------------------------------
+
+# Some basic exceptions
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::Jabber;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Jabber Exception";
+
+package OpenSRF::EX::JabberDisconnected;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "JabberDisconnected Exception";
+
+=head2 OpenSRF::EX::Jabber
+
+Thrown when there is a problem using the Jabber service
+
+=cut
+
+package OpenSRF::EX::Transport;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Transport Exception";
+
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::InvalidArg;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Invalid Arg Exception";
+
+=head2 OpenSRF::EX::InvalidArg
+
+Thrown where an argument to a method was invalid or not provided
+
+=cut
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::NotADomainObject;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Must be a Domain Object";
+
+=head2 OpenSRF::EX::NotADomainObject
+
+Thrown where a OpenSRF::DomainObject::oilsScalar or
+OpenSRF::DomainObject::oilsPair was passed a value that
+is not a perl scalar or a OpenSRF::DomainObject.
+
+=cut
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::ArrayOutOfBounds;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Tied array access on a nonexistant index";
+
+=head2 OpenSRF::EX::ArrayOutOfBounds
+
+Thrown where a TIEd array (OpenSRF::DomainObject::oilsArray) was accessed at
+a nonexistant index
+
+=cut
+
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::Socket;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Socket Exception";
+
+=head2 OpenSRF::EX::Socket
+
+Thrown when there is a network layer exception
+
+=cut
+
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::Config;
+use base 'OpenSRF::EX::PANIC';
+our $ex_msg_header = "Config Exception";
+
+=head2 OpenSRF::EX::Config
+
+Thrown when a package requires a config option that it cannot retrieve
+or the config file itself cannot be loaded
+
+=cut
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::User;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "User Exception";
+
+=head2 OpenSRF::EX::User
+
+Thrown when an error occurs due to user identification information
+
+=cut
+
+package OpenSRF::EX::Session;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Session Error";
+
+
+1;
diff --git a/src/perlmods/OpenSRF/System.pm b/src/perlmods/OpenSRF/System.pm
new file mode 100644 (file)
index 0000000..d78e422
--- /dev/null
@@ -0,0 +1,364 @@
+package OpenSRF::System;
+use strict; use warnings;
+use base 'OpenSRF';
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::Transport::Listener;
+use OpenSRF::Transport;
+use OpenSRF::UnixServer;
+use OpenSRF::Utils;
+use OpenSRF::Utils::LogServer;
+use OpenSRF::DOM;
+use OpenSRF::EX qw/:try/;
+use POSIX ":sys_wait_h";
+use strict;
+
+=head2 Name/Description
+
+OpenSRF::System
+
+To start the system: OpenSRF::System->bootstrap();
+
+Simple system process management and automation.  After instantiating the class, simply call
+bootstrap() to launch the system.  Each launched process is stored as a process-id/method-name
+pair in a local hash.  When we receive a SIG{CHILD}, we loop through this hash and relaunch
+any child processes that may have terminated.  
+
+Currently automated processes include launching the internal Unix Servers, launching the inbound 
+connections for each application, and starting the system shell.
+
+
+Note: There should be only one instance of this class
+alive at any given time.  It is designed as a globel process handler and, hence, will cause much
+oddness if you call the bootstrap() method twice or attempt to create two of these by trickery.
+There is a single instance of the class created on the first call to new().  This same instance is 
+returned on subsequent calls to new().
+
+=cut
+
+$| = 1;
+
+sub APPS { return qw( opac ); } #circ cat storage ); }
+
+sub DESTROY {}
+
+# ----------------------------------------------
+
+$SIG{INT} = sub { instance()->killall(); };
+
+$SIG{HUP} = sub{ instance()->hupall(); };
+
+#$SIG{CHLD} = \&process_automation;
+
+
+# Go ahead and set the config
+
+set_config();
+
+# ----------------------------------------------
+# Set config options
+sub set_config {
+
+       my $config = OpenSRF::Utils::Config->load( 
+               config_file => "/pines/conf/oils.conf" );
+
+       if( ! $config ) { throw OpenSRF::EX::Config "System could not load config"; }
+
+       my $tran = $config->transport->implementation;
+
+       eval "use $tran;";
+       if( $@ ) {
+               throw OpenSRF::EX::PANIC ("Cannot find transport implementation: $@" );
+       }
+
+       OpenSRF::Transport->message_envelope( $tran->get_msg_envelope );
+       OpenSRF::Transport::PeerHandle->set_peer_client( $tran->get_peer_client );
+       OpenSRF::Transport::Listener->set_listener( $tran->get_listener );
+
+
+}
+
+
+
+
+
+
+# ----------------------------------------------
+
+{ 
+       # --- 
+       # put $instance in a closure and return it for requests to new()
+       # since there should only be one System instance running
+       # ----- 
+       my $instance;
+       sub instance { return __PACKAGE__->new(); }
+       sub new {
+               my( $class ) = @_;
+
+               if( ! $instance ) {
+                       $class = ref( $class ) || $class;
+                       my $self = {};
+                       $self->{'pid_hash'} = {};
+                       bless( $self, $class );
+                       $instance = $self;
+               }
+               return $instance;
+       }
+}
+
+# ----------------------------------------------
+# Commands to execute at system launch
+
+sub _unixserver {
+       my( $app ) = @_;
+       return "OpenSRF::UnixServer->new( '$app' )->serve()";
+}
+
+sub _listener {
+       my( $app ) = @_;
+       return "OpenSRF::Transport::Listener->new( '$app' )->initialize()->listen()";
+}
+
+#sub _shell { return "OpenSRF::Shell->listen()"; }
+
+
+# ----------------------------------------------
+# Boot up the system
+
+sub bootstrap {
+
+       my $self = __PACKAGE__->instance();
+
+       my $config = OpenSRF::Utils::Config->current;
+
+       my $apps = $config->system->apps;
+       my $server_type = $config->system->server_type;
+       $server_type ||= "basic";
+
+       if(  $server_type eq "prefork" ) { 
+               $server_type = "Net::Server::PreForkSimple"; 
+       } else { 
+               $server_type = "Net::Server::Single"; 
+       }
+
+       _log( " * Server type: $server_type", INTERNAL );
+
+       eval "use $server_type";
+
+       if( $@ ) {
+               throw OpenSRF::EX::PANIC ("Cannot set $server_type: $@" );
+       }
+
+       push @OpenSRF::UnixServer::ISA, $server_type;
+
+       _log( " * System boostrap" );
+
+       # Start a process group and make me the captain
+       setpgrp( 0, 0 ); 
+
+       $0 = "System";
+       
+       # --- Boot the Unix servers
+       $self->launch_unix($apps);
+
+       _sleep();
+
+       # --- Boot the listeners
+       $self->launch_listener($apps);
+
+       _sleep();
+
+       # --- Start the system shell
+#if ($config->system->shell) {
+#              eval " 
+#                      use OpenSRF::Shell;
+#                      $self->launch_shell() if ($config->system->shell);
+#              ";
+#
+#              if ($@) {
+#                      warn "ARRRGGG! Can't start the shell...";
+#              }
+#      }
+
+       # --- Now we wait for our brood to perish
+       _log( " * System is ready..." );
+       while( 1 ) { sleep; }
+       exit;
+}
+
+
+
+# ----------------------------------------------
+# Bootstraps a single client connection.  
+
+sub bootstrap_client {
+
+       my $self = __PACKAGE__->instance();
+       my $config = OpenSRF::Utils::Config->current;
+
+       my $app = "client";
+
+       OpenSRF::Transport::PeerHandle->construct( $app );
+
+}
+
+sub bootstrap_logger {
+
+       $0 = "Log Server";
+       OpenSRF::Utils::LogServer->serve();
+
+}
+
+
+# ----------------------------------------------
+# Cycle through the known processes, reap the dead child 
+# and put a new child in its place. (MMWWAHAHHAHAAAA!)
+
+sub process_automation {
+
+       my $self = __PACKAGE__->instance();
+
+       foreach my $pid ( keys %{$self->pid_hash} ) {
+
+               if( waitpid( $pid, WNOHANG ) == $pid ) {
+
+                       my $method = $self->pid_hash->{$pid};
+                       delete $self->pid_hash->{$pid};
+
+                       my $newpid =  OpenSRF::Utils::safe_fork();
+                       _log( "Relaunching => $method" );
+
+                       if( $newpid ) {
+                               $self->pid_hash( $newpid, $method );
+                       }
+                       else { $0 = $method; eval $method; exit; }
+               }
+       }
+
+       $SIG{CHLD} = \&process_automation;
+}
+
+
+# ----------------------------------------------
+# Launch the Unix Servers
+
+sub launch_unix {
+       my( $self, $apps ) = @_;
+
+       foreach my $app ( @$apps ) {
+
+               _log( " * Starting UnixServer for $app..." );
+
+               my $pid = OpenSRF::Utils::safe_fork();
+               if( $pid ) {
+                       $self->pid_hash( $pid , _unixserver( $app ) );
+               }
+               else {
+                       my $apname = $app;
+                       $apname =~ tr/[a-z]/[A-Z]/;
+                       $0 = "Unix Server ($apname)";
+                       eval _unixserver( $app );
+                       exit;
+               }
+       }
+}
+
+# ----------------------------------------------
+# Launch the inbound clients
+
+sub launch_listener {
+
+       my( $self, $apps ) = @_;
+
+       foreach my $app ( @$apps ) {
+
+               _log( " * Starting Listener for $app..." );
+
+               my $pid = OpenSRF::Utils::safe_fork();
+               if ( $pid ) {
+                       $self->pid_hash( $pid , _listener( $app ) );
+               }
+               else {
+                       my $apname = $app;
+                       $apname =~ tr/[a-z]/[A-Z]/;
+                       $0 = "Listener ($apname)";
+                       eval _listener( $app );
+                       exit;
+               }
+       }
+}
+
+# ----------------------------------------------
+
+=head comment
+sub launch_shell {
+
+       my $self = shift;
+
+       my $pid = OpenSRF::Utils::safe_fork();
+
+       if( $pid ) { $self->pid_hash( $pid , _shell() ); }
+       else {
+               $0 = "System Shell";
+               for( my $x = 0; $x != 10; $x++ ) {
+                       eval _shell();
+                       if( ! $@ ) { last; }
+               }
+               exit;
+       }
+}
+=cut
+
+
+# ----------------------------------------------
+
+sub pid_hash {
+       my( $self, $pid, $method ) = @_;
+       $self->{'pid_hash'}->{$pid} = $method
+               if( $pid and $method );
+       return $self->{'pid_hash'};
+}
+
+# ----------------------------------------------
+# If requested, the System can shut down.
+
+sub killall {
+
+       $SIG{CHLD} = 'IGNORE';
+       $SIG{INT} = 'IGNORE';
+       kill( 'INT', -$$ ); #kill all in process group
+       exit;
+
+}
+
+# ----------------------------------------------
+# Handle $SIG{HUP}
+sub hupall {
+
+       _log( "HUPping brood" );
+       $SIG{CHLD} = 'IGNORE';
+       $SIG{HUP} = 'IGNORE';
+       set_config(); # reload config
+       kill( 'HUP', -$$ );
+#      $SIG{CHLD} = \&process_automation;
+       $SIG{HUP} = sub{ instance()->hupall(); };
+}
+
+
+# ----------------------------------------------
+# Log to debug, and stdout
+
+sub _log {
+       my $string = shift;
+       OpenSRF::Utils::Logger->debug( $string );
+       print $string . "\n";
+}
+
+# ----------------------------------------------
+
+sub _sleep {
+       select( undef, undef, undef, 0.3 );
+}
+
+1;
+
+
diff --git a/src/perlmods/OpenSRF/Transport.pm b/src/perlmods/OpenSRF/Transport.pm
new file mode 100644 (file)
index 0000000..b1a8bbb
--- /dev/null
@@ -0,0 +1,189 @@
+package OpenSRF::Transport;
+use strict; use warnings;
+use base 'OpenSRF';
+use OpenSRF::DOM;
+use OpenSRF::AppSession;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::EX qw/:try/;
+
+#------------------ 
+# --- These must be implemented by all Transport subclasses
+# -------------------------------------------
+
+=head2 get_listener
+
+Returns the package name of the package the system will use to 
+gather incoming requests
+
+=cut
+
+sub get_listener { shift()->alert_abstract(); }
+
+=head2 get_peer_client
+
+Returns the name of the package responsible for client communication
+
+=cut
+
+sub get_peer_client { shift()->alert_abstract(); } 
+
+=head2 get_msg_envelope
+
+Returns the name of the package responsible for parsing incoming messages
+
+=cut
+
+sub get_msg_envelope { shift()->alert_abstract(); } 
+
+# -------------------------------------------
+
+our $message_envelope;
+my $logger = "OpenSRF::Utils::Logger"; 
+
+
+
+=head2 message_envelope( [$envelope] );
+
+Sets the message envelope class that will allow us to extract
+information from the messages we receive from the low 
+level transport
+
+=cut
+
+sub message_envelope {
+       my( $class, $envelope ) = @_;
+       if( $envelope ) {
+               $message_envelope = $envelope;
+               eval "use $envelope;";
+               if( $@ ) {
+                       $logger->error( 
+                                       "Error loading message_envelope: $envelope -> $@", ERROR);
+               }
+       }
+       return $message_envelope;
+}
+
+=head2 handler( $data )
+
+Creates a new MessageWrapper, extracts the remote_id, session_id, and message body
+from the message.  Then, creates or retrieves the AppSession object with the session_id and remote_id. 
+Finally, creates the message document from the body of the message and calls
+the handler method on the message document.
+
+=cut
+
+sub handler {
+       my( $class, $service, $data ) = @_;
+
+       $logger->transport( "Transport handler() received $data", INTERNAL );
+
+       # pass data to the message envelope 
+       my $helper = $class->message_envelope->new( $data );
+
+       # Extract message information
+       my $remote_id   = $helper->get_remote_id();
+       my $sess_id     = $helper->get_sess_id();
+       my $body        = $helper->get_body();
+       my $type        = $helper->get_msg_type();
+
+       $logger->transport( 
+                       "Transport building/retrieving session: $service, $remote_id, $sess_id", DEBUG );
+
+       # See if the app_session already exists.  If so, make 
+       # sure the sender hasn't changed if we're a server
+       my $app_session = OpenSRF::AppSession->find( $sess_id );
+       if( $app_session and $app_session->endpoint == $app_session->SERVER() and
+                       $app_session->remote_id ne $remote_id ) {
+               $logger->transport( "Backend Gone or invalid sender", INTERNAL );
+               my $res = OpenSRF::DomainObject::oilsBrokenSession->new();
+               $res->status( "Backend Gone or invalid sender, Reconnect" );
+               $app_session->status( $res );
+               return 1;
+       } 
+
+       # Retrieve or build the app_session as appropriate (server_build decides which to do)
+       $logger->transport( "AppSession is valid or does not exist yet", INTERNAL );
+       $app_session = OpenSRF::AppSession->server_build( $sess_id, $remote_id, $service );
+
+       if( ! $app_session ) {
+               throw OpenSRF::EX::Session ("Transport::handler(): No AppSession object returned from server_build()");
+       }
+
+       # Create a document from the XML contained within the message 
+       my $doc; 
+       eval { $doc = OpenSRF::DOM->new->parse_string($body); };
+       if( $@ ) {
+
+               $logger->transport( "Received bogus XML", INFO );
+               $logger->transport( "Bogus XML data: \n $body \n", INTERNAL );
+               my $res = OpenSRF::DomainObject::oilsXMLParseError->new( status => "XML Parse Error --- $body" );
+
+               $app_session->status($res);
+               $app_session->kill_me;
+               return 1;
+       }
+
+       $logger->transport( "Transport::handler() creating \n$body", INTERNAL );
+
+       # We need to disconnect the session if we got a jabber error on the client side.  For
+       # server side, we'll just tear down the session and go away.
+       if (defined($type) and $type eq 'error') {
+               # If we're a server
+               if( $app_session->endpoint == $app_session->SERVER() ) {
+                       $app_session->kill_me;
+                       return 1;
+               } else {
+                       $app_session->reset;
+                       $app_session->state( $app_session->DISCONNECTED );
+                       $app_session->push_resend( $app_session->app_request( $doc->documentElement->firstChild->threadTrace ) );
+                       return 1;
+               }
+       }
+
+       # cycle through and pass each oilsMessage contained in the message
+       # up to the message layer for processing.
+       for my $msg ($doc->documentElement->childNodes) {
+
+               $logger->transport( 
+                               "Transport::handler()passing to message handler \n".$msg->toString(1), DEBUG );
+
+               $logger->transport( 
+                               "Transport passing up ".$msg->type." from ".
+                               $app_session->remote_id . " with threadTrace [" . $msg->threadTrace."]", INFO );
+
+               next unless (   $msg->nodeName eq 'oils:domainObject' &&
+                               $msg->getAttribute('name') eq 'oilsMessage' );
+
+               if( $app_session->endpoint == $app_session->SERVER() ) {
+
+                       try {  
+
+                               if( ! $msg->handler( $app_session ) ) { return 0; }
+
+                       } catch Error with {
+
+                               my $e = shift;
+                               my $res = OpenSRF::DomainObject::oilsServerError->new();
+                               $res->status( $res->status . "\n" . $e->text );
+                               $logger->error($res->stringify);
+                               $app_session->status($res) if $res;
+                               $app_session->kill_me;
+                               return 0;
+
+                       };
+
+               } else { 
+
+                       if( ! $msg->handler( $app_session ) ) { return 0; } 
+
+               }
+
+       }
+
+
+       return $app_session;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/Jabber.pm b/src/perlmods/OpenSRF/Transport/Jabber.pm
new file mode 100644 (file)
index 0000000..3b45ac5
--- /dev/null
@@ -0,0 +1,11 @@
+package OpenSRF::Transport::Jabber;
+use base qw/OpenSRF::Transport/;
+
+
+sub get_listener { return "OpenSRF::Transport::Jabber::JInbound"; }
+
+sub get_peer_client { return "OpenSRF::Transport::Jabber::JPeerConnection"; }
+
+sub get_msg_envelope { return "OpenSRF::Transport::Jabber::JMessageWrapper"; }
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/Jabber/JInbound.pm b/src/perlmods/OpenSRF/Transport/Jabber/JInbound.pm
new file mode 100644 (file)
index 0000000..a381274
--- /dev/null
@@ -0,0 +1,101 @@
+package OpenSRF::Transport::Jabber::JInbound;
+use strict;use warnings;
+use base qw/OpenSRF::Transport::Jabber::JabberClient/;
+use OpenSRF::EX;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+
+my $logger = "OpenSRF::Utils::Logger";
+
+=head1 Description
+
+This is the jabber connection where all incoming client requests will be accepted.
+This connection takes the data, passes it off to the system then returns to take
+more data.  Connection params are all taken from the config file and the values
+retreived are based on the $app name passed into new().
+
+This service should be loaded at system startup.
+
+=cut
+
+# XXX This will be overhauled to connect as a component instead of as
+# a user.  all in good time, though.
+
+{
+       my $unix_sock;
+       sub unix_sock { return $unix_sock; }
+       my $instance;
+
+       sub new {
+               my( $class, $app ) = @_;
+               $class = ref( $class ) || $class;
+               if( ! $instance ) {
+                       my $app_state = $app . "_inbound";
+                       my $config = OpenSRF::Utils::Config->current;
+
+                       if( ! $config ) {
+                               throw OpenSRF::EX::Jabber( "No suitable config found" );
+                       }
+
+                       my $host                        = $config->transport->server->primary;
+                       my $username    = $config->transport->users->$app;
+                       my $password    = $config->transport->auth->password;
+                       my $debug               = $config->transport->llevel->$app_state;
+                       my $log                 = $config->transport->log->$app_state;
+                       my $resource    = "system";
+
+
+                       my $self = __PACKAGE__->SUPER::new( 
+                                       username                => $username,
+                                       host                    => $host,
+                                       resource                => $resource,
+                                       password                => $password,
+                                       log_file                => $log,
+                                       debug                   => $debug,
+                                       );
+                                       
+                                       
+                       my $f = $config->dirs->sock_dir;
+                       $unix_sock = join( "/", $f, $config->unix_sock->$app );
+                       bless( $self, $class );
+                       $instance = $self;
+               }
+               $instance->SetCallBacks( message => \&handle_message );
+               return $instance;
+       }
+
+}
+       
+# ---
+# All incoming messages are passed untouched to the Unix Server for processing.  The
+# Unix socket is closed by the Unix Server as soon as it has received all of the
+# data.  This means we can go back to accepting more incoming connection.
+# -----
+sub handle_message { 
+       my $sid = shift;
+       my $message = shift;
+
+       my $packet = $message->GetXML();
+
+       $logger->transport( "JInbound $$ received $packet", INTERNAL );
+
+       # Send the packet to the unix socket for processing.
+       my $sock = unix_sock();
+       my $socket;
+       my $x = 0;
+       for( ;$x != 5; $x++ ) { #try 5 times
+               if( $socket = IO::Socket::UNIX->new( Peer => $sock  ) ) {
+                       last;
+               }
+       }
+       if( $x == 5 ) {
+               throw OpenSRF::EX::Socket( 
+                       "Unable to connect to UnixServer: socket-file: $sock \n :=> $! " );
+       }
+       print $socket $packet;
+       close( $socket );
+}
+
+
+1;
+
diff --git a/src/perlmods/OpenSRF/Transport/Jabber/JMessageWrapper.pm b/src/perlmods/OpenSRF/Transport/Jabber/JMessageWrapper.pm
new file mode 100644 (file)
index 0000000..15a6de5
--- /dev/null
@@ -0,0 +1,91 @@
+package OpenSRF::Transport::Jabber::JMessageWrapper;
+use Jabber::NodeFactory;
+use Net::Jabber qw(Client);
+use Net::Jabber::Message;
+use base qw/ Net::Jabber::Message OpenSRF /;
+use OpenSRF::Utils::Logger qw(:level);
+use strict; use warnings;
+
+=head1 Description
+
+OpenSRF::Transport::Jabber::JMessageWrapper
+
+Provides a means to extract information about a Jabber
+message when all you have is the raw XML.  The API
+implemented here should be implemented by any Transport
+helper/MessageWrapper class.
+
+=cut
+
+sub DESTROY{}
+
+my $logger = "OpenSRF::Utils::Logger";
+my $_node_factory = Jabber::NodeFactory->new( fromstr => 1 );
+
+
+=head2 new( Net::Jabber::Message/$raw_xml )
+
+Pass in the raw Jabber message as XML and create a new 
+JMessageWrapper
+
+=cut
+
+sub new {
+       my( $class, $xml ) = @_;
+       $class = ref( $class ) || $class;
+
+       return undef unless( $xml );
+       
+       my $self;
+
+       if( ref( $xml ) ) {
+               $self = $xml;
+       } else {
+               $logger->transport( "MWrapper got: " . $xml, INTERNAL );
+               my $node = $_node_factory->newNodeFromStr( $xml );
+               $self = $class->SUPER::new();
+               $self->SetFrom( $node->attr('from') );
+               $self->SetThread( $node->getTag('thread')->data );
+               $self->SetBody( $node->getTag('body')->data );
+       }
+
+       bless( $self, $class );
+
+       $logger->transport( "MessageWrapper $self after blessing", INTERNAL );
+
+       return $self;
+
+}
+
+=head2 get_remote_id
+
+Returns the JID (user@host/resource) of the remote user
+
+=cut
+sub get_remote_id {
+       my( $self ) = @_;
+       return $self->GetFrom();
+}
+
+=head2 get_sess_id
+
+Returns the Jabber thread associated with this message
+
+=cut
+sub get_sess_id {
+       my( $self ) = @_;
+       return $self->GetThread();
+}
+
+=head2 get_body
+
+Returns the message body of the Jabber message
+
+=cut
+sub get_body {
+       my( $self ) = @_;
+       return $self->GetBody();
+}
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/Jabber/JPeerConnection.pm b/src/perlmods/OpenSRF/Transport/Jabber/JPeerConnection.pm
new file mode 100644 (file)
index 0000000..766de42
--- /dev/null
@@ -0,0 +1,80 @@
+package OpenSRF::Transport::Jabber::JPeerConnection;
+use strict;
+use base qw/OpenSRF::Transport::Jabber::JabberClient/;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+
+=head1 Description
+
+Represents a single connection to a remote peer.  The 
+Jabber values are loaded from the config file.  
+
+Subclasses OpenSRF::Transport::JabberClient.
+
+=cut
+
+=head2 new()
+
+       new( $appname );
+
+       The $appname parameter tells this class how to find the correct
+       Jabber username, password, etc to connect to the server.
+
+=cut
+
+our $main_instance;
+our %apps_hash;
+
+sub retrieve { 
+       my( $class, $app ) = @_;
+       my @keys = keys %apps_hash;
+       OpenSRF::Utils::Logger->transport( 
+                       "Requesting peer for $app and we have @keys", INTERNAL );
+       return $apps_hash{$app};
+}
+
+
+
+sub new {
+       my( $class, $app ) = @_;
+       my $config = OpenSRF::Utils::Config->current;
+
+       if( ! $config ) {
+               throw OpenSRF::EX::Config( "No suitable config found" );
+       }
+
+       my $app_stat    = $app . "_peer";
+       my $host                        = $config->transport->server->primary;
+       my $username    = $config->transport->users->$app;
+       my $password    = $config->transport->auth->password;
+       my $debug               = $config->transport->llevel->$app_stat;
+       my $log                 = $config->transport->log->$app_stat;
+       my $resource    = $config->env->hostname . "_$$";
+
+       OpenSRF::EX::Config->throw( "JPeer could not load all necesarry values from config" )
+               unless ( $host and $username and $password and $resource );
+
+
+       my $self = __PACKAGE__->SUPER::new( 
+               username                => $username,
+               host                    => $host,
+               resource                => $resource,
+               password                => $password,
+               log_file                => $log,
+               debug                   => $debug,
+               );      
+                                       
+        bless( $self, $class );
+
+        $self->SetCallBacks( message => sub {
+                        my $msg = $_[1];
+                        OpenSRF::Utils::Logger->transport( 
+                                "JPeer passing \n$msg \n to Transport->handler for $app", INTERNAL );
+                        OpenSRF::Transport->handler( $app, $msg ); } );
+
+       $apps_hash{$app} = $self;
+       return $apps_hash{$app};
+}
+       
+1;
+
diff --git a/src/perlmods/OpenSRF/Transport/Jabber/JabberClient.pm b/src/perlmods/OpenSRF/Transport/Jabber/JabberClient.pm
new file mode 100644 (file)
index 0000000..50eb6ae
--- /dev/null
@@ -0,0 +1,277 @@
+package OpenSRF::Transport::Jabber::JabberClient;
+use strict; use warnings;
+use OpenSRF::EX;
+use Net::Jabber qw( Client );
+use base qw( OpenSRF Net::Jabber::Client );
+use OpenSRF::Utils::Logger qw(:level);
+
+=head1 Description
+
+OpenSRF::Transport::Jabber::JabberClient
+
+Subclasses Net::Jabber::Client and, hence, provides the same
+functionality.  What it provides in addition is mainly some logging
+and exception throwing on the call to 'initialize()', which sets
+up the connection and authentication.
+
+=cut
+
+my $logger = "OpenSRF::Utils::Logger";
+
+sub DESTROY{};
+
+
+=head2 new()
+
+Creates a new JabberClient object.  The parameters should be self explanatory.
+If not, see Net::Jabber::Client for more.  
+
+debug and log_file are not required if you don't care to log the activity, 
+however all other parameters are.
+
+%params:
+
+       host
+       username
+       resource        
+       password
+       debug    
+       log_file
+
+=cut
+
+sub new {
+
+       my( $class, %params ) = @_;
+
+       $class = ref( $class ) || $class;
+
+       my $host                        = $params{'host'}                       || return undef;
+       my $username    = $params{'username'}   || return undef;
+       my $resource    = $params{'resource'}   || return undef;
+       my $password    = $params{'password'}   || return undef;
+       my $debug               = $params{'debug'};              
+       my $log_file    = $params{'log_file'};
+
+       my $self;
+
+       if( $debug and $log_file ) {
+               $self = Net::Jabber::Client->new( 
+                               debuglevel => $debug, debugfile => $log_file );
+       }
+       else { $self = Net::Jabber::Client->new(); }
+
+       bless( $self, $class );
+
+       $self->host( $host );
+       $self->username( $username );
+       $self->resource( $resource );
+       $self->password( $password );
+
+       $logger->transport( "Creating Jabber instance: $host, $username, $resource",
+                       $logger->INFO );
+
+       $self->SetCallBacks( send => 
+                       sub { $logger->transport( "JabberClient in 'send' callback: @_", INTERNAL ); } );
+
+
+       return $self;
+}
+
+# -------------------------------------------------
+
+=head2 gather()
+
+Gathers all Jabber messages sitting in the collection queue 
+and hands them each to their respective callbacks.  This call
+does not block (calls Process(0))
+
+=cut
+
+sub gather { my $self = shift; $self->Process( 0 ); }
+
+# -------------------------------------------------
+
+=head2 listen()
+
+Blocks and gathers incoming messages as they arrive.  Does not return
+unless an error occurs.
+
+Throws an OpenSRF::EX::JabberException if the call to Process ever fails.
+
+=cut
+sub listen {
+       my $self = shift;
+       while(1) {
+               my $o = $self->process( -1 );
+               if( ! defined( $o ) ) {
+                       throw OpenSRF::EX::Jabber( "Listen Loop failed at 'Process()'" );
+               }
+       }
+}
+
+# -------------------------------------------------
+
+sub password {
+       my( $self, $password ) = @_;
+       $self->{'oils:password'} = $password if $password;
+       return $self->{'oils:password'};
+}
+
+# -------------------------------------------------
+
+sub username {
+       my( $self, $username ) = @_;
+       $self->{'oils:username'} = $username if $username;
+       return $self->{'oils:username'};
+}
+       
+# -------------------------------------------------
+
+sub resource {
+       my( $self, $resource ) = @_;
+       $self->{'oils:resource'} = $resource if $resource;
+       return $self->{'oils:resource'};
+}
+
+# -------------------------------------------------
+
+sub host {
+       my( $self, $host ) = @_;
+       $self->{'oils:host'} = $host if $host;
+       return $self->{'oils:host'};
+}
+
+# -------------------------------------------------
+
+=head2 send()
+
+       Sends a Jabber message.
+       
+       %params:
+               to                      - The JID of the recipient
+               thread  - The Jabber thread
+               body            - The body of the message
+
+=cut
+
+sub send {
+       my( $self, %params ) = @_;
+
+       my $to = $params{'to'} || return undef;
+       my $body = $params{'body'} || return undef;
+       my $thread = $params{'thread'};
+
+       my $msg = Net::Jabber::Message->new();
+
+       $msg->SetTo( $to );
+       $msg->SetThread( $thread ) if $thread;
+       $msg->SetBody( $body );
+
+       $logger->transport( 
+                       "JabberClient Sending message to $to with thread $thread and body: \n$body", INTERNAL );
+
+       $self->Send( $msg );
+}
+
+
+=head2 inintialize()
+
+Connect to the server and log in.  
+
+Throws an OpenSRF::EX::JabberException if we cannot connect
+to the server or if the authentication fails.
+
+=cut
+
+# --- The logging lines have been commented out until we decide 
+# on which log files we're using.
+
+sub initialize {
+
+       my $self = shift;
+
+       my $host                        = $self->host; 
+       my $username    = $self->username;
+       my $resource    = $self->resource;
+       my $password    = $self->password;
+
+       my $jid = "$username\@$host\/$resource";
+
+       # --- 5 tries to connect to the jabber server
+       my $x = 0;
+       for( ; $x != 5; $x++ ) {
+               $logger->transport( "$jid: Attempting to connecto to server...$x", WARN );
+               if( $self->Connect( 'hostname' => $host ) ) {
+                       last; 
+               }
+               else { sleep 3; }
+       }
+
+       if( $x == 5 ) {
+               die "could not connect to server $!\n";
+               throw OpenSRF::EX::Jabber( " Could not connect to Jabber server" );
+       }
+
+       $logger->transport( "Logging into jabber as $jid " .
+                       "from " . ref( $self ), DEBUG );
+
+       # --- Log in
+       my @a = $self->AuthSend( 'username' => $username, 
+               'password' => $password, 'resource' => $resource );
+
+       if( $a[0] eq "ok" ) { 
+               $logger->transport( " * $jid: Jabber authenticated and connected", DEBUG );
+       }
+       else {
+               throw OpenSRF::EX::Jabber( " * $jid: Unable to authenticate: @a" );
+       }
+
+       return $self;
+}
+
+sub construct {
+       my( $class, $app ) = @_;
+       $logger->transport("Constructing new Jabber connection for $app", INTERNAL );
+       $class->peer_handle( 
+                       $class->new( $app )->initialize() );
+}
+
+sub process {
+
+       my( $self, $timeout ) = @_;
+       if( ! $timeout ) { $timeout = 0; }
+
+       unless( $self->Connected() ) {
+               OpenSRF::EX::Jabber->throw( 
+                 "This JabberClient instance is no longer connected to the server", ERROR );
+       }
+
+       my $val;
+
+       if( $timeout eq "-1" ) {
+               $val = $self->Process();
+       }
+       else { $val = $self->Process( $timeout ); }
+
+       if( $timeout eq "-1" ) { $timeout = " "; }
+       
+       if( ! defined( $val ) ) {
+               OpenSRF::EX::Jabber->throw( 
+                 "Call to Net::Jabber::Client->Process( $timeout ) failed", ERROR );
+       }
+       elsif( ! $val ) {
+               $logger->transport( 
+                       "Call to Net::Jabber::Client->Process( $timeout ) returned 0 bytes of data", DEBUG );
+       }
+       elsif( $val ) {
+               $logger->transport( 
+                       "Call to Net::Jabber::Client->Process( $timeout ) successfully returned data", INTERNAL );
+       }
+
+       return $val;
+
+}
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/Listener.pm b/src/perlmods/OpenSRF/Transport/Listener.pm
new file mode 100644 (file)
index 0000000..f9de8a4
--- /dev/null
@@ -0,0 +1,43 @@
+package OpenSRF::Transport::Listener;
+use base 'OpenSRF';
+use OpenSRF::Utils::Logger qw(:level);
+
+
+=head1 Description
+
+This is the empty class that acts as the subclass of the transport listener.  My API
+includes
+
+new( $app )
+       create a new Listener with appname $app
+
+initialize()
+       Perform any transport layer connections/authentication.
+
+listen()
+       Block, wait for, and process incoming messages
+
+=cut
+
+=head2 set_listener()
+
+Sets my superclass.  Pass in a string representing the perl module
+(e.g. OpenSRF::Transport::Jabber::JInbound) to be used as the
+superclass and it will be pushed onto @ISA.
+
+=cut
+
+sub set_listener {
+       my( $class, $listener ) = @_;
+       if( $listener ) {
+               eval "use $listener;";
+               if( $@ ) {
+                       OpenSRF::Utils::Logger->error(
+                                       "Unable to set transport listener: $@", ERROR );
+               }
+               unshift @ISA, $listener;
+       }
+}
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/PeerHandle.pm b/src/perlmods/OpenSRF/Transport/PeerHandle.pm
new file mode 100644 (file)
index 0000000..e0bd53b
--- /dev/null
@@ -0,0 +1,40 @@
+package OpenSRF::Transport::PeerHandle;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX;
+use base 'OpenSRF';
+use vars '@ISA';
+
+my $peer;
+
+=head2 peer_handle( $handle )
+
+Assigns the object that will act as the peer connection handle.
+
+=cut
+sub peer_handle {
+       my( $class, $handle ) = @_;
+       if( $handle ) { $peer = $handle; }
+       return $peer;
+}
+
+
+=head2 set_peer_client( $peer )
+
+Sets the class that will act as the superclass of this class.
+Pass in a string representing the module to be used as the superclass,
+and that module is 'used' and unshifted into @ISA.  We now have that
+classes capabilities.  
+
+=cut
+sub set_peer_client {
+       my( $class, $peer ) = @_;
+       if( $peer ) {
+               eval "use $peer;";
+               if( $@ ) {
+                       throw OpenSRF::EX::PANIC ( "Unable to set peer client: $@" );
+               }
+               unshift @ISA, $peer;
+       }
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/SlimJabber.pm b/src/perlmods/OpenSRF/Transport/SlimJabber.pm
new file mode 100644 (file)
index 0000000..7963b93
--- /dev/null
@@ -0,0 +1,18 @@
+package OpenSRF::Transport::SlimJabber;
+use base qw/OpenSRF::Transport/;
+
+=head2 OpenSRF::Transport::SlimJabber
+
+Implements the Transport interface for providing the system with appropriate
+classes for handling transport layer messaging
+
+=cut
+
+
+sub get_listener { return "OpenSRF::Transport::SlimJabber::Inbound"; }
+
+sub get_peer_client { return "OpenSRF::Transport::SlimJabber::PeerConnection"; }
+
+sub get_msg_envelope { return "OpenSRF::Transport::SlimJabber::MessageWrapper"; }
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/SlimJabber/Client.pm b/src/perlmods/OpenSRF/Transport/SlimJabber/Client.pm
new file mode 100644 (file)
index 0000000..fc1afb8
--- /dev/null
@@ -0,0 +1,541 @@
+package OpenSRF::Transport::SlimJabber::Client;
+use strict; use warnings;
+use OpenSRF::EX;
+#use Net::Jabber qw( Client );
+use base qw( OpenSRF );
+use OpenSRF::Utils::Logger qw(:level);
+use Time::HiRes qw(ualarm);
+
+use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
+use IO::Socket::INET;
+
+=head1 Description
+
+OpenSRF::Transport::SlimJabber::Client
+
+Home-brewed slimmed down jabber connection agent. Supports SSL connections
+with a config file options:
+
+  transport->server->port # the ssl port
+  transport->server->ssl  # is this ssl?
+
+=cut
+
+my $logger = "OpenSRF::Utils::Logger";
+
+sub DESTROY{
+       my $self = shift;
+       my $socket = $self->{_socket};
+       if( $socket and $socket->connected() ) {
+               print $socket "</stream:stream>";
+               close( $socket );
+       }
+};
+
+
+=head2 new()
+
+Creates a new Client object.
+
+debug and log_file are not required if you don't care to log the activity, 
+however all other parameters are.
+
+%params:
+
+       username
+       resource        
+       password
+       debug    
+       log_file
+
+=cut
+
+sub new {
+
+       my( $class, %params ) = @_;
+
+       $class = ref( $class ) || $class;
+
+       my $conf = OpenSRF::Utils::Config->current;
+
+       my $host                        = $conf->transport->server->primary;
+       my $port                        = $conf->transport->server->port;
+       my $username    = $params{'username'}   || return undef;
+       my $resource    = $params{'resource'}   || return undef;
+       my $password    = $params{'password'}   || return undef;
+
+       my $jid = "$username\@$host\/$resource";
+
+
+       my $self = bless {} => $class;
+
+       $self->jid( $jid );
+       $self->host( $host );
+       $self->port( $port );
+       $self->username( $username );
+       $self->resource( $resource );
+       $self->password( $password );
+       $self->{temp_buffer} = "";
+
+       $logger->transport( "Creating Client instance: $host:$port, $username, $resource",
+                       $logger->INFO );
+
+       return $self;
+}
+
+# clears the tmp buffer as well as the TCP buffer
+sub buffer_reset { 
+
+       my $self = shift;
+       $self->{temp_buffer} = ""; 
+
+       my $fh = $self->{_socket};
+       set_nonblock( $fh );
+       my $t_buf = "";
+       while( sysread( $fh, $t_buf, 4096 ) ) {} 
+       set_block( $fh );
+}
+# -------------------------------------------------
+
+=head2 gather()
+
+Gathers all Jabber messages sitting in the collection queue 
+and hands them each to their respective callbacks.  This call
+does not block (calls Process(0))
+
+=cut
+
+sub gather { my $self = shift; $self->process( 0 ); }
+
+# -------------------------------------------------
+
+=head2 listen()
+
+Blocks and gathers incoming messages as they arrive.  Does not return
+unless an error occurs.
+
+Throws an OpenSRF::EX::JabberException if the call to Process ever fails.
+
+=cut
+sub listen {
+       my $self = shift;
+
+       my $sock = $self->unix_sock();
+       my $socket = IO::Socket::UNIX->new( Peer => $sock  );
+       $logger->transport( "Unix Socket opened by Listener", INTERNAL );
+       
+       throw OpenSRF::EX::Socket( "Unable to connect to UnixServer: socket-file: $sock \n :=> $! " )
+               unless ($socket->connected);
+               
+       while(1) {
+               my $o = $self->process( -1 );
+               $logger->transport( "Call to process() in listener returned:\n $o", INTERNAL );
+               if( ! defined( $o ) ) {
+                       throw OpenSRF::EX::Jabber( "Listen Loop failed at 'process()'" );
+               }
+               print $socket $o;
+
+       }
+       throw OpenSRF::EX::Socket( "How did we get here?!?!" );
+}
+
+sub set_nonblock {
+       my $fh = shift;
+       my      $flags = fcntl($fh, F_GETFL, 0)
+               or die "Can't get flags for the socket: $!\n";
+
+       $logger->transport( "Setting NONBLOCK: original flags: $flags", INTERNAL );
+
+       fcntl($fh, F_SETFL, $flags | O_NONBLOCK)
+               or die "Can't set flags for the socket: $!\n";
+
+       return $flags;
+}
+
+sub reset_fl {
+       my $fh = shift;
+       my $flags = shift;
+       $logger->transport( "Restoring BLOCK: to flags $flags", INTERNAL );
+       fcntl($fh, F_SETFL, $flags) if defined $flags;
+}
+
+sub set_block {
+       my $fh = shift;
+
+       my      $flags = fcntl($fh, F_GETFL, 0)
+               or die "Can't get flags for the socket: $!\n";
+
+       $flags &= ~O_NONBLOCK;
+
+       fcntl($fh, F_SETFL, $flags)
+               or die "Can't set flags for the socket: $!\n";
+}
+
+
+sub timed_read {
+       my ($self, $timeout) = @_;
+
+       $logger->transport( "Temp Buffer Contained: \n". $self->{temp_buffer}, INTERNAL) if $self->{temp_buffer};
+       if( $self->can( "app" ) ) {
+               $logger->transport( "timed_read called for ".$self->app.", I am: ".$self->jid, INTERNAL );
+       }
+
+       # See if there is a complete message in the temp_buffer
+       # that we can return
+       if( $self->{temp_buffer} ) {
+               my $buffer = $self->{temp_buffer};
+               my $complete = 0;
+               $self->{temp_buffer} = '';
+
+               my ($tag) = ($buffer =~ /<([^\s\?\>]+)/o);
+               $logger->transport("Using tag: $tag  ", INTERNAL);
+
+               if ( $buffer =~ /^(.*?<\/$tag>)(.*)/s) {
+                       $buffer = $1;
+                       $self->{temp_buffer} = $2;
+                       $complete++;
+                       $logger->transport( "completed read with $buffer", INTERNAL );
+               } elsif ( $buffer =~ /^<$tag[^>]*?\/>(.*)/) {
+                       $self->{temp_buffer} = $1;
+                       $complete++;
+                       $logger->transport( "completed read with $buffer", INTERNAL );
+               } else {
+                       $self->{temp_buffer} = $buffer;
+               }
+                               
+               if( $buffer and $complete ) {
+                       return $buffer;
+               }
+
+       }
+       ############
+
+       my $fh = $self->{_socket};
+
+       unless( $fh and $fh->connected ) {
+               throw OpenSRF::EX::Socket ("Attempted read on closed socket", ERROR );
+       }
+
+       $logger->transport( "Temp Buffer After first attempt: \n ".$self->{temp_buffer}, INTERNAL) if $self->{temp_buffer};
+
+       my $flags;
+       if (defined($timeout) && !$timeout) {
+               $flags = set_nonblock( $fh );
+       }
+
+       $timeout ||= 0;
+       $logger->transport( "Calling timed_read with timetout $timeout", INTERNAL );
+
+
+       my $complete = 0;
+       my $first_read = 1;
+       my $xml = '';
+       eval {
+               my $tag = '';
+               eval {
+                       no warnings;
+                       local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
+
+                       # alarm needs a number greater => 1.
+                       my $alarm_timeout = $timeout;
+                       if( $alarm_timeout > 0 and $alarm_timeout < 1 ) {
+                               $alarm_timeout = 1;
+                       }
+                       alarm $alarm_timeout;
+                       do {    
+
+                               my $buffer = $self->{temp_buffer};
+                               $self->{temp_buffer} = '';
+                               #####
+
+                               my $ff =  fcntl($fh, F_GETFL, 0);
+                               if ($ff == ($ff | O_NONBLOCK) and $timeout > 0 ) {
+                                       #throw OpenSRF::EX::ERROR ("File flags are set to NONBLOCK but timeout is $timeout", ERROR );
+                               }
+
+                               my $t_buf = "";
+                               my $read_size = 1024;
+                               my $f = 0;
+                               while( my $n = sysread( $fh, $t_buf, $read_size ) ) {
+                                       $buffer .= $t_buf;
+                                       if( $n < $read_size ) {
+                                               #reset_fl( $fh, $f ) if $f;
+                                               set_block( $fh );
+                                               last;
+                                       }
+                                       # see if there is any more data to grab...
+                                       $f = set_nonblock( $fh );
+                               }
+
+                               #sysread($fh, $buffer, 2048, length($buffer) );
+                               #sysread( $fh, $t_buf, 2048 );
+                               #$buffer .= $t_buf;
+
+                               #####
+                               $logger->transport(" Got [$buffer] from the socket", INTERNAL);
+
+                               if ($first_read) {
+                                       $logger->transport(" First read Buffer\n [$buffer]", INTERNAL);
+                                       ($tag) = ($buffer =~ /<([^\s\?\>]+){1}/o);
+                                       $first_read--;
+                                       $logger->transport("Using tag: $tag  ", INTERNAL);
+                               }
+
+                               if (!$first_read && $buffer =~ /^(.*?<\/$tag>){1}(.*)/s) {
+                                       $buffer = $1;
+                                       $self->{temp_buffer} = $2;
+                                       $complete++;
+                                       $logger->transport( "completed read with $buffer", INTERNAL );
+                               } elsif (!$first_read && $buffer =~ /^<$tag[^>]*?\/>(.*)/) {
+                                       $self->{temp_buffer} = $1;
+                                       $complete++;
+                                       $logger->transport( "completed read with $buffer", INTERNAL );
+                               }
+                               
+                               $xml .= $buffer;
+
+                       } while (!$complete && $xml);
+                       alarm(0);
+               };
+               alarm(0);
+       };
+
+       $logger->transport( "XML Read: $xml", INTERNAL );
+       #reset_fl( $fh, $flags) if defined $flags;
+       set_block( $fh ) if defined $flags;
+
+       if ($complete) {
+               return $xml;
+       }
+       if( $@ ) {
+               return undef;
+       }
+       return "";
+}
+
+
+# -------------------------------------------------
+
+sub tcp_connected {
+
+       my $self = shift;
+       return 1 if ($self->{_socket} and $self->{_socket}->connected);
+       return 0;
+}
+
+sub password {
+       my( $self, $password ) = @_;
+       $self->{'oils:password'} = $password if $password;
+       return $self->{'oils:password'};
+}
+
+# -------------------------------------------------
+
+sub username {
+       my( $self, $username ) = @_;
+       $self->{'oils:username'} = $username if $username;
+       return $self->{'oils:username'};
+}
+       
+# -------------------------------------------------
+
+sub resource {
+       my( $self, $resource ) = @_;
+       $self->{'oils:resource'} = $resource if $resource;
+       return $self->{'oils:resource'};
+}
+
+# -------------------------------------------------
+
+sub jid {
+       my( $self, $jid ) = @_;
+       $self->{'oils:jid'} = $jid if $jid;
+       return $self->{'oils:jid'};
+}
+
+sub port {
+       my( $self, $port ) = @_;
+       $self->{'oils:port'} = $port if $port;
+       return $self->{'oils:port'};
+}
+
+sub host {
+       my( $self, $host ) = @_;
+       $self->{'oils:host'} = $host if $host;
+       return $self->{'oils:host'};
+}
+
+# -------------------------------------------------
+
+=head2 send()
+
+       Sends a Jabber message.
+       
+       %params:
+               to                      - The JID of the recipient
+               thread  - The Jabber thread
+               body            - The body of the message
+
+=cut
+
+sub send {
+       my $self = shift;
+       my %params = @_;
+
+       my $to = $params{'to'} || return undef;
+       my $body = $params{'body'} || return undef;
+       my $thread = $params{'thread'} || "";
+       my $router_command = $params{'router_command'} || "";
+       my $router_class = $params{'router_class'} || "";
+
+       my $msg = OpenSRF::Transport::SlimJabber::MessageWrapper->new;
+
+       $msg->setTo( $to );
+       $msg->setThread( $thread ) if $thread;
+       $msg->setBody( $body );
+       $msg->set_router_command( $router_command );
+       $msg->set_router_class( $router_class );
+
+
+       $logger->transport( 
+                       "JabberClient Sending message to $to with thread $thread and body: \n$body", INTERNAL );
+
+       my $soc = $self->{_socket};
+       print $soc $msg->toString;
+}
+
+
+=head2 inintialize()
+
+Connect to the server and log in.  
+
+Throws an OpenSRF::EX::JabberException if we cannot connect
+to the server or if the authentication fails.
+
+=cut
+
+# --- The logging lines have been commented out until we decide 
+# on which log files we're using.
+
+sub initialize {
+
+       my $self = shift;
+
+       my $jid         = $self->jid; 
+       my $host        = $self->host; 
+       my $port        = $self->port; 
+       my $username    = $self->username;
+       my $resource    = $self->resource;
+       my $password    = $self->password;
+
+       my $stream = <<"        XML";
+<stream:stream to='$host' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
+       XML
+
+       my $auth = <<"  XML";
+<iq id='123' type='set'>
+<query xmlns='jabber:iq:auth'>
+<username>$username</username>
+<password>$password</password>
+<resource>$resource</resource>
+</query>
+</iq>
+       XML
+
+
+       # --- 5 tries to connect to the jabber server
+       my $socket;
+       for(1..5) {
+               $logger->transport( "$jid: Attempting to connect to server... (Try # $_)", WARN );
+               $socket = IO::Socket::INET->new( PeerHost => $host,
+                                                PeerPort => $port,
+                                                Proto    => 'tcp' );
+               last if ( $socket and $socket->connected );
+               sleep 3;
+       }
+
+       unless ( $socket and $socket->connected ) {
+               throw OpenSRF::EX::Jabber( " Could not connect to Jabber server: $!" );
+       }
+
+       $logger->transport( "Logging into jabber as $jid " .
+                       "from " . ref( $self ), DEBUG );
+
+       print $socket $stream;
+
+       my $buffer;
+       eval {
+               eval {
+                       local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
+                       alarm 3;
+                       sysread($socket, $buffer, 4096);
+                       $logger->transport( "Login buffer 1: $buffer", INTERNAL );
+                       alarm(0);
+               };
+               alarm(0);
+       };
+
+       print $socket $auth;
+
+       if( $socket and $socket->connected() ) {
+               $self->{_socket} = $socket;
+       } else {
+               throw OpenSRF::EX::Jabber( " ** Unable to connect to Jabber server", ERROR );
+       }
+
+
+       $buffer = $self->timed_read(10);
+
+       if( $buffer ) {$logger->transport( "Login buffer 2: $buffer", INTERNAL );}
+
+       if( $buffer and $buffer =~ /type=["\']result["\']/ ) { 
+               $logger->transport( " * $jid: Jabber authenticated and connected", DEBUG );
+       } else {
+               if( !$buffer ) { $buffer = " "; }
+               $socket->close;
+               throw OpenSRF::EX::Jabber( " * $jid: Unable to authenticate: $buffer", ERROR );
+       }
+
+       return $self;
+}
+
+sub construct {
+       my( $class, $app ) = @_;
+       $logger->transport("Constructing new Jabber connection for $app", INTERNAL );
+       $class->peer_handle( 
+                       $class->new( $app )->initialize() );
+}
+
+sub process {
+
+       my( $self, $timeout ) = @_;
+
+       $timeout ||= 0;
+       undef $timeout if ( $timeout == -1 );
+
+       unless( $self->{_socket}->connected ) {
+               OpenSRF::EX::JabberDisconnected->throw( 
+                 "This JabberClient instance is no longer connected to the server", ERROR );
+       }
+
+       my $val = $self->timed_read( $timeout );
+
+       $timeout = "FOREVER" unless ( defined $timeout );
+       
+       if ( ! defined( $val ) ) {
+               OpenSRF::EX::Jabber->throw( 
+                 "Call to Client->timed_read( $timeout ) failed", ERROR );
+       } elsif ( ! $val ) {
+               $logger->transport( 
+                       "Call to Client->timed_read( $timeout ) returned 0 bytes of data", DEBUG );
+       } elsif ( $val ) {
+               $logger->transport( 
+                       "Call to Client->timed_read( $timeout ) successfully returned data", INTERNAL );
+       }
+
+       return $val;
+
+}
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/SlimJabber/Inbound.pm b/src/perlmods/OpenSRF/Transport/SlimJabber/Inbound.pm
new file mode 100644 (file)
index 0000000..9dc5ad0
--- /dev/null
@@ -0,0 +1,94 @@
+package OpenSRF::Transport::SlimJabber::Inbound;
+use strict;use warnings;
+use base qw/OpenSRF::Transport::SlimJabber::Client/;
+use OpenSRF::EX;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+
+my $logger = "OpenSRF::Utils::Logger";
+
+=head1 Description
+
+This is the jabber connection where all incoming client requests will be accepted.
+This connection takes the data, passes it off to the system then returns to take
+more data.  Connection params are all taken from the config file and the values
+retreived are based on the $app name passed into new().
+
+This service should be loaded at system startup.
+
+=cut
+
+# XXX This will be overhauled to connect as a component instead of as
+# a user.  all in good time, though.
+
+{
+       my $unix_sock;
+       sub unix_sock { return $unix_sock; }
+       my $instance;
+
+       sub new {
+               my( $class, $app ) = @_;
+               $class = ref( $class ) || $class;
+               if( ! $instance ) {
+                       my $app_state = $app . "_inbound";
+                       my $config = OpenSRF::Utils::Config->current;
+
+                       if( ! $config ) {
+                               throw OpenSRF::EX::Jabber( "No suitable config found" );
+                       }
+
+                       my $username    = $config->transport->users->$app;
+                       my $password    = $config->transport->auth->password;
+                       my $resource    = "system_" . $config->env->hostname . "_$$";
+
+
+                       my $self = __PACKAGE__->SUPER::new( 
+                                       username                => $username,
+                                       resource                => $resource,
+                                       password                => $password,
+                                       );
+
+                       $self->{app} = $app;
+                                       
+                                       
+                       my $f = $config->dirs->sock_dir;
+                       $unix_sock = join( "/", $f, $config->unix_sock->$app );
+                       bless( $self, $class );
+                       $instance = $self;
+               }
+               return $instance;
+       }
+
+}
+       
+sub listen {
+       my $self = shift;
+       
+       my $config = OpenSRF::Utils::Config->current;
+       my $router = $config->system->router_target;
+       $self->send( to => $router, 
+                       body => "registering", router_command => "register" , router_class => $self->{app} );
+                       
+       while(1) {
+               my $sock = $self->unix_sock();
+               my $socket = IO::Socket::UNIX->new( Peer => $sock  );
+       
+               throw OpenSRF::EX::Socket( "Unable to connect to UnixServer: socket-file: $sock \n :=> $! " )
+                       unless ($socket->connected);
+
+               my $o = $self->process( -1 );
+
+               if( ! defined( $o ) ) {
+                       throw OpenSRF::EX::Jabber( "Listen Loop failed at 'process()'" );
+               }
+               print $socket $o;
+
+               $socket->close;
+
+       }
+
+       throw OpenSRF::EX::Socket( "How did we get here?!?!" );
+}
+
+1;
+
diff --git a/src/perlmods/OpenSRF/Transport/SlimJabber/MessageWrapper.pm b/src/perlmods/OpenSRF/Transport/SlimJabber/MessageWrapper.pm
new file mode 100644 (file)
index 0000000..7ac8f3a
--- /dev/null
@@ -0,0 +1,99 @@
+package OpenSRF::Transport::SlimJabber::MessageWrapper;
+use OpenSRF::DOM;
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $xml = shift;
+
+       my ($doc, $msg);
+       if ($xml) {
+               $doc = OpenSRF::DOM->new->parse_string($xml);
+               $msg = $doc->documentElement;
+       } else {
+               $doc = OpenSRF::DOM->createDocument;
+               $msg = $doc->createElement( 'message' );
+               $doc->documentElement->appendChild( $msg );
+       }
+
+       
+       my $self = { msg_node => $msg };
+
+       return bless $self => $class;
+}
+
+sub toString {
+       my $self = shift;
+       return $self->{msg_node}->toString(@_);
+}
+
+sub get_body {
+       my $self = shift;
+       my ($t_body) = grep {$_->nodeName eq 'body'} $self->{msg_node}->childNodes;
+       if( $t_body ) {
+               my $body = $t_body->textContent;
+               return $body;
+       }
+       return "";
+}
+
+sub get_sess_id {
+       my $self = shift;
+       my ($t_node) = grep {$_->nodeName eq 'thread'} $self->{msg_node}->childNodes;
+       if( $t_node ) {
+               return $t_node->textContent;
+       }
+       return "";
+}
+
+sub get_msg_type {
+       my $self = shift;
+       $self->{msg_node}->getAttribute( 'type' );
+}
+
+sub get_remote_id {
+       my $self = shift;
+
+       #
+       my $rid = $self->{msg_node}->getAttribute( 'router_from' );
+       return $rid if $rid;
+
+       return $self->{msg_node}->getAttribute( 'from' );
+}
+
+sub setType {
+       my $self = shift;
+       $self->{msg_node}->setAttribute( type => shift );
+}
+
+sub setTo {
+       my $self = shift;
+       $self->{msg_node}->setAttribute( to => shift );
+}
+
+sub setThread {
+       my $self = shift;
+       $self->{msg_node}->appendTextChild( thread => shift );
+}
+
+sub setBody {
+       my $self = shift;
+       my $body = shift;
+       $self->{msg_node}->appendTextChild( body => $body );
+}
+
+sub set_router_command {
+       my( $self, $router_command ) = @_;
+       if( $router_command ) {
+               $self->{msg_node}->setAttribute( router_command => $router_command );
+       }
+}
+sub set_router_class {
+       my( $self, $router_class ) = @_;
+       if( $router_class ) {
+               $self->{msg_node}->setAttribute( router_class => $router_class );
+       }
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/Transport/SlimJabber/PeerConnection.pm b/src/perlmods/OpenSRF/Transport/SlimJabber/PeerConnection.pm
new file mode 100644 (file)
index 0000000..b65ab35
--- /dev/null
@@ -0,0 +1,99 @@
+package OpenSRF::Transport::SlimJabber::PeerConnection;
+use strict;
+use base qw/OpenSRF::Transport::SlimJabber::Client/;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX qw/:try/;
+
+=head1 Description
+
+Represents a single connection to a remote peer.  The 
+Jabber values are loaded from the config file.  
+
+Subclasses OpenSRF::Transport::SlimJabber::Client.
+
+=cut
+
+=head2 new()
+
+       new( $appname );
+
+       The $appname parameter tells this class how to find the correct
+       Jabber username, password, etc to connect to the server.
+
+=cut
+
+our %apps_hash;
+
+sub retrieve { 
+       my( $class, $app ) = @_;
+       my @keys = keys %apps_hash;
+       OpenSRF::Utils::Logger->transport( 
+                       "Requesting peer for $app and we have @keys", INTERNAL );
+       return $apps_hash{$app};
+}
+
+
+
+sub new {
+       my( $class, $app ) = @_;
+       my $config = OpenSRF::Utils::Config->current;
+
+       if( ! $config ) {
+               throw OpenSRF::EX::Config( "No suitable config found" );
+       }
+
+       my $app_stat    = $app . "_peer";
+       my $username    = $config->transport->users->$app;
+       my $password    = $config->transport->auth->password;
+       my $resource    = $config->env->hostname . "_$$";
+
+       OpenSRF::EX::Config->throw( "JPeer could not load all necesarry values from config" )
+               unless ( $username and $password and $resource );
+
+
+       my $self = __PACKAGE__->SUPER::new( 
+               username                => $username,
+               resource                => $resource,
+               password                => $password,
+               );      
+                                       
+       bless( $self, $class );
+
+       $self->app($app);
+
+       $apps_hash{$app} = $self;
+       return $apps_hash{$app};
+}
+
+sub process {
+       my $self = shift;
+       my $val = $self->SUPER::process(@_);
+       return 0 unless $val;
+       OpenSRF::Utils::Logger->transport( "Calling transport handler for ".$self->app." with: $val", INTERNAL );
+       my $t;
+#try {
+       $t = OpenSRF::Transport->handler($self->app, $val);
+
+#      } catch OpenSRF::EX with {
+#              my $e = shift;
+#              $e->throw();
+
+#      } catch Error with { return undef; }
+
+       return $t;
+}
+
+sub app {
+       my $self = shift;
+       my $app = shift;
+       if( $app ) {
+               OpenSRF::Utils::Logger->transport( "PEER changing app to $app: ".$self->jid, INTERNAL );
+       }
+
+       $self->{app} = $app if ($app);
+       return $self->{app};
+}
+
+1;
+
diff --git a/src/perlmods/OpenSRF/UnixServer.pm b/src/perlmods/OpenSRF/UnixServer.pm
new file mode 100644 (file)
index 0000000..8164974
--- /dev/null
@@ -0,0 +1,160 @@
+package OpenSRF::UnixServer;
+use strict; use warnings;
+use base qw/OpenSRF/;
+use OpenSRF::EX;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::Transport::PeerHandle;
+use OpenSRF::Application;
+use OpenSRF::AppSession;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::System;
+use vars qw/@ISA/;
+use Carp;
+
+# XXX Need to add actual logging statements in the code
+my $logger = "OpenSRF::Utils::Logger";
+
+sub DESTROY { confess "Dying $$"; }
+
+=head1 What am I
+
+All inbound messages are passed on to the UnixServer for processing.
+We take the data, close the Unix socket, and pass the data on to our abstract
+'process()' method.  
+
+Our purpose is to 'multiplex' a single TCP connection into multiple 'client' connections.
+So when you pass data down the Unix socket to us, we have been preforked and waiting
+to disperse new data among us.
+
+=cut
+
+{
+       my $app;
+       sub app { return $app; }
+
+       sub new {
+               my( $class, $app1 ) = @_;
+               if( ! $app1 ) {
+                       throw OpenSRF::EX::InvalidArg( "UnixServer requires an app name to run" );
+               }
+               $app = $app1;
+               my $self = bless( {}, $class );
+               if( OpenSRF::Utils::Config->current->system->server_type !~ /fork/i ) {
+                       $self->child_init_hook();
+               }
+               return $self;
+       }
+
+}
+
+=head2 process_request()
+
+Takes the incoming data, closes the Unix socket and hands the data untouched 
+to the abstract process() method.  This method is implemented in our subclasses.
+
+=cut
+
+sub process_request {
+
+       my $self = shift;
+       my $data; my $d;
+       while( $d = <STDIN> ) { $data .= $d; }
+
+
+       if( ! $data or ! defined( $data ) or $data eq "" ) {
+               throw OpenSRF::EX::Socket(
+                               "Unix child received empty data from socket" );
+       }
+
+       if( ! close( $self->{server}->{client} ) ) {
+               $logger->debug( "Error closing Unix socket: $!", ERROR );
+       }
+
+
+       my $app = $self->app();
+       $logger->transport( "UnixServer for $app received $data", INTERNAL );
+
+       my $app_session = OpenSRF::Transport->handler( $self->app(), $data );
+       my $config = OpenSRF::Utils::Config->current;
+
+
+       my $keepalive = OpenSRF::Utils::Config->current->system->keep_alive;
+
+       my $req_counter = 0;
+       while( $app_session->state and $app_session->state != $app_session->DISCONNECTED() and
+                       $app_session->find( $app_session->session_id ) ) {
+               
+
+               my $before = time;
+               $logger->transport( "UnixServer calling queue_wait $keepalive", INTERNAL );
+               $app_session->queue_wait( $keepalive );
+               my $after = time;
+
+               if( ($after - $before) >= $keepalive ) { 
+
+                       my $res = OpenSRF::DomainObject::oilsConnectStatus->new(
+                                                                       status => "Disconnected on timeout",
+                                                                       statusCode => STATUS_TIMEOUT);
+                       $app_session->status($res);
+                       $app_session->state( $app_session->DISCONNECTED() );
+                       last;
+               }
+
+       }
+
+       my $x = 0;
+       while( 1 ) {
+               $logger->transport( "Looping on zombies " . $x++ , DEBUG);
+               last unless ( $app_session->queue_wait(0));
+       }
+
+       $logger->transport( "Timed out, disconnected, or auth failed", INFO );
+       $app_session->kill_me;
+               
+}
+
+
+sub serve {
+       my( $self ) = @_;
+       my $config = OpenSRF::Utils::Config->current;
+       my $app = $self->app();
+       my $conf_base =  $config->dirs->conf_dir;
+       my $conf = join( "/", $conf_base, $config->unix_conf->$app );
+       $logger->transport( 
+                       "Running UnixServer as @OpenSRF::UnixServer::ISA for $app with conf file: $conf", INTERNAL );
+       $self->run( 'conf_file' => $conf );
+}
+
+sub configure_hook {
+       my $self = shift;
+       my $app = $self->app;
+       my $config = OpenSRF::Utils::Config->current;
+
+       $logger->debug( "Setting application implementaion for $app", DEBUG );
+
+       OpenSRF::Application->application_implementation( $config->application_implementation->$app );
+       OpenSRF::Application->application_implementation->initialize()
+               if (OpenSRF::Application->application_implementation->can('initialize'));
+       return OpenSRF::Application->application_implementation;
+}
+
+sub child_finish_hook {
+       my $self = shift;
+       OpenSRF::AppSession->kill_client_session_cache;
+}
+
+sub child_init_hook { 
+
+       my $self = shift;
+       $logger->transport( 
+                       "Creating PeerHandle from UnixServer child_init_hook", INTERNAL );
+       OpenSRF::Transport::PeerHandle->construct( $self->app() );
+       my $peer_handle = OpenSRF::System::bootstrap_client();
+       OpenSRF::Application->application_implementation->child_init
+               if (OpenSRF::Application->application_implementation->can('child_init'));
+       return $peer_handle;
+
+}
+
+1;
+
diff --git a/src/perlmods/OpenSRF/Utils.pm b/src/perlmods/OpenSRF/Utils.pm
new file mode 100644 (file)
index 0000000..297cade
--- /dev/null
@@ -0,0 +1,390 @@
+package OpenSRF::Utils;
+
+=head1 NAME 
+
+OpenSRF::Utils
+
+=head1 DESCRIPTION 
+
+This is a container package for methods that are useful to derived modules.
+It has no constructor, and is generally not useful by itself... but this
+is where most of the generic methods live.
+
+=head1 METHODS 
+
+
+=cut
+
+use vars qw/@ISA $AUTOLOAD %EXPORT_TAGS @EXPORT_OK @EXPORT $VERSION/;
+push @ISA, 'Exporter';
+
+$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
+
+use Time::Local;
+use Errno;
+use POSIX;
+use FileHandle;
+#use Cache::FileCache;
+#use Storable qw(dclone);
+use Digest::MD5 qw(md5 md5_hex md5_base64);
+use Exporter;
+
+# This turns errors into warnings, so daemons don't die.
+#$Storable::forgive_me = 1;
+
+%EXPORT_TAGS = (common => [qw(interval_to_seconds seconds_to_interval sendmail)], daemon => [qw(safe_fork set_psname daemonize)]);
+
+Exporter::export_ok_tags('common','daemon');  # add aa, cc and dd to @EXPORT_OK
+
+sub AUTOLOAD {
+       my $self = shift;
+       my $type = ref($self) or return undef;
+
+       my $name = $AUTOLOAD;
+       $name =~ s/.*://;   # strip fully-qualified portion
+
+       if (defined($_[0])) {
+               return $self->{$name} = shift;
+       }
+       return $self->{$name};
+}
+
+
+sub _sub_builder {
+       my $self = shift;
+       my $class = ref($self) || $self;
+       my $part = shift;
+       unless ($class->can($part)) {
+               *{$class.'::'.$part} =
+                       sub {
+                               my $self = shift;
+                               my $new_val = shift;
+                               if ($new_val) {
+                                       $$self{$part} = $new_val;
+                               }
+                               return $$self{$part};
+               };
+       }
+}
+
+#sub standalone_ipc_cache {
+#      my $self = shift;
+#      my $class = ref($self) || $self;
+#      my $uniquifier = shift || return undef;
+#      my $expires = shift || 3600;
+
+#      return new Cache::FileCache ( { namespace => $class.'::'.$uniquifier, default_expires_in => $expires } );
+#}
+
+sub sendmail {
+       my $self = shift;
+        my $message = shift || $self;
+
+        open SM, '|/usr/sbin/sendmail -U -t' or return 0;
+        print SM $message;
+        close SM or return 0;
+        return 1;
+}
+
+sub __strip_comments {
+       my $self = shift;
+       my $config_file = shift;
+       my ($line, @done);
+       while (<$config_file>) {
+               s/^\s*(.*)\s*$/$1/o if (lc($$self{keep_space}) ne 'true');
+               /^(.*)$/o;
+               $line .= $1;
+               # keep new lines if keep_space is true
+               if ($line =~ /^$/o && (lc($$self{keep_space}) ne 'true')) {
+                       $line = '';
+                       next;
+               }
+               if (/^([^<]+)\s*<<\s*(\w+)\s*$/o) {
+                       $line = "$1 = ";
+                       my $breaker = $2;
+                       while (<$config_file>) {
+                               chomp;
+                               last if (/^$breaker/);
+                               $line .= $_;
+                       }
+               }
+
+               if ($line =~ /^#/ && $line !~ /^#\s*include\s+/o) {
+                       $line = '';
+                       next;
+               }
+               if ($line =~ /\\$/o) {
+                       chomp $line;
+                       $line =~ s/^\s*(.*)\s*\\$/$1/o;
+                       next;
+               }
+               push @done, $line;
+               $line = '';
+       }
+       return @done;
+}
+
+
+=head2 $thing->encrypt(@stuff)
+
+Returns a one way hash (MD5) of the values appended together.
+
+=cut
+
+sub encrypt {
+       my $self = shift;
+       return md5_hex(join('',@_));
+}
+
+=head2 $utils_obj->es_time('field') OR noo_es_time($timestamp)
+
+Returns the epoch-second style timestamp for the value stored in
+$utils_obj->{field}.  Returns B<0> for an empty or invalid date stamp, and
+assumes a PostgreSQL style datestamp to be supplied.
+
+=cut
+
+sub es_time {
+       my $self = shift;
+       my $part = shift;
+       my $es_part = $part.'_ES';
+       return $$self{$es_part} if (exists($$self{$es_part}) && defined($$self{$es_part}) && $$self{$es_part});
+       if (!$$self{$part} or $$self{$part} !~ /\d+/) {
+               return 0;
+
+       }
+       my @tm = reverse($$self{$part} =~ /([\d\.]+)/og);
+       if ($tm[5] > 0) {
+               $tm[5] -= 1;
+       }
+
+        return $$self{$es_part} = noo_es_time($$self{$part});
+}
+
+=head2 noo_es_time($timestamp) (non-OO es_time)
+
+Returns the epoch-second style timestamp for the B<$timestamp> passed
+in.  Returns B<0> for an empty or invalid date stamp, and
+assumes a PostgreSQL style datestamp to be supplied.
+
+=cut
+
+sub noo_es_time {
+        my $timestamp = shift;
+
+        my @tm = reverse($timestamp =~ /([\d\.]+)/og);
+        if ($tm[5] > 0) {
+                $tm[5] -= 1;
+        }
+        return timelocal(int($tm[1]), int($tm[2]), int($tm[3]), int($tm[4]) || 1, int($tm[5]), int($tm[6]) || 2002 );
+}
+
+
+=head2 $thing->interval_to_seconds('interval') OR interval_to_seconds('interval')
+
+=head2 $thing->seconds_to_interval($seconds) OR seconds_to_interval($seconds)
+
+Returns the number of seconds for any interval passed, or the interval for the seconds.
+This is the generic version of B<interval> listed below.
+
+The interval must match the regex I</\s*\+?\s*(\d+)\s*(\w{1})\w*\s*/g>, for example
+B<2 weeks, 3 d and 1hour + 17 Months> or
+B<1 year, 5 Months, 2 weeks, 3 days and 1 hour of seconds> meaning 46148400 seconds.
+
+       my $expire_time = time() + $thing->interval_to_seconds('17h 9m');
+
+The time size indicator may be one of
+
+=over 2
+
+=item s[econd[s]]
+
+for seconds
+
+=item m[inute[s]]
+
+for minutes
+
+=item h[our[s]]
+
+for hours
+
+=item d[ay[s]]
+
+for days
+
+=item w[eek[s]]
+
+for weeks
+
+=item M[onth[s]]
+
+for months (really (365 * 1d)/12 ... that may get smarter, though)
+
+=item y[ear[s]]
+
+for years (this is 365 * 1d)
+
+=back
+
+=cut
+sub interval_to_seconds {
+       my $self = shift;
+        my $interval = shift || $self;
+
+        $interval =~ s/and/,/g;
+        $interval =~ s/,//g;
+
+        my $amount = 0;
+        while ($interval =~ /\s*\+?\s*(\d+)\s*(\w{1})\w*\s*/g) {
+                $amount += $1 if ($2 eq 's');
+                $amount += 60 * $1 if ($2 eq 'm');
+                $amount += 60 * 60 * $1 if ($2 eq 'h');
+                $amount += 60 * 60 * 24 * $1 if ($2 eq 'd');
+                $amount += 60 * 60 * 24 * 7 * $1 if ($2 eq 'w');
+                $amount += ((60 * 60 * 24 * 365)/12) * $1 if ($2 eq 'M');
+                $amount += 60 * 60 * 24 * 365 * $1 if ($2 eq 'y');
+        }
+        return $amount;
+}
+
+sub seconds_to_interval {
+       my $self = shift;
+        my $interval = shift || $self;
+
+        my $limit = shift || 's';
+        $limit =~ s/^(.)/$1/o;
+
+        my ($y,$ym,$M,$Mm,$w,$wm,$d,$dm,$h,$hm,$m,$mm,$s,$string);
+        my ($year, $month, $week, $day, $hour, $minute, $second) =
+                ('year','Month','week','day', 'hour', 'minute', 'second');
+
+        if ($y = int($interval / (60 * 60 * 24 * 365))) {
+                $string = "$y $year". ($y > 1 ? 's' : '');
+                $ym = $interval % (60 * 60 * 24 * 365);
+        } else {
+                $ym = $interval;
+        }
+        return $string if ($limit eq 'y');
+
+        if ($M = int($ym / ((60 * 60 * 24 * 365)/12))) {
+                $string .= ($string ? ', ':'')."$M $month". ($M > 1 ? 's' : '');
+                $Mm = $ym % ((60 * 60 * 24 * 365)/12);
+        } else {
+                $Mm = $ym;
+        }
+        return $string if ($limit eq 'M');
+
+        if ($w = int($Mm / 604800)) {
+                $string .= ($string ? ', ':'')."$w $week". ($w > 1 ? 's' : '');
+                $wm = $Mm % 604800;
+        } else {
+                $wm = $Mm;
+        }
+        return $string if ($limit eq 'w');
+
+        if ($d = int($wm / 86400)) {
+                $string .= ($string ? ', ':'')."$d $day". ($d > 1 ? 's' : '');
+                $dm = $wm % 86400;
+        } else {
+                $dm = $wm;
+        }
+        return $string if ($limit eq 'd');
+
+        if ($h = int($dm / 3600)) {
+                $string .= ($string ? ', ' : '')."$h $hour". ($h > 1 ? 's' : '');
+                $hm = $dm % 3600;
+        } else {
+                $hm = $dm;
+        }
+        return $string if ($limit eq 'h');
+
+        if ($m = int($hm / 60)) {
+                $string .= ($string ? ', ':'')."$m $minute". ($m > 1 ? 's' : '');
+                $mm = $hm % 60;
+        } else {
+                $mm = $hm;
+        }
+        return $string if ($limit eq 'm');
+
+        if ($s = int($mm)) {
+                $string .= ($string ? ', ':'')."$s $second". ($s > 1 ? 's' : '');
+        } else {
+                $string = "Brand New!!!" unless ($string);
+        }
+        return $string;
+}
+
+sub full {
+       my $self = shift;
+       $$self{empty} = 0;
+}
+
+=head2 $utils_obj->set_psname('string') OR set_psname('string')
+
+Sets the name of this process in a B<ps> listing to B<string>.
+
+
+=cut
+
+sub set_psname {
+       my $self = shift;
+       my $PS_NAME = shift || $self;
+       $0 = $PS_NAME if ($PS_NAME);
+}
+
+=head2 $utils_obj->daemonize('ps_name') OR daemonize('ps_name')
+
+Turns the current process into a daemon.  B<ps_name> is optional, and is used
+as the argument to I<< set_psname() >> if passed.
+
+
+=cut
+
+sub daemonize {
+       my $self = shift;
+       my $PS_NAME = shift || $self;
+       my $pid;
+       if ($pid = safe_fork($self)) {
+               exit 0;
+       } elsif (defined($pid)) {
+               set_psname($PS_NAME);
+               chdir '/';
+               setsid;
+               return $$;
+       }
+}
+
+=head2 $utils_obj->safe_fork('ps_name') OR safe_fork('ps_name');
+
+Forks the current process in a retry loop.  B<ps_name> is optional, and is used
+as the argument to I<< set_psname() >> if passed.
+
+
+=cut
+
+sub safe_fork {
+       my $self = shift;
+       my $pid;
+
+FORK:
+       {
+               if (defined($pid = fork())) {
+                       srand(time ^ ($$ + ($$ << 15))) unless ($pid);
+                       return $pid;
+               } elsif ($! == EAGAIN) {
+                       $self->error("Can't fork()!  $!, taking 5 and trying again.") if (ref $self);
+                       sleep 5;
+                       redo FORK;
+               } else {
+                       $self->error("Can't fork()! $!") if ($! && ref($self));
+                       exit $!;
+               }
+       }
+}
+
+#------------------------------------------------------------------------------------------------------------------------------------
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Utils/Cache.pm b/src/perlmods/OpenSRF/Utils/Cache.pm
new file mode 100644 (file)
index 0000000..cb54514
--- /dev/null
@@ -0,0 +1,63 @@
+package OpenSRF::Utils::Cache;
+use strict; use warnings;
+use base qw/Cache::Memcached OpenSRF/;
+use Cache::Memcached;
+use OpenSRF::Utils::Config;
+
+
+=head OpenSRF::Utils::Cache
+
+This class just subclasses Cache::Memcached.
+see Cache::Memcached for more options.
+
+The value passed to the call to current is the cache type
+you wish to access.  The below example sets/gets data
+from the 'user' cache.
+
+my $cache = OpenSRF::Utils::Cache->current("user");
+$cache->set( "key1", "value1" [, $expire_secs ] );
+my $val = $cache->get( "key1" );
+
+
+=cut
+
+sub DESTROY {}
+my %caches;
+
+
+sub current { 
+
+       my ( $class, $c_type )  = @_;
+       return undef unless $c_type;
+
+       return $caches{$c_type} if exists $caches{$c_type};
+       return $caches{$c_type} = $class->new( $c_type );
+
+}
+
+
+sub new {
+
+       my( $class, $c_type ) = @_;
+       return undef unless $c_type;
+
+       return $caches{$c_type} if exists $caches{$c_type};
+
+       $class = ref( $class ) || $class;
+
+       my $config = OpenSRF::Utils::Config->current;
+       my $cache_servers = $config->mem_cache->$c_type;
+
+       my $instance = Cache::Memcached->new( { servers => $cache_servers } ); 
+
+       return bless( $instance, $class );
+
+}
+
+
+1;
+
+
+
+
+
diff --git a/src/perlmods/OpenSRF/Utils/Config.pm b/src/perlmods/OpenSRF/Utils/Config.pm
new file mode 100755 (executable)
index 0000000..29d3656
--- /dev/null
@@ -0,0 +1,434 @@
+package OpenSRF::Utils::Config::Section;
+
+no strict 'refs';
+
+use vars qw/@ISA $AUTOLOAD $VERSION/;
+push @ISA, qw/OpenSRF::Utils/;
+
+use OpenSRF::Utils (':common');
+
+$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
+
+my %SECTIONCACHE;
+my %SUBSECTION_FIXUP;
+
+#use overload '""' => \&OpenSRF::Utils::Config::dump_ini;
+
+sub SECTION {
+       my $sec = shift;
+       return $sec->__id(@_);
+}
+
+sub new {
+       my $self = shift;
+       my $class = ref($self) || $self;
+
+       $self = bless {}, $class;
+
+       my $lines = shift;
+
+       for my $line (@$lines) {
+
+               #($line) = split(/\s+\/\//, $line);
+               #($line) = split(/\s+#/, $line);
+
+               if ($line =~ /^\s*\[([^\[\]]+)\]/) {
+                       $self->_sub_builder('__id');
+                       $self->__id( $1 );
+                       next;
+               }
+
+               my ($protokey,$value,$keytype,$key);
+               if ($line =~ /^([^=\s]+)\s*=\s*(.*)/s) {
+                       ($protokey,$value) = ($1,$2);
+                       ($keytype,$key) = split(/:/,$protokey);
+               }
+
+               $key = $protokey unless ($key);
+
+               if ($keytype ne $key) {
+                       $keytype = lc $keytype;
+                       if ($keytype eq 'list') {
+                               $value = [split /\s*,\s*/, $value];
+                       } elsif ($keytype eq 'bool') {
+                               $value = do{ $value =~ /^t|y|1/i ? 1 : 0; };
+                       } elsif ($keytype eq 'interval') {
+                               $value = interval_to_seconds($value);
+                       } elsif ($keytype eq 'subsection') {
+                               if (exists $SECTIONCACHE{$value}) {
+                                       $value = $SECTIONCACHE{$value};
+                               } else {
+                                       $SUBSECTION_FIXUP{$value}{$self->SECTION} = $key ;
+                                       next;
+                               }
+                       }
+               }
+
+               $self->_sub_builder($key);
+               $self->$key($value);
+       }
+
+       no warnings;
+       if (my $parent_def = $SUBSECTION_FIXUP{$self->SECTION}) {
+               my ($parent_section, $parent_key) = each %$parent_def;
+               $SECTIONCACHE{$parent_section}->{$parent_key} = $self;
+               delete $SUBSECTION_FIXUP{$self->SECTION};
+       }
+
+       $SECTIONCACHE{$self->SECTION} = $self;
+
+       return $self;
+}
+
+package OpenSRF::Utils::Config;
+
+use vars qw/@ISA $AUTOLOAD $VERSION $OpenSRF::Utils::ConfigCache/;
+push @ISA, qw/OpenSRF::Utils/;
+
+use FileHandle;
+use OpenSRF::Utils (':common');  
+use OpenSRF::Utils::Log (':levels');
+
+#use overload '""' => \&OpenSRF::Utils::Config::dump_ini;
+
+sub import {
+       my $class = shift;
+       my $config_file = shift;
+
+       return unless $config_file;
+
+       $class->load( config_file => $config_file);
+}
+
+sub dump_ini {
+       no warnings;
+        my $self = shift;
+        my $string;
+       my $included = 0;
+       if ($self->isa('OpenSRF::Utils::Config')) {
+               if (UNIVERSAL::isa(scalar(caller()), 'OpenSRF::Utils::Config' )) {
+                       $included = 1;
+               } else {
+                       $string = "# Main File:  " . $self->FILE . "\n\n" . $string;
+               }
+       }
+        for my $section ( ('__id', grep { $_ ne '__id' } sort keys %$self) ) {
+               next if ($section eq 'env' && $self->isa('OpenSRF::Utils::Config'));
+                if ($section eq '__id') {
+                       $string .= '['.$self->SECTION."]\n" if ($self->isa('OpenSRF::Utils::Config::Section'));
+               } elsif (ref($self->$section)) {
+                        if (ref($self->$section) =~ /ARRAY/o) {
+                                $string .= "list:$section = ". join(', ', @{$self->$section}) . "\n";
+                       } elsif (UNIVERSAL::isa($self->$section,'OpenSRF::Utils::Config::Section')) {
+                               if ($self->isa('OpenSRF::Utils::Config::Section')) {
+                                       $string .= "subsection:$section = " . $self->$section->SECTION . "\n";
+                                       next;
+                               } else {
+                                       next if ($self->$section->{__sub} && !$included);
+                                       $string .= $self->$section . "\n";
+                               }
+                        } elsif (UNIVERSAL::isa($self->$section,'OpenSRF::Utils::Config')) {
+                               $string .= $self->$section . "\n";
+                       }
+               } else {
+                       next if $section eq '__sub';
+                               $string .= "$section = " . $self->$section . "\n";
+               }
+        }
+       if ($included) {
+               $string =~ s/^/## /gm;
+               $string = "# Subfile:  " . $self->FILE . "\n#" . '-'x79 . "\n".'#include "'.$self->FILE."\"\n". $string;
+       }
+
+        return $string;
+}
+
+=head1 NAME
+OpenSRF::Utils::Config
+
+=head1 SYNOPSIS
+
+  use OpenSRF::Utils::Config;
+
+  my $config_obj = OpenSRF::Utils::Config->load( config_file   => '/config/file.cnf' );
+
+  my $attrs_href = $config_obj->attributes();
+
+  $config_obj->attributes->no_db(0);
+
+  open FH, '>'.$config_obj->FILE() . '.new';
+  print FH $config_obj;
+  close FH;
+
+
+=head1 DESCRIPTION
+
+This module is mainly used by other modules to load a configuration file.
+
+=head1 NOTES
+
+Hashrefs of sections can be returned by calling a method of the object of the same name as the section.
+They can be set by passing a hashref back to the same method.  Sections will B<NOT> be autovivicated, though.
+
+Here be a config file example, HAR!:
+
+ [datasource]
+ # backend=XMLRPC
+ backend=DBI
+ subsection:definition=devel_db
+
+ [devel_db]
+ dsn=dbi:Pg(RaiseError => 0, AutoCommit => 1):dbname=dcl;host=nsite-dev
+ user=postgres
+ pw=postgres
+ #readonly=1
+ [live_db]
+ dsn=dbi:Pg(RaiseError => 0, AutoCommit => 1):dbname=dcl
+ user=n2dcl
+ pw=dclserver
+ #readonly=1
+
+ [devel_xmlrpc]
+ subsection:definition=devel_rpc
+ [logs]
+ base=/var/log/nsite
+ debug=debug.log
+ error=error.log
+ [debug]
+ enabled=1
+ level=ALL
+ [devel_rpc]
+ url=https://localhost:9000/
+ proto=SSL
+ SSL_cipher_list=ALL
+ SSL_verify_mode=5
+ SSL_use_cert=1
+ SSL_key_file=client-key.pem
+ SSL_cert_file=client-cert.pem
+ SSL_ca_file=cacert.pem
+ log_level=4
+ [dirs]
+ base_dir=/home/miker/cvs/NOC/monitor_core/
+ cert_dir=certs/
+
+=head1 METHODS
+
+
+=cut
+
+
+$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
+
+
+=head2 OpenSRF::Utils::Config->load( config_file => '/some/config/file.cnf' )
+
+Returns a OpenSRF::Utils::Config object representing the config file that was loaded.
+The most recently loaded config file (hopefully the only one per app)
+is stored at $OpenSRF::Utils::ConfigCache. Use OpenSRF::Utils::Config::current() to get at it.
+
+
+=cut
+
+sub load {
+       my $pkg = shift;
+       $pkg = ref($pkg) || $pkg;
+
+       my %args = @_;
+
+       (my $new_pkg = $args{config_file}) =~ s/\W+/_/g;
+       $new_pkg .= "::$pkg";
+       $new_section_pkg .= "${new_pkg}::Section";
+
+       {       eval <<"                PERL";
+
+                       package $new_pkg;
+                       use base $pkg;
+                       sub section_pkg { return '$new_section_pkg'; }
+
+                       package $new_section_pkg;
+                       use base "${pkg}::Section";
+       
+               PERL
+       }
+
+       return $new_pkg->_load( %args );
+}
+
+sub _load {
+       my $pkg = shift;
+       $pkg = ref($pkg) || $pkg;
+       my $self = {@_};
+       bless $self, $pkg;
+
+       no warnings;
+       if ((exists $$self{config_file} and OpenSRF::Utils::Config->current) and (OpenSRF::Utils::Config->current->FILE eq $$self{config_file}) and (!$self->{force})) {
+               delete $$self{force};
+               return OpenSRF::Utils::Config->current();
+       }
+
+       $self->_sub_builder('__id');
+       $self->FILE($$self{config_file});
+       delete $$self{config_file};
+       return undef unless ($self->FILE);
+
+       $self->load_config();
+       $self->load_env();
+       $self->mangle_dirs();
+       $self->mangle_logs();
+
+       $OpenSRF::Utils::ConfigCache = $self unless $self->nocache;
+       delete $$self{nocache};
+       delete $$self{force};
+       return $self;
+}
+
+sub sections {
+       my $self = shift;
+       my %filters = @_;
+
+       my @parts = (grep { UNIVERSAL::isa($_,'OpenSRF::Utils::Config::Section') } values %$self);
+       if (keys %filters) {
+               my $must_match = scalar(keys %filters);
+               my @ok_parts;
+               foreach my $part (@parts) {
+                       my $part_count = 0;
+                       for my $fkey (keys %filters) {
+                               $part_count++ if ($part->$key eq $filters{$key});
+                       }
+                       push @ok_parts, $part if ($part_count == $must_match);
+               }
+               return @ok_parts;
+       }
+       return @parts;
+}
+
+sub current {
+       return $OpenSRF::Utils::ConfigCache;
+}
+
+sub FILE {
+       return shift()->__id(@_);
+}
+
+sub load_env {
+       my $self = shift;
+       my $host = `hostname -f`  || `uname -n`;
+       chomp $host;
+       $$self{env} = $self->section_pkg->new;
+       $$self{env}{hostname} = $host;
+}
+
+sub mangle_logs {
+       my $self = shift;
+       return unless ($self->logs && $self->dirs && $self->dirs->log_dir);
+       for my $i ( keys %{$self->logs} ) {
+               next if ($self->logs->$i =~ /^\//);
+               $self->logs->$i($self->dirs->log_dir."/".$self->logs->$i);
+       }
+}
+
+sub mangle_dirs {
+       my $self = shift;
+       return unless ($self->dirs && $self->dirs->base_dir);
+       for my $i ( keys %{$self->dirs} ) {
+               if ( $i ne 'base_dir' ) {
+                       next if ($self->dirs->$i =~ /^\//);
+                       my $dir_tmp = $self->dirs->base_dir."/".$self->dirs->$i;
+                       $dir_tmp =~ s#//#/#go;
+                       $dir_tmp =~ s#/$##go;
+                       $self->dirs->$i($dir_tmp);
+               }
+       }
+}
+
+sub load_config {
+       my $self = shift;
+       my $config = new FileHandle $self->FILE, 'r';
+       unless ($config) {
+               OpenSRF::Utils::Log->error("Could not open ".$self->FILE.": $!\n");
+               die "Could not open ".$self->FILE.": $!\n";
+       }
+       my @stripped_config = $self->__strip_comments($config) if (defined $config);
+
+       my $chunk = [];
+       for my $line (@stripped_config) {
+               no warnings;
+               next unless ($line);
+
+               if ($line =~ /^\s*\[/ and @$chunk) {
+                       my $section = $self->section_pkg->new($chunk);
+
+                       my $sub_name = $section->SECTION;
+                       $self->_sub_builder($sub_name);
+                       $self->$sub_name($section);
+
+                       #$self->{$section->SECTION} = $section;
+
+                       $chunk = [];
+                       push @$chunk,$line;
+                       next;
+               } 
+               if ($line =~ /^#\s*include\s+"(\S+)"\s*$/o) {
+                        my $included_file = $1;
+                       my $section = OpenSRF::Utils::Config->load(config_file => $included_file, nocache => 1);
+
+                       my $sub_name = $section->FILE;
+                       $self->_sub_builder($sub_name);
+                       $self->$sub_name($section);
+
+                       for my $subsect (keys %$section) {
+                               next if ($subsect eq '__id');
+
+                               $self->_sub_builder($subsect);
+                               $self->$subsect($$section{$subsect});
+
+                               #$self->$subsect($section->$subsect);
+                               $self->$subsect->{__sub} = 1;
+                       }
+                       next;
+               }
+
+               push @$chunk,$line;
+       }
+       my $section = $self->section_pkg->new($chunk) if (@$chunk);
+       my $sub_name = $section->SECTION;
+       $self->_sub_builder($sub_name);
+       $self->$sub_name($section);
+
+}
+
+
+#------------------------------------------------------------------------------------------------------------------------------------
+
+=head1 SEE ALSO
+
+       OpenSRF::Utils
+
+=head1 BUGS
+
+No know bugs, but report any to miker@purplefrog.com.
+
+=head1 COPYRIGHT AND LICENSING
+
+Mike Rylander, Copyright 2000-2004
+
+The OpenSRF::Utils::Config module is free software. You may distribute under the terms
+of the GNU General Public License version 2 or greater.
+
+=cut
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Utils/LogServer.pm b/src/perlmods/OpenSRF/Utils/LogServer.pm
new file mode 100644 (file)
index 0000000..c27f512
--- /dev/null
@@ -0,0 +1,149 @@
+package OpenSRF::Utils::LogServer;
+use strict; use warnings;
+use base qw(OpenSRF);
+use IO::Socket::INET;
+use FileHandle;
+use OpenSRF::Utils::Config;
+use Fcntl;
+use Time::HiRes qw(gettimeofday);
+use OpenSRF::Utils::Logger;
+
+=head2 Name
+
+OpenSRF::Utils::LogServer
+
+=cut
+
+=head2 Synopsis
+
+Networ Logger
+
+=cut
+
+=head2 Description
+
+
+=cut
+
+
+
+our $config;
+our $port;
+our $bufsize = 4096;
+our $proto;
+our @file_info;
+
+
+sub DESTROY {
+       for my $file (@file_info) {
+               if( $file->handle ) {
+                       close( $file->handle );
+               }
+       }
+}
+
+
+sub serve {
+
+       $config = OpenSRF::Utils::Config->current;
+
+       unless ($config) { throw OpenSRF::EX::Config ("No suitable config found"); }
+
+       $port = $config->system->log_port;
+       $proto = $config->system->log_proto;
+
+
+       my $server = IO::Socket::INET->new(
+               LocalPort       => $port,
+               Proto                   => $proto )
+       or die "Error creating server socket : $@\n"; 
+
+
+
+       while ( 1 ) {
+               my $client = <$server>;
+               process( $client );
+       }
+
+       close( $server );
+}
+
+sub process {
+       my $client = shift;
+       my @params = split(/\|/,$client);
+       my $log = shift @params;
+
+       if( (!$log) || (!@params) ) {
+               warn "Invalid logging params: $log\n";
+               return;
+       }
+
+       # Put |'s back in since they are stripped 
+       # from the message by 'split'
+       my $message;
+       if( @params > 1 ) {
+               foreach my $param (@params) {
+                       if( $param ne $params[0] ) {
+                               $message .= "|";
+                       }
+                       $message .= $param;
+               }
+       }
+       else{ $message = "@params"; }
+
+       my @lines = split( "\n", $message );
+       my $time = format_time();
+
+       my $fh;
+
+       my ($f_obj) = grep { $_->name eq $log } @file_info;
+
+       unless( $f_obj and ($fh=$f_obj->handle) ) {
+               my $file = $config->logs->$log;
+
+               sysopen( $fh, $file, O_WRONLY|O_APPEND|O_CREAT ) 
+                       or warn "Cannot sysopen $log: $!";
+               $fh->autoflush(1);
+
+               my $obj = new OpenSRF::Utils::NetLogFile( $log, $file, $fh );
+               push @file_info, $obj;
+       }
+
+       foreach my $line (@lines) {
+               print $fh "$time $line\n" || die "$!";
+       }
+
+}
+
+sub format_time {
+       my ($s, $ms) = gettimeofday();
+       my @time = localtime( $s );
+       $ms = substr( $ms, 0, 3 );
+       my $year = $time[5] + 1900;
+       my $mon = $time[4] + 1;
+       my $day = $time[3];
+       my $hour = $time[2];
+       my $min = $time[1];
+       my $sec = $time[0];
+       $mon = "0" . "$mon" if ( length($mon) == 1 );
+       $day = "0" . "$day" if ( length($day) == 1 );
+       $hour = "0" . "$hour" if ( length($hour) == 1 );
+       $min = "0" . "$min" if (length($min) == 1 );
+       $sec = "0" . "$sec" if (length($sec) == 1 );
+
+       my $proc = $$;
+       while( length( $proc ) < 5 ) { $proc = "0" . "$proc"; }
+       return "[$year-$mon-$day $hour:$min:$sec.$ms $proc]";
+}
+
+
+package OpenSRF::Utils::NetLogFile;
+
+sub new{ return bless( [ $_[1], $_[2], $_[3] ], $_[0] ); }
+
+sub name { return $_[0]->[0]; }
+sub file { return $_[0]->[1]; }
+sub handle { return $_[0]->[2]; }
+
+
+1;
diff --git a/src/perlmods/OpenSRF/Utils/Logger.pm b/src/perlmods/OpenSRF/Utils/Logger.pm
new file mode 100644 (file)
index 0000000..8ebf12b
--- /dev/null
@@ -0,0 +1,355 @@
+package OpenSRF::Utils::Logger;
+use strict;
+use vars qw($AUTOLOAD @EXPORT_OK %EXPORT_TAGS);
+use Exporter;
+use base qw/OpenSRF Exporter/;
+use FileHandle;
+use Time::HiRes qw(gettimeofday);
+use OpenSRF::Utils::Config;
+use Fcntl;
+
+@EXPORT_OK = qw/ NONE ERROR WARN INFO DEBUG INTERNAL /;
+
+%EXPORT_TAGS = ( level => [ qw/ NONE ERROR WARN INFO DEBUG INTERNAL / ] );
+
+# XXX Update documentation
+
+=head1 Description
+
+OpenSRF::Utils::Logger
+
+General purpose logging package.  The logger searches $config->logs->$log_name for the 
+actual file to log to.  Any file in the config may be logged to.  If the user attempts to 
+log to a log file that does not exist within the config, then the messages will to 
+to STDERR.  
+
+There are also a set of predefined log levels.  Currently they are
+NONE, ERROR, WARN, INFO, DEBUG, INTERNAL, and ALL.  You can select one of these log levels
+when you send messages to the logger.  The message will be logged if it is of equal or greater
+'importance' than the global log level, found at $config->system->debug.  If you don't specify
+a log level, a defaul will be provided.  Current defaults are:
+
+error                  -> ERROR
+
+debug                  -> DEBUG
+
+transport      -> INTERNAL
+
+message                -> INFO
+
+method         -> INFO
+
+All others are logged to INFO by default.
+
+You write to a log by calling the log's method.  
+
+use OpenSRF::Utils::Logger qw(:level);
+
+my $logger = "OpenSRF::Utils::Logger";
+
+$logger->debug( "degug message" );
+$logger->transport( "debug message", DEBUG );
+$logger->blahalb( "I'll likely end up at STDERR with a log level of INFO" );
+
+will only write the time, line number, and file that the method was called from.
+
+Note also that all messages with a log level of ERROR are written to the "error" log
+in addition to the intended log file.
+
+=cut
+
+# Just set this first and not during every call to the logger
+# XXX this should be added to the sig{hup} handler once it exists.
+
+
+##############
+#   1. check config, if file exists write to that file locally
+#      2. If not in config and set to remote, send to socket.  if not remote log to stderr
+
+my $config; 
+my $file_hash;
+my $trace_active = 0;
+
+my $port;
+my $proto;
+my $peer;
+my $socket;
+my $remote_logging = 0;
+
+my $LEVEL = "OpenSRF::Utils::LogLevel";
+my $FILE       = "OpenSRF::Utils::LogFile";
+
+# --- Log levels - values and names
+
+my $none                       = $LEVEL->new( 1,               "NONE" ); 
+my $error              = $LEVEL->new( 10,      "ERRR" ); 
+my $warn                       = $LEVEL->new( 20,      "WARN" ); 
+my $info                       = $LEVEL->new( 30,      "INFO" );
+my $debug              = $LEVEL->new( 40,      "DEBG" );
+my $internal   = $LEVEL->new( 50,      "INTL" );
+my $all                        = $LEVEL->new( 100,     "ALL " );
+
+
+sub NONE                       { return $none; } 
+sub ERROR              { return $error;        } 
+sub WARN                       { return $warn; } 
+sub INFO                       { return $info; } 
+sub DEBUG              { return $debug;        } 
+sub INTERNAL   { return $internal; }
+sub ALL                        { return $all;          }
+
+# Known log files and their default log levels
+my $known_logs = [
+       $FILE->new( "error",            &ERROR ),
+       $FILE->new( "debug",            &DEBUG ),
+       $FILE->new( "transport",&INTERNAL ),
+       $FILE->new( "message",  &INFO ),
+       $FILE->new( "method",   &INFO ),
+       ];
+
+
+
+
+# ---------------------------------------------------------
+
+{
+       my $global_llevel;
+       sub global_llevel { return $global_llevel; }
+
+       sub set_config {
+
+               $config = OpenSRF::Utils::Config->current;
+
+               if( defined($config) ) { 
+
+                       $global_llevel =  $config->system->debug; 
+                       $port = $config->system->log_port;
+                       $proto = $config->system->log_proto;
+                       $peer = $config->system->log_server;
+                       $remote_logging = $config->system->remote_log;
+
+                       {
+                               no strict "refs";
+                               $global_llevel = &{$global_llevel};
+                       }
+                       #$trace_active = $config->system->trace;
+                       build_file_hash();
+               }
+
+               else { 
+                       $global_llevel = DEBUG; 
+                       warn "*** Logger found no suitable config.  Using STDERR ***\n";
+               }
+       }
+}
+
+sub build_file_hash { 
+       $file_hash = {};
+       # XXX This breaks Config encapsulation and should be cleaned.
+       foreach my $log ( grep { !($_ =~ /__id/) } (keys %{$config->logs}) ) {
+               $file_hash->{$log} = $config->logs->$log;
+       }
+}
+
+# ---------------------------------------------------------
+
+sub AUTOLOAD {
+
+
+       my( $self, $string, $llevel ) = @_;
+       my $log         = $AUTOLOAD;
+       $log                    =~ s/.*://;   # strip fully-qualified portion
+
+       unless( defined($config) or global_llevel() ) {
+               set_config();
+       }
+
+       # Build the sub here so we can use the enclosed $log variable.
+       # This is some weird Perl s*** that only satan could dream up.
+       # We mangle the symbol table so that future calls to $logger->blah
+       # will no longer require the autoload.  
+       # The $log variable (above) will contain the name of the log 
+       # log file the user is attempting to log to.  This is true, however,  
+       # even though the above code is presumably run only the first time
+       # the call to $logger->blah is made.  
+
+       no strict "refs";
+
+       *{$log} = sub { 
+
+               if( global_llevel()->level == NONE->level ) { return; }
+
+               my( $class, $string, $llevel ) = @_;
+
+               # see if we can return
+               if( $llevel ) { 
+                       # if level is passed in as a string, cast it to a level object
+                       ref( $llevel ) || do{ $llevel = &{$llevel} };
+                       return if ($llevel->level > global_llevel()->level); 
+               }
+
+               else { # see if there is a default llevel, set to INFO if not.
+                       my $log_obj;
+                       foreach my $l ( @$known_logs ) {
+                               if( $l->name eq $log ) { $log_obj = $l and last; }
+                       }
+                       if( $log_obj ) { $llevel = $log_obj->def_level; }
+                       else { $llevel = INFO; }
+               }
+
+
+               # again, see if we can get out of this 
+               return if ($llevel->level > global_llevel()->level); 
+       
+               my @caller = caller();
+               push( @caller, (caller(1))[3] );
+
+               # In the absense of a config, we write to STDERR
+
+               if( ! defined($config)  ) { 
+                       _write_stderr( $string, $llevel->name, @caller); 
+                       return;
+               }
+
+               if( $remote_logging ) {
+                       _write_net( $log, $string, $llevel->name, @caller );
+               
+               } elsif ( my $file = $file_hash->{$log} ) {
+                       _write_local( $file, $string, $llevel->name, @caller );
+
+               } else {
+                       _write_stderr( $string, $llevel->name, @caller); 
+               }
+
+       
+               if( $llevel->name eq ERROR->name ) { # send all error to stderr
+                       _write_stderr( $string, $llevel->name, @caller); 
+               }
+
+               if( $llevel->name eq ERROR->name and $log ne "error" ) {
+                       if( my $e_file = $file_hash->{"error"}  ) {
+                               if( ! $remote_logging ) {
+                                       _write_local( $e_file, $string, $llevel->name, @caller );
+                               }
+                       }
+               }
+       
+       };
+       
+       $self->$log( $string, $llevel );
+}
+
+
+# write_net expects a log_type_name and not a log_file_name for the first parameter
+my $net_buffer = "";
+my $counter = 0;
+sub _write_net {
+
+
+       my( $log, $string, $llevel, @caller ) = @_;
+       my( $pack, $file, $line_no ) = @caller;
+       my @lines = split( "\n", $string );
+
+       my $message = "$log|"."-" x 33 . 
+               "\n$log|[$0 $llevel] $line_no $pack".
+               "\n$log|[$0 $llevel] $file";
+
+       foreach my $line (@lines) {
+               $message .= "\n$log|[$0 $llevel] $line";
+       }
+
+       $net_buffer .= "$message\n";
+
+       # every 4th load is sent on the socket
+       if( $counter++ % 4 ) { return; }
+
+       unless( $socket ) {
+               $socket = IO::Socket::INET->new(
+                               PeerAddr        => $peer,
+                               PeerPort        => $port,
+                               Proto           => $proto )
+                       or die "Unable to open socket to log server";  
+       }
+
+       $socket->send( $net_buffer );
+       $net_buffer = "";
+
+}
+
+sub _write_local {
+
+       my( $log, $string, $llevel, @caller ) = @_;
+       my( $pack, $file, $line_no ) = @caller;
+       my @lines = split( "\n", $string );
+       my $time = format_time();
+       sysopen( SINK, $log, O_NONBLOCK|O_WRONLY|O_APPEND|O_CREAT ) 
+               or die "Cannot sysopen $log: $!";
+       binmode(SINK, ':utf8');
+       print SINK "-" x 23 . "\n";
+       print SINK "$time [$0 $llevel] $line_no $pack \n";
+       print SINK "$time [$0 $llevel] $file\n";
+       foreach my $line (@lines) {
+               print SINK "$time [$0 $llevel] $line\n";
+       }
+       close( SINK );
+
+}
+
+sub _write_stderr {
+       my( $string, $llevel, @caller ) = @_;
+       my( $pack, $file, $line_no ) = @caller;
+       my @lines = split( "\n", $string );
+       my $time = format_time();
+       print STDERR "-" x 23 . "\n";
+       print STDERR "$time [$0 $llevel] $line_no $pack\n";
+       print STDERR "$time [$0 $llevel] $file\n";
+       foreach my $line (@lines) {
+               print STDERR "$time [$0 $llevel] $line\n";
+       }
+}
+
+sub format_time {
+       my ($s, $ms) = gettimeofday();
+       my @time = localtime( $s );
+       $ms = substr( $ms, 0, 3 );
+       my $year = $time[5] + 1900;
+       my $mon = $time[4] + 1;
+       my $day = $time[3];
+       my $hour = $time[2];
+       my $min = $time[1];
+       my $sec = $time[0];
+       $mon = "0" . "$mon" if ( length($mon) == 1 );
+       $day = "0" . "$day" if ( length($day) == 1 );
+       $hour = "0" . "$hour" if ( length($hour) == 1 );
+       $min = "0" . "$min" if (length($min) == 1 );
+       $sec = "0" . "$sec" if (length($sec) == 1 );
+
+       my $proc = $$;
+       while( length( $proc ) < 5 ) { $proc = "0" . "$proc"; }
+       return "[$year-$mon-$day $hour:$min:$sec.$ms $proc]";
+}
+
+
+# ----------------------------------------------
+# --- Models a log level
+package OpenSRF::Utils::LogLevel;
+
+sub new { return bless( [ $_[1], $_[2] ], $_[0] ); }
+
+sub level { return $_[0]->[0]; }
+sub name       { return $_[0]->[1]; }
+
+# ----------------------------------------------
+
+package OpenSRF::Utils::LogFile;
+use OpenSRF::Utils::Config;
+
+sub new{ return bless( [ $_[1], $_[2] ], $_[0] ); }
+
+sub name { return $_[0]->[0]; }
+sub def_level { return $_[0]->[1]; }
+
+
+# ----------------------------------------------
+
+1;
diff --git a/src/router/Makefile b/src/router/Makefile
new file mode 100644 (file)
index 0000000..9a5c0b5
--- /dev/null
@@ -0,0 +1,31 @@
+# set this shell variable prior to calling make to run with malloc_check enabled
+#MALLOC_CHECK_=1 # XXX debug only
+
+CC = gcc
+LIB_DIR=../../lib
+CC_OPTS = -Wall -O2 -I /usr/include/libxml2 -I /usr/include/libxml2/libxml -I ../../include -I ../../../../cc/libxml2-2.6.16
+LD_OPTS = -lxml2
+EXE_LD_OPTS = -L $(LIB_DIR) -lxml2 -ltransport 
+LIB_SOURCES = generic_utils.c transport_socket.c transport_session.c transport_message.c transport_client.c
+
+TARGETS=generic_utils.o transport_socket.o transport_message.o transport_session.o transport_client.o 
+
+all: router basic_client
+
+basic_client: lib
+       $(CC) $(CC_OPTS) $(EXE_LD_OPTS) basic_client.c -o $@
+
+# --- Libs -----------------------------------------------
+       
+lib: 
+       $(CC) -c $(CC_OPTS)     $(LIB_SOURCES)  
+       $(CC) -shared -W1 $(LD_OPTS) $(TARGETS) -o $(LIB_DIR)/libtransport.so
+
+
+# The router is compiled as a static binary because of some 
+# necessary #defines that would break the library
+router: 
+       $(CC) $(LD_OPTS) -D_ROUTER $(CC_OPTS)   $(LIB_SOURCES) transport_router.c -o $@ 
+
+clean:
+       /bin/rm -f *.o ../../lib/libtransport.so router basic_client
diff --git a/src/router/router.c b/src/router/router.c
new file mode 100644 (file)
index 0000000..24dbdbe
--- /dev/null
@@ -0,0 +1,729 @@
+#include "transport_router.h"
+#include <sys/types.h>
+#include <signal.h>
+
+
+char* router_resource;
+transport_router_registrar* routt;
+
+void sig_hup_handler( int a ) { 
+       router_registrar_free( routt ); 
+       config_reader_free();   
+       log_free();
+       free( router_resource );
+       exit(0); 
+}
+
+
+int main( int argc, char* argv[] ) {
+
+       if( argc < 2 ) {
+               fatal_handler( "Usage: %s <path_to_config_file>", argv[0] );
+               exit(0);
+       }
+
+
+       config_reader_init( argv[1] );  
+       if( conf_reader == NULL ) fatal_handler( "main(): Config is NULL" ); 
+
+       /* laod the config options */
+       char* server                    = config_value("//router/transport/server");
+       char* port                              = config_value("//router/transport/port");
+       char* username                  = config_value("//router/transport/username");
+       char* password                  = config_value("//router/transport/password");
+       router_resource         = config_value("//router/transport/resource");
+       char* con_timeout               = config_value("//router/transport/connect_timeout" );
+       char* max_retries               = config_value("//router/transport/max_reconnect_attempts" );
+
+       fprintf(stderr, "%s %s %s %s", server, port, username, password );
+
+       int iport                       = atoi( port );
+       int con_itimeout        = atoi( con_timeout );
+       int max_retries_        = atoi(max_retries);
+
+       if( iport < 1 ) { 
+               fatal_handler( "Port is negative or 0" );
+               return 99;
+       }
+
+
+       /* build the router_registrar */
+       transport_router_registrar* router_registrar = 
+               router_registrar_init( server, iport, username, password, router_resource, 0, con_itimeout ); 
+
+       routt = router_registrar;
+
+       free(server);
+       free(port);
+       free(username);
+       free(password);
+       free(con_timeout);
+       free(max_retries);
+
+       signal(SIGHUP,sig_hup_handler);
+
+
+       int counter = 0;
+       /* wait for incoming... */
+       while( ++counter <= max_retries_ ) {
+
+               /* connect to jabber */
+               if( router_registrar_connect( router_registrar ) )  {
+                       info_handler( "Connected..." );
+                       counter = 0;
+                       listen_loop( router_registrar );
+               } else  
+                       warning_handler( "Could not connect to Jabber" );
+
+               /* this should never happen */
+               warning_handler( "Jabber server probably went away, attempting reconnect" );
+
+               sleep(5);
+       }
+
+
+       router_registrar_free( router_registrar );
+       config_reader_free();   
+       return 1;
+
+}
+
+transport_router_registrar* router_registrar_init( char* server, 
+               int port, char* username, char* password, 
+               char* resource, int client_timeout, int con_timeout ) {
+
+       if( server == NULL ) { return NULL; }
+       
+       /* allocate a new router_registrar object */
+       size_t size = sizeof( transport_router_registrar );
+       transport_router_registrar* router_registrar = (transport_router_registrar*) safe_malloc( size );
+
+       router_registrar->client_timeout        = client_timeout;
+       router_registrar->jabber = jabber_connect_init( server, port, username, password, resource, con_timeout );
+       return router_registrar;
+
+}
+
+jabber_connect* jabber_connect_init( char* server, 
+               int port, char* username, char* password, char* resource, int connect_timeout ) {
+
+       size_t len = sizeof(jabber_connect);
+       jabber_connect* jabber = (jabber_connect*) safe_malloc( len );
+
+       jabber->port                            = port;
+       jabber->connect_timeout = connect_timeout;
+
+       jabber->server                          = strdup(server);
+       jabber->username                        = strdup(username);
+       jabber->password                        = strdup(password);
+       jabber->resource                        = strdup(resource);
+
+       if( jabber->server == NULL || jabber->username == NULL ||
+                       jabber->password == NULL || jabber->resource == NULL ) {
+               fatal_handler( "jabber_init(): Out of Memory" );
+               return NULL;
+       }
+
+       /* build the transport client */
+       jabber->t_client = client_init( jabber->server, jabber->port );
+
+       return jabber;
+}
+
+/* connect the router_registrar to jabber */
+int router_registrar_connect( transport_router_registrar* router ) {
+       return j_connect( router->jabber );
+}
+
+/* connect a jabber_connect object jabber */
+int j_connect( jabber_connect* jabber ) {
+       if( jabber == NULL ) { return 0; }
+       return client_connect( jabber->t_client, 
+                       jabber->username, jabber->password, jabber->resource, jabber->connect_timeout );
+}
+
+int fill_fd_set( transport_router_registrar* router, fd_set* set ) {
+       
+       int max_fd;
+       FD_ZERO(set);
+
+       int router_fd = router->jabber->t_client->session->sock_obj->sock_fd;
+       max_fd = router_fd;
+       FD_SET( router_fd, set );
+
+       server_class_node* cur_node = router->server_class_list;
+       while( cur_node != NULL ) {
+               int cur_class_fd = cur_node->jabber->t_client->session->sock_obj->sock_fd;
+               if( cur_class_fd > max_fd ) 
+                       max_fd = cur_class_fd;
+               FD_SET( cur_class_fd, set );
+               cur_node = cur_node->next;
+       }
+
+       FD_CLR( 0, set );
+       return max_fd;
+}
+
+
+void listen_loop( transport_router_registrar* router ) {
+
+       if( router == NULL )
+               return;
+
+       int select_ret;
+       int router_fd = router->jabber->t_client->session->sock_obj->sock_fd;
+       transport_message* cur_msg;
+
+       while(1) {
+
+               fd_set listen_set;
+               int max_fd = fill_fd_set( router, &listen_set );
+
+               if( max_fd < 1 ) 
+                       fatal_handler( "fill_fd_set return bogus max_fd: %d", max_fd );
+
+               int num_handled = 0;
+               info_handler( "Going into select" );
+
+               if( (select_ret=select(max_fd+ 1, &listen_set, NULL, NULL, NULL)) < 0 ) {
+
+                       warning_handler( "Select returned error %d", select_ret );
+                       warning_handler( "Select Error %d on fd %d", errno );
+                       perror( "Select Error" );
+                       warning_handler( "Errors: EBADF %d, EINTR %d, EINVAL %d, ENOMEM %d",
+                                       EBADF, EINTR, EINVAL, ENOMEM );
+                       continue;
+
+               } else {
+
+                       info_handler( "Select returned %d", select_ret );
+                       
+                       if( FD_ISSET( router_fd, &listen_set ) ) {
+                               cur_msg = client_recv( router->jabber->t_client, 1 );
+                               router_registrar_handle_msg( router, cur_msg );
+                               message_free( cur_msg );
+                               if( ++num_handled == select_ret ) 
+                                       continue;
+                       }
+
+                       /* cycle through the children and find any whose fd's are ready for reading */
+                       server_class_node* cur_node = router->server_class_list;
+                       while( cur_node != NULL ) {
+                               info_handler("searching child activity" );
+                               int cur_fd = cur_node->jabber->t_client->session->sock_obj->sock_fd;
+
+                               if( FD_ISSET(cur_fd, &listen_set) ) {
+                                       ++num_handled;
+                                       FD_CLR(cur_fd,&listen_set);
+                                       info_handler( "found active child %s", cur_node->server_class );
+
+                                       cur_msg = client_recv( cur_node->jabber->t_client, 1 );
+                                       info_handler( "%s received from %s", cur_node->server_class, cur_msg->sender );
+                                       int handle_ret = server_class_handle_msg( router, cur_node, cur_msg );
+
+                                       if( handle_ret == -1 ) {
+                                               warning_handler( "server_class_handle_msg() returned -1" );
+                                               cur_node = router->server_class_list; /*start over*/
+                                               continue;
+
+                                       } else if( handle_ret == 0 ) {
+                                               /* delete and continue */
+                                               warning_handler( "server_class_handle_msg() returned 0" );
+                                               server_class_node* tmp_node = cur_node->next;
+                                               remove_server_class( router, cur_node );        
+                                               cur_node = tmp_node;
+                                               continue;
+                                       } 
+
+                                       info_handler( "%s handled message successfully", cur_node->server_class );
+                                       /* dont free message here */
+                                       if( num_handled == select_ret ) 
+                                               break;
+                               }
+                               if( num_handled == select_ret ) 
+                                       break;
+                               cur_node = cur_node->next;
+
+                       } /* cycling through the server_class list */
+
+               } /* no select errors */
+       } 
+}
+
+
+/* determine where to route top level messages */
+int router_registrar_handle_msg( transport_router_registrar* router_registrar, transport_message* msg ) {
+
+       info_handler( "Received class: %s : command %s:  body: %s", msg->router_class, msg->router_command, msg->body );
+
+       if( router_registrar == NULL || msg == NULL ) { return 0; }
+
+       info_handler("Looking for server_class_node %s...",msg->router_class);
+       server_class_node* active_class_node = find_server_class( router_registrar, msg->router_class );
+
+       if( active_class_node == NULL ) { 
+               info_handler("Could not find server_class_node %s, creating one.",msg->router_class);
+
+               /* there is no server_class for msg->router_class so we build it here */
+               if( strcmp( msg->router_command, "register") == 0 ) {
+
+                       info_handler("Adding server_class_node for %s",msg->router_class);
+                       active_class_node = 
+                               init_server_class( router_registrar, msg->sender, msg->router_class ); 
+
+                       if( active_class_node == NULL ) {
+                               fatal_handler( "router_listen(): active_class_node == NULL for %s", msg->sender );
+                               return 0;
+                       }
+
+                       if (router_registrar->server_class_list != NULL) {
+                               active_class_node->next = router_registrar->server_class_list;
+                               router_registrar->server_class_list->prev = active_class_node;
+                       }
+                       router_registrar->server_class_list = active_class_node;
+
+                       //spawn_server_class( (void*) active_class_node );
+
+               } else {
+                       warning_handler( "router_register_handler_msg(): Bad Command [%s] for class [%s]",
+                               msg->router_command, msg->router_class );
+               }
+
+       } else if( strcmp( msg->router_command, "register") == 0 ) {
+               /* there is a server_class for msg->router_class so we 
+                       need to either add a new server_node or update the existing one */
+
+               
+               server_node* s_node = find_server_node( active_class_node, msg->sender );
+
+               if( s_node != NULL ) {
+                       s_node->available = 1;
+                       s_node->upd_time = time(NULL);
+                       info_handler( "Found matching registered server: %s. Updating.",
+                                       s_node->remote_id );
+               } else {
+                       s_node = init_server_node( msg->sender );
+
+                       info_handler( "Adding server_node for: %s.", s_node->remote_id );
+
+                       if (s_node == NULL ) {
+                               warning_handler( " Could not create new xerver_node for %s.",
+                                       msg->sender );
+                               return 0;
+                       }
+
+                       s_node->next = active_class_node->current_server_node->next;
+                       s_node->prev = active_class_node->current_server_node;
+
+                       active_class_node->current_server_node->next->prev = s_node;
+                       active_class_node->current_server_node->next = s_node;
+               }
+
+
+       } else if( strcmp( msg->router_command, "unregister") == 0 ) {
+
+               if( ! unregister_server_node( active_class_node, msg->sender ) )
+                       remove_server_class( router_registrar, active_class_node );
+
+       } else {
+               warning_handler( "router_register_handler_msg(): Bad Command [%s] for class [%s]",
+                       msg->router_command, msg->router_class );
+       }
+
+       return 1;
+}
+
+
+/* removes a server class node from the top level router_registrar */
+int unregister_server_node( server_class_node* active_class_node, char* remote_id ) {
+
+       server_node* d_node = find_server_node( active_class_node, remote_id );
+
+       if ( d_node != NULL ) {
+
+               info_handler( "Removing server_node for: %s.", d_node->remote_id );
+
+               if ( d_node->next == NULL ) {
+                       warning_handler( "NEXT is NULL in ring [%s] -- "
+                               "THIS SHOULD NEVER HAPPEN",
+                               d_node->remote_id );
+
+               }
+               
+               if ( d_node->prev == NULL ) {
+                       warning_handler( "PREV is NULL in a ring [%s] -- "
+                               "THIS SHOULD NEVER HAPPEN",
+                               d_node->remote_id );
+
+               }
+
+               if ( d_node->next == d_node && d_node->prev == d_node) {
+                       info_handler( "Last node, setting ring to NULL: %s.",
+                               d_node->remote_id );
+
+                       active_class_node->current_server_node = NULL;
+
+                       server_node_free( d_node );
+                       return 0;
+
+               } else {
+                       info_handler( "Nodes remain, splicing: %s, %s",
+                               d_node->prev->remote_id,
+                               d_node->next->remote_id);
+
+               info_handler( "d_node => %x, next => %x, prev => %x",
+                                       d_node, d_node->next, d_node->prev );
+
+
+                       d_node->prev->next = d_node->next;
+                       d_node->next->prev = d_node->prev;
+
+                       info_handler( "prev => %x, prev->next => %x, prev->prev => %x",
+                               d_node->prev, d_node->prev->next, d_node->prev->prev );
+
+                       info_handler( "next => %x, next->next => %x, next->prev => %x",
+                               d_node->next, d_node->next->next, d_node->next->prev );
+                               
+                       if (active_class_node->current_server_node == d_node)
+                               active_class_node->current_server_node = d_node->next;
+
+
+                       server_node_free( d_node );
+               }
+       } 
+
+       return 1;
+}
+
+server_node * find_server_node ( server_class_node * class, const char * remote_id ) {
+
+       if ( class == NULL ) {
+               warning_handler(" find_server_node(): bad arg!");
+               return NULL;
+       }
+
+       server_node * start_node = class->current_server_node;
+       server_node * node = class->current_server_node;
+
+       do {
+               if (node == NULL)
+                       return NULL;
+
+               if ( strcmp(node->remote_id, remote_id) == 0 )
+                       return node;
+
+               node = node->next;
+
+       } while ( node != start_node );
+
+       return NULL;
+}
+
+/* if we return -1, then we just deleted the server_class you were looking for
+       if we return 0, then some other error has occured
+       we return 1 otherwise */
+int remove_server_class( transport_router_registrar* router, server_class_node* class ) {
+       if( class == NULL )
+               return 0;
+
+       transport_message * msg = NULL;
+       while ( (msg = client_recv(class->jabber->t_client, 0)) != NULL ) {
+               server_class_handle_msg(router, class, msg);
+               message_free(msg);
+       }
+       
+       free( class->server_class );
+       class->server_class = NULL;
+
+       find_server_class( router, router_resource ); /* find deletes for us */
+
+       if( router->server_class_list == NULL ) 
+               return 0;
+       return 1;
+}
+
+server_class_node * find_server_class ( transport_router_registrar * router, const char * class_id ) {
+
+       if ( router == NULL ) {
+               warning_handler(" find_server_class(): bad arg!");
+               return NULL;
+       }
+
+       info_handler( "Finding server class for %s", class_id );
+       server_class_node * class = router->server_class_list;
+       server_class_node * dead_class = NULL;
+
+       while ( class != NULL ) {
+
+               if ( class->server_class == NULL ) {
+                       info_handler( "Found an empty server class" );
+
+                       if ( class->prev != NULL ) {
+                               class->prev->next = class->next;
+                               if( class->next != NULL ) {
+                                       class->next->prev = class->prev;
+                               }
+
+                       } else {
+                               info_handler( "Empty class is the first on the list" );
+                               if( class->next != NULL ) 
+                                       router->server_class_list = class->next;
+
+                               else { /* we're the last class node in the class node list */
+                                       info_handler( "Empty class is the last on the list" );
+                                       server_class_node_free( router->server_class_list );
+                                       router->server_class_list = NULL;
+                                       break;
+                               }
+                                       
+                       }
+
+                       dead_class = class;
+                       class = class->next;
+
+                       info_handler( "Tossing our dead class" );
+                       server_class_node_free( dead_class );
+
+                       if ( class == NULL )
+                               return NULL;
+               }
+
+               if ( strcmp(class->server_class, class_id) == 0 )
+                       return class;
+               info_handler( "%s != %s", class->server_class, class_id );
+
+               class = class->next;
+       }
+
+       return NULL;
+}
+
+/* builds a new server class and connects to the jabber server with the new resource */
+server_class_node* init_server_class( 
+               transport_router_registrar* router, char* remote_id, char* server_class ) {
+
+       size_t len = sizeof( server_class_node );
+       server_class_node* node = (server_class_node*) safe_malloc( len );
+
+       node->jabber = jabber_connect_init( router->jabber->server,
+                       router->jabber->port, router->jabber->username, 
+                       router->jabber->password, server_class, router->jabber->connect_timeout );
+
+
+
+       node->server_class = strdup( server_class );
+       if( server_class == NULL ) {
+               fatal_handler( "imit_server_class(): out of memory for %s", server_class );
+               return NULL;
+       }
+
+       info_handler( "Received class to init_server_class: %s", server_class );
+       node->current_server_node = init_server_node( remote_id );
+       if( node->current_server_node == NULL ) {
+               fatal_handler( "init_server_class(): NULL server_node for %s", remote_id );
+               return NULL;
+       }
+
+
+       if( ! j_connect( node->jabber ) ) {
+               fatal_handler( "Unable to init server class %s", node->server_class );
+               return NULL;
+       }
+
+       info_handler( "Jabber address in init for %s : address %x : username %s : resource %s", 
+                       node->server_class, node->jabber->t_client->session->sock_obj->sock_fd, 
+                       node->jabber->username,  node->jabber->resource );
+
+       return node;
+
+}
+
+/* builds a new server_node to be added to the ring of server_nodes */
+server_node* init_server_node(  char* remote_id ) {
+
+       info_handler( "Initing server node for %s", remote_id );
+       server_node* current_server_node;
+       size_t size = sizeof( server_node);
+       current_server_node = (server_node*) safe_malloc( size );
+
+       current_server_node->remote_id = strdup(remote_id);
+       if( current_server_node->remote_id == NULL ) {
+               fatal_handler("init_server_class(): Out of Memory for %s", remote_id );
+               return NULL;
+       }
+       
+       current_server_node->reg_time = time(NULL);     
+       current_server_node->available = 1;
+       current_server_node->next = current_server_node;
+       current_server_node->prev = current_server_node;
+
+
+       return current_server_node;
+
+}
+
+int  server_class_handle_msg( transport_router_registrar* router, 
+               server_class_node* s_node, transport_message* msg ) {
+
+       if( s_node->current_server_node == NULL ) {
+               /* return error to client ??!*/
+               /* WE have no one to send the message to */
+               warning_handler( "We no longer have any servers for %s : " 
+                               "no one to send the message to. Sending error message to %s", s_node->server_class, msg->sender );
+               free( msg->recipient );  
+
+               char* rec = strdup( msg->sender );
+               if( rec == NULL ) {
+                       fatal_handler( "class msg_handler: out of memory");
+                       return 0;
+               }
+
+               info_handler( "Building error message to return for %s", s_node->server_class);
+               msg->recipient = rec;
+               set_msg_error(msg, "cancel", 501);
+
+               client_send_message( s_node->jabber->t_client, msg );
+               message_free( msg );
+
+               remove_server_class( router, s_node );
+
+               return -1;
+       }
+
+       info_handler( "[%s] Received from %s to \n%s", 
+                       s_node->server_class, msg->sender, msg->recipient );
+
+       if( msg->is_error ) {
+               warning_handler( "We've received an error message type: %s : code: %d", 
+                               msg->error_type, msg->error_code );
+
+               if( strcmp( msg->error_type, "cancel" ) == 0 ) {
+                       warning_handler( "Looks like we've lost a server!" );
+                       server_node* dead_node = find_server_node( s_node, msg->sender );
+
+                       if( dead_node != NULL ) { 
+                               //message_free( msg );
+                               transport_message* tmp = dead_node->last_sent;
+
+                               /* copy over last sent, it will be freed in the unregister function */
+                               transport_message* tmp2 = message_init( tmp->body, tmp->subject, tmp->thread,
+                                               tmp->recipient, tmp->sender );
+                                       
+                               message_set_router_info( tmp2, tmp->router_from,  
+                                               tmp->router_to, tmp->router_class, tmp->router_command, tmp->broadcast );
+
+                               if( ! unregister_server_node( s_node, dead_node->remote_id ) ) { 
+                                       /* WE have no one to send the message to */
+                                       warning_handler( "We no longer have any servers for %s : " 
+                                                       "no one to send the message to.", s_node->server_class );
+                                       free( msg->recipient );  
+
+                                       char* rec = strdup( msg->router_from );
+                                       if( rec == NULL ) {
+                                               fatal_handler( "class msg_handler: out of memory");
+                                               return 0;
+                                       }
+
+                                       info_handler( "Building error message to return for %s", s_node->server_class);
+                                       msg->recipient = rec;
+                                       client_send_message( s_node->jabber->t_client, msg );
+                                       message_free( tmp2 );
+                                       message_free( msg );
+                                       return 0;
+
+                               } else {
+                                       msg = tmp2;
+                               }
+                       }
+               }
+       } 
+
+
+       server_node* c_node = s_node->current_server_node->next;
+
+       /* not implemented yet */
+       while( ! c_node->available ) {
+               if( c_node == s_node->current_server_node ) {
+                       warning_handler("No server_node's are available for %s", s_node->server_class );
+                       /* XXX send error message to client */
+                       return 0;
+               }
+               c_node = c_node->next;
+       }
+       s_node->current_server_node = c_node;
+
+       transport_message * new_msg =
+               message_init(   msg->body, msg->subject, msg->thread, 
+                               s_node->current_server_node->remote_id, msg->sender );
+
+       message_set_router_info( new_msg, NULL, msg->sender, NULL, NULL, 0 );
+
+       info_handler( "[%s] Routing message from [%s]\nto [%s]", s_node->server_class, new_msg->sender, new_msg->recipient );
+
+       message_free( s_node->current_server_node->last_sent );
+       s_node->current_server_node->last_sent = msg;
+
+       if ( new_msg != NULL && client_send_message( s_node->jabber->t_client, new_msg ) ) {
+               s_node->current_server_node->serve_count++;
+               s_node->current_server_node->la_time = time(NULL);
+               message_free( new_msg ); // XXX
+               return 1;
+       }
+       info_handler( "message sent" );
+       message_free( new_msg ); // XXX
+
+       return 0;
+}
+
+int router_registrar_free( transport_router_registrar* router_registrar ) {
+       if( router_registrar == NULL ) return 0;
+       jabber_connect_free( router_registrar->jabber );
+
+       /* free the server_class list XXX */
+       while( router_registrar->server_class_list != NULL ) {
+               remove_server_class(router_registrar, router_registrar->server_class_list);
+       }
+
+       free( router_registrar );
+
+
+
+       return 1;
+}
+
+
+int server_class_node_free( server_class_node* node ) {
+       if( node == NULL ) { return 0; }
+       if( node->server_class != NULL ) 
+               free( node->server_class );
+
+       jabber_connect_free( node->jabber );
+
+       /* just in case, free the list */
+       while( node->current_server_node != NULL ) {
+               unregister_server_node( node, node->current_server_node->remote_id );
+       }
+       free( node );
+       return 1;
+}
+
+int server_node_free( server_node* node ) {
+       if( node == NULL ) { return 0; }
+       message_free( node->last_sent );
+       free( node->remote_id );
+       free( node );
+       return 1;
+}
+
+int jabber_connect_free( jabber_connect* jabber ) {
+       if( jabber == NULL ) { return 0; }
+       client_free( jabber->t_client );
+       free( jabber->username );
+       free( jabber->password );
+       free( jabber->resource );
+       free( jabber->server );
+       free( jabber );
+       return 1;
+}
+
+
diff --git a/src/router/router.h b/src/router/router.h
new file mode 100644 (file)
index 0000000..4cdbfbd
--- /dev/null
@@ -0,0 +1,234 @@
+#include "transport_client.h"
+#include "transport_message.h"
+#include <time.h>
+#include <sys/select.h>
+
+#ifndef TRANSPORT_ROUTER_H
+#define TRANSPORT_ROUTER_H
+
+// ----------------------------------------------------------------------
+// Jabber router_registrar/load balancer.  There is a top level linked list of 
+// server_class_nodes.  A server class represents the a cluster of Jabber
+// clients that define a single logical routing endpoint.  Each of these 
+// server_class_nodes maintains a list of connected server_nodes, which
+// represents the pool of connected server endpoints.  A request 
+// directed at a particular class is routed to the next available
+// server endpoint.
+//
+// ----------------------------------------------------------------------
+
+
+// ----------------------------------------------------------------------
+// Defines an element in a server list.  The server list is a circular
+// doubly linked list.  User is responsible for freeing a server_node with 
+// server_node_free()
+// ----------------------------------------------------------------------
+struct server_node_struct {
+
+       struct server_node_struct* next;
+       struct server_node_struct* prev;
+
+       time_t la_time; /* last time we sent a message to a server */
+       time_t reg_time;        /* time we originally registered */
+       time_t upd_time;        /* last re-register time */
+       int available;          /* true if we may be used */
+
+       int serve_count; /* how many messages we've sent */
+
+       /* jabber remote id  for this server node*/
+       char* remote_id;
+
+       /* we cache the last sent message in case our remote 
+               endpoint has gone away.  If it has, the next server
+               node in the list will re-send our last message */
+       transport_message* last_sent;
+
+};
+typedef struct server_node_struct server_node;
+
+
+// ----------------------------------------------------------------------
+// Models a basic jabber connection structure.  Any component that 
+// connects to jabber will have one of these.
+// ----------------------------------------------------------------------
+struct jabber_connect_struct {
+
+       char* server;
+       int port;
+       char* username;
+       char* password;
+       char* resource;
+       int connect_timeout;
+
+       transport_client* t_client;
+};
+typedef struct jabber_connect_struct jabber_connect;
+
+
+
+// ----------------------------------------------------------------------
+// Defines an element in the list of server classes.  User is 
+// responsible for freeing a server_class_node with 
+// server_class_node_free().
+// The server_node_list is a doubly (not circular) linked list
+// ----------------------------------------------------------------------
+struct server_class_node_struct {
+
+       /* the name of our class.  This will be used as the jabber
+        resource when we create a class level connection*/
+       char* server_class;
+
+       /* the current node in the ring of available server nodes */
+       server_node* current_server_node;
+
+       /* next and prev class_node pointers */
+       struct server_class_node_struct* next;
+       struct server_class_node_struct* prev;
+
+       /* our jabber connection struct */
+       jabber_connect* jabber;
+
+};
+typedef struct server_class_node_struct server_class_node;
+
+// ----------------------------------------------------------------------
+// Top level router_registrar object.  Maintains the list of 
+// server_class_nodes and the top level router jabber connection.
+// ----------------------------------------------------------------------
+struct transport_router_registrar_struct {
+
+       /* the list of server class nodes */
+       server_class_node* server_class_list;
+
+       /* if we don't hear from the client in this amount of time
+               we consider them dead... */ 
+       /* not currently used */
+       int client_timeout; /* seconds */
+
+       /* our top level connection to the jabber server */
+       jabber_connect* jabber; 
+
+
+};
+typedef struct transport_router_registrar_struct transport_router_registrar;
+
+
+
+
+// ----------------------------------------------------------------------
+// Returns an allocated transport_router_registrar.  The user is responsible for
+// freeing the allocated memory with router_registrar_free()
+// client_timeout is unused at this time.
+// connect_timeout is how long we will wait for a failed jabber connect
+// attempt for the top level connection.
+// ----------------------------------------------------------------------
+transport_router_registrar* router_registrar_init( char* server, 
+               int port, char* username, char* password, char* resource, 
+               int client_timeout, int connect_timeout );
+
+// ----------------------------------------------------------------------
+// Connects the top level router_registrar object to the Jabber server.
+// ----------------------------------------------------------------------
+int router_registrar_connect( transport_router_registrar* router );
+
+// ----------------------------------------------------------------------
+// Connects the given jabber_connect object to the Jabber server
+// ----------------------------------------------------------------------
+int j_connect( jabber_connect* jabber );
+
+
+// ----------------------------------------------------------------------
+// Builds and initializes a jabber_connect object. User is responsible
+// for freeing the memory with jabber_connect_free();
+// ----------------------------------------------------------------------
+jabber_connect* jabber_connect_init( char* server, 
+               int port, char* username, char* password, 
+               char* resource, int connect_timeout );
+
+// ----------------------------------------------------------------------
+// Allocates and initializes a server class instance.  This will be
+// called when a new class message arrives.  It will connect to Jabber
+// as router_registrar->username@router_registrar->server/new_class
+// ----------------------------------------------------------------------
+server_class_node* init_server_class( 
+               transport_router_registrar* router_registrar, char* remote_id, char* server_class ); 
+
+// ----------------------------------------------------------------------
+// Allocates and initializes a server_node object.  The object must
+// be freed with server_node_free().  
+// remote_id is the full jabber login for the remote server connection
+// I.e. where we send messages when we want to send them to this 
+// server.
+// ----------------------------------------------------------------------
+server_node* init_server_node(  char* remote_id );
+
+
+// ----------------------------------------------------------------------
+// Routes messages sent to the provided server_class_node's class
+// ----------------------------------------------------------------------
+int  server_class_handle_msg( transport_router_registrar* router, 
+               server_class_node* s_node, transport_message* msg );
+
+// ----------------------------------------------------------------------
+// Determines what to do with an inbound register/unregister message.
+// ----------------------------------------------------------------------
+int router_registrar_handle_msg( transport_router_registrar*, transport_message* msg );
+
+// ----------------------------------------------------------------------
+// Deallocates the memory occupied by the given server_node
+// ----------------------------------------------------------------------
+int server_node_free( server_node* node );
+
+// ----------------------------------------------------------------------
+// Deallocates the memory used by the given server_class_node.  This
+// will also free any attached server_node's.
+// ----------------------------------------------------------------------
+int server_class_node_free( server_class_node* node );
+
+// ----------------------------------------------------------------------
+// Deallocates the memory used by a server_node
+// ----------------------------------------------------------------------
+int server_node_free( server_node* node );
+
+
+// ----------------------------------------------------------------------
+// Deallocates a jabber_connect node
+// ----------------------------------------------------------------------
+int jabber_connect_free( jabber_connect* jabber );
+
+// ----------------------------------------------------------------------
+// Deallocates the memory used by the router_registrar.  This will also call
+// server_class_node_free on any attached server_class_nodes.
+// ----------------------------------------------------------------------
+int router_registrar_free( transport_router_registrar* router_registrar );
+
+
+// ----------------------------------------------------------------------
+//  Returns the server_node with the given Jabber remote_id
+// ----------------------------------------------------------------------
+server_node * find_server_node ( server_class_node * class, const char * remote_id );
+
+
+// ----------------------------------------------------------------------
+// Returns the server_class_node object with the given class_name
+// ----------------------------------------------------------------------
+server_class_node * find_server_class ( transport_router_registrar * router, const char * class_id );
+
+// ----------------------------------------------------------------------
+// Removes a server class from the top level router_registrar
+// ----------------------------------------------------------------------
+int unregister_server_node( server_class_node* active_class_node, char* remote_id );
+
+int fill_fd_set( transport_router_registrar* router, fd_set* set );
+
+void listen_loop( transport_router_registrar* router );
+
+
+int remove_server_class( transport_router_registrar* router, server_class_node* class );
+// ----------------------------------------------------------------------
+// Adds a handler for the SIGUSR1 that we send to wake all the 
+// listening threads.
+// ----------------------------------------------------------------------
+//void sig_handler( int sig );
+
+#endif