--- /dev/null
+#!/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();
+
--- /dev/null
+#!/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
+
+
--- /dev/null
+<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">
+
+<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">
+
+<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">
+
+<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">
+
+<oils:domainObjectAttr value="CONNECT" name="type"/>
+ <oils:userAuth hashseed="237" secret="89dd8c65300d4af126cf467779ff1820" username="bill"/>
+ <oils:domainObjectAttr value="1" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+</oils:domainObject>
+
+ </pre>
+
+
+ <h2> DISCONNECT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="DISCONNECT" name="type"/>
+ <oils:domainObjectAttr value="0" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> STATUS Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="STATUS" name="type"/>
+ <oils:domainObjectAttr value="0" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsConnectStatus">
+ <oils:domainObjectAttr value="Connection Successful" name="status"/>
+ <oils:domainObjectAttr value="200" name="statusCode"/>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> REQUEST Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="REQUEST" name="type"/>
+ <oils:domainObjectAttr value="4" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsMethod">
+ <oils:domainObjectAttr value="mult" name="method"/>
+ <oils:params>
+ <oils:param>1</oils:param>
+ <oils:param>2</oils:param>
+ </oils:params>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> RESULT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="RESULT" name="type"/>
+ <oils:domainObjectAttr value="4" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsResult">
+ <oils:domainObjectAttr value="OK" name="status"/>
+ <oils:domainObjectAttr value="200" name="statusCode"/>
+ <oils:domainObject name="oilsScalar">2</oils:domainObject>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+
+ </body>
+
+</html>
+
+
--- /dev/null
+#!/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";
+
--- /dev/null
+#!/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]);
+}
+
--- /dev/null
+#!/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;
+
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+// ---------------------------------------------------------------------------------
+// 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
--- /dev/null
+#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
--- /dev/null
+// 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') + '"';
+ }
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/** @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();
+}
+
+
+
--- /dev/null
+/** @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 );
+}
--- /dev/null
+/** @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 );
+}
+
+
--- /dev/null
+// -----------------------------------------------------------------------------
+// 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();
+}
+
+
--- /dev/null
+// ------------------------------------------------------------------
+// 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;
+}
+
+
+
+
+
--- /dev/null
+// -----------------------------------------------------------------------------
+// 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 );
+}
+
+
+
+
+
--- /dev/null
+/** @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() {}
+
+
--- /dev/null
+// ------------------------------------------------------------------
+// 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 );
+}
--- /dev/null
+# 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
--- /dev/null
+#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;
+
+}
+
+
+
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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 );
+}
+
--- /dev/null
+#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;
+}
+*/
+
--- /dev/null
+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
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 ' */
+ NAD_SAFE(nad->cdata, nad->ccur + 6, nad->clen);
+ memcpy(nad->cdata + nad->ccur, "'", 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 < */
+ NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+ memcpy(nad->cdata + nad->ccur, "<", 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 > */
+ NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+ memcpy(nad->cdata + nad->ccur, ">", 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 < */
+ memcpy(nad->cdata + nad->ccur, "&", 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
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
+
+
+
+
+
--- /dev/null
+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;
--- /dev/null
+package OpenSRF::App::Client;
+use base 'OpenSRF';
+use OpenSRF::Utils::Logger qw/:level/;
+
+
+1;
--- /dev/null
+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 \⊂
+# }
+#
+# if( $method_name eq "mult" ) {
+# return \&mult;
+# }
+#
+# if( $method_name eq "div" ) {
+# return \÷
+# }
+
+# 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;
--- /dev/null
+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 \⊂
+# }
+#
+# if( $method_name eq "mult" ) {
+# return \&mult;
+# }
+#
+# if( $method_name eq "div" ) {
+# return \÷
+# }
+#
+# 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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+package OpenSRF::DOM::Element::param;
+use base 'OpenSRF::DOM::Element';
+
+1;
--- /dev/null
+package OpenSRF::DOM::Element::params;
+use base 'OpenSRF::DOM::Element';
+
+1;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
+
+
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
+
--- /dev/null
+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;
--- /dev/null
+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;
+
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
+
--- /dev/null
+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;
--- /dev/null
+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;
+
--- /dev/null
+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;
+
--- /dev/null
+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;
--- /dev/null
+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;
+
+
+
+
+
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+# 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
--- /dev/null
+#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;
+}
+
+
--- /dev/null
+#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