branching version 1.0
authormiker <miker@9efc2488-bf62-4759-914b-345cdb29e865>
Thu, 16 Oct 2008 19:29:12 +0000 (19:29 +0000)
committermiker <miker@9efc2488-bf62-4759-914b-345cdb29e865>
Thu, 16 Oct 2008 19:29:12 +0000 (19:29 +0000)
git-svn-id: svn://svn.open-ils.org/OpenSRF/branches/rel_1_0@1453 9efc2488-bf62-4759-914b-345cdb29e865

266 files changed:
trunk/AUTHORS [new file with mode: 0644]
trunk/COPYING [new file with mode: 0644]
trunk/ChangeLog [new file with mode: 0644]
trunk/DCO-1.1.txt [new file with mode: 0644]
trunk/INSTALL [new file with mode: 0644]
trunk/LICENSE.txt [new file with mode: 0644]
trunk/Makefile.am [new file with mode: 0644]
trunk/NEWS [new file with mode: 0644]
trunk/README [new file with mode: 0644]
trunk/autogen.sh [new file with mode: 0755]
trunk/bin/opensrf-perl.pl [new file with mode: 0755]
trunk/bin/osrf_config.in [new file with mode: 0644]
trunk/bin/osrf_ctl.sh [new file with mode: 0755]
trunk/configure.ac [new file with mode: 0644]
trunk/doc/Application-HOWTO.txt [new file with mode: 0644]
trunk/doc/OpenSRF-Messaging-Protocol.html [new file with mode: 0644]
trunk/doc/Persist-API.html [new file with mode: 0644]
trunk/doc/Roadmap.txt [new file with mode: 0644]
trunk/doc/dokuwiki-doc-stubber.pl [new file with mode: 0755]
trunk/examples/fieldmapper2cdbi.xsl [new file with mode: 0644]
trunk/examples/fieldmapper2javascript.xsl [new file with mode: 0644]
trunk/examples/fieldmapper2perl.xsl [new file with mode: 0644]
trunk/examples/gen-fieldmapper.xml [new file with mode: 0644]
trunk/examples/math_bench.pl [new file with mode: 0755]
trunk/examples/math_xul_client/Makefile.in [new file with mode: 0644]
trunk/examples/math_xul_client/install.js [new file with mode: 0644]
trunk/examples/math_xul_client/math/content/conf/client_config.xml [new file with mode: 0644]
trunk/examples/math_xul_client/math/content/contents.rdf [new file with mode: 0644]
trunk/examples/math_xul_client/math/content/math.xul [new file with mode: 0644]
trunk/examples/math_xul_client/math/content/math_app.js [new file with mode: 0644]
trunk/examples/math_xul_client/math/content/math_overlay.xul [new file with mode: 0644]
trunk/examples/math_xul_client/math/locale/en-US/contents.rdf [new file with mode: 0644]
trunk/examples/math_xul_client/math/locale/en-US/math.dtd [new file with mode: 0644]
trunk/examples/math_xul_client/math/skin/contents.rdf [new file with mode: 0644]
trunk/examples/math_xul_client/math/skin/math.css [new file with mode: 0644]
trunk/examples/multisession-test.pl [new file with mode: 0755]
trunk/examples/opensrf.xml.example [new file with mode: 0644]
trunk/examples/opensrf_core.xml.example [new file with mode: 0644]
trunk/examples/register.pl [new file with mode: 0755]
trunk/examples/srfsh.xml.example [new file with mode: 0644]
trunk/examples/srfsh_config.xsd [new file with mode: 0644]
trunk/include/opensrf/log.h [new file with mode: 0644]
trunk/include/opensrf/md5.h [new file with mode: 0644]
trunk/include/opensrf/osrfConfig.h [new file with mode: 0644]
trunk/include/opensrf/osrf_app_session.h [new file with mode: 0644]
trunk/include/opensrf/osrf_application.h [new file with mode: 0644]
trunk/include/opensrf/osrf_big_hash.h [new file with mode: 0644]
trunk/include/opensrf/osrf_big_list.h [new file with mode: 0644]
trunk/include/opensrf/osrf_cache.h [new file with mode: 0644]
trunk/include/opensrf/osrf_hash.h [new file with mode: 0644]
trunk/include/opensrf/osrf_json.h [new file with mode: 0644]
trunk/include/opensrf/osrf_json_utils.h [new file with mode: 0644]
trunk/include/opensrf/osrf_json_xml.h [new file with mode: 0644]
trunk/include/opensrf/osrf_legacy_json.h [new file with mode: 0644]
trunk/include/opensrf/osrf_list.h [new file with mode: 0644]
trunk/include/opensrf/osrf_message.h [new file with mode: 0644]
trunk/include/opensrf/osrf_prefork.h [new file with mode: 0644]
trunk/include/opensrf/osrf_settings.h [new file with mode: 0644]
trunk/include/opensrf/osrf_stack.h [new file with mode: 0644]
trunk/include/opensrf/osrf_system.h [new file with mode: 0644]
trunk/include/opensrf/osrf_transgroup.h [new file with mode: 0644]
trunk/include/opensrf/sha.h [new file with mode: 0644]
trunk/include/opensrf/socket_bundle.h [new file with mode: 0644]
trunk/include/opensrf/string_array.h [new file with mode: 0644]
trunk/include/opensrf/transport_client.h [new file with mode: 0644]
trunk/include/opensrf/transport_message.h [new file with mode: 0644]
trunk/include/opensrf/transport_session.h [new file with mode: 0644]
trunk/include/opensrf/utils.h [new file with mode: 0644]
trunk/include/opensrf/xml_utils.h [new file with mode: 0644]
trunk/src/Makefile.am [new file with mode: 0644]
trunk/src/c-apps/Makefile.am [new file with mode: 0644]
trunk/src/c-apps/osrf_dbmath.c [new file with mode: 0644]
trunk/src/c-apps/osrf_math.c [new file with mode: 0644]
trunk/src/c-apps/osrf_version.c [new file with mode: 0644]
trunk/src/c-apps/timejson.c [new file with mode: 0644]
trunk/src/extras/docgen.xsl [new file with mode: 0644]
trunk/src/gateway/Makefile.am [new file with mode: 0644]
trunk/src/gateway/apachetools.c [new file with mode: 0644]
trunk/src/gateway/apachetools.h [new file with mode: 0644]
trunk/src/gateway/osrf_http_translator.c [new file with mode: 0644]
trunk/src/gateway/osrf_json_gateway.c [new file with mode: 0644]
trunk/src/java/Makefile.am [new file with mode: 0644]
trunk/src/java/deps.inc [new file with mode: 0644]
trunk/src/java/deps.sh [new file with mode: 0755]
trunk/src/java/org/opensrf/ClientSession.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Message.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Method.java [new file with mode: 0644]
trunk/src/java/org/opensrf/MethodException.java [new file with mode: 0644]
trunk/src/java/org/opensrf/MultiSession.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Request.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Result.java [new file with mode: 0644]
trunk/src/java/org/opensrf/ServerSession.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Session.java [new file with mode: 0644]
trunk/src/java/org/opensrf/SessionException.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Stack.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Status.java [new file with mode: 0644]
trunk/src/java/org/opensrf/Sys.java [new file with mode: 0644]
trunk/src/java/org/opensrf/net/xmpp/XMPPException.java [new file with mode: 0644]
trunk/src/java/org/opensrf/net/xmpp/XMPPMessage.java [new file with mode: 0644]
trunk/src/java/org/opensrf/net/xmpp/XMPPReader.java [new file with mode: 0644]
trunk/src/java/org/opensrf/net/xmpp/XMPPSession.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/MathBench.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestCache.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestClient.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestConfig.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestJSON.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestLog.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestMultiSession.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestSettings.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestThread.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestXMLFlattener.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestXMLTransformer.java [new file with mode: 0644]
trunk/src/java/org/opensrf/test/TestXMPP.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/Cache.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/Config.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/ConfigException.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/FileLogger.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/JSONException.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/JSONReader.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/JSONWriter.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/Logger.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/OSRFObject.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/OSRFRegistry.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/OSRFSerializable.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/SettingsClient.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/Utils.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/XMLFlattener.java [new file with mode: 0644]
trunk/src/java/org/opensrf/util/XMLTransformer.java [new file with mode: 0644]
trunk/src/javascript/DojoSRF.js [new file with mode: 0644]
trunk/src/javascript/JSON_v0.js [new file with mode: 0644]
trunk/src/javascript/JSON_v1.js [new file with mode: 0644]
trunk/src/javascript/md5.js [new file with mode: 0644]
trunk/src/javascript/opensrf.js [new file with mode: 0644]
trunk/src/javascript/opensrf_xhr.js [new file with mode: 0644]
trunk/src/javascript/opensrf_xmpp.js [new file with mode: 0644]
trunk/src/jserver/Makefile.am [new file with mode: 0644]
trunk/src/jserver/osrf_chat.c [new file with mode: 0644]
trunk/src/jserver/osrf_chat.h [new file with mode: 0644]
trunk/src/jserver/osrf_chat_main.c [new file with mode: 0644]
trunk/src/libopensrf/Makefile.am [new file with mode: 0644]
trunk/src/libopensrf/Makefile.json [new file with mode: 0644]
trunk/src/libopensrf/basic_client.c [new file with mode: 0644]
trunk/src/libopensrf/log.c [new file with mode: 0644]
trunk/src/libopensrf/md5.c [new file with mode: 0644]
trunk/src/libopensrf/opensrf.c [new file with mode: 0644]
trunk/src/libopensrf/osrfConfig.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_app_session.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_application.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_big_hash.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_big_list.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_cache.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_hash.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_json_object.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_json_parser.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_json_test.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_json_tools.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_json_xml.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_legacy_json.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_list.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_message.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_prefork.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_settings.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_stack.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_system.c [new file with mode: 0644]
trunk/src/libopensrf/osrf_transgroup.c [new file with mode: 0644]
trunk/src/libopensrf/sha.c [new file with mode: 0644]
trunk/src/libopensrf/socket_bundle.c [new file with mode: 0644]
trunk/src/libopensrf/socket_test.c [new file with mode: 0644]
trunk/src/libopensrf/string_array.c [new file with mode: 0644]
trunk/src/libopensrf/transport_client.c [new file with mode: 0644]
trunk/src/libopensrf/transport_message.c [new file with mode: 0644]
trunk/src/libopensrf/transport_session.c [new file with mode: 0644]
trunk/src/libopensrf/utils.c [new file with mode: 0644]
trunk/src/libopensrf/xml_utils.c [new file with mode: 0644]
trunk/src/perl/Changes [new file with mode: 0644]
trunk/src/perl/MANIFEST [new file with mode: 0644]
trunk/src/perl/Makefile.PL [new file with mode: 0644]
trunk/src/perl/README [new file with mode: 0644]
trunk/src/perl/inc/Module/Install.pm [new file with mode: 0644]
trunk/src/perl/inc/Module/Install/Base.pm [new file with mode: 0644]
trunk/src/perl/inc/Module/Install/Can.pm [new file with mode: 0644]
trunk/src/perl/inc/Module/Install/Fetch.pm [new file with mode: 0644]
trunk/src/perl/inc/Module/Install/Makefile.pm [new file with mode: 0644]
trunk/src/perl/inc/Module/Install/Metadata.pm [new file with mode: 0644]
trunk/src/perl/inc/Module/Install/Win32.pm [new file with mode: 0644]
trunk/src/perl/inc/Module/Install/WriteAll.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/AppSession.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Application.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Application/Client.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Application/Demo/Math.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Application/Demo/MathDB.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Application/Persist.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Application/Settings.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/DomainObject/oilsMessage.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/DomainObject/oilsMethod.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/DomainObject/oilsResponse.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/EX.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/MultiSession.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/System.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/Listener.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/PeerHandle.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/SlimJabber.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/Client.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/Inbound.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/MessageWrapper.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/PeerConnection.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/XMPPMessage.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/XMPPReader.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/UnixServer.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Utils.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Utils/Cache.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Utils/Config.pm [new file with mode: 0755]
trunk/src/perl/lib/OpenSRF/Utils/JSON.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Utils/LogServer.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Utils/Logger.pm [new file with mode: 0644]
trunk/src/perl/lib/OpenSRF/Utils/SettingsClient.pm [new file with mode: 0755]
trunk/src/perl/lib/OpenSRF/Utils/SettingsParser.pm [new file with mode: 0755]
trunk/src/perl/t/00-load.t [new file with mode: 0644]
trunk/src/perl/t/01-Application.t [new file with mode: 0644]
trunk/src/perl/t/02-AppSession.t [new file with mode: 0644]
trunk/src/perl/t/03-DomainObject.t [new file with mode: 0644]
trunk/src/perl/t/04-EX.t [new file with mode: 0644]
trunk/src/perl/t/05-MultiSession.t [new file with mode: 0644]
trunk/src/perl/t/06-System.t [new file with mode: 0644]
trunk/src/perl/t/07-Transport.t [new file with mode: 0644]
trunk/src/perl/t/08-UnixServer.t [new file with mode: 0644]
trunk/src/perl/t/09-Utils.t [new file with mode: 0644]
trunk/src/perl/t/pod-coverage.t [new file with mode: 0644]
trunk/src/perl/t/pod.t [new file with mode: 0644]
trunk/src/ports/strn_compat/Makefile.in [new file with mode: 0644]
trunk/src/ports/strn_compat/strndup.c [new file with mode: 0644]
trunk/src/ports/strn_compat/strndup.h [new file with mode: 0644]
trunk/src/ports/strn_compat/strnlen.c [new file with mode: 0644]
trunk/src/ports/strn_compat/strnlen.h [new file with mode: 0644]
trunk/src/python/Makefile.am [new file with mode: 0644]
trunk/src/python/opensrf.py [new file with mode: 0755]
trunk/src/python/osrf/__init__.py [new file with mode: 0644]
trunk/src/python/osrf/app.py [new file with mode: 0644]
trunk/src/python/osrf/apps/__init__.py [new file with mode: 0644]
trunk/src/python/osrf/apps/example.py [new file with mode: 0644]
trunk/src/python/osrf/cache.py [new file with mode: 0644]
trunk/src/python/osrf/conf.py [new file with mode: 0644]
trunk/src/python/osrf/const.py [new file with mode: 0644]
trunk/src/python/osrf/ex.py [new file with mode: 0644]
trunk/src/python/osrf/gateway.py [new file with mode: 0644]
trunk/src/python/osrf/http_translator.py [new file with mode: 0644]
trunk/src/python/osrf/json.py [new file with mode: 0644]
trunk/src/python/osrf/log.py [new file with mode: 0644]
trunk/src/python/osrf/net.py [new file with mode: 0644]
trunk/src/python/osrf/net_obj.py [new file with mode: 0644]
trunk/src/python/osrf/server.py [new file with mode: 0644]
trunk/src/python/osrf/ses.py [new file with mode: 0644]
trunk/src/python/osrf/set.py [new file with mode: 0644]
trunk/src/python/osrf/stack.py [new file with mode: 0644]
trunk/src/python/osrf/system.py [new file with mode: 0644]
trunk/src/python/osrf/xml_obj.py [new file with mode: 0644]
trunk/src/python/setup.py.in [new file with mode: 0644]
trunk/src/python/srfsh.py [new file with mode: 0755]
trunk/src/router/Makefile.am [new file with mode: 0644]
trunk/src/router/osrf_router.c [new file with mode: 0644]
trunk/src/router/osrf_router.h [new file with mode: 0644]
trunk/src/router/osrf_router_main.c [new file with mode: 0644]
trunk/src/srfsh/Makefile.am [new file with mode: 0644]
trunk/src/srfsh/srfsh.c [new file with mode: 0644]

diff --git a/trunk/AUTHORS b/trunk/AUTHORS
new file mode 100644 (file)
index 0000000..5650c91
--- /dev/null
@@ -0,0 +1 @@
+#AUTHORS
diff --git a/trunk/COPYING b/trunk/COPYING
new file mode 100644 (file)
index 0000000..6235a64
--- /dev/null
@@ -0,0 +1,17 @@
+ Copyright (C) 2005-2007, Georgia Public Library Service and others
+
+ 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, 
+ MA 02111-1307 USA
+
diff --git a/trunk/ChangeLog b/trunk/ChangeLog
new file mode 100644 (file)
index 0000000..3323187
--- /dev/null
@@ -0,0 +1 @@
+#ChangeLog
diff --git a/trunk/DCO-1.1.txt b/trunk/DCO-1.1.txt
new file mode 100644 (file)
index 0000000..c729097
--- /dev/null
@@ -0,0 +1,29 @@
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+
+Signed-off-by: [submitter's name and email address here]
+
diff --git a/trunk/INSTALL b/trunk/INSTALL
new file mode 100644 (file)
index 0000000..d3c5b40
--- /dev/null
@@ -0,0 +1,237 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
+2006, 2007 Free Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+Briefly, the shell commands `./configure; make; make install' should
+configure, build, and install this package.  The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+   It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring.  Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'.  You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.
+
+     Running `configure' might take a while.  While running, it prints
+     some messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+  6. Often, you can also type `make uninstall' to remove the installed
+     files again.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about.  Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+   You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here
+is an example:
+
+     ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you can use GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory.  After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc.  You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS KERNEL-OS
+
+   See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug.  Until the bug is fixed you can use this workaround:
+
+     CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+     Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally `config.cache'.  FILE defaults to `/dev/null' to
+     disable caching.
+
+`--config-cache'
+`-C'
+     Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options.  Run
+`configure --help' for more details.
+
diff --git a/trunk/LICENSE.txt b/trunk/LICENSE.txt
new file mode 100644 (file)
index 0000000..113c29b
--- /dev/null
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/trunk/Makefile.am b/trunk/Makefile.am
new file mode 100644 (file)
index 0000000..bf334d2
--- /dev/null
@@ -0,0 +1,123 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+if !DEBUG
+MAYBE_DEBUG = -DNDEBUG
+endif
+
+export PREFIX                   = @prefix@
+export TMP                      = @TMP@
+export LIBXML2_HEADERS          = @LIBXML2_HEADERS@
+export APR_HEADERS              = @APR_HEADERS@
+export ETCDIR                   = @sysconfdir@
+export APXS2                    = @APXS2@
+export APACHE2_HEADERS          = @APACHE2_HEADERS@
+export DEF_CFLAGS              = -D_LARGEFILE64_SOURCE $(MAYBE_DEBUG) -pipe -g -Wall -O2 -fPIC -I@abs_top_srcdir@/include/ -I$(LIBXML2_HEADERS) -I$(APACHE2_HEADERS) -I$(APR_HEADERS) @INCLUDES@
+export DEF_LDLIBS              = -lopensrf
+export VAR                     = @localstatedir@
+export PID                     = @localstatedir@/run/opensrf
+export SOCK                    = @localstatedir@/lock/opensrf
+export LOG                     = @localstatedir@/log/opensrf
+export srcdir                  = @srcdir@
+
+AM_CFLAGS = $(DEF_CFLAGS)
+
+DOC_FILES = @srcdir@/doc/Application-HOWTO.txt \
+           @srcdir@/doc/dokuwiki-doc-stubber.pl \
+           @srcdir@/doc/OpenSRF-Messaging-Protocol.html \
+           @srcdir@/doc/Persist-API.html \
+           @srcdir@/doc/Roadmap.txt
+
+EXAMPLES_FILES = @srcdir@/examples/fieldmapper2cdbi.xsl \
+                @srcdir@/examples/fieldmapper2javascript.xsl \
+                @srcdir@/examples/fieldmapper2perl.xsl \
+                @srcdir@/examples/gen-fieldmapper.xml \
+                @srcdir@/examples/math_bench.pl \
+                @srcdir@/examples/multisession-test.pl \
+                @srcdir@/examples/register.pl \
+                @srcdir@/examples/srfsh_config.xsd \
+                @srcdir@/examples/math_xul_client/math \
+                @srcdir@/examples/math_xul_client/install.js
+
+strn_compat_FILES = @srcdir@/src/ports/strn_compat/strndup.c \
+                   @srcdir@/src/ports/strn_compat/strndup.h \
+                   @srcdir@/src/ports/strn_compat/strnlen.c \
+                   @srcdir@/src/ports/strn_compat/strnlen.h
+
+python_FILES = @srcdir@/src/python/opensrf.py \
+              @srcdir@/src/python/setup.py \
+              @srcdir@/src/python/srfsh.py \
+              @srcdir@/src/python/osrf
+
+java_FILES = @srcdir@/src/java/deps.inc \
+            @srcdir@/src/java/deps.sh \
+            @srcdir@/src/java/org
+
+libosrf_FILES = @srcdir@/src/libopensrf/basic_client.c \
+               @srcdir@/src/libopensrf/osrf_big_hash.c \
+               @srcdir@/src/libopensrf/osrf_big_list.c \
+               @srcdir@/src/libopensrf/osrfConfig.c
+
+
+EXTRA_DIST = $(DOC_FILES) $(EXAMPLES_FILES) $(libosrf_FILES) $(strn_compat_FILES) $(python_FILES) $(java_FILES) @srcdir@/autogen.sh @srcdir@/src/extras @srcdir@/DCO-1.1.txt @srcdir@/LICENSE.txt @srcdir@/src/perl @srcdir@/src/javascript
+
+opensrfincludedir = @includedir@/opensrf
+
+OSRFINC=@srcdir@/include/opensrf
+
+opensrfinclude_HEADERS = $(OSRFINC)/log.h \
+                               $(OSRFINC)/md5.h \
+                               $(OSRFINC)/osrf_application.h \
+                               $(OSRFINC)/osrf_app_session.h \
+                               $(OSRFINC)/osrf_big_hash.h \
+                               $(OSRFINC)/osrf_big_list.h \
+                               $(OSRFINC)/osrf_cache.h \
+                               $(OSRFINC)/osrfConfig.h \
+                               $(OSRFINC)/osrf_hash.h \
+                               $(OSRFINC)/osrf_json.h \
+                               $(OSRFINC)/osrf_json_utils.h \
+                               $(OSRFINC)/osrf_json_xml.h \
+                               $(OSRFINC)/osrf_legacy_json.h \
+                               $(OSRFINC)/osrf_list.h \
+                               $(OSRFINC)/osrf_message.h \
+                               $(OSRFINC)/osrf_prefork.h \
+                               $(OSRFINC)/osrf_settings.h \
+                               $(OSRFINC)/osrf_stack.h \
+                               $(OSRFINC)/osrf_system.h \
+                               $(OSRFINC)/osrf_transgroup.h \
+                               $(OSRFINC)/sha.h \
+                               $(OSRFINC)/socket_bundle.h \
+                               $(OSRFINC)/string_array.h \
+                               $(OSRFINC)/transport_client.h \
+                               $(OSRFINC)/transport_message.h \
+                               $(OSRFINC)/transport_session.h \
+                               $(OSRFINC)/utils.h \
+                               $(OSRFINC)/xml_utils.h
+
+
+
+SUBDIRS = src
+
+jserver:
+       make -s -C src jserver
+
+jserver-install:
+       make -s -C src jserver-install
+
+javascript-install:
+       make -s -C src javascript-install
+
+install-data-hook:
+       cp @srcdir@/src/gateway/apachetools.h @includedir@/opensrf/apachetools.h
+
diff --git a/trunk/NEWS b/trunk/NEWS
new file mode 100644 (file)
index 0000000..2aafebb
--- /dev/null
@@ -0,0 +1 @@
+#NEWS
diff --git a/trunk/README b/trunk/README
new file mode 100644 (file)
index 0000000..267b3c6
--- /dev/null
@@ -0,0 +1,51 @@
+README for OpenSRF 1.0 RC
+
+Installing prerequisites:
+========================
+
+OpenSRF has a number of prerequisite packages that must be installed
+before you can successfully configure, compile, and install OpenSRF.
+On Debian and Ubuntu, the easiest way to install these prerequisites
+is to use the Makefile.install prerequisite installer for Evergreen.
+
+Issue the following commands as the root user to install prerequisites
+using the Makefile.install prerequisite installer, substituting "debian"
+or "ubuntu" for <osname> below:
+
+aptitude install wget make
+wget http://svn.open-ils.org/trac/ILS/export/10741/trunk/Open-ILS/src/extras/Makefile.install
+make -f Makefile.install <osname>
+
+Note: You may also be able to use "centos" to install the OpenSRF
+prerequisites for CentOS 5 and RHEL 5, or "gentoo" for Gentoo - but
+these are less tested distributions. Your patches and suggestions for
+improvement are welcome!
+
+Configuration and compilation instructions:
+==========================================
+
+For the time being, we are still installing everything in the /openils/
+directory (with the exception of the Perl modules, which are installed
+into system directories). Issue the following commands to configure and
+build OpenSRF:
+
+./configure --with-prefix=/openils --with-sysconfdir=/openils/conf
+make
+
+Installation instructions:
+=========================
+
+Once you have configured and compiled OpenSRF, issue the following
+command as the root user to install OpenSRF:
+
+make install
+
+This will install OpenSRF, including example configuration files in
+/openils/conf/ that you can use as templates for your own configuration files.
+
+Getting help:
+============
+
+Need help installing or using OpenSRF? Join the mailing lists at
+http://evergreen-ils.org/listserv.php or contact us on the Freenode
+IRC network on the #evergreen channel.
diff --git a/trunk/autogen.sh b/trunk/autogen.sh
new file mode 100755 (executable)
index 0000000..1a8d53f
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# autogen.sh - generates configure using the autotools
+
+OS=`uname`
+if [ "$OS" = "Darwin" ]; then
+    : ${LIBTOOLIZE=glibtoolize}
+elif [ "$OS" = "Linux" ]; then
+    : ${LIBTOOLIZE=libtoolize}
+fi
+
+: ${ACLOCAL=aclocal}
+: ${AUTOHEADER=autoheader}
+: ${AUTOMAKE=automake}
+: ${AUTOCONF=autoconf}
+
+
+${LIBTOOLIZE} --force --copy
+${ACLOCAL}
+${AUTOMAKE} --add-missing
+
+
+${AUTOCONF}
+
+SILENT=`which ${LIBTOOLIZE} ${ACLOCAL} ${AUTOHEADER} ${AUTOMAKE} ${AUTOCONF}`
+case "$?" in
+    0 )
+        echo All build tools found.
+        ;;
+    1)
+        echo
+        echo "--------------------------------------------------------------"
+        echo "          >>> Some build tools are missing! <<<"
+        echo Please make sure your system has the GNU autoconf and automake
+        echo toolchains installed.
+        echo "--------------------------------------------------------------"
+        exit
+        ;;
+esac
+
+echo 
+echo "---------------------------------------------"
+echo "autogen finished running, now run ./configure"
+echo "---------------------------------------------"
diff --git a/trunk/bin/opensrf-perl.pl b/trunk/bin/opensrf-perl.pl
new file mode 100755 (executable)
index 0000000..d66d19f
--- /dev/null
@@ -0,0 +1,250 @@
+#!/usr/bin/perl
+# ---------------------------------------------------------------
+# Copyright (C) 2008  Georgia Public Library Service
+# Bill Erickson <erickson@esilibrary.com>
+#
+# 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.
+# ---------------------------------------------------------------
+use strict; use warnings;
+use Getopt::Long;
+use Net::Domain qw/hostfqdn/;
+use POSIX qw/setsid :sys_wait_h/;
+use OpenSRF::Utils::Logger q/$logger/;
+use OpenSRF::System;
+use OpenSRF::Transport::PeerHandle;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Transport::Listener;
+use OpenSRF::Utils;
+use OpenSRF::Utils::Config;
+
+my $opt_action = undef;
+my $opt_service = undef;
+my $opt_config = undef;
+my $opt_pid_dir = '/tmp';
+my $opt_no_daemon = 0;
+my $opt_settings_pause = 0;
+my $opt_help = 0;
+my $verbose = 0;
+my $sclient;
+my $hostname = hostfqdn();
+my @hosted_services;
+
+GetOptions(
+    'action=s' => \$opt_action,
+    'service=s' => \$opt_service,
+    'config=s' => \$opt_config,
+    'pid-dir=s' => \$opt_pid_dir,
+    'no-daemon' => \$opt_no_daemon,
+    'settings-startup-pause=i' => \$opt_settings_pause,
+    'help' => \$opt_help,
+    'verbose' => \$verbose,
+);
+
+
+sub haltme {
+    kill('INT', -$$); #kill all in process group
+    exit;
+};
+$SIG{INT} = \&haltme;
+$SIG{TERM} = \&haltme;
+
+sub get_pid_file {
+    my $service = shift;
+    return "$opt_pid_dir/$service.pid";
+}
+
+# stop a specific service
+sub do_stop {
+    my $service = shift;
+    my $pid_file = get_pid_file($service);
+    if(-e $pid_file) {
+        my $pid = `cat $pid_file`;
+        chomp $pid;
+        msg("stopping servivce pid=$pid $service", 1);
+        kill('INT', $pid);
+        waitpid($pid, 0);
+        unlink $pid_file;
+    } else {
+        msg("$service not running");
+    }
+    return 1;
+}
+
+sub do_init {
+    OpenSRF::System->bootstrap_client(config_file => $opt_config);
+    die "Unable to bootstrap client for requests\n"
+        unless OpenSRF::Transport::PeerHandle->retrieve;
+
+    load_settings(); # load the settings config if we can
+
+    my $sclient = OpenSRF::Utils::SettingsClient->new;
+    my $apps = $sclient->config_value("activeapps", "appname");
+
+    # disconnect the top-level network handle
+    OpenSRF::Transport::PeerHandle->retrieve->disconnect;
+
+    if($apps) {
+        $apps = [$apps] unless ref $apps;
+        for my $app (@$apps) {
+            push(@hosted_services, $app) 
+                if $sclient->config_value('apps', $app, 'language') =~ /perl/i;
+        }
+    }
+    return 1;
+}
+
+# start a specific service
+sub do_start {
+    my $service = shift;
+    if(-e get_pid_file($service)) {
+        msg("$service is already running");
+        return;
+    }
+
+    load_settings() if $service eq 'opensrf.settings';
+
+    my $sclient = OpenSRF::Utils::SettingsClient->new;
+    my $apps = $sclient->config_value("activeapps", "appname");
+    OpenSRF::Transport::PeerHandle->retrieve->disconnect;
+
+    if(grep { $_ eq $service } @hosted_services) {
+        return unless do_daemon($service);
+        launch_net_server($service);
+        launch_listener($service);
+        $0 = "OpenSRF controller [$service]";
+        while(my $pid = waitpid(-1, 0)) {
+            $logger->debug("Cleaning up Perl $service process $pid");
+        }
+    }
+
+    msg("$service is not configured to run on $hostname");
+    return 1;
+}
+
+sub do_start_all {
+    msg("starting all services for $hostname", 1);
+    if(grep {$_ eq 'opensrf.settings'} @hosted_services) {
+        do_start('opensrf.settings');
+        # in batch mode, give opensrf.settings plenty of time to start 
+        # before any non-Perl services try to connect
+        sleep $opt_settings_pause if $opt_settings_pause;
+    }
+    for my $service (@hosted_services) {
+        do_start($service) unless $service eq 'opensrf.settings';
+    }
+    return 1;
+}
+
+sub do_stop_all {
+    msg("stopping all services for $hostname", 1);
+    do_stop($_) for @hosted_services;
+    return 1;
+}
+
+# daemonize us.  return true if we're the child, false if parent
+sub do_daemon {
+    return 1 if $opt_no_daemon;
+    my $service = shift;
+    my $pid_file = get_pid_file($service);
+    #exit if OpenSRF::Utils::safe_fork();
+    return 0 if OpenSRF::Utils::safe_fork();
+    msg("starting servivce pid=$$ $service", 1);
+    chdir('/');
+    setsid();
+    close STDIN;
+    close STDOUT;
+    close STDERR;
+    `echo $$ > $pid_file`;
+    return 1;
+}
+
+# parses the local settings file
+sub load_settings {
+    my $conf = OpenSRF::Utils::Config->current;
+    my $cfile = $conf->bootstrap->settings_config;
+    return unless $cfile;
+    my $parser = OpenSRF::Utils::SettingsParser->new();
+    $parser->initialize( $cfile );
+    $OpenSRF::Utils::SettingsClient::host_config =
+        $parser->get_server_config($conf->env->hostname);
+}
+
+# starts up the unix::server master process
+sub launch_net_server {
+    my $service = shift;
+    push @OpenSRF::UnixServer::ISA, 'Net::Server::PreFork';
+    unless(OpenSRF::Utils::safe_fork()) {
+        $0 = "OpenSRF Drone [$service]";
+        OpenSRF::UnixServer->new($service)->serve;
+        exit;
+    }
+    return 1;
+}
+
+# starts up the inbound listener process
+sub launch_listener {
+    my $service = shift;
+    unless(OpenSRF::Utils::safe_fork()) {
+        $0 = "OpenSRF listener [$service]";
+        OpenSRF::Transport::Listener->new($service)->initialize->listen;
+        exit;
+    }
+    return 1;
+}
+
+sub msg {
+    my $m = shift;
+    my $v = shift;
+    print "* $m\n" unless $v and not $verbose;
+}
+
+sub do_help {
+    print <<HELP;
+
+    Usage: perl $0 --pid_dir /var/run/opensrf --config /etc/opensrf/opensrf_core.xml --service opensrf.settings --action start
+
+    --action <action>
+        Actions include start, stop, restart, and start_all, stop_all, and restart_all
+
+    --service <service>
+        Specifies which OpenSRF service to control
+
+    --config <file>
+        OpenSRF configuration file 
+        
+    --pid-dir <dir>
+        Directory where process-specific PID files are kept
+        
+    --no-daemon
+        Do not detach and run as a daemon process.  Useful for debugging.
+
+    --settings-startup-pause
+        How long to give the opensrf.settings server to start up when running 
+        in batch mode (start_all).  The purpose is to give plenty of time for
+        the settings server to be up and active before any non-Perl services
+        attempt to connect.
+        
+    --help
+        Print this help message
+HELP
+exit;
+}
+
+
+do_help() if $opt_help or not $opt_action;
+do_init() and do_start($opt_service) if $opt_action eq 'start';
+do_stop($opt_service) if $opt_action eq 'stop';
+do_init() and do_stop($opt_service) and do_start($opt_service) if $opt_action eq 'restart';
+do_init() and do_start_all() if $opt_action eq 'start_all';
+do_init() and do_stop_all() if $opt_action eq 'stop_all';
+do_init() and do_stop_all() and do_start_all() if $opt_action eq 'restart_all';
+
+
diff --git a/trunk/bin/osrf_config.in b/trunk/bin/osrf_config.in
new file mode 100644 (file)
index 0000000..70b5c38
--- /dev/null
@@ -0,0 +1,133 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+#
+
+# Shows configuration options of OSRF
+
+prefix=@prefix@
+exec_prefix=@prefix@
+datarootdir=@datarootdir@
+
+function showInstalled {
+        JAVA=@OSRF_INSTALL_JAVA@
+        PYTHON=@OSRF_INSTALL_PYTHON@
+        if test "$JAVA" = "true"; then
+           echo "OSRF_JAVA"
+        fi
+        if test "$PYTHON" = "true"; then
+           echo "OSRF_PYTHON"
+        fi
+}
+
+function showAll {
+        echo @PACKAGE_STRING@  
+        echo PREFIX=@prefix@
+        echo BINDIR=@bindir@
+        echo LIBDIR=@libdir@
+        echo TMP=@TMP@
+        echo INCLUDEDIR=@includedir@
+        echo SYSCONFDIR=@sysconfdir@
+        echo APXS2=@APXS2@
+        echo APACHE2_HEADERS=@APACHE2_HEADERS@
+        echo APR_HEADERS=@APR_HEADERS@
+        echo LIBXML2_HEADERS=@LIBXML2_HEADERS@
+        echo 
+        echo "Installed modules:"
+        showInstalled;
+}
+
+function cconfig {
+        
+sed -e 's|osrf|@abs_top_srcdir@/src/python/osrf|g' \
+    -e 's|srfsh\.py|@abs_top_srcdir@/src/python/srfsh.py|g' @srcdir@/src/python/setup.py.in > @srcdir@/src/python/setup.py
+}
+
+function showHelp {
+        echo 
+        echo "------------------------------------------------------------"
+        echo " osrf_config                                                "
+        echo " Shows configuration of opensrf                             "
+        echo "------------------------------------------------------------"
+        echo
+        echo "Usage: osrf_config [--option]"
+        echo 
+        echo "Options: "
+        echo
+        echo "--help                  displays help"
+        echo "--version               displays version number of osrf"
+        echo "--installed             displays options that were installed"
+        echo "--prefix                displays prefix"
+        echo "--bindir                displays bindir"
+        echo "--libdir                displays libdir"
+        echo "--tmp                   displays tmp"
+        echo "--includedir            displays includedir"
+        echo "--sysconfdir            displays sysconfdir"
+        echo "--apxs                  displays location of apxs"
+        echo "--apache                displays location of apache2 headers"
+        echo "--apr                   displays location of apr headers"
+        echo "--libxml                displays location of libxml2 headers" 
+        echo
+}
+
+case "$1" in
+     --installed)
+               showInstalled;
+               ;;
+     --cconfig) cconfig;
+               ;;
+     --libxml)
+               echo @LIBXML2_HEADERS@;
+               ;;
+     --apr)
+               echo @APR_HEADERS@;
+               ;;
+     --apache)
+               echo @APACHE2_HEADERS@;
+               ;;
+     --prefix)
+               echo @prefix@
+               ;;
+     --version) 
+               echo @PACKAGE_STRING@;
+               ;;
+     --bindir)
+               echo @bindir@
+               ;;
+     --libdir)
+               echo @libdir@;
+               ;;
+     --sysconfdir)
+               echo @sysconfdir@;
+               ;;
+     --localstatedir)
+               echo @localstatedir@;
+               ;;
+     --tmpdir)
+               echo @TMP@;
+               ;;
+     --apxs)
+               echo @APXS2@;
+               ;;
+     --includedir)
+               echo @includedir@;
+               ;;
+     --docdir)
+               echo @docdir@;
+               ;;
+     --help)
+               showHelp;
+               ;;
+     *)
+               showAll;
+               ;;
+esac
diff --git a/trunk/bin/osrf_ctl.sh b/trunk/bin/osrf_ctl.sh
new file mode 100755 (executable)
index 0000000..f696dc9
--- /dev/null
@@ -0,0 +1,219 @@
+#!/bin/bash
+#
+# 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.
+
+OPT_ACTION=""
+OPT_CONFIG=""
+OPT_PID_DIR=""
+
+# ---------------------------------------------------------------------------
+# Make sure we're running as the correct user
+# ---------------------------------------------------------------------------
+[ $(whoami) != 'opensrf' ] && echo 'Must run as user "opensrf"' && exit;
+
+
+function usage {
+       echo "";
+       echo "usage: $0 [OPTION]... -c <c_config> -a <action>";
+       echo "";
+       echo "Mandatory parameters:";
+       echo -e "  -a\t\taction to perform";
+       echo "";
+       echo "Optional parameters:";
+       echo -e "  -c\t\tfull path to C configuration file (opensrf_core.xml)";
+       echo -e "  -d\t\tstore PID files in this directory";
+       echo -e "  -l\t\taccept 'localhost' as the fully-qualified domain name";
+       echo "";
+       echo "Actions include:";
+       echo -e "\tstart_router"
+       echo -e "\tstop_router"
+       echo -e "\trestart_router"
+       echo -e "\tstart_perl"
+       echo -e "\tstop_perl"
+       echo -e "\trestart_perl"
+       echo -e "\tstart_c"
+       echo -e "\tstop_c"
+       echo -e "\trestart_c"
+       echo -e "\tstart_osrf"
+       echo -e "\tstop_osrf"
+       echo -e "\trestart_osrf"
+       echo -e "\tstop_all" 
+       echo -e "\tstart_all"
+       echo -e "\trestart_all"
+       echo "";
+       echo "Examples:";
+       echo "  $0 -a restart_all";
+       echo "  $0 -l -c opensrf_core.xml -a restart_all";
+       echo "";
+       exit;
+}
+
+# Get root directory of this script
+function basepath {
+       BASEDIR=""
+       script_path="$1"
+       IFS="/"
+       for p in $script_path
+       do
+               if [ -z "$BASEDIR" ] && [ -n "$p" ]; then
+                       BASEDIR="$p"
+               fi;
+       done
+       BASEDIR="/$BASEDIR"
+       IFS=
+}
+
+basepath $0
+
+# ---------------------------------------------------------------------------
+# Load the command line options and set the global vars
+# ---------------------------------------------------------------------------
+while getopts  "a:d:c:lh" flag; do
+       case $flag in   
+               "a")            OPT_ACTION="$OPTARG";;
+               "c")            OPT_CONFIG="$OPTARG";;
+               "d")            OPT_PID_DIR="$OPTARG";;
+               "l")            export OSRF_HOSTNAME="localhost";;
+               "h"|*)  usage;;
+       esac;
+done
+
+OSRF_CONFIG=`find $BASEDIR -name osrf_config`
+
+[ -z "$OPT_CONFIG" ] && OPT_CONFIG=`$OSRF_CONFIG --sysconfdir`/opensrf_core.xml;
+if [ ! -r "$OPT_CONFIG" ]; then
+       echo "Please specify the location of the opensrf_core.xml file using the -c flag";
+       exit 1;
+fi;
+[ -z "$OPT_PID_DIR" ] && OPT_PID_DIR=`$OSRF_CONFIG --localstatedir`/run;
+[ -z "$OPT_ACTION" ] && usage;
+
+PID_ROUTER="$OPT_PID_DIR/router.pid";
+PID_OSRF_PERL="$OPT_PID_DIR/osrf_perl.pid";
+PID_OSRF_C="$OPT_PID_DIR/osrf_c.pid";
+
+
+# ---------------------------------------------------------------------------
+# Utility code for checking the PID files
+# ---------------------------------------------------------------------------
+function do_action {
+
+       action="$1"; 
+       pidfile="$2";
+       item="$3"; 
+
+       if [ $action == "start" ]; then
+
+               if [ -e $pidfile ]; then
+                       pid=$(cat $pidfile);
+                       echo "$item already started : $pid";
+                       return 0;
+               fi;
+               echo "Starting $item";
+       fi;
+
+       if [ $action == "stop" ]; then
+
+               if [ ! -e $pidfile ]; then
+                       echo "$item not running";
+                       return 0;
+               fi;
+
+        while read pid; do
+            echo "Stopping $item process $pid..."
+            kill -s INT $pid
+        done < $pidfile;
+               rm -f $pidfile;
+
+       fi;
+
+       return 0;
+}
+
+
+# ---------------------------------------------------------------------------
+# Start / Stop functions
+# ---------------------------------------------------------------------------
+
+
+function start_router {
+       do_action "start" $PID_ROUTER "OpenSRF Router";
+       opensrf_router $OPT_CONFIG routers
+       pid=$(ps ax | grep "OpenSRF Router" | grep -v grep | awk '{print $1}')
+       echo $pid > $PID_ROUTER;
+       return 0;
+}
+
+function stop_router {
+       do_action "stop" $PID_ROUTER "OpenSRF Router";
+       return 0;
+}
+
+function start_perl {
+    echo "Starting OpenSRF Perl";
+    opensrf-perl.pl --pid-dir $OPT_PID_DIR \
+        --config $OPT_CONFIG --action start_all --settings-startup-pause 3
+       return 0;
+}
+
+function stop_perl {
+    echo "Stopping OpenSRF Perl";
+    opensrf-perl.pl --pid-dir $OPT_PID_DIR --config $OPT_CONFIG --action stop_all
+       sleep 1;
+       return 0;
+}
+
+function start_c {
+       host=$OSRF_HOSTNAME
+       if [ "_$host" == "_" ]; then
+               host=$(perl -MNet::Domain=hostfqdn -e 'print hostfqdn()');
+       fi;
+
+       do_action "start" $PID_OSRF_C "OpenSRF C (host=$host)";
+       opensrf-c $host $OPT_CONFIG opensrf;
+       pid=$(ps ax | grep "OpenSRF System-C" | grep -v grep | awk '{print $1}')
+       echo $pid > "$PID_OSRF_C";
+       return 0;
+}
+
+function stop_c {
+       do_action "stop" $PID_OSRF_C "OpenSRF C";
+       sleep 1;
+       return 0;
+}
+
+
+
+# ---------------------------------------------------------------------------
+# Do the requested action
+# ---------------------------------------------------------------------------
+case $OPT_ACTION in
+       "start_router") start_router;;
+       "stop_router") stop_router;;
+       "restart_router") stop_router; start_router;;
+       "start_perl") start_perl;;
+       "stop_perl") stop_perl;;
+       "restart_perl") stop_perl; start_perl;;
+       "start_c") start_c;;
+       "stop_c") stop_c;;
+       "restart_c") stop_c; start_c;;
+       "start_osrf") start_perl; start_c;;
+       "stop_osrf") stop_perl; stop_c;;
+       "restart_osrf") stop_perl; stop_c; start_perl; start_c;;
+       "stop_all") stop_c; stop_perl; stop_router;;
+       "start_all") start_router; start_perl; start_c;;
+       "restart_all") stop_c; stop_perl; stop_router; start_router; start_perl; start_c;;
+       *) usage;;
+esac;
+
+
+
diff --git a/trunk/configure.ac b/trunk/configure.ac
new file mode 100644 (file)
index 0000000..4b9e62c
--- /dev/null
@@ -0,0 +1,294 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+#
+# Process this file with autoconf to produce a configure script.
+
+
+#-------------------------------
+# Initialization
+#-------------------------------
+
+export PATH=${PATH}:/usr/sbin
+AC_PREREQ(2.59)
+AC_INIT([OpenSRF],[trunk],[open-ils-dev@list.georgialibraries.org])
+AM_INIT_AUTOMAKE([OpenSRF], [trunk])
+AC_REVISION($Revision: 0.1 $)
+AC_CONFIG_SRCDIR([configure.ac])
+AC_PREFIX_DEFAULT([/opensrf/])
+
+
+AC_SUBST(prefix)
+AC_SUBST(sysconfdir)
+
+
+AC_DEFUN([AC_PYTHON_MOD],[
+    if test -z $PYTHON;
+    then
+        PYTHON="python"
+    fi
+    AC_MSG_CHECKING($PYTHON_NAME module: $1)
+        $PYTHON -c "import $1" 2>/dev/null
+        if test $? -eq 0;
+        then
+                AC_MSG_RESULT(yes)
+                eval AS_TR_CPP(HAVE_PYMOD_$1)=yes
+        else
+                AC_MSG_ERROR(failed to find required module $1)
+               exit 1
+        fi
+])
+
+
+
+#-------------------------------
+# Installation options
+#-------------------------------
+
+# build and install the java libs?
+AC_ARG_ENABLE([java],
+[  --enable-java    enable building and installing the java libraries],
+[case "${enableval}" in
+    yes) OSRF_INSTALL_JAVA=true ;;
+    no) OSRF_INSTALL_JAVA=false ;; 
+  *) AC_MSG_ERROR([please choose another value for --enable-java (supported values are yes or no)]) ;;
+esac],
+[OSRF_INSTALL_JAVA=false])
+
+AM_CONDITIONAL([BUILDJAVA], [test x$OSRF_INSTALL_JAVA = xtrue])
+AC_SUBST([OSRF_INSTALL_JAVA])
+
+# build and install the python modules
+AC_ARG_ENABLE([python],
+[  --enable-python  enable building and installing python modules],
+[case "${enableval}" in
+  yes) OSRF_INSTALL_PYTHON=true ;;
+  no) OSRF_INSTALL_PYTHON=false ;;
+  *) AC_MSG_ERROR([please choose another value for --enable-python (supported values are yes or no)]) ;;
+esac],
+[OSRF_INSTALL_PYTHON=false])
+
+AM_CONDITIONAL([BUILDPYTHON], [test x$OSRF_INSTALL_PYTHON = xtrue])
+AC_SUBST([OSRF_INSTALL_PYTHON])
+
+# enable debug?
+
+AC_ARG_ENABLE(debug,
+[  --enable-debug    Turn on debugging],
+[case "${enableval}" in
+  yes) debug=true ;;
+  no)  debug=false ;;
+  *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;;
+esac],[debug=false])
+AM_CONDITIONAL(DEBUG, test x$debug = xtrue)
+
+# path to the directory containing the java dependency jar files (included if java installs)
+if test $OSRF_INSTALL_JAVA; then
+       AC_SUBST([OSRF_JAVA_DEPSDIR], [/opt/java])
+fi
+
+
+
+#--------------------------------
+# Checks for programs.
+#--------------------------------
+
+AC_PROG_LIBTOOL
+AC_PROG_AWK
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+#------------------------------
+# Set install path variables
+#------------------------------
+AC_ARG_WITH([tmp],
+[  --with-tmp=path              location for the tmp dir for openSRF (default is /tmp)],
+[TMP=${withval}],
+[TMP=/tmp])
+AC_SUBST([TMP])
+
+AC_ARG_WITH([apxs],
+[  --with-apxs=path                 location of apxs (default is /usr/bin/apxs2)],
+[APXS2=${withval}],
+[APXS2=/usr/bin/apxs2])
+AC_SUBST([APXS2])
+
+AC_ARG_WITH([apache],
+[  --with-apache=path               location of the apache headers (default is /usr/include/apache2)],
+[APACHE2_HEADERS=${withval}],
+[APACHE2_HEADERS=/usr/include/apache2])
+AC_SUBST([APACHE2_HEADERS])
+
+AC_ARG_WITH([apr],
+[  --with-apr=path                  location of the apr headers (default is /usr/include/apr-1.0/)],
+[APR_HEADERS=${withval}],
+[APR_HEADERS=/usr/include/apr-1.0])
+AC_SUBST([APR_HEADERS])
+
+AC_ARG_WITH([libxml],
+[  --with-libxml=path               location of the libxml headers (default is /usr/include/libxml2/))],
+[LIBXML2_HEADERS=${withval}],
+[LIBXML2_HEADERS=/usr/include/libxml2/])
+AC_SUBST([LIBXML2_HEADERS])
+
+AC_ARG_WITH([includes],
+[  --with-includes=DIRECTORIES      a colon-separated list of directories that will be added to the list the compiler searches for header files (Example: --with-includes=/path/headers:/anotherpath/moreheaders)],
+[EXTRA_USER_INCLUDES=${withval}])
+
+AC_ARG_WITH([libraries],
+[  --with-libraries=DIRECTORIES     a colon-separated list of directories to search for libraries (Example: --with-libraries=/lib:/usr/lib)],
+[EXTRA_USER_LIBRARIES=${withval}])
+
+# Change these lists to proper compiler/linker options
+
+IFSBAK=${IFS}
+IFS="${IFS}:"
+
+for dir in $EXTRA_USER_INCLUDES; do
+       if test -d "$dir"; then
+         INCLUDES="$INCLUDES -I$dir"
+       else
+         AC_MSG_WARN([*** Include directory $dir does not exist.])
+       fi
+done
+AC_SUBST(INCLUDES)
+
+for dir in $EXTRA_USER_LIBRARIES; do
+       if test -d "$dir"; then
+         LIBDIRS="$LIBDIRS -L$dir"
+       else
+         AC_MSG_WARN([*** Library directory $dir does not exist.])
+       fi
+done
+AC_SUBST(LIBDIRS)
+
+IFS=${IFSBAK}
+
+#--------------------------------
+# Check for dependencies.
+#--------------------------------
+
+#APACHE PREFORK DEV TEST
+AC_MSG_CHECKING([APXS])
+if test -f "${APXS2}"; then
+AC_MSG_RESULT([yes])
+else
+AC_MSG_ERROR([*** apxs not found, aborting])
+fi  
+
+#PYTHON TESTS
+if test x$OSRF_INSTALL_PYTHON = xtrue; then
+        AC_CHECK_PROG([HAVE_PYTHON],python,yes,no)
+        if test $HAVE_PYTHON = "no"; then
+                AC_MSG_ERROR([*** python not found, aborting])
+        fi
+        AC_PYTHON_MOD([setuptools])
+fi
+
+
+#-----------------------------
+# Checks for libraries.
+#-----------------------------
+
+AC_CHECK_LIB([dl], [dlerror], [],AC_MSG_ERROR(***OpenSRF requires libdl))
+AC_SEARCH_LIBS([mc_req_free], [memcache], [], AC_MSG_ERROR(***OpenSRF requires memcache development headers))
+AC_CHECK_LIB([ncurses], [initscr], [], AC_MSG_ERROR(***OpenSRF requires ncurses development headers))
+AC_CHECK_LIB([readline], [readline], [], AC_MSG_ERROR(***OpenSRF requires readline development headers))
+AC_CHECK_LIB([xml2], [xmlAddID], [], AC_MSG_ERROR(***OpenSRF requires xml2 development headers))
+
+
+
+#-----------------------------
+# Checks for header files.
+#-----------------------------
+
+AC_HEADER_STDC
+AC_HEADER_SYS_WAIT
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h malloc.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/socket.h sys/time.h sys/timeb.h syslog.h unistd.h])
+
+#------------------------------------------------------------------
+# Checks for typedefs, structures, and compiler characteristics.
+#------------------------------------------------------------------
+
+AC_C_CONST
+AC_C_INLINE
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_HEADER_TIME
+AC_STRUCT_TM
+
+#----------------------------------
+# Checks for library functions.
+#----------------------------------
+
+AC_FUNC_FORK
+AC_FUNC_MALLOC
+AC_FUNC_SELECT_ARGTYPES
+AC_TYPE_SIGNAL
+AC_FUNC_STRFTIME
+AC_FUNC_STRTOD
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([bzero dup2 gethostbyname gethostname gettimeofday memset select socket strcasecmp strchr strdup strerror strncasecmp strndup strrchr strtol])
+
+#------------------------------------
+# Configuration and output
+#------------------------------------
+
+AC_CONFIG_FILES([Makefile
+                 examples/math_xul_client/Makefile
+                 src/Makefile
+                 src/c-apps/Makefile
+                 src/gateway/Makefile
+                 src/java/Makefile
+                 src/jserver/Makefile
+                 src/libopensrf/Makefile
+                 src/ports/strn_compat/Makefile
+                 src/python/Makefile
+                 src/router/Makefile
+                 src/srfsh/Makefile
+                bin/osrf_config], [if test -e "./bin/osrf_config"; then chmod 755 bin/osrf_config; fi])
+
+
+AC_OUTPUT
+
+bin/osrf_config --cconfig
+
+AC_MSG_RESULT([])
+AC_MSG_RESULT([--------------------- Configuration options:  -----------------------])
+
+if test "$OSRF_INSTALL_JAVA" = "true" ; then
+       AC_MSG_RESULT([OSRF install java?:              yes])
+       AC_MSG_RESULT([Java deps dir:                   $OSRF_JAVA_DEPSDIR])
+else
+       AC_MSG_RESULT([OSRF install java?:              no])
+fi
+
+if test "$OSRF_INSTALL_PYTHON" = "true" ; then
+        AC_MSG_RESULT([OSRF install python?:            yes])
+else
+        AC_MSG_RESULT([OSRF install python?:            no])
+fi
+
+       AC_MSG_RESULT(Installation directory prefix:            ${prefix})
+       AC_MSG_RESULT(Tmp dir location:                         ${TMP})
+       AC_MSG_RESULT(APXS2 location:                           ${APXS2})
+       AC_MSG_RESULT(Apache headers location:                  ${APACHE2_HEADERS})
+       AC_MSG_RESULT(APR headers location:                     ${APR_HEADERS})
+       AC_MSG_RESULT(libxml2 headers location:                 ${LIBXML2_HEADERS})
+
+
+
+
+
+AC_MSG_RESULT([----------------------------------------------------------------------])
diff --git a/trunk/doc/Application-HOWTO.txt b/trunk/doc/Application-HOWTO.txt
new file mode 100644 (file)
index 0000000..41deae1
--- /dev/null
@@ -0,0 +1,95 @@
+OpenSRF Application development API
+-----------------------------------
+
+OpenSRF offers a very simple Application development API to users of the
+framework.  All that is required is to create a Perl module that subclasses
+the OpenSRF::Application package and register it's methods with the
+Method Dispatcher framework.
+
+Each method executes in the OpenSRF::Application namespace as an instance of
+the custom Application.  There are some instance methods on this object which
+can be used by the method to alter the behavior or response that the method
+sends to the client:
+
+  $self->api_name  # returns the name of the method as called by the client
+
+  $self->method    # returns the actual perl name of the sub implementing the
+                   # method
+
+  my $meth = $self->method_lookup( 'api_name' )
+                   # constructs a new instance object implementing another
+                   # method in the same Application package as a subrequest
+
+  my ($subresult) = $meth->run( @params )
+                   # runs the subrequest method and returns the array of
+                  # results
+
+
+The method is also handed an OpenSRF::AppRequest object that has been
+constructed for the client request that generated the call to the method.
+This OpenSRF::AppRequest object is used to communicate back to the client,
+passing status messages or streaming response packets.
+
+All that is required to register an Application with OpenSRF is to place a
+setting in the configuration file that names the module that implements the
+new Application, and to add the new Application's symbolic name to the list of
+servers that should be started by OpenSRF.
+
+   Example Application
+   -------------------
+
+  # Perl module and package implementing an math server.
+  package MyMathServer;
+  use OpenSRF::Application;
+  use base 'OpenSRF::Application';
+
+  sub do_math {
+     my $self = shift;    # instance of MyMathServer
+     
+     my $client = shift;  # instance of OpenSRF::AppRequest connected
+                          # to the client
+     
+     my $left_side = shift;
+     my $op = shift;
+     my $right_side = shift;
+     
+     return eval "$left_side $op $right_side";
+  }
+
+  __PACKAGE__->register_method(
+     api_name => 'useless.do_math',
+     argc => 3,
+     method => 'do_math'
+  );
+
+  1;
+
+
+  
+  # Another Perl module and package implementing a square-root server on top
+  # of the previous math server
+  package MySquareRootServer;
+  use OpenSRF::Application;
+  use base 'OpenSRF::Application';
+  use MyMathServer;
+
+  sub sqrt_math {
+     my $self = shift;    # instance of MySquareRootServer
+     
+     my $client = shift;  # instance of OpenSRF::AppRequest connected
+                          # to the client
+     
+     my $math_method = $self->method_lookup('useless.do_math');
+     my ($result) = $math_method->run( @_ );
+     
+     return sqrt( $result );
+  }
+
+  __PACKAGE__->register_method(
+     api_name => 'simple.sqrt',
+     argc => 3,
+     method => 'sqrt_math'
+  );
+
+  1;
+
diff --git a/trunk/doc/OpenSRF-Messaging-Protocol.html b/trunk/doc/OpenSRF-Messaging-Protocol.html
new file mode 100644 (file)
index 0000000..1eeff04
--- /dev/null
@@ -0,0 +1,297 @@
+<html>
+
+       <head>
+
+               <title> OILS Messaging </title>
+
+       </head>
+
+       <body>
+
+
+               <h1> Abstract </h1>
+
+               <p>
+
+               The OpenSRF 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> messages provide general information to the transport layer and 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
+               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>.  
+
+               <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
+
+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 )
+
+               return STATUS with statusCode STATUS_OK and continue
+
+       while ( msg = recv() and virtual session is active ) {
+
+
+               if ( msg.type == REQUEST )
+
+                       Record the threadTrace.  Pass the REQUEST to the application layer for processing.
+                       When the application layer has completed processing, respond to the client
+                       with a STATUS message with statusCode STATUS_COMPLETE and threadTrace matching
+                       the threadTrace of the REQUEST.  Once the final STATUS_COMPLETE message is sent,
+                       the session is over.  Return to outer server loop. 
+
+                       /* Note: during REQUEST processing by the application layer, the application may 
+                               opt to send RESULT and/or STATUS messages to the client.  The server side
+                               transport mechanism is not concerned with these messages.  The server only 
+                               needs to be notified when the REQUEST has been sucessfully completed. */
+
+               if( message.type == DISCONNECT )
+
+                       Virtual session has ended. Return to outer loop.
+
+
+       } // Sessin loop
+
+} // Main server loop
+
+
+
+               </pre>
+
+
+               <br>
+               <h1> XML Examples</h1>
+               <br>
+
+
+               <h2> Protocol Element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="1" name="protocol"/>
+
+               </pre>
+
+               <h2> threadTrace Element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="1" name="threadTrace"/>
+
+               </pre>
+
+               <h2> CONNECT Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="CONNECT" name="type"/>
+       &lt;oils:domainObjectAttr value="1" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+&lt;/oils:domainObject>
+
+               </pre>
+
+
+               <h2> DISCONNECT Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="DISCONNECT" name="type"/>
+       &lt;oils:domainObjectAttr value="0" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> STATUS Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="STATUS" name="type"/>
+       &lt;oils:domainObjectAttr value="0" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsConnectStatus">
+               &lt;oils:domainObjectAttr value="Connection Successful" name="status"/>
+               &lt;oils:domainObjectAttr value="200" name="statusCode"/>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> REQUEST Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="REQUEST" name="type"/>
+       &lt;oils:domainObjectAttr value="4" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsMethod">
+               &lt;oils:domainObjectAttr value="mult" name="method"/>
+               &lt;oils:params>[1, 2]&lt;/oils:params>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> RESULT Message </h2>
+               
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="RESULT" name="type"/>
+       &lt;oils:domainObjectAttr value="4" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsResult">
+               &lt;oils:domainObjectAttr value="OK" name="status"/>
+               &lt;oils:domainObjectAttr value="200" name="statusCode"/>
+               &lt;oils:domainObject name="oilsScalar">2&lt;/oils:domainObject>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+               
+
+       </body>
+
+</html>
+
+
diff --git a/trunk/doc/Persist-API.html b/trunk/doc/Persist-API.html
new file mode 100644 (file)
index 0000000..657e206
--- /dev/null
@@ -0,0 +1,651 @@
+<html>
+       <head>
+               <title>OpenSRF Persistence Application API-Namespace</title>
+               <style><!--
+
+                       * {             font-family: sans-serif;
+                                       font-size: 13pt;
+                       }
+
+                       .sectionhead {  border: solid black 1px;
+                                       background-color: #333333;
+                                       font-weight: bold;
+                                       color: white;
+                                       text-align: center;
+                                       padding: 5px;
+                                       margin: 5px;
+                       }
+
+                       .section {      border: solid black 1px;
+                                       background-color: lightgray;
+                                       color: black;
+                                       padding: 5px;
+                                       margin: 5px;
+                                       margin-bottom: 15px;
+                       }
+
+                       .listheader {   font-weight: bold;
+                                       margin-top: 15px
+                       }
+                       
+                       .value {        font-style: italic;
+                                       margin-top: 5px;
+                                       margin-left: 20px;
+                       }
+
+                       .description {  margin-top: 0px;
+                                       margin-left: 30px;
+                       }
+
+                       //-->
+               </style>
+
+       </head>
+       <body>
+               <center>
+                       <h2>OpenSRF Persistence Application API-Namespace</h2>
+                       <hr width="90%">
+               </center>
+
+               <div style="margin: 30px;">
+                       The Persistence Application provides other OpenSRF Applications with a standard API for
+                       sharing and caching data.  These data are stored in Slots, and there are three basic
+                       interfaces for interacting with Slots:  QUEUEs, STACKs and OBJECTs.
+               </div>
+
+               <ul>
+                       <li> <h3>General Persistence Slot methods</h3>
+
+                       Methods used to create, set up and destroy slots.  
+                       <br><br>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.slot.create( slot_name )</div>
+
+                               Creates a Persistence Slot.
+
+                               <div class="listheader">Parameters:</div>
+                               <div class="value">slot_name (optional)</div>
+                               <div class="description">
+                                       The name of the Persistence Slot to create.  If a name is not specified
+                                       then the Slot is given a generic, unique name.  Automatically named Slots
+                                       are destroyed as soon as they are empty.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The name of the Slot that was created.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.slot.create_expirable( slot_name, expire_interval )</div>
+
+                               Creates a Persistence Slot that is automatically destroyed after the specified interval.
+
+                               <div class="listheader">Parameters:</div>
+                               <div class="value">slot_name</div>
+                               <div class="description">The name of the Persistence Slot to create.</div>
+                               <div class="value">expire_interval</div>
+                               <div class="description">
+                                       An interval describing how long to wait after an access has
+                                       occured on the Slot before automatically destroying it.  The interval
+                                       can be specified using a fairly complex, human readable format, or as
+                                       a number of seconds.  For example:
+                                       <ul>
+                                               <li> 1 day, 2 hours and 35 minutes </li>
+                                               <li> +2h </li>
+                                               <li> 1 week </li>
+                                               <li> 300 </li>
+                                       </ul>
+
+                                       A setting of 0 (zero) disables automatic expiration for a Slot.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The name of the Slot that was created.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.slot.set_expire( slot_name, expire_interval )</div>
+
+                               Sets or disables the expiration interval on an existing Persistence Slot.
+
+                               <div class="listheader">Parameters:</div>
+                               <div class="value">slot_name</div>
+                               <div class="description">The name of the Persistence Slot to update.</div>
+                               <div class="value">expire_interval</div>
+                               <div class="description">
+                                       An interval describing how long to wait after an access has
+                                       occured on the Slot before automatically destroying it.  The interval
+                                       can be specified using a fairly complex, human readable format, or as
+                                       a number of seconds.  For example:
+                                       <ul>
+                                               <li> 1 day, 2 hours and 35 minutes </li>
+                                               <li> +2h </li>
+                                               <li> 1 week </li>
+                                               <li> 300 </li>
+                                       </ul>
+
+                                       A setting of 0 (zero) disables automatic expiration for a Slot.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The number of seconds the requested interval represents.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.slot.destroy( slot_name )</div>
+
+                               Destroys a Persistence Slot.
+
+                               <div class="listheader">Parameters:</div>
+                               <div class="value">slot_name</div>
+                               <div class="description">The name of the Persistence Slot to destroy.</div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The name of the Slot that was destroyed.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <br>
+                       </li>
+
+                       <li> <h3>QUEUE API-Namespace Slot methods</h3>
+
+                       Uses the Slot in FIFO mode, pushing values onto one end an pulling them off the other.
+                       The QUEUE API-Namespace is useful for creating an ordered message passing access point.
+
+                       <br><br>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.queue.push( slot_name, object )</div>
+
+                               Adds an object to a Slot in FIFO order.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">The name of the Persistence Slot to use for storing the object.</div>
+
+                               <div class="value">object</div>
+                               <div class="description">The object that should be pushed onto the front of the QUEUE.</div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The name of the Slot that was used.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.queue.pop( slot_name )</div>
+
+                               Removes and returns the next value in a QUEUE type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which an object should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The next object on the QUEUE Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.queue.peek( slot_name )</div>
+
+                               Returns the next value in a QUEUE type Slot <u>without</u> removing it.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which an object should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The next object on the QUEUE Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.queue.peek.all( slot_name )</div>
+
+                               Returns all values in a QUEUE type Slot <u>without</u> removing them.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which the objects should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       A stream of all objects on the QUEUE Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.queue.peek.all.atomic( slot_name )</div>
+
+                               Returns all values in a QUEUE type Slot <u>without</u> removing them.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which the objects should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       A single array of all objects on the QUEUE Slot, or an empty
+                                       array if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.queue.length( slot_name )</div>
+
+                               Returns the number of objects in the QUEUE type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot in question.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The number of objects in the Persistence Slot.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.queue.size( slot_name )</div>
+
+                               Returns the number bytes taken up by the JSON encoded version of
+                               the objects in the QUEUE type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot in question.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The space, in bytes, used by the JSON encoded
+                                       objects in the Persistence Slot.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <br>
+                       </li>
+
+                       <li> <h3>STACK style Slot methods</h3>
+
+                       Uses the Slot in FILO mode, pushing and pulling objects at the same end of a list.
+                       The STACK API-Namespace is useful for creating a global Application context stack.
+
+                       <br><br>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.stack.push( slot_name, object )</div>
+
+                               Adds an object to a Slot in FILO order.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">The name of the Persistence Slot to use for storing the object.</div>
+
+                               <div class="value">object</div>
+                               <div class="description">The object that should be pushed onto the front of the STACK.</div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The name of the Slot that was used.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.stack.pop( slot_name )</div>
+
+                               Removes and returns the next value in a STACK type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which an object should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The next object on the STACK Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.stack.peek( slot_name )</div>
+
+                               Returns the next value in a STACK type Slot <u>without</u> removing it.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which an object should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The next object on the STACK Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.stack.peek.all( slot_name )</div>
+
+                               Returns all values in a STACK type Slot <u>without</u> removing them.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which the objects should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       A stream of all objects on the STACK Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.stack.peek.all.atomic( slot_name )</div>
+
+                               Returns all values in a STACK type Slot <u>without</u> removing them.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which the objects should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       A single array of all objects on the STACK Slot, or an empty
+                                       array if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.stack.depth( slot_name )</div>
+
+                               Returns the number of objects in the STACK type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot in question.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The number of objects in the Persistence Slot.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.stack.size( slot_name )</div>
+
+                               Returns the number bytes taken up by the JSON encoded version of
+                               the objects in the STACK type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot in question.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The space, in bytes, used by the JSON encoded
+                                       objects in the Persistence Slot.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <br>
+                       </li>
+
+                       <li> <h3>OBJECT style Slot methods</h3>
+
+                       Uses the Slot in Single Object mode, storing a single object in the Slot.
+
+                       <br><br>
+
+                       The OBJECT API-Namespace is useful for globally caching unique objects.
+
+                       <br><br>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.object.set( slot_name, object )</div>
+
+                               Sets the value of a Slot.  If the Slot has been used in STACK or QUEUE
+                               mode and <b>opensrf.persist.object.set</b> is called then all objects currently
+                               in the Slot will be lost.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">The name of the Persistence Slot to use for storing the object.</div>
+
+                               <div class="value">object</div>
+                               <div class="description">The object that should be set as the one object in the Slot.</div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">The name of the Slot that was used.</div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.object.get( slot_name )</div>
+
+                               Removes and returns the value in an OBJECT type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which the object should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The object in the OBJECT Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.object.peek( slot_name )</div>
+
+                               Returns the value in an OBJECT type Slot <u>without</u> removing it.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot from which the object should be retrieved.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The object on the OBJECT Slot, or an empty
+                                       (NULL) result if the Slot is empty.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       <div class="section">
+                               <div class="sectionhead">opensrf.persist.object.size( slot_name )</div>
+
+                               Returns the number bytes taken up by the JSON encoded version of
+                               the object in the OBJECT type Slot.
+
+                               <div class="listheader">Parameters:</div>
+
+                               <div class="value">slot_name</div>
+                               <div class="description">
+                                       The name of the Persistence Slot in question.
+                               </div>
+
+
+                               <div class="listheader">Returns:</div>
+                               
+                               <div class="value">Success</div>
+                               <div class="description">
+                                       The space, in bytes, used by the JSON encoded
+                                       object in the Persistence Slot.
+                               </div>
+
+                               <div class="value">Failure</div>
+                               <div class="description">An empty (NULL) result.</div>
+                       </div>
+
+                       </li>
+               </ul>
+       </body>
+</html>
diff --git a/trunk/doc/Roadmap.txt b/trunk/doc/Roadmap.txt
new file mode 100644 (file)
index 0000000..f86ccd0
--- /dev/null
@@ -0,0 +1,51 @@
+Roadmap for OpenSRF === The high points as of Febuary 2005
+----------------------------------------------------------
+
+We will attempt to keep this file up to date as requirements for
+Open-ILS change over time, as they are certain to...
+
+==========
+
+# Version 0.1 (January 2005) -- Evolving CVS version ... stay tuned
+       
+       * Session failure recovery
+       * Initial specification for Application API
+       * Basic Application support
+       * Sub-requests within one Application
+
+# Version 0.2 (Febuary/March 2005) -- First full release as a package
+
+       * Centralized Application configuration management
+       * Automatic cross-server API discovery
+       * Transparent remote server sub-requests
+       * Object Persistence Application implementing Stacks, Queues
+         and Object Stores based on SQLite
+
+# Version 0.3 (March/April 2005) -- Version for use in the Alpha
+                                    release of Open-ILS
+
+       * Partial implementation of a BROADCAST mode for addressing all
+         Servers of a particular Class on one Router
+       * Distributed version of the Object Persistence Application
+
+# Version 0.5 (July/August 2005)
+
+       * Full implementation of a BROADCAST mode for addressing all
+         Servers of any number of Classes on any number of Routers
+       * Client side use of transparent API discovery; the client
+         libraries will automatically discover and use the correct
+         Server for any valid method request
+
+# Version 0.7 (October/November 2005)
+
+       * Basic inter-server session migration support in addition
+         to session failure recovery due to server failure
+
+# Version 0.9 (Some time in 2006)
+
+       * Built in distributed transaction support.
+
+# Version 1.0 (in the future)
+
+       * Who knows?
+
diff --git a/trunk/doc/dokuwiki-doc-stubber.pl b/trunk/doc/dokuwiki-doc-stubber.pl
new file mode 100755 (executable)
index 0000000..c098cc8
--- /dev/null
@@ -0,0 +1,114 @@
+#!/usr/bin/perl -w
+use OpenSRF::System qw(SYSCONFDIR/opensrf_core.xml);
+use Getopt::Long
+
+$| = 1;
+
+my $cvs_base = 'http://open-ils.org/cgi-bin/viewcvs.cgi/ILS/Open-ILS/src/perlmods/';
+my $nest = 0;
+my $service;
+my $filter;
+my $sort_ignore;
+
+GetOptions(    'cvs_base=s'    => \$cvs_base,
+               'nest'          => \$nest,
+               'service=s'     => \$service,
+               'ignore=s'      => \$sort_ignore,
+               'filter=s'      => \$filter,
+);
+
+unless( $service ) {
+       print "usage: $0 -s <service name> [-c <cvs repo base URL> -f <regex filter for method names> -n]\n";
+       exit;
+}
+
+OpenSRF::System->bootstrap_client();
+my $session = OpenSRF::AppSession->create( $service );
+
+my $req; 
+if ($filter) {
+       $req = $session->request('opensrf.system.method', $filter);
+} else {
+       $req = $session->request('opensrf.system.method.all');
+}
+
+my $count = 1;
+my %m;
+while( my $meth = $req->recv(60) ) {
+       $meth = $meth->content;
+
+       $api_name = $meth->{api_name};
+
+       $m{$api_name}{api_name} = $meth->{api_name};
+
+       $m{$api_name}{package} = $meth->{package};
+       $m{$api_name}{method} = $meth->{method};
+
+       $m{$api_name}{api_level} = int $meth->{api_level};
+       $m{$api_name}{server_class} = $meth->{server_class} || '**ALL**';
+       $m{$api_name}{stream} = int($meth->{stream} || 0);
+       $m{$api_name}{cachable} = int($meth->{cachable} || 0);
+
+       $m{$api_name}{note} = $meth->{note} || 'what I do';
+       ($m{$api_name}{cvs} = $m{$api_name}{package}) =~ s/::/\//go;
+
+       $m{$api_name}{stream} = $m{$api_name}{stream}?'Yes':'No';
+       $m{$api_name}{cachable} = $m{$api_name}{cachable}?'Yes':'No';
+
+       print STDERR "." unless ($count % 10);
+
+       $count++;
+}
+
+warn "\nThere are ".scalar(keys %m)." methods published by $service\n";
+
+my @m_list;
+if (!$sort_ignore) {
+       @m_list = sort keys %m;
+} else {
+       @m_list =
+               map { ($$_[0]) }
+               sort {
+                       $$a[1] cmp $$b[1]
+                               ||
+                       length($$b[0]) <=> length($$a[0])
+               } map {
+                       [$_ =>
+                       do {
+                               (my $x = $_) =~ s/^$sort_ignore//go;
+                               $x;
+                       } ]
+               } keys %m;
+}
+
+for my $meth ( @m_list ) {
+
+       my $pad = 0;
+       my $header = '=====';
+       if ($nest) {
+               no warnings;
+               (my $x = $meth) =~ s/\./$pad++;$1/eg;
+       }
+       $pad = ' 'x$pad;
+
+       print <<"       METHOD";
+$pad$header $meth $header
+
+$m{$meth}{note}
+
+  * [[osrf-devel:terms#opensrf_api-level|API Level]]: $m{$meth}{api_level}
+  * [[osrf-devel:terms#opensrf_server_class|Server Class]]: $m{$meth}{server_class}
+  * Implementation Method: [[$cvs_base/$m{$meth}{cvs}.pm|$m{$meth}{package}\::$m{$meth}{method}]]
+  * Streaming [[osrf-devel:terms#opensrf_method|Method]]: $m{$meth}{stream}
+  * Cachable [[osrf-devel:terms#opensrf_method|Method]]: $m{$meth}{cachable}
+
+  * **Parameters:**
+    * //param1//\\\\ what it is...
+  * **Returns:**
+    * //Success//\\\\ successful format
+    * //Failure//\\\\ failure format (exception, etc)
+
+
+       METHOD
+}
+
diff --git a/trunk/examples/fieldmapper2cdbi.xsl b/trunk/examples/fieldmapper2cdbi.xsl
new file mode 100644 (file)
index 0000000..f6ad904
--- /dev/null
@@ -0,0 +1,79 @@
+<xsl:stylesheet
+       version='1.0'
+       xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+       xmlns:opensrf="http://opensrf.org/xmlns/opensrf"
+       xmlns:cdbi="http://opensrf.org/xmlns/opensrf/cdbi"
+       xmlns:database="http://opensrf.org/xmlns/opensrf/database"
+       xmlns:perl="http://opensrf.org/xmlns/opensrf/perl"
+       xmlns:javascript="http://opensrf.org/xmlns/opensrf/javascript"
+       xmlns:c="http://opensrf.org/xmlns/opensrf/c">
+       <xsl:output method="text" />
+       <xsl:strip-space elements="*"/>
+
+       <xsl:template match="/">
+               <xsl:apply-templates select="opensrf:fieldmapper/opensrf:classes"/>
+1;
+       </xsl:template>
+
+
+       <!-- sub-templates -->
+       <xsl:template match="opensrf:classes">
+               <xsl:for-each select="opensrf:class">
+                       <xsl:sort select="@id"/>
+                       <xsl:apply-templates select="."/>
+               </xsl:for-each>
+               <xsl:apply-templates select="opensrf:class/opensrf:links/opensrf:link[@type='has_a']"/>
+               <xsl:apply-templates select="opensrf:class/opensrf:links/opensrf:link[@type='has_many']"/>
+       </xsl:template>
+
+
+       
+       <xsl:template match="opensrf:class">
+               #-------------------------------------------------------------------------------
+               # <xsl:value-of select="$driver"/> Class definition for "<xsl:value-of select="@id"/>" (<xsl:value-of select="cdbi:class"/>)
+               #-------------------------------------------------------------------------------
+               package <xsl:value-of select="@cdbi:class"/>;
+               use base '<xsl:value-of select="cdbi:superclass"/>';
+
+               __PACKAGE__->table("<xsl:value-of select="database:table[@rdbms=$driver]/database:name"/>");
+               <xsl:if test="database:table[@rdbms=$driver]/database:sequence">
+                       __PACKCAGE__->sequence("<xsl:value-of select="database:table[@rdbms=$driver]/database:sequence"/>");
+               </xsl:if>
+
+               __PACKAGE__->columns(Primary => <xsl:apply-templates select="opensrf:fields/opensrf:field[@database:primary='true']"/>);
+               <xsl:if test="opensrf:fields/opensrf:field[@database:required='true' and not(@database:primary='true')]">
+                       __PACKAGE__->columns(
+                               Essential => <xsl:apply-templates
+                                               select="opensrf:fields/opensrf:field[@database:required='true' and not(@database:primary='true')]"/>
+                       );
+               </xsl:if>
+               <xsl:if test="opensrf:fields/opensrf:field[not(@database:required='true') and not(@database:primary='true')]">
+                       __PACKAGE__->columns(
+                               Others => <xsl:apply-templates
+                                               select="opensrf:fields/opensrf:field[not(@database:required='true') and not(@database:primary='true')]"/>
+                       );
+               </xsl:if>
+       </xsl:template>
+
+
+
+       <xsl:template match="database:table">
+       </xsl:template>
+
+
+
+       <xsl:template match="opensrf:field">
+               '<xsl:value-of select='@name'/>',
+       </xsl:template>
+
+
+
+       <xsl:template match="opensrf:link">
+               <xsl:variable name='source' select='@source'/>
+               <xsl:value-of select="../../@cdbi:class"/>-><xsl:value-of select="@type"/>(
+                       <xsl:value-of select="@field"/> => '<xsl:value-of select="//*[@id=$source]/@cdbi:class"/>'
+               );
+
+       </xsl:template>
+</xsl:stylesheet>
+
diff --git a/trunk/examples/fieldmapper2javascript.xsl b/trunk/examples/fieldmapper2javascript.xsl
new file mode 100644 (file)
index 0000000..e2f8e1d
--- /dev/null
@@ -0,0 +1,297 @@
+<xsl:stylesheet
+       version='1.0'
+       xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+       xmlns:opensrf="http://opensrf.org/xmlns/opensrf"
+       xmlns:cdbi="http://opensrf.org/xmlns/opensrf/cdbi"
+       xmlns:database="http://opensrf.org/xmlns/opensrf/database"
+       xmlns:perl="http://opensrf.org/xmlns/opensrf/perl"
+       xmlns:javascript="http://opensrf.org/xmlns/opensrf/javascript"
+       xmlns:c="http://opensrf.org/xmlns/opensrf/c">
+       <xsl:output method="text" />
+       <xsl:strip-space elements="xsl:*"/>
+       <xsl:variable name="last_field_pos"/>
+
+       <xsl:template match="/">
+// support functions
+var IE = false;
+var unit_test = false;
+function instanceOf(object, constructorFunction) {
+       if(!IE) {
+               while (object != null) {
+                       if (object == constructorFunction.prototype)
+                               return true;
+                       object = object.__proto__;
+               }
+       } else {
+               while(object != null) {
+                       if( object instanceof constructorFunction )
+                               return true;
+                       object = object.__proto__;
+               }
+       }
+       return false;
+}
+// Top level superclass
+function Fieldmapper(array) {
+        this.array = [];
+        if(array) {
+                if (array.constructor == Array) {
+                        this.array = array;
+                } else if ( instanceOf( array, String ) || instanceOf( array, Number ) ) {
+
+                        var obj = null;
+                        if (this.cacheable) {
+                                try {
+                                        obj = this.baseClass.obj_cache[this.classname][array];
+                                } catch (E) {};
+                        }
+
+                        if (!obj) {
+                                obj = user_request(
+                                        'open-ils.proxy',
+                                        'open-ils.proxy.proxy',
+                                        [
+                                                mw.G.auth_ses[0],
+                                                'open-ils.storage',
+                                                'open-ils.storage.direct.' + this.db_type + '.retrieve',
+                                                array
+                                        ]
+                                )[0];
+
+                                if (this.cacheable) {
+                                        if (this.baseClass.obj_cache[this.classname] == null)
+                                                this.baseClass.obj_cache[this.classname] = {};
+
+                                        this.baseClass.obj_cache[this.classname][obj.id()] = obj;
+                                }
+                        }
+                        this.array = obj.array;
+
+                } else {
+                        throw new FieldmapperException( "Attempt to build fieldmapper object with something wierd");
+                }
+        }
+}
+Fieldmapper.prototype.baseClass = Fieldmapper;
+Fieldmapper.prototype.obj_cache = {};
+
+Fieldmapper.prototype.search function (type,field,value) {
+
+       var list = user_request(
+               'open-ils.proxy',
+               'open-ils.proxy.proxy',
+               [
+                       mw.G.auth_ses[0],
+                       'open-ils.storage',
+                       'open-ils.storage.direct.' + type.db_type + '.search.' + field,
+                       array
+               ]
+       )[0];
+       if (type.cacheable) {
+               if (type.baseClass.obj_cache[type.classname] == null)
+                       type.baseClass.obj_cache[type.classname] = {};
+               for (var i in list) {
+                       type.baseClass.obj_cache[type.classname][list[i].id()] = list[i];
+               }
+       }
+       return list;
+}
+Fieldmapper.prototype.clone = function() {
+        var obj = new this.constructor();
+
+        for( var i in this.array ) {
+                var thing = this.array[i];
+                if(thing == null) continue;
+
+                if( thing._isfieldmapper ) {
+                        obj.array[i] = thing.clone();
+                } else {
+
+                        if(instanceOf(thing, Array)) {
+                                obj.array[i] = new Array();
+
+                                for( var j in thing ) {
+
+                                        if( thing[j]._isfieldmapper )
+                                                obj.array[i][j] = thing[j].clone();
+                                        else
+                                                obj.array[i][j] = thing[j];
+                                }
+                        } else {
+                                obj.array[i] = thing;
+                        }
+                }
+        }
+        return obj;
+}
+  
+function FieldmapperException(message) {
+        this.message = message;
+}
+
+FieldmapperException.toString = function() {
+        return "FieldmapperException: " + this.message + "\n";
+
+}
+
+       
+               <xsl:apply-templates select="opensrf:fieldmapper/opensrf:classes"/>
+       </xsl:template>
+
+
+
+
+
+<!-- sub-templates -->
+       <xsl:template match="opensrf:fieldmapper/opensrf:classes">
+               <xsl:for-each select="opensrf:class">
+                       <xsl:sort select="@id"/>
+                       <xsl:apply-templates select="."/>
+               </xsl:for-each>
+       </xsl:template>
+
+
+
+
+       <xsl:template match="opensrf:class">
+               <xsl:apply-templates select="@javascript:class"/>
+               <xsl:apply-templates select="opensrf:fields"/>
+               <xsl:apply-templates select="opensrf:links/opensrf:link[@type='has_many']"/>
+       </xsl:template>
+
+
+
+
+       <xsl:template match="opensrf:fields">
+               <xsl:apply-templates select="opensrf:field"/>
+       </xsl:template>
+
+
+
+
+
+       <xsl:template match="@javascript:class">
+
+// Class definition for "<xsl:value-of select="."/>"
+
+function <xsl:value-of select="."/> (array) {
+
+       if (!instanceOf(this, <xsl:value-of select="."/>))
+               return new <xsl:value-of select="."/>(array);
+
+       this.baseClass.call(this,array);
+       this.classname = "<xsl:value-of select="."/>";
+       this._isfieldmapper = true;
+       this.uber = <xsl:value-of select="."/>.baseClass.prototype;
+}
+
+<xsl:value-of select="."/>.prototype                   = new <xsl:value-of select="../javascript:superclass"/>();
+<xsl:value-of select="."/>.prototype.constructor       = <xsl:value-of select="."/>;
+<xsl:value-of select="."/>.baseClass                   = <xsl:value-of select="../javascript:superclass"/>;
+<xsl:value-of select="."/>.prototype.cachable          = true;
+<xsl:value-of select="."/>.prototype.fields            = [];
+<xsl:value-of select="."/>.last_real_field             = 2;
+<!-- XXX This needs to come from somewhere else!!!! -->
+<xsl:value-of select="."/>.prototype.db_type           = "<xsl:value-of select="../database:table[@rdbms='Pg']/database:name"/>";
+<xsl:value-of select="."/>.prototype.isnew = function(new_value) {
+        if(arguments.length == 1) { this.array[0] = new_value; }
+        return this.array[0];
+}
+<xsl:value-of select="."/>.prototype.ischanged = function(new_value) {
+        if(arguments.length == 1) { this.array[1] = new_value; }
+        return this.array[1];
+}
+<xsl:value-of select="."/>.prototype.isdeleted = function(new_value) {
+        if(arguments.length == 1) { this.array[2] = new_value; }
+        return this.array[2];
+}
+       </xsl:template>
+
+
+
+
+
+       <!-- scalar valued fields and "has_a" relationships -->
+       <xsl:template match="opensrf:field">
+
+               <xsl:variable name="num"><xsl:number/></xsl:variable>
+               <xsl:variable name="field_pos" select="$num + 2"/>
+               <xsl:variable name="last_field_pos" select="$field_pos + 1"/>
+               <xsl:variable name="field_name" select="@name"/>
+
+// Accessor/mutator for <xsl:value-of select="../../@javascript:class"/>.<xsl:value-of select="$field_name"/>:
+<xsl:value-of select="../../@javascript:class"/>.last_real_field++;
+<xsl:value-of select="../../@javascript:class"/>.prototype.fields.push("<xsl:value-of select="$field_name"/>");
+<xsl:value-of select="../../@javascript:class"/>.prototype.<xsl:value-of select="$field_name"/> = function (new_value) {
+        if(arguments.length == 1) { this.array[<xsl:value-of select="$field_pos"/>] = new_value; }
+
+               <xsl:if test="../../opensrf:links/opensrf:link[@field=$field_name and @type='has_a']">
+                       <!-- We have a fkey on this field.  Go fetch the referenced object. -->
+                       <xsl:variable
+                               name="source"
+                               select="../../opensrf:links/opensrf:link[@field=$field_name and @type='has_a']/@source"/>
+
+        var val = this.array[<xsl:value-of select="$field_pos"/>];
+
+        if (!instanceOf(this.array[<xsl:value-of select="$field_pos"/>], <xsl:value-of select="$source"/>)) {
+               if (this.array[<xsl:value-of select="$field_pos"/>] != null) {
+                       this.array[<xsl:value-of select="$field_pos"/>] = new <xsl:value-of select="$source"/>(val);
+               }
+       }
+               </xsl:if>
+       return this.array[<xsl:value-of select="$field_pos"/>];
+}
+       </xsl:template>
+
+
+
+
+
+       <!-- "has_many" relationships -->
+       <xsl:template match="opensrf:links/opensrf:link[@type='has_many']">
+               <xsl:variable name="num"><xsl:number/></xsl:variable>
+               <xsl:variable name="source"><xsl:value-of select="@source"/></xsl:variable>
+               <xsl:variable name="classname"><xsl:value-of select="../../@javascript:class"/></xsl:variable>
+               <xsl:variable name="id"><xsl:value-of select="../../@id"/></xsl:variable>
+               <xsl:variable name="fkey" select="//*[@id=$source]/opensrf:links/opensrf:link[@type='has_a' and @source=$id]/@field"/>
+               <xsl:variable name="pkey" select="../../opensrf:fields/opensrf:field[@database:primary='true']/@name"/>
+
+// accessor for <xsl:value-of select="$classname"/>:
+<xsl:value-of select="$classname"/>.prototype.<xsl:value-of select="@field"/> = function () {
+       var _pos = <xsl:value-of select="$classname"/>.last_real_field + <xsl:value-of select="$num"/>;
+       if (!instanceOf(this.array[_pos], Array)) {
+               this.array[_pos] = [];
+       if (this.array[_pos].length == 0) {
+               /* get the real thing.
+                * search where <xsl:value-of select="$source"/>.<xsl:value-of select="$fkey"/>()
+                * equals this.<xsl:value-of select="../../opensrf:fields/opensrf:field[@database:primary='true']/@name"/>();
+                */
+               this.array[_pos] = this.uber.search(
+                       <xsl:value-of select="$source"/>,
+                       "<xsl:value-of select="$fkey"/>",
+                       this.<xsl:value-of select="$pkey"/>()
+               );
+       }
+       return this.array[_pos];
+}
+
+       </xsl:template>
+
+
+
+
+</xsl:stylesheet>
+
diff --git a/trunk/examples/fieldmapper2perl.xsl b/trunk/examples/fieldmapper2perl.xsl
new file mode 100644 (file)
index 0000000..8ef601e
--- /dev/null
@@ -0,0 +1,225 @@
+<xsl:stylesheet
+       version='1.0'
+       xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+       xmlns:opensrf="http://opensrf.org/xmlns/opensrf"
+       xmlns:cdbi="http://opensrf.org/xmlns/opensrf/cdbi"
+       xmlns:database="http://opensrf.org/xmlns/opensrf/database"
+       xmlns:perl="http://opensrf.org/xmlns/opensrf/perl"
+       xmlns:javascript="http://opensrf.org/xmlns/opensrf/javascript"
+       xmlns:c="http://opensrf.org/xmlns/opensrf/c">
+       <xsl:output method="text" />
+       <xsl:strip-space elements="xsl:*"/>
+       <xsl:variable name="last_field_pos"/>
+
+       <xsl:template match="/">
+package Fieldmapper;
+use OpenSRF::Utils::JSON;
+use Data::Dumper;
+use base 'OpenSRF::Application';
+
+use OpenSRF::Utils::Logger;
+my $log = 'OpenSRF::Utils::Logger';
+
+sub new {                                                            
+        my $self = shift;                                            
+        my $value = shift;                                              
+       if (defined $value) {
+               if (!ref($value) or ref($value) ne 'ARRAY') {
+                       # go fetch the object by id...
+               }
+        } else {
+               $value = [];
+       }
+        return bless $value => $self->class_name;                    
+}
+
+sub decast {
+        my $self = shift;
+        return [ @$self ];
+}       
+
+sub DESTROY {} 
+
+sub class_name {
+        my $class_name = shift;
+        return ref($class_name) || $class_name;
+}
+
+sub isnew { return $_[0][0]; }
+sub ischanged { return $_[0][1]; }
+sub isdeleted { return $_[0][2]; }
+
+
+               <xsl:apply-templates select="opensrf:fieldmapper/opensrf:classes"/>
+
+1;
+       </xsl:template>
+
+
+
+
+
+<!-- sub-templates -->
+       <xsl:template match="opensrf:fieldmapper/opensrf:classes">
+               <xsl:for-each select="opensrf:class">
+                       <xsl:sort select="@id"/>
+                       <xsl:apply-templates select="."/>
+                       <xsl:apply-templates select="opensrf:links/opensrf:link[@type='has_many']"/>
+               </xsl:for-each>
+       </xsl:template>
+
+
+
+
+       <xsl:template match="opensrf:class">
+               <xsl:apply-templates select="@perl:class"/>
+               <xsl:apply-templates select="opensrf:fields"/>
+       </xsl:template>
+
+
+
+
+       <xsl:template match="opensrf:fields">
+               <xsl:apply-templates select="opensrf:field"/>
+       </xsl:template>
+
+
+
+
+
+       <xsl:template match="@perl:class">
+#-------------------------------------------------------------------------------
+# Class definition for "<xsl:value-of select="."/>"
+#-------------------------------------------------------------------------------
+package <xsl:value-of select="."/>;
+use base "<xsl:value-of select="../perl:superclass"/>";
+
+{      my @real;
+       sub real_fields {
+               push @real, @_ if (@_);
+               return @real;
+       }
+}
+
+{      my $last_real;
+       sub last_real_field : lvalue {
+               $last_real;
+       }
+}
+
+       <xsl:if test="../@cdbi:class">
+sub cdbi {
+       return "<xsl:value-of select="../@cdbi:class"/>";
+}
+       </xsl:if>
+
+sub json_hint {
+        return "<xsl:value-of select="../@id"/>";
+}
+
+
+sub is_virtual {
+       <xsl:choose>
+               <xsl:when test="../@virutal">
+       return 1;
+               </xsl:when>
+               <xsl:otherwise>
+       return 0;
+               </xsl:otherwise>
+       </xsl:choose>
+}
+
+       </xsl:template>
+
+
+
+
+
+       <!-- scalar valued fields and "has_a" relationships -->
+       <xsl:template match="opensrf:field">
+
+               <xsl:variable name="num"><xsl:number/></xsl:variable>
+               <xsl:variable name="field_pos" select="$num + 2"/>
+               <xsl:variable name="last_field_pos" select="$field_pos + 1"/>
+               <xsl:variable name="field_name" select="@name"/>
+               <xsl:variable name="classname" select="../../@perl:class"/>
+
+# Accessor/mutator for <xsl:value-of select="$classname"/>::<xsl:value-of select="$field_name"/>:
+__PACKAGE__->last_real_field()++;
+__PACKAGE__->real_fields("<xsl:value-of select="$field_name"/>");
+sub <xsl:value-of select="$field_name"/> {
+       my $self = shift;
+       my $new_val = shift;
+       $self->[<xsl:value-of select="$field_pos"/>] = $new_val if (defined $new_val);
+
+               <xsl:if test="../../opensrf:links/opensrf:link[@field=$field_name and @type='has_a']">
+                       <!-- We have a fkey on this field.  Go fetch the referenced object. -->
+                       <xsl:variable
+                               name="source"
+                               select="../../opensrf:links/opensrf:link[@field=$field_name and @type='has_a']/@source"/>
+                       <xsl:variable
+                               name="sourceclass"
+                               select="//*[@id=$source]/@perl:class"/>
+
+        my $val = $self->[<xsl:value-of select="$field_pos"/>];
+
+       if (defined $self->[<xsl:value-of select="$field_pos"/>]) {
+               if (!UNIVERSAL::isa($self->[<xsl:value-of select="$field_pos"/>], <xsl:value-of select="$sourceclass"/>)) {
+                       $self->[<xsl:value-of select="$field_pos"/>] = <xsl:value-of select="$sourceclass"/>->new($val);
+               }
+       }
+               </xsl:if>
+
+       return $self->[<xsl:value-of select="$field_pos"/>];
+}
+
+
+sub clear_<xsl:value-of select="$field_name"/> {
+       my $self = shift;
+       $self->[<xsl:value-of select="$field_pos"/>] = undef;
+       return 1;
+}
+
+       </xsl:template>
+
+
+
+
+
+
+       <!-- "has_many" relationships -->
+       <xsl:template match="opensrf:links/opensrf:link[@type='has_many']">
+               <xsl:variable name="num"><xsl:number/></xsl:variable>
+               <xsl:variable name="source"><xsl:value-of select="@source"/></xsl:variable>
+               <xsl:variable name="sourceclass"><xsl:value-of select="//*[@id=$source]/@perl:class"/></xsl:variable>
+               <xsl:variable name="classname"><xsl:value-of select="../../@perl:class"/></xsl:variable>
+               <xsl:variable name="id"><xsl:value-of select="../../@id"/></xsl:variable>
+               <xsl:variable name="fkey" select="//*[@id=$source]/opensrf:links/opensrf:link[@type='has_a' and @source=$id]/@field"/>
+               <xsl:variable name="pkey" select="../../opensrf:fields/opensrf:field[@database:primary='true']/@name"/>
+
+# accessor for <xsl:value-of select="$classname"/>::<xsl:value-of select="@field"/>:
+sub <xsl:value-of select="@field"/> {
+       my $self = shift;
+       my $_pos = <xsl:value-of select="$classname"/>->last_real_field + <xsl:value-of select="$num"/>;
+       if (!ref($self->[$_pos]) ne 'ARRAY') {
+               $self->[$_pos] = [];
+       if (@{$self->[$_pos]} == 0) {
+               # get the real thing.
+               # search where <xsl:value-of select="$sourceclass"/>-><xsl:value-of select="$fkey"/> == $self-><xsl:value-of select="$pkey"/>;
+       }
+       return $self->[$_pos];
+}
+
+       </xsl:template>
+
+
+
+
+</xsl:stylesheet>
+
diff --git a/trunk/examples/gen-fieldmapper.xml b/trunk/examples/gen-fieldmapper.xml
new file mode 100644 (file)
index 0000000..2695030
--- /dev/null
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fieldmapper
+       xmlns="http://opensrf.org/xmlns/opensrf"
+       xmlns:cdbi="http://opensrf.org/xmlns/opensrf/cdbi"
+       xmlns:database="http://opensrf.org/xmlns/opensrf/database"
+       xmlns:perl="http://opensrf.org/xmlns/opensrf/perl"
+       xmlns:javascript="http://opensrf.org/xmlns/opensrf/javascript"
+       xmlns:c="http://opensrf.org/xmlns/opensrf/c">
+
+       <classes>
+               <!-- template class definition -->
+               <!--
+               <class
+                       id=""
+                       perl:class=""
+                       cdbi:class=""
+                       javascript:class="">
+       
+                       <database:table rdbms="Pg">
+                               <database:name></database:name>
+                               <database:sequence></database:sequence>
+                       </database:table>
+                       <database:table rdbms="MySQL">
+                               <database:name></database:name>
+                       </database:table>
+       
+                       <javascript:superclass>Fieldmapper</javascript:superclass>
+                       <perl:superclass>Fieldmapper</perl:superclass>
+                       <cdbi:superclass>OpenILS::Storage::CDBI</cdbi:superclass>
+                       
+                       <fields>
+                               <field name="" datatype="" database:primary="" database:required="" default="" database:default=""/>
+                       </fields>
+                       
+                       <links>
+                               <link field="" source="" javascript:list="" type=""/>
+                       </links>
+       
+               </class>
+               -->
+       
+               <class
+                       id="asvr"
+                       virtual="0"
+                       perl:class="Fieldmapper::action::survey_response"
+                       cdbi:class="action::survey_response"
+                       javascript:class="asvr"
+                       c:class="asvr">
+                       <database:table rdbms="Pg">
+                               <database:name>action.survey_response</database:name>
+                               <database:sequence>action.survey_response_id_seq</database:sequence>
+                       </database:table>
+                       <database:table rdbms="MySQL">
+                               <database:name>action_survey_response</database:name>
+                       </database:table>
+       
+                       <javascript:superclass>Fieldmapper</javascript:superclass>
+                       <perl:superclass>Fieldmapper</perl:superclass>
+                       <cdbi:superclass>OpenILS::Storage::CDBI</cdbi:superclass>
+                       
+                       <fields>
+                               <field
+                                       name="id"
+                                       datatype="int"
+                                       database:primary="true" />
+                                       
+                               <field
+                                       name="survey"
+                                       datatype="int"
+                                       database:required="true" />
+
+                               <field
+                                       name="question"
+                                       datatype="int"
+                                       database:required="true" />
+
+                               <field
+                                       name="answer"
+                                       datatype="int"
+                                       database:required="true" />
+
+                               <field
+                                       name="usr"
+                                       datatype="int"
+                                       database:required="true" />
+
+                               <field
+                                       name="response_group_id"
+                                       datatype="int"
+                                       database:required="true" />
+
+                               <field
+                                       name="answser_date"
+                                       datatype="timestamp"
+                                       database:required="true" />
+
+                               <field
+                                       name="effective_date"
+                                       datatype="timestamp"
+                                       database:required="true" />
+
+                       </fields>
+
+                       <links>
+                               <link field="survey" source="asv" type="has_a"/>
+                               <link field="question" source="asvq" type="has_a"/>
+                               <link field="answer" source="asva" type="has_a"/>
+                               <!-- <link field="usr" source="au" type="has_a"/> -->
+                       </links>
+
+               </class>
+
+               <class
+                       id="asvq"
+                       perl:class="Fieldmapper::action::survey_question"
+                       cdbi:class="action::survey_question"
+                       javascript:class="asvq"
+                       c:class="asvq">
+                       <database:table rdbms="Pg">
+                               <database:name>action.survey_question</database:name>
+                               <database:sequence>action.survey_question_id_seq</database:sequence>
+                       </database:table>
+                       <database:table rdbms="MySQL">
+                               <database:name>action_survey_question</database:name>
+                       </database:table>
+       
+                       <javascript:superclass>Fieldmapper</javascript:superclass>
+                       <perl:superclass>Fieldmapper</perl:superclass>
+                       <cdbi:superclass>OpenILS::Storage::CDBI</cdbi:superclass>
+                       
+                       <fields>
+                               <field
+                                       name="id"
+                                       datatype="int"
+                                       database:primary="true" />
+                                       
+                               <field
+                                       name="survey"
+                                       datatype="int"
+                                       database:required="true" />
+
+                               <field
+                                       name="question"
+                                       datatype="text"
+                                       database:required="true" />
+
+                       </fields>
+
+                       <links>
+                               <link field="survey" source="asv" type="has_a"/>
+                               <link field="answers" source="asva" type="has_many"/>
+                       </links>
+               </class>
+
+               <class
+                       id="asva"
+                       perl:class="Fieldmapper::action::survey_answer"
+                       cdbi:class="action::survey_answer"
+                       javascript:class="asva"
+                       c:class="asva">
+                       <database:table rdbms="Pg">
+                               <database:name>action.survey_answer</database:name>
+                               <database:sequence>action.survey_answer_id_seq</database:sequence>
+                       </database:table>
+                       <database:table rdbms="MySQL">
+                               <database:name>action_survey_answer</database:name>
+                       </database:table>
+       
+                       <javascript:superclass>Fieldmapper</javascript:superclass>
+                       <perl:superclass>Fieldmapper</perl:superclass>
+                       <cdbi:superclass>OpenILS::Storage::CDBI</cdbi:superclass>
+                       
+                       <fields>
+                               <field
+                                       name="id"
+                                       datatype="int"
+                                       database:primary="true" />
+                                       
+                               <field
+                                       name="question"
+                                       datatype="int"
+                                       database:required="true" />
+
+                               <field
+                                       name="answer"
+                                       datatype="text"
+                                       database:required="true" />
+
+                       </fields>
+
+                       <links>
+                               <link field="question" source="asvq" type="has_a"/>
+                               <link field="responses" source="asvr" type="has_many"/>
+                       </links>
+               </class>
+
+               <class
+                       id="aou"
+                       perl:class="Fieldmapper::actor::org_unit"
+                       cdbi:class="actor::org_unit"
+                       javascript:class="aou"
+                       c:class="aou">
+                       <database:table rdbms="Pg">
+                               <database:name>actor.org_unit</database:name>
+                               <database:sequence>actor.org_unit_id_seq</database:sequence>
+                       </database:table>
+                       <database:table rdbms="MySQL">
+                               <database:name>actor_org_unit</database:name>
+                       </database:table>
+       
+                       <javascript:superclass>Fieldmapper</javascript:superclass>
+                       <perl:superclass>Fieldmapper</perl:superclass>
+                       <cdbi:superclass>OpenILS::Storage::CDBI</cdbi:superclass>
+                       
+                       <fields>
+                               <field
+                                       name="id"
+                                       datatype="int"
+                                       database:primary="true" />
+                       </fields>
+               </class>
+       
+               <!-- Survey class definition -->
+               <class
+                       id="asv"
+                       perl:class="Fieldmapper::action::survey"
+                       cdbi:class="action::survey"
+                       javascript:class="asv"
+                       c:class="asv">
+                       <database:table rdbms="Pg">
+                               <database:name>action.survey</database:name>
+                               <database:sequence>action.survey_id_seq</database:sequence>
+                       </database:table>
+                       <database:table rdbms="MySQL">
+                               <database:name>action_survey</database:name>
+                       </database:table>
+       
+                       <javascript:superclass>Fieldmapper</javascript:superclass>
+                       <perl:superclass>Fieldmapper</perl:superclass>
+                       <cdbi:superclass>OpenILS::Storage::CDBI</cdbi:superclass>
+
+                       <methods interface='authenticated' service='open-ils.proxy'>
+                               <create method='open-ils.storage.direct.action.survey.create'/>
+                               <retrieve method='open-ils.storage.direct.action.survey.retrieve'/>
+                               <search method='open-ils.storage.direct.action.survey.search'/>
+                               <update method='open-ils.storage.direct.action.survey.update'/>
+                               <delete method='open-ils.storage.direct.action.survey.delete'/>
+                       </methods>
+                       
+                       <methods interface='trusted' service='open-ils.storage'>
+                               <create method='open-ils.storage.direct.action.survey.create'/>
+                               <retrieve method='open-ils.storage.direct.action.survey.retrieve'/>
+                               <search method='open-ils.storage.direct.action.survey.search'/>
+                               <update method='open-ils.storage.direct.action.survey.update'/>
+                               <delete method='open-ils.storage.direct.action.survey.delete'/>
+                       </methods>
+                       
+                       <fields>
+                               <field
+                                       name="id"
+                                       datatype="int"
+                                       database:primary="true" />
+                                       
+                               <field
+                                       name="name"
+                                       datatype="text"
+                                       database:required="true" />
+                                       
+                               <field
+                                       name="description"
+                                       datatype="text"
+                                       database:required="true" />
+                                       
+                               <field
+                                       name="owner"
+                                       datatype="int"
+                                       database:required="true" />
+                                       
+                               <field
+                                       name="start_date"
+                                       datatype="timestamp"
+                                       database:required="true"
+                                       database:default="now()" />
+                                       
+                               <field
+                                       name="end_date"
+                                       datatype="timestamp"
+                                       database:required="true"
+                                       database:default="now() + '1 month'" />
+                                       
+                               <field
+                                       name="usr_summary"
+                                       datatype="bool"
+                                       database:required="true"
+                                       default="t" />
+                                       
+                               <field
+                                       name="opac"
+                                       datatype="bool"
+                                       database:required="true"
+                                       default="f" />
+                                       
+                               <field
+                                       name="poll"
+                                       datatype="bool"
+                                       database:required="true"
+                                       default="f" />
+
+                               <field
+                                       name="required"
+                                       datatype="bool"
+                                       database:required="true"
+                                       default="f" />
+
+                       </fields>
+       
+                       <links>
+                               <link field="questions" source="asvq" type="has_many"/>
+                               <link field="responses" source="asvr" type="has_many"/>
+                               <link field="owner" source="aou" type="has_a"/>
+                       </links>
+       
+               </class>
+
+       </classes>
+</fieldmapper>
+
diff --git a/trunk/examples/math_bench.pl b/trunk/examples/math_bench.pl
new file mode 100755 (executable)
index 0000000..6dd94ac
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+use strict; use warnings;
+use OpenSRF::System;
+use Time::HiRes qw/time/;
+use OpenSRF::Utils::Logger;
+my $log = "OpenSRF::Utils::Logger";
+
+# Test script which runs queries agains the opensrf.math service and reports on
+# the average round trip time of the requests.
+
+# how many batches of 4 requests do we send
+my $count = $ARGV[0];
+print "usage: $0 <num_requests>\n" and exit unless $count;
+
+# * connect to the Jabber network
+OpenSRF::System->bootstrap_client( config_file => "SYSCONFDIR/opensrf_core.xml" );
+$log->set_service('math_bench');
+
+# * create a new application session for the opensrf.math service
+my $session = OpenSRF::AppSession->create( "opensrf.math" );
+
+my @times; # "delta" times for each round trip
+
+# we're gonna call methods "add", "sub", "mult", and "div" with
+# params 1, 2.  The hash below maps the method name to the 
+# expected response value
+my %vals = ( add => 3, sub => -1, mult => 2, div => 0.5 );
+
+# print the counter grid 
+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 ) { # cycle through add, sub, mult, and div
+
+               my $starttime = time();
+
+               # * Fires the request and gathers the response object, which in this case
+               # is just a string
+               my $resp = $session->request( $mname, 1, 2 )->gather(1);
+               push @times, time() - $starttime;
+
+
+               if( "$resp" eq $vals{$mname} ) { 
+                       # we got the response we expected
+                       print "+"; 
+
+               } elsif($resp) { 
+                       # we got some other response     
+                       print "\n* BAD Data:  $resp\n";
+
+               } else { 
+                       # we got no data
+                       print "Received nothing\n";     
+               }
+
+               $c++;
+
+       }
+
+       print " [$c] \n" unless $scale % 25;
+}
+
+my $total = 0;
+
+$total += $_ for (@times);
+
+$total /= scalar(@times);
+
+print "\n\n\tAverage Round Trip Time: $total Seconds\n";
+
+
diff --git a/trunk/examples/math_xul_client/Makefile.in b/trunk/examples/math_xul_client/Makefile.in
new file mode 100644 (file)
index 0000000..41ac95c
--- /dev/null
@@ -0,0 +1,19 @@
+all: clean client
+       @echo
+
+client:
+       @echo We need the OpenSRF javascript code...
+       mkdir math/content/OpenSRF/
+       cp ../../src/javascript/*.js math/content/OpenSRF/
+       @echo We need a log directory...
+       mkdir math/content/log
+       touch math/content/log/preserve.directory.when.zipping
+       @echo We also need a math/content/conf/client_config.xml pointing to a running OpenSRF Math application.
+       @echo Then we package this into a Mozilla XPI file...
+       zip -q -r math.xpi install.js math/
+       @echo After installing the XPI, use the chrome URL:
+       @echo chrome://math/content/
+
+clean:
+       @echo Removing the OpenSRF javascript code, the log directory, and math.xpi...
+       rm -rf math/content/OpenSRF/ math/content/log math.xpi
diff --git a/trunk/examples/math_xul_client/install.js b/trunk/examples/math_xul_client/install.js
new file mode 100644 (file)
index 0000000..0b10d3d
--- /dev/null
@@ -0,0 +1,98 @@
+// ripped from Evergreen installation file
+
+/* We'll want to make this more flexible later */
+
+install();
+
+// ----------------------------------------------------------------------------
+// Performs the install
+// ----------------------------------------------------------------------------
+function install() {
+
+       // ----------------------------------------------------------------------------
+       var _authors = "PINES";
+       var _package = "math";
+       var _packg_l = "math";
+       var _version = "0.0.1";
+       // ----------------------------------------------------------------------------
+
+       var err; // track the error
+
+       err = initInstall( _package, "/"+_authors+"/"+_package, _version );
+       if( err != 0 ) { return warn( "initInstall: " + err );}
+
+       // ----------------------------------------------------------------------------
+       // Discovers the path to the install directory
+       // ----------------------------------------------------------------------------
+       install_dir = getFolder("Chrome", _packg_l );
+       logComment( "Installing to: " + install_dir );
+
+       // ----------------------------------------------------------------------------
+       // Directory where the 'content' is stored
+       // ----------------------------------------------------------------------------
+       content_dir = getFolder( install_dir, "content" );
+       if( err != 0 ) { return warn("getFolder:content_dir: " + err);}
+       
+       // ----------------------------------------------------------------------------
+       // Directory where the skins are stored
+       // ----------------------------------------------------------------------------
+       skin_dir = getFolder( install_dir, "skin" );
+       if( err != 0 ) { return warn("getFolder:skin: " + err);}
+
+       // ----------------------------------------------------------------------------
+       // Directory where the local data is stored
+       // ----------------------------------------------------------------------------
+       locale_dir = getFolder( install_dir, "locale" );
+       if( err != 0 ) { return warn("getFolder:locale: " + err);}
+
+       // ----------------------------------------------------------------------------
+       // Sets the install directory for Evergreen
+       // ----------------------------------------------------------------------------
+       err = setPackageFolder(install_dir);
+       if( err != 0 ) { return warn("setPackageFolder: " + err);}
+       
+       // ----------------------------------------------------------------------------
+       // Searches the .xpi file for the directory name stored in _packg_l and
+       // copies that directory from the .xpi into Mozilla's chrome directory.
+       // In this case, we are copying over the entire evergreen folder
+       // ----------------------------------------------------------------------------
+       err = addDirectory( _packg_l )
+       if( err != 0 ) { return warn("addDirectory: " + err);}
+       
+
+       // ----------------------------------------------------------------------------
+       // Register the content directory
+       // The final argument is where Mozilla should expect to find the contents.rdf 
+       // file *after* installation for the CONTENT portion of the package
+       // ----------------------------------------------------------------------------
+       err = registerChrome( Install.CONTENT, content_dir );
+       if( err != 0 ) { return warn("registerChrome:content  " + err );}
+       
+       // ----------------------------------------------------------------------------
+       // Register the skin directory
+       // ----------------------------------------------------------------------------
+       err = registerChrome( Install.SKIN, skin_dir );
+       if( err != 0 ) { return warn("registerChrome:skin " + err );}
+
+       // ----------------------------------------------------------------------------
+       // Register the locale directory 
+       // ----------------------------------------------------------------------------
+       //err = registerChrome( Install.LOCALE, locale_dir );
+       //if( err != 0 ) { return warn("registerChrome:locale " + err );}
+
+       err = registerChrome( Install.LOCALE, getFolder(locale_dir, "en-US") );
+       if( err != 0 ) { return warn("registerChrome:locale " + err );}
+
+       // ----------------------------------------------------------------------------
+       // Do it.
+       // ----------------------------------------------------------------------------
+       performInstall();
+       
+}
+
+function warn( message ) {
+       alert( message );
+       logComment( message );
+       return;
+}
+
diff --git a/trunk/examples/math_xul_client/math/content/conf/client_config.xml b/trunk/examples/math_xul_client/math/content/conf/client_config.xml
new file mode 100644 (file)
index 0000000..0dc75a9
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" ?>
+
+<oils_config>
+
+       <!-- General system settings -->
+
+       <system>
+               <hostname>myhostname</hostname>
+
+               <!-- log_level
+                       Levels run between 0 and 3.  O is no logging.  3 is full debug output -->
+               <log_level>0</log_level>
+
+               <!--  stdout_log
+                       When set to 0, no stdout logging is done, when set to 1, all logging
+                       goes to both stdout as well as their destined log file, if set to
+                       2, the messages only go to stdout.  -->
+               <stdout_log>0</stdout_log>
+       </system>
+
+
+       <!-- Log files -->
+
+       <logs>
+               <debug>debug.log</debug>
+               <transport>transport.log</transport>
+               <error>error.log</error>
+       </logs>
+
+       <!-- Remote services -->
+
+       <remote_service>
+               <math>router@localhost/math</math>
+               <mathdb>router@localhost/mathdb</mathdb>
+               <storage>router@localhost/storage</storage>
+       </remote_service>
+       
+       <!-- Transport -->
+
+       <transport>
+               <transport_impl>jabber_connection</transport_impl>
+
+               <!-- connect_timeout doubles as both the low level transport timeout
+                       as well as the app connection timeout -->
+               <connect_timeout>15</connect_timeout>
+               <username>math_user</username>
+               <password>math_user_password</password>
+               <primary>localhost</primary>
+               <port>5222</port>
+               <ssl>0</ssl>
+       </transport>
+
+</oils_config>
diff --git a/trunk/examples/math_xul_client/math/content/contents.rdf b/trunk/examples/math_xul_client/math/content/contents.rdf
new file mode 100644 (file)
index 0000000..7a8fb13
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+       xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
+
+       <RDF:Seq about="urn:mozilla:package:root">
+               <RDF:li resource="urn:mozilla:package:math"/>
+       </RDF:Seq>
+
+       <RDF:Description about="urn:mozilla:package:math"
+               chrome:displayName="Math"
+               chrome:author="PINES"
+               chrome:name="math"
+               chrome:extension="true"/>
+
+</RDF:RDF>
+
diff --git a/trunk/examples/math_xul_client/math/content/math.xul b/trunk/examples/math_xul_client/math/content/math.xul
new file mode 100644 (file)
index 0000000..d6a1371
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!-- Test Application: Math -->
+
+<!-- Stylesheets -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://math/skin/math.css" type="text/css"?>
+
+<!-- Localization -->
+<!DOCTYPE window SYSTEM "chrome://math/locale/math.dtd">
+
+<window id="math_win" title="&math.title;" orient="vertical" style="overflow: auto"
+       xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+       <!-- Overlays for this XUL file -->
+       <?xul-overlay href="chrome://math/content/math_overlay.xul"?>
+
+       <!-- OpenSRF -->
+       <script>var myPackageDir = "math";</script>
+       <script src="OpenSRF/JSON_v1.js" />
+       <script src="OpenSRF/md5.js" />
+       <script src="OpenSRF/opensrf_utils.js" />
+       <script src="OpenSRF/opensrf_config.js" />
+       <script src="OpenSRF/opensrf_dom_element.js" />
+       <script src="OpenSRF/opensrf_domain_object.js" />
+       <script src="OpenSRF/opensrf_transport.js" />
+       <script src="OpenSRF/opensrf_jabber_transport.js" />
+       <script src="OpenSRF/opensrf_msg_stack.js" />
+       <script src="OpenSRF/opensrf_app_session.js" />
+
+       <!-- The logic for this app -->
+       <script src="math_app.js" />
+
+       <!-- Layout to be filled in by overlays and javascript -->
+       <vbox id="main_vbox" class="test_class">
+       </vbox>
+
+</window>
+
diff --git a/trunk/examples/math_xul_client/math/content/math_app.js b/trunk/examples/math_xul_client/math/content/math_app.js
new file mode 100644 (file)
index 0000000..2b98694
--- /dev/null
@@ -0,0 +1,50 @@
+// connect and stup
+
+var ses;
+
+function execute( opp ) {
+
+       var a = document.getElementById("num1");
+       var b = document.getElementById("num2");
+       do_stuff( opp, a.value, b.value );
+
+}
+
+function do_stuff( opp, a, b ) {
+
+
+
+       try {
+
+               if( ses == null || ! AppSession.transport_handle.connected() ) {
+
+                       /* deprecated */
+                       ses = new AppSession( "user_name", "12345", "math" );
+                       if( ! ses.connect() ) { alert( "Connect timed out!" ); }
+               }
+
+               var meth = new oilsMethod(opp, [ a, b ] );
+
+               var req = new AppRequest( ses, meth );
+               req.make_request();
+               var resp = req.recv( 5000 );
+               if( ! resp ) {
+                       alert( "NO response from server!!!" );
+                       quit(); return;
+               }
+       
+               var scalar = resp.getContent();
+               var value = scalar.getValue();
+
+               var lab = document.getElementById( "answer" );
+               lab.value = "Answer: " + value;
+               req.finish();
+
+       } catch( E ) { alert( E.message ); }    
+
+}
+
+
+function quit() { ses.disconnect(); window.close(); }
+
+
diff --git a/trunk/examples/math_xul_client/math/content/math_overlay.xul b/trunk/examples/math_xul_client/math/content/math_overlay.xul
new file mode 100644 (file)
index 0000000..1a4b69c
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://math/skin/math.css" type="text/css"?>
+<!DOCTYPE overlay SYSTEM "chrome://math/locale/math.dtd">
+<overlay id="math_overlay" 
+       xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<vbox id="main_vbox" flex="1">
+       <label value="&math.title;" />
+       <textbox id="num1" />
+       <textbox id="num2" />
+       <grid flex="1" class="test_class">
+               <columns>
+                       <column />
+                       <column />
+               </columns>
+               <rows>
+                       <row>
+                               <button label="&math.add;" oncommand="execute('add');"/>
+                               <button label="&math.sub;" oncommand="execute('sub');"/>
+                       </row>
+                       <row>
+                               <button label="&math.mul;" oncommand="execute('mult');"/>
+                               <button label="&math.div;" oncommand="execute('div');"/>
+                       </row>
+               </rows>
+       </grid>
+       <label id="answer" value="" />
+       <button label="&math.quit;" oncommand="quit();"/>
+</vbox>
+
+</overlay>
diff --git a/trunk/examples/math_xul_client/math/locale/en-US/contents.rdf b/trunk/examples/math_xul_client/math/locale/en-US/contents.rdf
new file mode 100644 (file)
index 0000000..01b2e7f
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+          xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
+       <RDF:Seq about="urn:mozilla:locale:root">
+               <RDF:li resource="urn:mozilla:locale:en-US"/>
+       </RDF:Seq>
+
+       <RDF:Description about="urn:mozilla:locale:en-US"
+               chrome:displayName="Math"
+               chrome:author="PINES"
+               chrome:name="en-US">
+               <chrome:packages>
+                       <RDF:Seq about="urn:mozilla:locale:en-US:packages">
+                               <RDF:li resource="urn:mozilla:locale:en-US:math"/>      
+                       </RDF:Seq>
+               </chrome:packages>
+       </RDF:Description>
+</RDF:RDF>
+
diff --git a/trunk/examples/math_xul_client/math/locale/en-US/math.dtd b/trunk/examples/math_xul_client/math/locale/en-US/math.dtd
new file mode 100644 (file)
index 0000000..16917f6
--- /dev/null
@@ -0,0 +1,7 @@
+<!ENTITY math.title "Math App">
+<!ENTITY math.add "Add">
+<!ENTITY math.sub "Subtract">
+<!ENTITY math.mul "Multiply">
+<!ENTITY math.div "Divide">
+<!ENTITY math.quit "Quit">
+
diff --git a/trunk/examples/math_xul_client/math/skin/contents.rdf b/trunk/examples/math_xul_client/math/skin/contents.rdf
new file mode 100644 (file)
index 0000000..b27f9ca
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+       xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
+
+       <RDF:Seq about="urn:mozilla:skin:root">
+               <RDF:li resource="urn:mozilla:skin:math/1.0"/>
+       </RDF:Seq>
+
+       <RDF:Description about="urn:mozilla:skin:math/1.0"
+               chrome:displayName="Math"
+               chrome:author="PINES"
+               chrome:name="math/1.0">
+
+               <chrome:packages>
+                       <RDF:Seq about="urn:mozilla:skin:math/1.0:packages">
+                               <RDF:li resource="urn:mozilla:skin:math/1.0:math"/>     
+                       </RDF:Seq>
+               </chrome:packages>
+       </RDF:Description>
+
+</RDF:RDF>
+
diff --git a/trunk/examples/math_xul_client/math/skin/math.css b/trunk/examples/math_xul_client/math/skin/math.css
new file mode 100644 (file)
index 0000000..106bd74
--- /dev/null
@@ -0,0 +1,4 @@
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); 
+@namespace html url("http://www.w3.org/TR/REC-html40"); 
+
+.test_class { border: solid thin blue; }
diff --git a/trunk/examples/multisession-test.pl b/trunk/examples/multisession-test.pl
new file mode 100755 (executable)
index 0000000..01ad495
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+use lib 'LIBDIR/perl5/';
+use OpenSRF::System;
+use OpenILS::Application::AppUtils;
+use OpenILS::Event;
+use OpenSRF::EX qw/:try/;
+use OpenSRF::Utils::JSON;
+use Data::Dumper;
+use OpenILS::Utils::Fieldmapper;
+use Digest::MD5 qw/md5_hex/;
+use OpenSRF::Utils qw/:daemon/;
+use OpenSRF::MultiSession;
+use OpenSRF::AppSession;
+use Time::HiRes qw/time/;
+
+my $config = shift;
+
+unless (-e $config) {
+       die "Gimme a config file!!!";
+}
+OpenSRF::System->bootstrap_client( config_file => $config );
+
+if (!@ARGV) {
+       @ARGV = ('open-ils.storage','opensrf.system.echo');
+}
+
+my $app = shift;
+
+my $count = 100;
+
+my $overhead = time;
+
+my $mses = OpenSRF::MultiSession->new( app => $app, cap => 10, api_level => 1 );
+
+$mses->success_handler(
+       sub {
+               my $ses = shift;
+               my $req = shift;
+               print $req->{params}->[0] . "\t: " . OpenSRF::Utils::JSON->perl2JSON($req->{response}->[0]->content)."\n";
+       }
+);
+
+$mses->failure_handler(
+       sub {
+               my $ses = shift;
+               my $req = shift;
+               warn "record $req->{params}->[0] failed: " . OpenSRF::Utils::JSON->perl2JSON($req->{response});
+       }
+);
+
+
+$mses->connect;
+
+my $start = time;
+$overhead = $start - $overhead;
+
+for (1 .. $count) {
+       $mses->request( @ARGV,$_ );
+}
+$mses->session_wait(1);
+$mses->disconnect;
+
+my $end = time;
+
+my @c = $mses->completed;
+my @f = $mses->failed;
+
+my $x = 0;
+$x += $_->{duration} for (@c);
+
+print "\n". '-'x40 . "\n";
+print "Startup Overhead: ".sprintf('%0.3f',$overhead)."s\n";
+print "Completed Commands: ".@c."\n";
+print "Failed Commands: ".@f."\n";
+print "Serial Run Time: ".sprintf('%0.3f',$x)."s\n";
+print "Serial Avg Time: ".sprintf('%0.3f',$x/$count)."s\n";
+print "Total Run Time: ".sprintf('%0.3f',$end-$start)."s\n";
+print "Total Avg Time: ".sprintf('%0.3f',($end-$start)/$count)."s\n";
+
diff --git a/trunk/examples/opensrf.xml.example b/trunk/examples/opensrf.xml.example
new file mode 100644 (file)
index 0000000..3b0ec67
--- /dev/null
@@ -0,0 +1,218 @@
+<?xml version="1.0"?>
+<!-- 
+vim:et:ts=2:sw=2:
+-->
+<opensrf version="0.0.3">
+<!-- 
+
+       There is one <host> entry for each server on the network.  Settings for the
+       'default' host are used for every setting that isn't overridden within a given 
+       host's config.  
+
+       To specify which applications a host is serving, list those applications
+       within that host's config section.  If the defaults are acceptible, then
+       that's all that needs to be added/changed.
+
+       Any valid XML may be added to the <default> block and server components will have 
+       acces to it.
+
+-->
+
+  <default>
+    <dirs>
+
+      <!-- opensrf log files go in this directory -->
+      <log>LOCALSTATEDIR/log</log>
+
+      <!-- opensrf unix domaind socket files go here -->
+      <sock>LOCALSTATEDIR/lock</sock>
+
+      <!-- opensrf pids go here -->
+      <pid>LOCALSTATEDIR/run</pid>
+
+      <!-- global config directory -->
+      <conf>SYSCONFDIR</conf>
+    </dirs>
+
+    <!-- prefork, simple. prefork is suggested -->
+    <server_type>prefork</server_type>
+
+    <!-- Default doesn't host any apps -->
+    <activeapps/>
+    <cache>
+      <global>
+        <servers>
+
+          <!-- memcached server ip:port -->
+          <server>127.0.0.1:10101</server>
+
+        </servers>
+
+        <!-- maximun time that anything may stay in the cache -->
+        <max_cache_time>86400</max_cache_time>
+
+      </global>
+    </cache>
+
+    <!--
+    These are the defaults for every served app.  Each server should 
+    duplicate the node layout for any nodes that need changing.
+    Any settings that are overridden in the server specific section 
+    will be used as the config values for that server.  Any settings that are
+    not overridden will fall back on the defaults
+    Note that overriding 'stateless' will break things
+    -->
+
+    <apps>
+      <opensrf.persist>
+
+        <!--
+        How many seconds to wait between server 
+        requests before timing out a stateful server session.
+        -->
+        <keepalive>1</keepalive>
+
+        <!--
+        if 1, then we support stateless sessions (no connect required),
+        if 0 then we don't
+        -->
+        <stateless>1</stateless>
+
+        <!--
+        Tells the servers which language this implementation is coded in 
+        In this case non "perl" servers will not be able to load the module
+        -->
+        <language>perl</language>
+
+        <!-- Module the implements this application -->
+        <implementation>OpenSRF::Application::Persist</implementation>
+
+        <!-- max stateful requests before a session automatically disconnects a client -->
+        <max_requests>97</max_requests>
+
+        <!-- settings for the backend application drones.  These are probably sane defaults -->
+        <unix_config>
+
+          <!-- unix socket file -->
+          <unix_sock>opensrf.persist_unix.sock</unix_sock>
+
+          <!-- pid file -->
+          <unix_pid>opensrf.persist_unix.pid</unix_pid>
+
+          <!-- max requests per process backend before a child is recycled -->
+          <max_requests>1000</max_requests>
+
+          <!-- log file for this application -->
+          <unix_log>opensrf.persist_unix.log</unix_log>
+
+          <!-- Number of children to pre-fork -->
+          <min_children>5</min_children>
+
+          <!-- maximun number of children to fork -->
+          <max_children>25</max_children>
+
+          <!-- minimun number of spare forked children -->
+          <min_spare_children>2</min_spare_children>
+
+          <!-- max number of spare forked children -->
+          <max_spare_children>5</max_spare_children>
+
+        </unix_config>
+
+        <!-- Any additional setting for a particular application go in the app_settings node -->
+        <app_settings>
+
+          <!-- sqlite database file -->
+          <dbfile>/path/to/dbfile/persist.db</dbfile>
+
+        </app_settings>
+      </opensrf.persist>
+
+      <opensrf.math>
+        <keepalive>3</keepalive>
+        <stateless>1</stateless>
+        <language>c</language>
+        <implementation>libosrf_math.so</implementation>
+        <max_requests>97</max_requests>
+        <unix_config>
+          <unix_sock>opensrf.math_unix.sock</unix_sock>
+          <unix_pid>opensrf.math_unix.pid</unix_pid>
+          <max_requests>1000</max_requests>
+          <unix_log>opensrf.math_unix.log</unix_log>
+          <min_children>5</min_children>
+          <max_children>15</max_children>
+          <min_spare_children>2</min_spare_children>
+          <max_spare_children>5</max_spare_children>
+        </unix_config>
+      </opensrf.math>
+
+      <opensrf.dbmath>
+        <keepalive>3</keepalive>
+        <stateless>1</stateless>
+        <language>c</language>
+        <implementation>libosrf_dbmath.so</implementation>
+        <max_requests>99</max_requests>
+        <unix_config>
+          <max_requests>1000</max_requests>
+          <unix_log>opensrf.dbmath_unix.log</unix_log>
+          <unix_sock>opensrf.dbmath_unix.sock</unix_sock>
+          <unix_pid>opensrf.dbmath_unix.pid</unix_pid>
+          <min_children>5</min_children>
+          <max_children>15</max_children>
+          <min_spare_children>2</min_spare_children>
+          <max_spare_children>5</max_spare_children>
+        </unix_config>
+      </opensrf.dbmath>
+
+      <opensrf.settings>
+        <keepalive>1</keepalive>
+        <stateless>0</stateless>
+        <language>perl</language>
+        <implementation>OpenSRF::Application::Settings</implementation>
+        <max_requests>17</max_requests>
+        <unix_config>
+          <unix_sock>opensrf.settings_unix.sock</unix_sock>
+          <unix_pid>opoensrf.settings_unix.pid</unix_pid>
+          <max_requests>1000</max_requests>
+          <unix_log>opensrf.settings_unix.log</unix_log>
+          <min_children>5</min_children>
+          <max_children>15</max_children>
+          <min_spare_children>3</min_spare_children>
+          <max_spare_children>5</max_spare_children>
+        </unix_config>
+      </opensrf.settings>
+    </apps>
+  </default>
+
+  <hosts>
+
+    <localhost>
+<!-- ^-=-
+       Must match the fully qualified domain name of the host 
+       on Linux, this is usually the output of "hostname -f"
+-->
+
+<!-- List all of the apps this server will be running -->
+      <activeapps>
+        <appname>opensrf.persist</appname>
+        <appname>opensrf.settings</appname>
+        <appname>opensrf.math</appname>
+        <appname>opensrf.dbmath</appname>
+      </activeapps>
+
+      <apps>
+
+<!-- Example of an app-specific setting override -->
+        <opensrf.persist>
+          <app_settings>
+            <dbfile>/different/path/to/dbfile/persist.db</dbfile>
+          </app_settings>
+        </opensrf.persist>
+
+      </apps>
+
+    </localhost>
+
+  </hosts>
+
+</opensrf>
diff --git a/trunk/examples/opensrf_core.xml.example b/trunk/examples/opensrf_core.xml.example
new file mode 100644 (file)
index 0000000..bfde5c9
--- /dev/null
@@ -0,0 +1,139 @@
+<?xml version="1.0"?>
+<!-- 
+vim:et:ts=2:sw=2:
+-->
+<config>
+
+  <!-- bootstrap config for OpenSRF apps -->
+  <opensrf>
+
+    <!-- The OpenSRF Routers's name on the network -->
+    <!-- You should never need to change thischange this -->
+    <router_name>router</router_name>
+
+    <routers>
+
+      <!-- List of router domains we should register with. 
+        We must at least have our default jabber domain in here -->
+      <router>localhost</router>
+
+    </routers>
+
+    <!-- Our jabber domain, currently only one domain is supported -->
+    <domain>localhost</domain>
+
+    <username>client</username>
+    <passwd>mypass</passwd>
+    <port>5222</port>
+
+    <!-- log to a local file -->
+    <logfile>LOCALSTATEDIR/log/osrfsys.log</logfile>
+
+    <!-- Log to syslog. You can use this same layout for 
+        defining the logging of all services in this file -->
+<!--
+    <logfile>syslog</logfile>
+    <syslog>local2</syslog>
+    <actlog>local1</actlog>
+-->
+
+    <!-- 0 None, 1 Error, 2 Warning, 3 Info, 4 debug, 5 Internal (Nasty) -->
+    <loglevel>3</loglevel>
+
+    <!-- config file for the services -->
+    <settings_config>SYSCONFDIR/opensrf.xml</settings_config>
+
+  </opensrf>
+
+  <!-- Update this if you use ChopChop -->
+  <chopchop>
+
+    <!-- Our jabber server -->
+    <domain>localhost</domain>
+    <port>5222</port>
+
+    <!-- used when multiple servers need to communicate -->
+    <s2sport>5269</s2sport>
+    <secret>secret</secret>
+    <listen_address>10.0.0.3</listen_address>
+    <loglevel>3</loglevel>
+    <logfile>LOCALSTATEDIR/log/osrfsys.log</logfile>
+  </chopchop>
+
+  <!-- The section between <gateway>...</gateway> is a standard OpenSRF C stack config file -->
+  <gateway>
+
+    <!--
+    we consider ourselves to be the "originating" client for requests,
+    which means we define the log XID string for log traces
+    -->
+    <client>true</client>
+
+    <!--  the routers's name on the network -->
+    <router_name>router</router_name>
+
+    <!-- jabber domains to connect to (domain1, domain2, ...) -->
+    <domain>localhost</domain>
+
+    <!--
+    These are the services that the gateway will serve. 
+    Any other requests will receive an HTTP_NOT_FOUND (404) 
+    DO NOT put any services here that you don't want the internet to have access to
+    -->
+    <services>
+      <service>opensrf.math</service>
+    </services>
+
+    <!-- jabber login info -->
+    <username>mylogin</username>
+    <passwd>mypassword</passwd>
+    <port>5222</port>
+    <logfile>LOCALSTATEDIR/log/gateway.log</logfile>
+    <loglevel>3</loglevel>
+
+  </gateway>
+
+  <!-- ======================================================================================== -->
+
+  <routers>
+    <router>
+
+      <trusted_domains>
+
+        <!-- servers on trusted domains are allowed to register apps with the router -->
+        <server>localhost</server>
+  
+        <!-- clients on trusted domains are allowed to send requests to this router -->
+        <client>localhost</client>
+  
+      </trusted_domains>
+  
+      <transport>
+  
+        <!-- jabber server are we connecting to -->
+        <server>localhost</server>
+        <port>5222</port>
+  
+        <!--
+        if this is changed, all "router_name" settings 
+        will need to be updated to match this setting
+        -->
+        <username>router</username>
+        <password>mypassword</password>
+  
+        <!-- router's jabber resource -->
+        <!-- do not change this -->
+        <resource>router</resource>
+        <connect_timeout>10</connect_timeout>
+        <max_reconnect_attempts>5</max_reconnect_attempts>
+  
+        </transport>
+      <logfile>LOCALSTATEDIR/log/router.log</logfile>
+      <loglevel>2</loglevel>
+  
+    </router>
+  </routers>
+
+  <!-- ======================================================================================== -->
+
+</config>
diff --git a/trunk/examples/register.pl b/trunk/examples/register.pl
new file mode 100755 (executable)
index 0000000..dfde4a6
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+# ----------------------------------------------------------------------
+# Utility script for registring users on a jabber server.  
+# ----------------------------------------------------------------------
+use Net::Jabber;
+use strict;
+
+if (@ARGV < 4) {
+    print "\nperl $0 <server> <port> <username> <password> \n\n";
+    exit(0);
+}
+
+my $server = $ARGV[0];
+my $port = $ARGV[1];
+my $username = $ARGV[2];
+my $password = $ARGV[3];
+my $resource = "test_${server}_$$";
+
+my $connection = Net::Jabber::Client->new;
+
+my $status = $connection->Connect(hostname=>$server, port=>$port);
+
+my @stat = $connection->RegisterSend(
+       $server, 
+       username => $username,
+       password => $password );
+
+
+print "Register results : @stat\n";
+
+
+if (!defined($status)) {
+    print "ERROR:  Jabber server is down or connection was not allowed.\n";
+    print "        ($!)\n";
+    exit(0);
+}
+
+my @result = $connection->AuthSend(
+       username=>$username, password=>$password, resource=>$resource);
+
+if ($result[0] ne "ok") {
+    print "ERROR: Authorization failed: $result[0] - $result[1]\n";
+    exit(0);
+}
+
+print "Logged in OK to $server:$port\nRegistration succeeded for $username\@$server!\n";
+
+$connection->Disconnect();
+
+
diff --git a/trunk/examples/srfsh.xml.example b/trunk/examples/srfsh.xml.example
new file mode 100644 (file)
index 0000000..e8dd6be
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!-- This file follows the standard bootstrap config file layout found in opensrf_core.xml -->
+<srfsh>
+  <router_name>router</router_name>
+  <domain>localhost</domain>
+  <username>myusername</username>
+  <passwd>mypassword</passwd>
+  <port>5222</port>
+  <logfile>LOCALSTATEDIR/log/srfsh.log</logfile>
+  <loglevel>4</loglevel>
+</srfsh>
diff --git a/trunk/examples/srfsh_config.xsd b/trunk/examples/srfsh_config.xsd
new file mode 100644 (file)
index 0000000..9f1fd8d
--- /dev/null
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+
+Copyright (C) 2007 Laurentian University
+Dan Scott <dscott@laurentian.ca>
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+-->
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+<!-- define types -->
+<xs:simpleType name="loglevelType">
+ <xs:restriction base="xs:positiveInteger">
+  <xs:maxInclusive value="4"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="portType">
+ <xs:restriction base="xs:positiveInteger">
+  <xs:maxInclusive value="65535"/>
+ </xs:restriction>
+</xs:simpleType>
+
+
+<!-- define simple elements -->
+<xs:element name="router_name" type="xs:string"/>
+<xs:element name="domain" type="xs:string"/>
+<xs:element name="username" type="xs:string"/>
+<xs:element name="passwd" type="xs:string"/>
+<xs:element name="port" type="portType"/>
+<xs:element name="logfile" type="xs:string"/>
+<xs:element name="loglevel" type="loglevelType"/>
+
+<!-- group type -->
+<xs:group name="srfshElements">
+ <xs:all>
+  <xs:element ref="router_name"/>
+  <xs:element ref="domains"/>
+  <xs:element ref="username"/>
+  <xs:element ref="passwd"/>
+  <xs:element ref="port"/>
+  <xs:element ref="logfile"/>
+  <xs:element ref="loglevel"/>
+ </xs:all>
+</xs:group>
+
+<!-- complex elements -->
+<xs:element name="domains">
+ <xs:complexType>
+  <xs:sequence>
+   <xs:element ref="domain" minOccurs="1" maxOccurs="unbounded"/>
+  </xs:sequence>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="srfsh">
+ <xs:complexType>
+  <xs:group ref="srfshElements"/>
+ </xs:complexType>
+</xs:element>
+
+</xs:schema>
diff --git a/trunk/include/opensrf/log.h b/trunk/include/opensrf/log.h
new file mode 100644 (file)
index 0000000..11c273e
--- /dev/null
@@ -0,0 +1,95 @@
+#include <opensrf/utils.h>
+
+#include <syslog.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+
+#ifndef OSRF_LOG_INCLUDED
+#define OSRF_LOG_INCLUDED
+
+/* log levels */
+#define OSRF_LOG_ERROR 1
+#define OSRF_LOG_WARNING 2
+#define OSRF_LOG_INFO 3
+#define OSRF_LOG_DEBUG 4
+#define OSRF_LOG_INTERNAL 5
+#define OSRF_LOG_ACTIVITY -1
+
+#define OSRF_LOG_TYPE_FILE 1
+#define OSRF_LOG_TYPE_SYSLOG 2
+#define OSRF_LOG_TYPE_STDERR 3
+
+#define OSRF_LOG_MARK __FILE__, __LINE__
+
+
+/* Initializes the logger. */
+void osrfLogInit( int type, const char* appname, int maxlevel );
+
+/** Sets the systlog facility for the regular logs */
+void osrfLogSetSyslogFacility( int facility );
+
+/** Sets the systlog facility for the activity logs */
+void osrfLogSetSyslogActFacility( int facility );
+
+/** Reroutes logged messages to standard error; */
+/** intended for development and debugging */
+void osrfLogToStderr( void );
+
+/** Undoes the effects of osrfLogToStderr() */
+void osrfRestoreLogType( void );
+
+/** Sets the log file to use if we're logging to a file */
+void osrfLogSetFile( const char* logfile );
+
+/* once we know which application we're running, call this method to
+ * set the appname so log lines can include the app name */
+void osrfLogSetAppname( const char* appname );
+
+/** Set or Get the global log level.  Any log statements with a higher level
+ * than "level" will not be logged */
+void osrfLogSetLevel( int loglevel );
+int osrfLogGetLevel( void );
+
+/* Log an error message */
+void osrfLogError( const char* file, int line, const char* msg, ... );
+
+/* Log a warning message */
+void osrfLogWarning( const char* file, int line, const char* msg, ... );
+
+/* log an info message */
+void osrfLogInfo( const char* file, int line, const char* msg, ... );
+
+/* Log a debug message */
+void osrfLogDebug( const char* file, int line, const char* msg, ... );
+
+/* Log an internal debug message */
+void osrfLogInternal( const char* file, int line, const char* msg, ... );
+
+/* Log an activity message */
+void osrfLogActivity( const char* file, int line, const char* msg, ... );
+
+void osrfLogCleanup( void );
+
+void osrfLogClearXid( void );
+/**
+ * Set the log XID.  This only sets the xid if we are not the origin client 
+ */
+void osrfLogSetXid(char* xid);
+/**
+ * Set the log XID regardless of whether we are the origin client
+ */
+void osrfLogForceXid(char* xid);
+void osrfLogMkXid( void );
+void osrfLogSetIsClient(int is);
+char* osrfLogGetXid( void );
+
+/* sets the activity flag */
+void osrfLogSetActivityEnabled( int enabled );
+
+/* returns the int representation of the log facility based on the facility name
+ * if the facility name is invalid, LOG_LOCAL0 is returned 
+ */
+int osrfLogFacilityToInt( char* facility );
+
+#endif
diff --git a/trunk/include/opensrf/md5.h b/trunk/include/opensrf/md5.h
new file mode 100644 (file)
index 0000000..53dd2b1
--- /dev/null
@@ -0,0 +1,35 @@
+/* --- The MD5 routines --- */
+
+/* MD5 routines, after Ron Rivest */
+/* Written by David Madore <david.madore@ens.fr>, with code taken in
+ * part from Colin Plumb. */
+/* Public domain (1999/11/24) */
+
+/* Note: these routines do not depend on endianness. */
+
+/* === The header === */
+
+/* Put this in md5.h if you don't like having everything in one big
+ * file. */
+
+#ifndef _DMADORE_MD5_H
+#define _DMADORE_MD5_H
+
+struct md5_ctx {
+  /* The four chaining variables */
+  unsigned long buf[4];
+  /* Count number of message bits */
+  unsigned long bits[2];
+  /* Data being fed in */
+  unsigned long in[16];
+  /* Our position within the 512 bits (always between 0 and 63) */
+  int b;
+};
+
+void MD5_transform (unsigned long buf[4], const unsigned long in[16]);
+void MD5_start (struct md5_ctx *context);
+void MD5_feed (struct md5_ctx *context, unsigned char inb);
+void MD5_stop (struct md5_ctx *context, unsigned char digest[16]);
+
+#endif /* not defined _DMADORE_MD5_H */
+
diff --git a/trunk/include/opensrf/osrfConfig.h b/trunk/include/opensrf/osrfConfig.h
new file mode 100644 (file)
index 0000000..64f9ca6
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+Bill Erickson <highfalutin@gmail.com>
+
+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.
+*/
+
+#ifndef _OSRF_CONFIG_H
+#define _OSRF_CONFIG_H
+
+#include <opensrf/xml_utils.h>
+#include <opensrf/utils.h>
+#include <opensrf/string_array.h>
+#include <opensrf/osrf_json.h>
+
+typedef struct {
+       jsonObject* config;
+       char* configContext;
+} osrfConfig;
+
+
+/**
+       Parses a new config file.  Caller is responsible for freeing the returned
+               config object when finished.  
+       @param configFile The XML config file to parse.
+       @param configContext Optional root of the subtree in the config file where 
+       we will look for values. If it's not provided,  searches will be 
+       performed from the root of the config file
+       @return The config object if the file parses successfully.  Otherwise
+               it returns NULL;
+*/
+osrfConfig* osrfConfigInit(const char* configFile, const char* configContext);
+
+/**
+       @return True if we have a default config defined
+*/
+int osrfConfigHasDefaultConfig();
+
+/**
+       Replaces the config object's json object.  This is useful
+       if you have a json object already and not an XML config
+       file to parse.
+       @param cfg The config object to alter
+       @param obj The json object to use when searching values
+*/
+void osrfConfigReplaceConfig(osrfConfig* cfg, const jsonObject* obj);
+
+/** Deallocates a config object 
+       @param cfg The config object to free
+*/
+void osrfConfigFree(osrfConfig* cfg);
+
+
+/* Assigns the default config file.  This file will be used whenever
+       NULL is passed to config retrieval functions 
+       @param cfg The config object to use as the default config
+*/
+void osrfConfigSetDefaultConfig(osrfConfig* cfg);
+
+/* frees the default config if one exists */
+void osrfConfigCleanup();
+
+
+/** 
+       Returns the value in the config found at 'path'.
+       If the value found at 'path' is a long or a double,
+       the value is stringified and then returned.
+       The caller must free the returned char* 
+
+       if there is a configContext, then it will be appended to 
+       the front of the path like so: //<configContext>/<path>
+       if no configContext was provided to osfConfigSetFile, then 
+       the path is interpreted literally.
+       @param cfg The config file to search or NULL if the default
+               config should be used
+       @param path The search path
+*/
+char* osrfConfigGetValue(const osrfConfig* cfg, const char* path, ...);
+
+
+/**
+ *  @see osrfConfigGetValue
+ *  @return jsonObject found at path
+ */
+jsonObject* osrfConfigGetValueObject(osrfConfig* cfg, char* path, ...);
+
+
+/** 
+       Puts the list of values found at 'path' into the pre-allocated 
+       string array.  
+       Note that the config node found at 'path' must be an array.
+       @param cfg The config file to search or NULL if the default
+               config should be used
+       @param arr An allocated string_array where the values will
+               be stored
+       @param path The search path
+       @return the number of values added to the string array;
+*/
+
+int osrfConfigGetValueList(const osrfConfig* cfg, osrfStringArray* arr,
+               const char* path, ...);
+
+
+#endif
diff --git a/trunk/include/opensrf/osrf_app_session.h b/trunk/include/opensrf/osrf_app_session.h
new file mode 100644 (file)
index 0000000..4c535ba
--- /dev/null
@@ -0,0 +1,181 @@
+#ifndef _OSRF_APP_SESSION
+#define _OSRF_APP_SESSION
+
+#include <opensrf/transport_client.h>
+#include <opensrf/osrf_message.h>
+#include <opensrf/osrf_system.h>
+#include <opensrf/string_array.h>
+#include <opensrf/osrfConfig.h>
+#include <opensrf/osrf_hash.h>
+#include <opensrf/osrf_list.h>
+
+#include <opensrf/osrf_json.h>
+
+
+
+#define        DEF_RECV_TIMEOUT 6 /* receive timeout */
+#define        DEF_QUEUE_SIZE  
+
+enum OSRF_SESSION_STATE { OSRF_SESSION_CONNECTING, OSRF_SESSION_CONNECTED, OSRF_SESSION_DISCONNECTED };
+enum OSRF_SESSION_TYPE { OSRF_SESSION_SERVER, OSRF_SESSION_CLIENT };
+
+/* entry point for data into the stack.  gets set in osrf_stack.c */
+int (*osrf_stack_entry_point) (transport_client* client, int timeout, int* recvd );
+
+struct osrf_app_request_struct {
+       /** Our controlling session */
+       struct osrf_app_session_struct* session;
+
+       /** our "id" */
+       int request_id;
+       /** True if we have received a 'request complete' message from our request */
+       int complete;
+       /** Our original request payload */
+       osrf_message* payload; 
+       /** List of responses to our request */
+       osrf_message* result;
+
+       /* if set to true, then a call that is waiting on a response, will reset the 
+               timeout and set this variable back to false */
+       int reset_timeout;
+};
+typedef struct osrf_app_request_struct osrfAppRequest;
+
+struct osrf_app_session_struct {
+
+       /** Our messag passing object */
+       transport_client* transport_handle;
+       /** Cache of active app_request objects */
+
+       //osrfAppRequest* request_queue;
+
+       osrfList* request_queue;
+
+       /** The original remote id of the remote service we're talking to */
+       char* orig_remote_id;
+       /** The current remote id of the remote service we're talking to */
+       char* remote_id;
+
+       /** Who we're talking to if we're a client.  
+               what app we're serving if we're a server */
+       char* remote_service;
+
+       /** The current request thread_trace */
+       int thread_trace;
+       /** Our ID */
+       char* session_id;
+
+       /* true if this session does not require connect messages */
+       int stateless;
+
+       /** The connect state */
+       enum OSRF_SESSION_STATE state;
+
+       /** SERVER or CLIENT */
+       enum OSRF_SESSION_TYPE type;
+
+       /** the current locale for this session **/
+       char* session_locale;
+
+       /* let the user use the session to store their own session data */
+       void* userData;
+
+       void (*userDataFree) (void*);
+
+    int transport_error;
+};
+typedef struct osrf_app_session_struct osrfAppSession;
+
+
+
+// -------------------------------------------------------------------------- 
+// PUBLIC API ***
+// -------------------------------------------------------------------------- 
+
+/** Allocates a initializes a new app_session */
+osrfAppSession* osrfAppSessionClientInit( const char* remote_service );
+
+/** Allocates and initializes a new server session.  The global session cache
+  * is checked to see if this session already exists, if so, it's returned 
+  */
+osrfAppSession* osrf_app_server_session_init(
+               const char* session_id, const char* our_app, const char* remote_id );
+
+/** sets the default locale for a session **/
+char* osrf_app_session_set_locale( osrfAppSession*, const char* );
+
+/** returns a session from the global session hash */
+osrfAppSession* osrf_app_session_find_session( const char* session_id );
+
+/** Builds a new app_request object with the given payload andn returns
+  * the id of the request.  This id is then used to perform work on the
+  * requeset.
+  */
+int osrfAppSessionMakeRequest(
+               osrfAppSession* session, const jsonObject* params,
+               const char* method_name, int protocol, string_array* param_strings);
+
+/** Sets the given request to complete state */
+void osrf_app_session_set_complete( osrfAppSession* session, int request_id );
+
+/** Returns true if the given request is complete */
+int osrf_app_session_request_complete( const osrfAppSession* session, int request_id );
+
+/** Does a recv call on the given request */
+osrf_message* osrfAppSessionRequestRecv(
+               osrfAppSession* session, int request_id, int timeout );
+
+/** Removes the request from the request set and frees the reqest */
+void osrf_app_session_request_finish( osrfAppSession* session, int request_id );
+
+/** Resends the orginal request with the given request id */
+int osrf_app_session_request_resend( osrfAppSession*, int request_id );
+
+/** Resets the remote connection target to that of the original*/
+void osrf_app_session_reset_remote( osrfAppSession* );
+
+/** Sets the remote target to 'remote_id' */
+void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id );
+
+/** pushes the given message into the result list of the app_request
+  * whose request_id matches the messages thread_trace 
+  */
+int osrf_app_session_push_queue( osrfAppSession*, osrf_message* msg );
+
+/** Attempts to connect to the remote service. Returns 1 on successful 
+  * connection, 0 otherwise.
+  */
+int osrf_app_session_connect( osrfAppSession* );
+int osrfAppSessionConnect( osrfAppSession* );
+
+/** Sends a disconnect message to the remote service.  No response is expected */
+int osrf_app_session_disconnect( osrfAppSession* );
+
+/**  Waits up to 'timeout' seconds for some data to arrive.
+  * Any data that arrives will be processed according to its
+  * payload and message type.  This method will return after
+  * any data has arrived.
+  */
+int osrf_app_session_queue_wait( osrfAppSession*, int timeout, int* recvd );
+
+/** Disconnects (if client), frees any attached app_reuqests, removes the session from the 
+  * global session cache and frees the session.  Needless to say, only call this when the
+  * session is completely done.
+  */
+void osrfAppSessionFree( osrfAppSession* );
+
+/* tells the request to reset it's wait timeout */
+void osrf_app_session_request_reset_timeout( osrfAppSession* session, int req_id );
+
+int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data );
+int osrfAppRequestRespondComplete(
+               osrfAppSession* ses, int requestId, const jsonObject* data ); 
+
+int osrfAppSessionStatus( osrfAppSession* ses, int type,
+               const char* name, int reqId, const char* message );
+
+void osrfAppSessionCleanup();
+
+
+
+#endif
diff --git a/trunk/include/opensrf/osrf_application.h b/trunk/include/opensrf/osrf_application.h
new file mode 100644 (file)
index 0000000..6cc5ab8
--- /dev/null
@@ -0,0 +1,184 @@
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+#include <opensrf/osrf_app_session.h>
+#include <opensrf/osrf_hash.h>
+
+#include <opensrf/osrf_json.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+
+/**
+  All OpenSRF methods take the signature
+  int methodName( osrfMethodContext* );
+  If a negative number is returned, it means an unknown error occured and an exception
+  will be returned to the client automatically.
+  If a positive number is returned, it means that libopensrf should send a 'Request Complete'
+  message following any messages sent by the method.
+  If 0 is returned, it tells libopensrf that the method completed successfully and 
+  there is no need to send any further data to the client.
+  */
+
+
+
+/** 
+  This macro verifies methods receive the correct parameters */
+#define _OSRF_METHOD_VERIFY_CONTEXT(d) \
+       if(!d) return -1; \
+       if(!d->session) { osrfLogError( OSRF_LOG_MARK,  "Session is NULL in app reqeust" ); return -1; }\
+       if(!d->method) { osrfLogError( OSRF_LOG_MARK,  "Method is NULL in app reqeust" ); return -1; }\
+       if(d->method->argc) {\
+               if(!d->params) { osrfLogError( OSRF_LOG_MARK,  "Params is NULL in app reqeust %s", d->method->name ); return -1; }\
+               if( d->params->type != JSON_ARRAY ) { \
+                       osrfLogError( OSRF_LOG_MARK,  "'params' is not a JSON array for method %s", d->method->name);\
+                       return -1; }\
+       }\
+       if( !d->method->name ) { osrfLogError( OSRF_LOG_MARK,  "Method name is NULL"); return -1; } 
+
+#ifdef OSRF_LOG_PARAMS 
+#define OSRF_METHOD_VERIFY_CONTEXT(d) \
+       _OSRF_METHOD_VERIFY_CONTEXT(d); \
+       char* __j = jsonObjectToJSON(d->params);\
+       if(__j) { \
+               osrfLogInfo( OSRF_LOG_MARK,  "CALL:     %s %s - %s", d->session->remote_service, d->method->name, __j);\
+               free(__j); \
+       } 
+#else
+#define OSRF_METHOD_VERIFY_CONTEXT(d) _OSRF_METHOD_VERIFY_CONTEXT(d); 
+#endif
+
+
+
+/* used internally to make sure the method description provided is OK */
+#define OSRF_METHOD_VERIFY_DESCRIPTION(app, d) \
+       if(!app) return -1; \
+       if(!d) return -1;\
+       if(!d->name) { osrfLogError( OSRF_LOG_MARK,  "No method name provided in description" ), return -1; } \
+       if(!d->symbol) { osrfLogError( OSRF_LOG_MARK,  "No method symbol provided in description" ), return -1; } \
+       if(!d->notes) d->notes = ""; \
+       if(!d->paramNotes) d->paramNotes = "";\
+       if(!d->returnNotes) d->returnNotes = "";
+
+
+
+
+/* Some well known parameters */
+#define OSRF_SYSMETHOD_INTROSPECT                              "opensrf.system.method"
+#define OSRF_SYSMETHOD_INTROSPECT_ATOMIC               "opensrf.system.method.atomic"
+#define OSRF_SYSMETHOD_INTROSPECT_ALL                  "opensrf.system.method.all"
+#define OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC   "opensrf.system.method.all.atomic"
+#define OSRF_SYSMETHOD_ECHO                                            "opensrf.system.echo"
+#define OSRF_SYSMETHOD_ECHO_ATOMIC                             "opensrf.system.echo.atomic"
+
+#define OSRF_METHOD_SYSTEM                     1
+#define OSRF_METHOD_STREAMING          2
+#define OSRF_METHOD_ATOMIC                     4
+#define OSRF_METHOD_CACHABLE           8
+
+       
+
+struct _osrfApplicationStruct {
+       void* handle;                                                                   /* the lib handle */
+       osrfHash* methods;
+   void (*onExit) (void);
+};
+typedef struct _osrfApplicationStruct osrfApplication;
+
+
+struct _osrfMethodStruct {
+       char* name;                                     /* the method name */
+       char* symbol;                           /* the symbol name (function) */
+       char* notes;                            /* public method documentation */
+       int argc;                                       /* how many args this method expects */
+       //char* paramNotes;                     /* Description of the params expected for this method */
+       int options;                            /* describes the various options for this method */
+       void* userData;                         /* You can put your weeeeeeed in it ... */
+
+       /*
+       int sysmethod;                          
+       int streaming;                          
+       int atomic;                                     
+       int cachable;                           
+       */
+}; 
+typedef struct _osrfMethodStruct osrfMethod;
+
+struct _osrfMethodContextStruct {
+       osrfAppSession* session;        /* the current session */
+       osrfMethod* method;                     /* the requested method */      
+       jsonObject* params;                     /* the params to the method */
+       int request;                                    /* request id */
+       jsonObject* responses;          /* array of cached responses. */
+};
+typedef struct _osrfMethodContextStruct osrfMethodContext;
+
+
+
+/** 
+  Register an application
+  @param appName The name of the application
+  @param soFile The library (.so) file that implements this application
+  @return 0 on success, -1 on error
+  */
+int osrfAppRegisterApplication( const char* appName, const char* soFile );
+
+/**
+  Register a method
+  Any method with  the OSRF_METHOD_STREAMING option set will have a ".atomic"
+  version of the method registered automatically
+  @param appName The name of the application that implements the method
+  @param methodName The fully qualified name of the method
+  @param symbolName The symbol name (function) that implements the method
+  @param notes Public documentation for this method.
+  @params argc The number of arguments this method expects 
+  @param streaming True if this is a streaming method that requires an atomic version
+  @return 0 on success, -1 on error
+  */
+int osrfAppRegisterMethod( const char* appName, const char* methodName, 
+               const char* symbolName, const char* notes, int argc, int options );
+
+
+int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName, 
+               const char* symbolName, const char* notes, int argc, int options, void* );
+
+/**
+  Finds the given method for the given app
+  @param appName The application
+  @param methodName The method to find
+  @return A method pointer or NULL if no such method 
+  exists for the given application
+  */
+osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName );
+
+/**
+  Runs the specified method for the specified application.
+  @param appName The name of the application who's method to run
+  @param methodName The name of the method to run
+  @param ses The app session attached to this request
+  @params reqId The request id for this request
+  @param params The method parameters
+  */
+int osrfAppRunMethod( const char* appName, const char* methodName, 
+               osrfAppSession* ses, int reqId, jsonObject* params );
+
+/**
+  Responds to the client with a method exception
+  @param ses The current session
+  @param request The request id
+  @param msg The debug message to send to the client
+  @return 0 on successfully sending of the message, -1 otherwise
+  */
+int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... );
+
+int osrfAppRespond( osrfMethodContext* context, const jsonObject* data );
+int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data );
+
+/* OSRF_METHOD_ATOMIC and/or OSRF_METHOD_CACHABLE and/or 0 for no special options */
+//int osrfAppProcessMethodOptions( char* method );
+
+/**
+ * Tells the backend process to run its child init function */
+int osrfAppRunChildInit(const char* appname);
+void osrfAppRunExitCode();
+
+
diff --git a/trunk/include/opensrf/osrf_big_hash.h b/trunk/include/opensrf/osrf_big_hash.h
new file mode 100644 (file)
index 0000000..a738755
--- /dev/null
@@ -0,0 +1,87 @@
+#ifndef OSRF_HASH_H
+#define OSRF_HASH_H
+
+#include <Judy.h>
+#include <opensrf/utils.h>
+#include <opensrf/string_array.h>
+
+#define OSRF_HASH_MAXKEY 256
+
+struct __osrfBigHashStruct {
+       Pvoid_t hash;                                                   /* the hash */
+       void (*freeItem) (char* key, void* item);       /* callback for freeing stored items */
+};
+typedef struct __osrfBigHashStruct osrfBigHash;
+
+
+struct __osrfBigHashIteratorStruct {
+       char* current;
+       osrfBigHash* hash;
+};
+typedef struct __osrfBigHashIteratorStruct osrfBigHashIterator;
+
+/**
+  Allocates a new hash object
+  */
+osrfBigHash* osrfNewBigHash();
+
+/**
+  Sets the given key with the given item
+  if "freeItem" is defined and an item already exists at the given location, 
+  then old item is freed and the new item is put into place.
+  if "freeItem" is not defined and an item already exists, the old item
+  is returned.
+  @return The old item if exists and there is no 'freeItem', returns NULL
+  otherwise
+  */
+void* osrfBigHashSet( osrfBigHash* hash, void* item, const char* key, ... );
+
+/**
+  Removes an item from the hash.
+  if 'freeItem' is defined it is used and NULL is returned,
+  else the freed item is returned
+  */
+void* osrfBigHashRemove( osrfBigHash* hash, const char* key, ... );
+
+void* osrfBigHashGet( osrfBigHash* hash, const char* key, ... );
+
+
+/**
+  @return A list of strings representing the keys of the hash. 
+  caller is responsible for freeing the returned string array 
+  with osrfStringArrayFree();
+  */
+osrfStringArray* osrfBigHashKeys( osrfBigHash* hash );
+
+/**
+  Frees a hash
+  */
+void osrfBigHashFree( osrfBigHash* hash );
+
+/**
+  @return The number of items in the hash
+  */
+unsigned long osrfBigHashGetCount( osrfBigHash* hash );
+
+
+
+
+/**
+  Creates a new list iterator with the given list
+  */
+osrfBigHashIterator* osrfNewBigHashIterator( osrfBigHash* hash );
+
+/**
+  Returns the next non-NULL item in the list, return NULL when
+  the end of the list has been reached
+  */
+void* osrfBigHashIteratorNext( osrfBigHashIterator* itr );
+
+/**
+  Deallocates the given list
+  */
+void osrfBigHashIteratorFree( osrfBigHashIterator* itr );
+
+void osrfBigHashIteratorReset( osrfBigHashIterator* itr );
+
+#endif
diff --git a/trunk/include/opensrf/osrf_big_list.h b/trunk/include/opensrf/osrf_big_list.h
new file mode 100644 (file)
index 0000000..ebe3da7
--- /dev/null
@@ -0,0 +1,142 @@
+#ifndef OSRF_BIG_LIST_H
+#define OSRF_BIG_LIST_H
+
+
+#include <stdio.h>
+#include <opensrf/utils.h>
+#include <Judy.h>
+
+/**
+  Items are stored as void*'s so it's up to the user to
+  manage the data wisely.  Also, if the 'freeItem' callback is defined for the list,
+  then, it will be used on any item that needs to be freed, so don't mix data
+  types in the list if you want magic freeing */
+
+struct __osrfBigListStruct {
+       Pvoid_t list;                                                   /* the list */
+       int size;                                                               /* how many items in the list including NULL items between non-NULL items */    
+       void (*freeItem) (void* item);  /* callback for freeing stored items */
+};
+typedef struct __osrfBigListStruct osrfBigList;
+
+
+struct __osrfBigBigListIteratorStruct {
+       osrfBigList* list;
+       unsigned long current;
+};
+typedef struct __osrfBigBigListIteratorStruct osrfBigBigListIterator;
+
+
+/**
+  Creates a new list iterator with the given list
+  */
+osrfBigBigListIterator* osrfNewBigListIterator( osrfBigList* list );
+
+/**
+  Returns the next non-NULL item in the list, return NULL when
+  the end of the list has been reached
+  */
+void* osrfBigBigListIteratorNext( osrfBigBigListIterator* itr );
+
+/**
+  Deallocates the given list
+  */
+void osrfBigBigListIteratorFree( osrfBigBigListIterator* itr );
+
+void osrfBigBigListIteratorReset( osrfBigBigListIterator* itr );
+
+
+/**
+  Allocates a new list
+  @param compress If true, the list will compress empty slots on delete.  If item positionality
+  is not important, then using this feature is reccomended to keep the list from growing indefinitely.
+  if item positionality is not important.
+  @return The allocated list
+  */
+osrfBigList* osrfNewBigList();
+
+/**
+  Pushes an item onto the end of the list.  This always finds the highest index
+  in the list and pushes the new item into the list after it.
+  @param list The list
+  @param item The item to push
+  @return 0 on success, -1 on failure
+  */
+int osrfBigListPush( osrfBigList* list, void* item );
+
+
+/**
+ * Removes the last item in the list
+ * See osrfBigListRemove for details on how the removed item is handled
+ * @return The item, unless 'freeItem' exists, then returns NULL
+ */
+void* osrfBigListPop( osrfBigList* list );
+
+/**
+  Puts the given item into the list at the specified position.  If there
+  is already an item at the given position and the list has it's 
+  "freeItem" function defined, then it will be used to free said item.
+  If no 'freeItem' callback is defined, then the displaced item will
+  be returned;
+  @param list The list
+  @param item The item to put into the list
+  @param position The position to place the item in
+  @return NULL in successfully inserting the new item and freeing
+  any displaced items.  Returns the displaced item if no "freeItem"
+  callback is defined.
+       */
+void* osrfBigListSet( osrfBigList* list, void* item, unsigned long position );
+
+/**
+  Returns the item at the given position
+  @param list The list
+  @param postiont the position
+  */
+void* osrfBigListGetIndex( osrfBigList* list, unsigned long  position );
+
+/**
+  Frees the list and all list items (if the list has a "freeItem" function defined )
+  @param list The list
+  */
+void osrfBigListFree( osrfBigList* list );
+
+/**
+  Removes the list item at the given index
+  @param list The list
+  @param position The position of the item to remove
+  @return A pointer to the item removed if "freeItem" is not defined
+  for this list, returns NULL if it is.
+  */
+void* osrfBigListRemove( osrfBigList* list, int position );
+
+/**
+  Finds the list item whose void* is the same as the one passed in
+  @param list The list
+  @param addr The pointer connected to the list item we're to find
+  @return the index of the item, or -1 if the item was not found
+  */
+int osrfBigListFind( osrfBigList* list, void* addr );
+
+
+void __osrfBigListSetSize( osrfBigList* list );
+
+
+/**
+  @return The number of non-null items in the list
+  */
+unsigned long osrfBigListGetCount( osrfBigList* list );
+
+/**
+ * May be used as a default memory freeing call
+ * Just calls free() on list items
+ */
+void osrfBigListVanillaFree( void* item );
+
+/**
+ * Tells the list to just call 'free()' on each item when
+ * an item or the whole list is destroyed
+ */
+void osrfBigListSetDefaultFree( osrfBigList* list );
+
+
+#endif
diff --git a/trunk/include/opensrf/osrf_cache.h b/trunk/include/opensrf/osrf_cache.h
new file mode 100644 (file)
index 0000000..7545237
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+Bill Erickson <highfalutin@gmail.com>
+
+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.
+*/
+
+
+#include <opensrf/osrf_json.h>
+#include <memcache.h>
+#include <opensrf/log.h>
+
+/**
+  osrfCache is a globally shared cache API
+  */
+
+
+/**
+  Initialize the cache.
+  @param serverStrings An array of "ip:port" strings to use as cache servers
+  @param size The size of the serverStrings array
+  @param maxCacheSeconds The maximum amount of time an object / string may
+       be cached.  Negative number means there is no limit
+  */
+int osrfCacheInit( const char* serverStrings[], int size, time_t maxCacheSeconds );
+
+
+/**
+  Puts an object into the cache
+  @param key The cache key
+  @param obj The object to cache
+  @param seconds The amount of time to cache the data, negative number means
+       to cache up to 'maxCacheSeconds' as set by osrfCacheInit()
+  @return 0 on success, -1 on error
+  */
+int osrfCachePutObject( char* key, const jsonObject* obj, time_t seconds );
+
+/**
+  Puts a string into the cache
+  @param key The cache key
+  @param value The string to cache
+  @param seconds The amount of time to cache the data, negative number means
+       to cache up to 'maxCacheSeconds' as set by osrfCacheInit()
+  @return 0 on success, -1 on error
+  */
+int osrfCachePutString( char* key, const char* value, time_t seconds);
+
+/**
+  Grabs an object from the cache.
+  @param key The cache key
+  @return The object (which must be freed) if it exists, otherwise returns NULL
+  */
+jsonObject* osrfCacheGetObject( const char* key, ... );
+
+/**
+  Grabs a string from the cache.
+  @param key The cache key
+  @return The string (which must be freed) if it exists, otherwise returns NULL
+  */
+char* osrfCacheGetString( const char* key, ... );
+
+/**
+  Removes the item with the given key from the cache.
+  @return 0 on success, -1 on error.
+  */
+int osrfCacheRemove( const char* key, ... );
+
+/**
+ * Sets the expire time to 'seconds' for the given key
+ */
+int osrfCacheSetExpire( time_t seconds, const char* key, ... );
+
+
+
+/**
+ * Clean up the global cache handles, etc.
+ */
+void osrfCacheCleanup();
diff --git a/trunk/include/opensrf/osrf_hash.h b/trunk/include/opensrf/osrf_hash.h
new file mode 100644 (file)
index 0000000..544805a
--- /dev/null
@@ -0,0 +1,86 @@
+#ifndef OSRF_HASH_H
+#define OSRF_HASH_H
+
+#include <opensrf/utils.h>
+#include <opensrf/string_array.h>
+#include <opensrf/osrf_list.h>
+
+struct _osrfHashStruct;
+typedef struct _osrfHashStruct osrfHash;
+
+struct _osrfHashIteratorStruct;
+typedef struct _osrfHashIteratorStruct osrfHashIterator;
+
+/**
+  Allocates a new hash object
+  */
+osrfHash* osrfNewHash();
+
+/** Installs a callback function for freeing stored items
+ */
+void osrfHashSetCallback( osrfHash* hash, void (*callback) (char* key, void* item) );
+
+/**
+  Sets the given key with the given item
+  if "freeItem" is defined and an item already exists at the given location, 
+  then old item is freed and the new item is put into place.
+  if "freeItem" is not defined and an item already exists, the old item
+  is returned.
+  @return The old item if exists and there is no 'freeItem', returns NULL
+  otherwise
+  */
+void* osrfHashSet( osrfHash* hash, void* item, const char* key, ... );
+
+/**
+  Removes an item from the hash.
+  if 'freeItem' is defined it is used and NULL is returned,
+  else the freed item is returned
+  */
+void* osrfHashRemove( osrfHash* hash, const char* key, ... );
+
+void* osrfHashGet( osrfHash* hash, const char* key, ... );
+
+
+/**
+  @return A list of strings representing the keys of the hash. 
+  caller is responsible for freeing the returned string array 
+  with osrfStringArrayFree();
+  */
+osrfStringArray* osrfHashKeys( osrfHash* hash );
+
+/**
+  Frees a hash
+  */
+void osrfHashFree( osrfHash* hash );
+
+/**
+  @return The number of items in the hash
+  */
+unsigned long osrfHashGetCount( osrfHash* hash );
+
+/**
+  Creates a new list iterator with the given list
+  */
+osrfHashIterator* osrfNewHashIterator( osrfHash* hash );
+
+int osrfHashIteratorHasNext( osrfHashIterator* itr );
+
+/**
+  Returns the next non-NULL item in the list, return NULL when
+  the end of the list has been reached
+  */
+void* osrfHashIteratorNext( osrfHashIterator* itr );
+
+/**
+  Returns a pointer to the key of the current hash item
+  */
+const char* osrfHashIteratorKey( const osrfHashIterator* itr );
+
+/**
+  Deallocates the given list
+  */
+void osrfHashIteratorFree( osrfHashIterator* itr );
+
+void osrfHashIteratorReset( osrfHashIterator* itr );
+
+#endif
diff --git a/trunk/include/opensrf/osrf_json.h b/trunk/include/opensrf/osrf_json.h
new file mode 100644 (file)
index 0000000..4fadfda
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+Copyright (C) 2006  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+#ifndef _JSON_H
+#define _JSON_H
+
+#include <opensrf/utils.h>
+#include <opensrf/osrf_list.h>
+#include <opensrf/osrf_hash.h>
+
+/* parser states */
+#define JSON_STATE_IN_OBJECT   0x1
+#define JSON_STATE_IN_ARRAY            0x2
+#define JSON_STATE_IN_STRING   0x4
+#define JSON_STATE_IN_UTF              0x8
+#define JSON_STATE_IN_ESCAPE   0x10
+#define JSON_STATE_IN_KEY              0x20
+#define JSON_STATE_IN_NULL             0x40
+#define JSON_STATE_IN_TRUE             0x80
+#define JSON_STATE_IN_FALSE            0x100
+#define JSON_STATE_IN_NUMBER   0x200
+#define JSON_STATE_IS_INVALID  0x400
+#define JSON_STATE_IS_DONE             0x800
+#define JSON_STATE_START_COMMEN        0x1000
+#define JSON_STATE_IN_COMMENT  0x2000
+#define JSON_STATE_END_COMMENT 0x4000
+
+
+/* object and array (container) states are pushed onto a stack so we
+ * can keep track of the object nest.  All other states are
+ * simply stored in the state field of the parser */
+#define JSON_STATE_SET(ctx,s) ctx->state |= s; /* set a state */
+#define JSON_STATE_REMOVE(ctx,s) ctx->state &= ~s; /* unset a state */
+#define JSON_STATE_CHECK(ctx,s) (ctx->state & s) ? 1 : 0 /* check if a state is set */
+#define JSON_STATE_POP(ctx) osrfListPop( ctx->stateStack ); /* remove a state from the stack */
+#define JSON_STATE_PUSH(ctx, state) osrfListPush( ctx->stateStack,(void*) state );/* push a state on the stack */
+#define JSON_STATE_PEEK(ctx) osrfListGetIndex(ctx->stateStack, ctx->stateStack->size -1) /* check which container type we're currently in */
+#define JSON_STATE_CHECK_STACK(ctx, s) (JSON_STATE_PEEK(ctx) == (void*) s ) ? 1 : 0  /* compare stack values */
+
+/* JSON types */
+#define JSON_HASH      0
+#define JSON_ARRAY     1
+#define JSON_STRING    2
+#define JSON_NUMBER    3
+#define JSON_NULL      4       
+#define JSON_BOOL      5
+
+#define JSON_PARSE_LAST_CHUNK 0x1 /* this is the last part of the string we're parsing */
+
+#define JSON_PARSE_FLAG_CHECK(ctx, f) (ctx->flags & f) ? 1 : 0 /* check if a parser state is set */
+
+#ifndef JSON_CLASS_KEY
+#define JSON_CLASS_KEY "__c"
+#endif
+#ifndef JSON_DATA_KEY
+#define JSON_DATA_KEY "__p"
+#endif
+
+
+struct jsonParserContextStruct {
+       int state;                                              /* what are we currently parsing */
+       const char* chunk;                              /* the chunk we're currently parsing */
+       int index;                                              /* where we are in parsing the current chunk */
+       int chunksize;                                  /* the size of the current chunk */
+       int flags;                                              /* parser flags */
+       osrfList* stateStack;           /* represents the nest of object/array states */
+       growing_buffer* buffer;         /* used to hold JSON strings, number, true, false, and null sequences */
+       growing_buffer* utfbuf;         /* holds the current unicode characters */
+       void* userData;                         /* opaque user pointer.  we ignore this */
+       const struct jsonParserHandlerStruct* handler; /* the event handler struct */
+};
+typedef struct jsonParserContextStruct jsonParserContext;
+
+struct jsonParserHandlerStruct {
+       void (*handleStartObject)       (void* userData);
+       void (*handleObjectKey)         (void* userData, char* key);
+       void (*handleEndObject)         (void* userData);
+       void (*handleStartArray)        (void* userData);
+       void (*handleEndArray)          (void* userData);
+       void (*handleNull)                      (void* userData);
+       void (*handleString)                    (void* userData, char* string);
+       void (*handleBool)                      (void* userData, int boolval);
+       void (*handleNumber)            (void* userData, const char* numstr);
+       void (*handleError)                     (void* userData, char* err, ...);
+};
+typedef struct jsonParserHandlerStruct jsonParserHandler;
+
+struct _jsonObjectStruct {
+       unsigned long size;     /* number of sub-items */
+       char* classname;                /* optional class hint (not part of the JSON spec) */
+       int type;                               /* JSON type */
+       struct _jsonObjectStruct* parent;       /* who we're attached to */
+       union __jsonValue {     /* cargo */
+               osrfHash*       h;              /* object container */
+               osrfList*       l;              /* array container */
+               char*           s;              /* string */
+               int                     b;              /* bool */
+//             double  n;              /* number */
+               double  n;              /* number */
+       } value;
+};
+typedef struct _jsonObjectStruct jsonObject;
+
+struct _jsonIteratorStruct {
+       jsonObject* obj; /* the object we're traversing */
+       osrfHashIterator* hashItr; /* the iterator for this hash */
+       char* key; /* if this object is an object, the current key */
+       unsigned long index; /* if this object is an array, the index */
+};
+typedef struct _jsonIteratorStruct jsonIterator;
+
+
+
+/** 
+ * Allocates a new parser context object
+ * @param handler The event handler struct
+ * @param userData Opaque user pointer which is available in callbacks
+ * and ignored by the parser
+ * @return An allocated parser context, NULL on error
+ */
+jsonParserContext* jsonNewParser( const jsonParserHandler* handler, void* userData);
+
+/**
+ * Deallocates a parser context
+ * @param ctx The context object
+ */
+void jsonParserFree( jsonParserContext* ctx );
+
+/**
+ * Parse a chunk of data.
+ * @param ctx The parser context
+ * @param data The data to parse
+ * @param datalen The size of the chunk to parser
+ * @param flags Reserved
+ */
+int jsonParseChunk( jsonParserContext* ctx, const char* data, int datalen, int flags );
+
+
+/**
+ * Parses a JSON string;
+ * @param str The string to parser
+ * @return The resulting JSON object or NULL on error
+ */
+jsonObject* jsonParseString( const char* str );
+jsonObject* jsonParseStringRaw( const char* str );
+
+jsonObject* jsonParseStringFmt( const char* str, ... );
+
+/**
+ * Parses a JSON string;
+ * @param str The string to parser
+ * @return The resulting JSON object or NULL on error
+ */
+jsonObject* jsonParseStringHandleError( void (*errorHandler) (const char*), char* str, ... );
+
+
+
+/**
+ * Creates a new json object
+ * @param data The string data this object will hold if 
+ * this object happens to be a JSON_STRING, NULL otherwise
+ * @return The allocated json object.  Must be freed with 
+ * jsonObjectFree()
+ */
+jsonObject* jsonNewObject(const char* data);
+jsonObject* jsonNewObjectFmt(const char* data, ...);
+
+/**
+ * Creates a new object of the given type
+ */
+jsonObject* jsonNewObjectType(int type);
+
+/**
+ * Creates a new number object from a double
+ */
+jsonObject* jsonNewNumberObject( double num );
+
+/**
+ * Creates a new number object from a numeric string
+ */
+jsonObject* jsonNewNumberStringObject( const char* numstr );
+
+/**
+ * Creates a new json bool
+ */
+jsonObject* jsonNewBoolObject(int val);
+
+/**
+ * Deallocates an object
+ */
+void jsonObjectFree( jsonObject* o );
+
+/**
+ * Returns all unused jsonObjects to the heap
+ */
+void jsonObjectFreeUnused( void );
+
+/**
+ * Forces the given object to become an array (if it isn't already one) 
+ * and pushes the new object into the array
+ */
+unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo);
+
+/**
+ * Forces the given object to become a hash (if it isn't already one)
+ * and assigns the new object to the key of the hash
+ */
+unsigned long jsonObjectSetKey(
+               jsonObject* o, const char* key, jsonObject* newo);
+
+
+/**
+ * Turns the object into a JSON string.  The string must be freed by the caller */
+char* jsonObjectToJSON( const jsonObject* obj );
+char* jsonObjectToJSONRaw( const jsonObject* obj );
+
+
+/**
+ * Retrieves the object at the given key
+ */
+jsonObject* jsonObjectGetKey( jsonObject* obj, const char* key );
+const jsonObject* jsonObjectGetKeyConst( const jsonObject* obj, const char* key );
+
+
+
+
+
+/** Allocates a new iterator 
+       @param obj The object over which to iterate.
+*/
+jsonIterator* jsonNewIterator(const jsonObject* obj);
+
+
+/** 
+       De-allocates an iterator 
+       @param iter The iterator object to free
+*/
+void jsonIteratorFree(jsonIterator* iter);
+
+/** 
+       Returns the object_node currently pointed to by the iterator
+       and increments the pointer to the next node
+       @param iter The iterator in question.
+ */
+jsonObject* jsonIteratorNext(jsonIterator* iter);
+
+
+/** 
+       @param iter The iterator.
+       @return True if there is another node after the current node.
+ */
+int jsonIteratorHasNext(const jsonIterator* iter);
+
+
+/** 
+       Returns a pointer to the object at the given index.  This call is
+       only valid if the object has a type of JSON_ARRAY.
+       @param obj The object
+       @param index The position within the object
+       @return The object at the given index.
+*/
+jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index );
+
+
+/* removes (and deallocates) the object at the given index (if one exists) and inserts 
+ * the new one.  returns the size on success, -1 on error 
+ * If obj is NULL, inserts a new object into the list with is_null set to true
+ */
+unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObject* newObj);
+
+/* removes the object at the given index and, if more items exist,
+ * re-indexes (shifts down by 1) the rest of the objects in the array
+ */
+unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index);
+
+/* removes (and deallocates) the object with key 'key' if it exists */
+unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key);
+
+/* returns a pointer to the string data held by this object if this object
+       is a string.  Otherwise returns NULL*/
+char* jsonObjectGetString(const jsonObject*);
+
+double jsonObjectGetNumber( const jsonObject* obj );
+
+/* sets the string data */
+void jsonObjectSetString(jsonObject* dest, const char* string);
+
+/* sets the number value for the object */
+void jsonObjectSetNumber(jsonObject* dest, double num);
+int jsonObjectSetNumberString(jsonObject* dest, const char* string);
+
+/* sets the class hint for this object */
+void jsonObjectSetClass(jsonObject* dest, const char* classname );
+const char* jsonObjectGetClass(const jsonObject* dest);
+
+int jsonBoolIsTrue( const jsonObject* boolObj );
+
+void jsonSetBool(jsonObject* bl, int val);
+
+jsonObject* jsonObjectClone( const jsonObject* o );
+
+
+/* tries to extract the string data from an object.
+       if object       -> NULL (the C NULL)
+       if array                ->      NULL  
+       if null         -> NULL 
+       if bool         -> NULL
+       if string/number the string version of either of those
+       The caller is responsible for freeing the returned string
+       */
+char* jsonObjectToSimpleString( const jsonObject* o );
+
+/**
+ Allocate a buffer and format a specified numeric value into it,
+ with up to 30 decimal digits of precision.   Caller is responsible
+ for freeing the buffer.
+ **/
+char* doubleToString( double num );
+
+/**
+ Return 1 if the string is numeric, otherwise return 0.
+ This validation follows the rules defined by the grammar at:
+ http://www.json.org/
+ **/
+int jsonIsNumeric( const char* s );
+
+/**
+ Allocate and reformat a numeric string into one that is valid
+ by JSON rules.  If the string is not numeric, return NULL.
+ Caller is responsible for freeing the buffer.
+ **/
+char* jsonScrubNumber( const char* s );
+
+/* provides an XPATH style search interface (e.g. /some/node/here) and 
+       return the object at that location if one exists.  Naturally,  
+       every element in the path must be a proper object ("hash" / {}).
+       Returns NULL if the specified node is not found 
+       Note also that the object returned is a clone and
+       must be freed by the caller
+*/
+jsonObject* jsonObjectFindPath( const jsonObject* obj, const char* path, ... );
+
+
+/* formats a JSON string from printing.  User must free returned string */
+char* jsonFormatString( const char* jsonString );
+
+/* sets the error handler for all parsers */
+void jsonSetGlobalErrorHandler(void (*errorHandler) (const char*));
+
+jsonObject* jsonParseFile( const char* filename );
+
+/* ------------------------------------------------------------------------- */
+/**
+ * The following methods provide a facility for serializing and
+ * deserializing "classed" JSON objects.  To give a JSON object a 
+ * class, simply call jsonObjectSetClass().  
+ * Then, calling jsonObjectEncodeClass() will convert the JSON
+ * object (and any sub-objects) to a JSON object with class 
+ * wrapper objects like so:
+ * { _c : "classname", _d : <json_thing> }
+ * In this example _c is the class key and _d is the data (object)
+ * key.  The keys are defined by the constants 
+ * OSRF_JSON_CLASS_KEY and OSRF_JSON_DATA_KEY
+ * To revive a serialized object, simply call
+ * jsonObjectDecodeClass()
+ */
+
+
+/** Converts a class-wrapped object into an object with the
+ * classname set
+ * Caller must free the returned object 
+ */ 
+jsonObject* jsonObjectDecodeClass( const jsonObject* obj );
+
+
+/** Converts an object with a classname into a
+ * class-wrapped (serialized) object
+ * Caller must free the returned object 
+ */ 
+jsonObject* jsonObjectEncodeClass( const jsonObject* obj );
+
+/* ------------------------------------------------------------------------- */
+
+
+/**
+ *     Generates an XML representation of a JSON object */
+char* jsonObjectToXML(const jsonObject*);
+
+
+/*
+ * Builds a JSON object from the provided XML 
+ */
+jsonObject* jsonXMLToJSONObject(const char* xml);
+
+
+#endif
diff --git a/trunk/include/opensrf/osrf_json_utils.h b/trunk/include/opensrf/osrf_json_utils.h
new file mode 100644 (file)
index 0000000..53faefa
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+Copyright (C) 2006  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+#ifndef OSRF_JSON_UTILS_H
+#define OSRF_JSON_UTILS_H
+
+/* ----------------------------------------------------------------------- */
+/* Clients need not include this file.  These are internal utilities only      */
+/* ----------------------------------------------------------------------- */
+
+#define JSON_EAT_WS(ctx)       \
+       while( ctx->index < ctx->chunksize ) {  \
+               if(!isspace(ctx->chunk[ctx->index])) break; \
+               ctx->index++;   \
+       } \
+       if( ctx->index >= ctx->chunksize ) return 0; \
+       c = ctx->chunk[ctx->index];
+
+#define JSON_CACHE_DATA(ctx, buf, size) \
+       while( (buf->n_used < size) && (ctx->index < ctx->chunksize) ) \
+               buffer_add_char(buf, ctx->chunk[ctx->index++]); 
+
+#define JSON_LOG_MARK __FILE__,__LINE__
+
+#define JSON_NUMBER_CHARS "0123456789.+-eE"
+
+/**
+ * These are the callbacks through which the top level parser 
+ * builds objects via the push parser
+ */
+void _jsonHandleStartObject(void*);
+void _jsonHandleObjectKey(void*, char* key);
+void _jsonHandleEndObject(void*);
+void _jsonHandleStartArray(void*);
+void _jsonHandleEndArray(void*);
+void _jsonHandleNull(void*);
+void _jsonHandleString(void*, char* string);
+void _jsonHandleBool(void*, int boolval);
+void _jsonHandleNumber(void*, const char* numstr);
+void _jsonHandleError(void*, char* str, ...);
+
+struct jsonInternalParserStruct {
+       jsonParserContext* ctx;
+       jsonObject* obj;
+       jsonObject* current;
+       char* lastkey;
+       void (*handleError) (const char*);
+};
+typedef struct jsonInternalParserStruct jsonInternalParser;
+
+jsonInternalParser* _jsonNewInternalParser();
+void _jsonInternalParserFree(jsonInternalParser* p);
+
+/**
+ * Calls the defined error handler with the given error message.
+ * @return -1
+ */
+int _jsonParserError( jsonParserContext* ctx, char* err, ... );
+
+
+/**
+ *
+ * @return 0 on continue, 1 if it goes past the end of the string, -1 on error
+ */
+int _jsonParserHandleUnicode( jsonParserContext* ctx );
+
+
+/**
+ * @param type 0 for null, 1 for true, 2 for false
+ * @return 0 on continue, 1 if it goes past the end of the string, -1 on error
+ */
+int _jsonParserHandleMatch( jsonParserContext* ctx, int type );
+
+/**
+ * @return 0 on continue, 1 on end of chunk, -1 on error 
+ */
+int _jsonParserHandleString( jsonParserContext* ctx );
+
+/**
+ * @return 0 on continue, 1 on end of chunk, -1 on error 
+ */
+int _jsonParserHandleNumber( jsonParserContext* ctx );
+
+
+void _jsonInsertParserItem( jsonInternalParser* p, jsonObject* newo );
+
+#endif
+
diff --git a/trunk/include/opensrf/osrf_json_xml.h b/trunk/include/opensrf/osrf_json_xml.h
new file mode 100644 (file)
index 0000000..ce06817
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef OSRF_JSON_XML_H
+#define OSRF_JSON_XML_H
+
+#ifdef OSRF_JSON_ENABLE_XML_UTILS
+
+#include <stdio.h>
+#include <string.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+
+#include <opensrf/osrf_json.h>
+#include <opensrf/utils.h>
+#include <opensrf/osrf_list.h>
+
+
+/**
+ *     Generates an XML representation of a JSON object */
+char* jsonObjectToXML( const jsonObject*);
+
+
+/*
+ * Builds a JSON object from the provided XML 
+ */
+jsonObject* jsonXMLToJSONObject(const char* xml);
+
+#endif
+#endif
diff --git a/trunk/include/opensrf/osrf_legacy_json.h b/trunk/include/opensrf/osrf_legacy_json.h
new file mode 100644 (file)
index 0000000..a825a8d
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+Bill Erickson <highfalutin@gmail.com>
+
+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.
+*/
+
+
+
+
+/* ---------------------------------------------------------------------------------------
+       JSON parser.
+ * --------------------------------------------------------------------------------------- */
+#ifndef LEGACY_JSON_H
+#define LEGACY_JSON_H
+
+#include <opensrf/osrf_json.h>
+#include <ctype.h>
+
+
+
+/* Parses the given JSON string and returns the built object. 
+ *     returns NULL (and prints parser error to stderr) on error.  
+ */
+
+jsonObject* json_parse_string(char* string);
+
+jsonObject* legacy_jsonParseString(const char* string);
+jsonObject* legacy_jsonParseStringFmt( const char* string, ... );
+
+jsonObject* json_parse_file( const char* filename );
+
+jsonObject* legacy_jsonParseFile( const char* string );
+
+
+
+/* does the actual parsing work.  returns 0 on success.  -1 on error and
+ * -2 if there was no object to build (string was all comments) 
+ */
+int _json_parse_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen);
+
+/* returns 0 on success and turns obj into a string object */
+int json_parse_json_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen);
+
+/* returns 0 on success and turns obj into a number or double object */
+int json_parse_json_number(char* string, unsigned long* index, jsonObject* obj, int current_strlen);
+
+/* returns 0 on success and turns obj into an 'object' object */
+int json_parse_json_object(char* string, unsigned long* index, jsonObject* obj, int current_strlen);
+
+/* returns 0 on success and turns object into an array object */
+int json_parse_json_array(char* string, unsigned long* index, jsonObject* obj, int current_strlen);
+
+/* churns through whitespace and increments index as it goes.
+ * eat_all == true means we should eat newlines, tabs
+ */
+void json_eat_ws(char* string, unsigned long* index, int eat_all, int current_strlen);
+
+int json_parse_json_bool(char* string, unsigned long* index, jsonObject* obj, int current_strlen);
+
+/* removes comments from a json string.  if the comment contains a class hint
+ * and class_hint isn't NULL, an allocated char* with the class name will be
+ * shoved into *class_hint.  returns 0 on success, -1 on parse error.
+ * 'index' is assumed to be at the second character (*) of the comment
+ */
+int json_eat_comment(char* string, unsigned long* index, char** class_hint, int parse_class, int current_strlen);
+
+/* prints a useful error message to stderr. always returns -1 */
+int json_handle_error(char* string, unsigned long* index, char* err_msg);
+
+int json_parse_json_null(char* string, unsigned long* index, jsonObject* obj, int current_strlen);
+
+
+char* legacy_jsonObjectToJSON( const jsonObject* obj );
+
+
+
+/* LEGACY ITERATOR CODE ---------------------------------------------------  
+   ------------------------------------------------------------------------ */
+
+struct _jsonObjectNodeStruct {
+       unsigned long index; /* our array position */
+       char* key; /* our hash key */
+       jsonObject* item; /* our object */
+};
+typedef struct _jsonObjectNodeStruct jsonObjectNode;
+
+
+
+/* utility object for iterating over hash objects */
+struct _jsonObjectIteratorStruct {
+    jsonIterator* iterator;
+       const jsonObject* obj; /* the topic object */
+       jsonObjectNode* current; /* the current node within the object */
+    int done;
+};
+typedef struct _jsonObjectIteratorStruct jsonObjectIterator;
+
+
+/** Allocates a new iterator 
+       @param obj The object over which to iterate.
+*/
+jsonObjectIterator* jsonNewObjectIterator(const jsonObject* obj);
+
+/** 
+       De-allocates an iterator 
+       @param iter The iterator object to free
+*/
+void jsonObjectIteratorFree(jsonObjectIterator* iter);
+
+/** 
+       Returns the object_node currently pointed to by the iterator
+       and increments the pointer to the next node
+       @param iter The iterator in question.
+ */
+jsonObjectNode* jsonObjectIteratorNext(jsonObjectIterator* iter);
+
+/** 
+       @param iter The iterator.
+       @return True if there is another node after the current node.
+ */
+int jsonObjectIteratorHasNext(const jsonObjectIterator* iter);
+
+
+#endif
+
+
diff --git a/trunk/include/opensrf/osrf_list.h b/trunk/include/opensrf/osrf_list.h
new file mode 100644 (file)
index 0000000..d740686
--- /dev/null
@@ -0,0 +1,143 @@
+#ifndef OSRF_LIST_H
+#define OSRF_LIST_H
+
+#include <opensrf/utils.h>
+
+#define OSRF_LIST_GET_INDEX(l, i) (!l || i >= l->size) ? NULL: l->arrlist[i]
+
+/**
+  Items are stored as void*'s so it's up to the user to
+  manage the data wisely.  Also, if the 'freeItem' callback is defined for the list,
+  then, it will be used on any item that needs to be freed, so don't mix data
+  types in the list if you want magic freeing */
+
+struct _osrfListStruct {
+       unsigned int size;                      /* how many items in the list including NULL items between non-NULL items */    
+       void (*freeItem) (void* item);  /* callback for freeing stored items */
+       void** arrlist;
+       int arrsize; /* how big is the currently allocated array */
+};
+typedef struct _osrfListStruct osrfList;
+
+
+struct _osrfListIteratorStruct {
+       const osrfList* list;
+       unsigned int current;
+};
+typedef struct _osrfListIteratorStruct osrfListIterator;
+
+osrfList* osrfNewListSize( unsigned int size );
+
+
+/**
+  Creates a new list iterator with the given list
+  */
+osrfListIterator* osrfNewListIterator( const osrfList* list );
+
+/**
+  Returns the next non-NULL item in the list, return NULL when
+  the end of the list has been reached
+  */
+void* osrfListIteratorNext( osrfListIterator* itr );
+
+/**
+  Deallocates the given list
+  */
+void osrfListIteratorFree( osrfListIterator* itr );
+
+void osrfListIteratorReset( osrfListIterator* itr );
+
+
+/**
+  Allocates a new list
+  @return The allocated list
+  */
+osrfList* osrfNewList();
+
+/**
+  Pushes an item onto the end of the list.  This always finds the highest index
+  in the list and pushes the new item into the list after it.
+  @param list The list
+  @param item The item to push
+  @return 0 on success, -1 on failure
+  */
+int osrfListPush( osrfList* list, void* item );
+
+
+/**
+ * Removes the last item in the list
+ * See osrfListRemove for details on how the removed item is handled
+ * @return The item, unless 'freeItem' exists, then returns NULL
+ */
+void* osrfListPop( osrfList* list );
+
+/**
+  Puts the given item into the list at the specified position.  If there
+  is already an item at the given position and the list has its 
+  "freeItem" function defined, then it will be used to free said item.
+  If no 'freeItem' callback is defined, then the displaced item will
+  be returned;
+  @param list The list
+  @param item The item to put into the list
+  @param position The position to place the item in
+  @return NULL in successfully inserting the new item and freeing
+  any displaced items.  Returns the displaced item if no "freeItem"
+  callback is defined.
+       */
+void* osrfListSet( osrfList* list, void* item, unsigned int position );
+
+/**
+  Returns the item at the given position
+  @param list The list
+  @param postiont the position
+  */
+void* osrfListGetIndex( const osrfList* list, unsigned int  position );
+
+/**
+  Frees the list and all list items (if the list has a "freeItem" function defined )
+  @param list The list
+  */
+void osrfListFree( osrfList* list );
+
+/**
+  Removes the list item at the given index
+  @param list The list
+  @param position The position of the item to remove
+  @return A pointer to the item removed if "freeItem" is not defined
+  for this list, returns NULL if it is.
+  */
+void* osrfListRemove( osrfList* list, unsigned int position );
+
+/**
+  Finds the list item whose void* is the same as the one passed in
+  @param list The list
+  @param addr The pointer connected to the list item we're to find
+  @return the index of the item, or -1 if the item was not found
+  */
+int osrfListFind( const osrfList* list, void* addr );
+
+/**
+  @return The number of non-null items in the list
+  */
+unsigned int osrfListGetCount( const osrfList* list );
+
+/**
+ * May be used as a default memory freeing call
+ * Just calls free() on list items
+ */
+void osrfListVanillaFree( void* item );
+
+/**
+ * Tells the list to just call 'free()' on each item when
+ * an item or the whole list is destroyed
+ */
+void osrfListSetDefaultFree( osrfList* list );
+
+/**
+ * Inserts the new item at the first free (null) slot
+ * in the array.  Item is shoved onto the end of the
+ * list if there are no null slots */
+int osrfListPushFirst( osrfList* list, void* item );
+
+
+#endif
diff --git a/trunk/include/opensrf/osrf_message.h b/trunk/include/opensrf/osrf_message.h
new file mode 100644 (file)
index 0000000..6030132
--- /dev/null
@@ -0,0 +1,142 @@
+#include <opensrf/string_array.h>
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+#include <opensrf/osrf_json.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>
+
+
+
+#ifndef osrf_message_h
+#define osrf_message_h
+
+#define OSRF_XML_NAMESPACE "http://open-ils.org/xml/namespaces/oils_v1"
+
+#define OSRF_STATUS_CONTINUE                                           100
+
+#define OSRF_STATUS_OK                                                         200
+#define OSRF_STATUS_ACCEPTED                                           202
+#define OSRF_STATUS_COMPLETE                                           205
+
+#define OSRF_STATUS_REDIRECTED                                 307
+
+#define OSRF_STATUS_BADREQUEST                                 400
+#define OSRF_STATUS_UNAUTHORIZED                                       401
+#define OSRF_STATUS_FORBIDDEN                                          403
+#define OSRF_STATUS_NOTFOUND                                           404
+#define OSRF_STATUS_NOTALLOWED                                 405
+#define OSRF_STATUS_TIMEOUT                                            408
+#define OSRF_STATUS_EXPFAILED                                          417
+
+#define OSRF_STATUS_INTERNALSERVERERROR                500
+#define OSRF_STATUS_NOTIMPLEMENTED                             501
+#define OSRF_STATUS_VERSIONNOTSUPPORTED                505
+
+
+enum M_TYPE { CONNECT, REQUEST, RESULT, STATUS, DISCONNECT };
+
+#define OSRF_MAX_PARAMS                                                                128;
+
+struct osrf_message_struct {
+
+       enum M_TYPE m_type;
+       int thread_trace;
+       int protocol;
+
+       /* if we're a STATUS message */
+       char* status_name;
+
+       /* if we're a STATUS or RESULT */
+       char* status_text;
+       int status_code;
+
+       int is_exception;
+
+       /* if we're a RESULT */
+       jsonObject* _result_content;
+
+       /* unparsed json string */
+       char* result_string;
+
+       /* if we're a REQUEST */
+       char* method_name;
+
+       jsonObject* _params;
+
+       /* in case anyone wants to make a list of us.  
+               we won't touch this variable */
+       struct osrf_message_struct* next;
+
+       char* full_param_string;
+
+       /* magical LOCALE hint */
+       char* sender_locale;
+
+       /* timezone offset from GMT of sender, in seconds */
+       int sender_tz_offset;
+
+};
+typedef struct osrf_message_struct osrf_message;
+typedef struct osrf_message_struct osrfMessage;
+
+/* Set the locale hint for this message.
+   default_locale is used if not set.
+   Returns NULL if msg or locale is not set, char* to msg->sender_locale on success.
+*/
+char* osrf_message_set_locale( osrf_message* msg, const char* locale );
+
+/* Set the default locale hint to be used for future outgoing messages.
+   Returns NULL if locale is NULL, const char* to default_locale otherwise.
+*/
+const char* osrf_message_set_default_locale( const char* locale );
+
+/* Get the current locale hint -- either the default or most recently received locale.
+   Returns const char* to current_locale.
+*/
+const char* osrf_message_get_last_locale(void);
+
+osrf_message* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol );
+//void osrf_message_set_request_info( osrf_message*, char* param_name, json* params );
+void osrf_message_set_status_info( osrf_message*,
+               const char* status_name, const char* status_text, int status_code );
+void osrf_message_set_result_content( osrf_message*, const char* json_string );
+void osrfMessageFree( osrfMessage* );
+void osrf_message_free( osrf_message* );
+char* osrf_message_to_xml( osrf_message* );
+char* osrf_message_serialize(const osrf_message*);
+
+/* count is the max number of messages we'll put into msgs[] */
+int osrf_message_deserialize(const char* json, osrf_message* msgs[], int count);
+
+
+
+/** Pushes any message retreived from the xml into the 'msgs' array.
+  * it is assumed that 'msgs' has beenn pre-allocated.
+  * Returns the number of message that are in the buffer.
+  */
+int osrf_message_from_xml( char* xml, osrf_message* msgs[] );
+
+void osrf_message_set_params( osrf_message* msg, const jsonObject* o );
+void osrf_message_set_method( osrf_message* msg, const char* method_name );
+void osrf_message_add_object_param( osrf_message* msg, const jsonObject* o );
+void osrf_message_add_param( osrf_message*, const char* param_string );
+
+
+jsonObject* osrfMessageGetResult( osrfMessage* msg );
+
+/**
+  Returns the message as a jsonObject
+  @return The jsonObject which must be freed by the caller.
+  */
+jsonObject* osrfMessageToJSON( const osrfMessage* msg );
+
+char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count );
+
+
+#endif
diff --git a/trunk/include/opensrf/osrf_prefork.h b/trunk/include/opensrf/osrf_prefork.h
new file mode 100644 (file)
index 0000000..a314da9
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef OSRF_PREFORK_H
+#define OSRF_PREFORK_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+
+#include <opensrf/utils.h>
+#include <opensrf/transport_message.h>
+#include <opensrf/transport_client.h>
+#include <opensrf/osrf_stack.h>
+#include <opensrf/osrf_settings.h>
+#include <opensrf/osrfConfig.h>
+
+
+/* we receive data.  we find the next child in
+       line that is available.  pass the data down that childs pipe and go
+       back to listening for more data.
+       when we receive SIGCHLD, we check for any dead children and clean up
+       their respective prefork_child objects, close pipes, etc.
+
+       we build a select fd_set with all the child pipes (going to the parent) 
+       when a child is done processing a request, it writes a small chunk of 
+       data to the parent to alert the parent that the child is again available 
+       */
+
+int osrf_prefork_run(const char* appname);
+
+#endif
diff --git a/trunk/include/opensrf/osrf_settings.h b/trunk/include/opensrf/osrf_settings.h
new file mode 100644 (file)
index 0000000..94b399f
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef OSRF_SETTINGS_H
+#define OSRF_SETTINGS_H
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdarg.h>
+
+#include <opensrf/log.h>
+#include <opensrf/utils.h>
+#include <opensrf/osrf_app_session.h>
+
+#include <opensrf/osrf_json.h>
+
+typedef struct { 
+       char* hostname; 
+       jsonObject* config; 
+} osrf_host_config;
+
+
+osrf_host_config* osrf_settings_new_host_config(const char* hostname);
+void osrf_settings_free_host_config(osrf_host_config*);
+char* osrf_settings_host_value(const char* path, ...);
+jsonObject* osrf_settings_host_value_object(const char* format, ...);
+int osrf_settings_retrieve(const char* hostname);
+
+#endif
+
diff --git a/trunk/include/opensrf/osrf_stack.h b/trunk/include/opensrf/osrf_stack.h
new file mode 100644 (file)
index 0000000..a898751
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef OSRF_STACK_H
+#define OSRF_STACK_H
+
+#include <opensrf/transport_client.h>
+#include <opensrf/osrf_message.h>
+#include <opensrf/osrf_app_session.h>
+
+osrfAppSession*  osrf_stack_transport_handler( transport_message* msg,
+               const char* my_service );
+
+#endif
diff --git a/trunk/include/opensrf/osrf_system.h b/trunk/include/opensrf/osrf_system.h
new file mode 100644 (file)
index 0000000..3272309
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef OSRF_SYSTEM_H
+#define OSRF_SYSTEM_H
+
+#include <opensrf/transport_client.h>
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+#include <opensrf/osrf_settings.h>
+#include <opensrf/osrfConfig.h>
+#include <opensrf/osrf_cache.h>
+
+
+
+/** Connects to jabber.  Returns 1 on success, 0 on failure 
+       contextnode is the location in the config file where we collect config info
+*/
+
+
+int osrf_system_bootstrap_client( char* config_file, char* contextnode );
+
+/* bootstraps a client adding the given resource string to the host/pid, etc. resource string */
+/**
+  Sets up the global connection.
+  @param configFile The OpenSRF bootstrap config file
+  @param contextNode The location in the config file where we'll find the necessary info
+  @param resource The login resource.  If NULL a default will be created
+  @return 1 on successs, 0 on failure.
+  */
+int osrfSystemBootstrapClientResc( const char* configFile,
+               const char* contextNode, const char* resource );
+
+/**
+  Bootstrap the server.
+  @param hostname The name of this host.  This is the name that will be used to 
+       load the settings.
+  @param configfile The OpenSRF bootstrap config file
+  @param contextnode The config context
+  @return 0 on success, -1 on error
+  */
+int osrfSystemBootstrap( const char* hostName, const char* configfile,
+               const char* contextNode );
+
+transport_client* osrfSystemGetTransportClient( void );
+
+/* disconnects and destroys the current client connection */
+int osrf_system_disconnect_client();
+int osrf_system_shutdown( void ); 
+
+
+/* this will clear the global transport client pointer without
+ * actually destroying the socket.  this is useful for allowing
+ * children to have their own socket, even though their parent
+ * already created a socket
+ */
+void osrfSystemIgnoreTransportClient();
+
+
+/** Initialize the cache connection */
+int osrfSystemInitCache(void);
+
+#endif
diff --git a/trunk/include/opensrf/osrf_transgroup.h b/trunk/include/opensrf/osrf_transgroup.h
new file mode 100644 (file)
index 0000000..9788d93
--- /dev/null
@@ -0,0 +1,122 @@
+#include <opensrf/transport_client.h>
+#include <opensrf/transport_message.h>
+#include <opensrf/osrf_list.h>
+#include <opensrf/osrf_hash.h>
+#include <opensrf/osrfConfig.h>
+#include <opensrf/utils.h>
+#include <time.h>
+
+/**
+  Maintains a set of transport clients 
+  */
+
+struct __osrfTransportGroupStruct {
+       osrfHash* nodes;                                                /* our hash of nodes keyed by domain */
+       osrfHashIterator* itr;                          /* points to the next node in the list */
+};
+typedef struct __osrfTransportGroupStruct osrfTransportGroup;
+
+
+struct __osrfTransportGroupNode {
+       transport_client* connection;           /* our connection to the network */
+       char* domain;                                                   /* the domain we're connected to */
+       char* username;                                         /* username used to connect to the group of servers */
+       char* password;                                         /* password used to connect to the group of servers */
+       char* resource;                                         /* the login resource */
+       int port;                                                               /* port used to connect to the group of servers */
+
+       int active;                                                             /* true if we're able to send data on this connection */
+       time_t lastsent;                                                /* the last time we sent a message */
+};
+typedef struct __osrfTransportGroupNode osrfTransportGroupNode;
+
+
+/**
+  Creates a new group node
+  @param domain The domain we're connecting to
+  @param port The port to connect on
+  @param username The login name
+  @param password The login password
+  @param resource The login resource
+  @return A new transport group node
+  */
+osrfTransportGroupNode* osrfNewTransportGroupNode( 
+               char* domain, int port, char* username, char* password, char* resource );
+
+
+/**
+  Allocates and initializes a new transport group.
+  The first node in the array is the default node for client connections.
+  @param nodes The nodes in the group.
+  */
+osrfTransportGroup* osrfNewTransportGroup( osrfTransportGroupNode* nodes[], int count );
+
+/**
+  Attempts to connect all of the nodes in this group.
+  @param grp The transport group
+  @return The number of nodes successfully connected
+  */
+int osrfTransportGroupConnectAll( osrfTransportGroup* grp );
+
+void osrfTransportGroupDisconnectAll( osrfTransportGroup* grp );
+
+
+/**
+  Sends a transport message by going to the next domain in the set.
+  if we have a connection for the recipient domain, then we consider it to be
+  a 'local' message.  Local messages have their recipient domains re-written to
+  match the domain of the next server in the set and they are sent directly to 
+  that server.  If we do not have a connection for the recipient domain, it is 
+  considered a 'remote' message and the message is sent directly (unchanged)
+  to the next connection in the set.
+
+  @param grp The transport group
+  @param msg The message to send 
+  @return 0 on normal successful send.  
+  Returns -1 if the message cannot be sent.  
+  */
+int osrfTransportGroupSend( osrfTransportGroup* grp, transport_message* msg );
+
+/**
+  Sends the message to the exact recipient.  No failover is attempted.
+  @return 0 on success, -1 on error.
+  */
+int osrfTransportGroupSendMatch( osrfTransportGroup* grp, transport_message* msg );
+
+
+int _osrfTGServerSend( osrfTransportGroup* grp, char* domain, transport_message* msg );
+int _osrfTGClientSend( osrfTransportGroup* grp, char* domain, transport_message* msg );
+
+/**
+  Waits on all connections for inbound data.
+  @param grp The transport group
+  @param timeout How long to wait for data.  0 means check for data
+  but don't wait, a negative number means to wait indefinitely
+  @return The received message or NULL if the timeout occurred before a 
+  message was received 
+ */
+transport_message* osrfTransportGroupRecvAll( osrfTransportGroup* grp, int timeout );
+
+/**
+  Waits for data from a single domain
+  @param grp The transport group
+  @param domain The domain to wait for data on
+  @param timeout see osrfTransportGroupRecvAll
+  */
+transport_message* osrfTransportGroupRecv( osrfTransportGroup* grp, char* domain, int timeout );
+
+/**
+  Tells the group that a message to the given domain failed
+  domain did not make it through;
+  @param grp The transport group
+  @param comain The failed domain
+  */
+void osrfTransportGroupSetInactive( osrfTransportGroup* grp, char* domain );
+
+
+/**
+  Finds a node in our list of nodes 
+  */
+osrfTransportGroupNode* __osrfTransportGroupFindNode( osrfTransportGroup* grp, char* domain );
+
+
diff --git a/trunk/include/opensrf/sha.h b/trunk/include/opensrf/sha.h
new file mode 100644 (file)
index 0000000..6c3d2d4
--- /dev/null
@@ -0,0 +1,41 @@
+// sha.h
+// Jabber client library
+//
+// Original Code Copyright (C) 1999-2001 Dave Smith (dave@jabber.org)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+//
+// Contributor(s): Julian Missig
+//
+// This Original Code has been modified by IBM Corporation. Modifications 
+// made by IBM described herein are Copyright (c) International Business 
+// Machines Corporation, 2002.
+//
+// Date             Modified by     Description of modification
+// 01/20/2002       IBM Corp.       Updated to libjudo 1.1.1
+// 2002-03-05       IBM Corp.       Updated to libjudo 1.1.5
+// 2002-07-09       IBM Corp.       Added Roster::getSession()
+//
+// =====================================================================================
+
+
+//#ifdef WIN32
+     char* shahash(const char* str);
+//#else
+//extern "C" {
+//     char* shahash(const char* str);
+//}
+//#endif
+
diff --git a/trunk/include/opensrf/socket_bundle.h b/trunk/include/opensrf/socket_bundle.h
new file mode 100644 (file)
index 0000000..bec656a
--- /dev/null
@@ -0,0 +1,111 @@
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <errno.h>
+
+
+//---------------------------------------------------------------
+// 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>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+
+#include <signal.h>
+
+#ifndef SOCKET_BUNDLE_H
+#define SOCKET_BUNDLE_H
+
+
+#define SERVER_SOCKET                  1
+#define CLIENT_SOCKET                  2
+
+#define INET 10 
+#define UNIX 11 
+
+
+/* models a single socket connection */
+struct socket_node_struct {
+       int endpoint;           /* SERVER_SOCKET or CLIENT_SOCKET */
+       int addr_type;          /* INET or UNIX */
+       int sock_fd;
+       int parent_id;          /* if we're a new client for a server socket, 
+                                                               this points to the server socket we spawned from */
+       struct socket_node_struct* next;
+};
+typedef struct socket_node_struct socket_node;
+
+
+/* Maintains the socket set */
+struct socket_manager_struct {
+       /* callback for passing up any received data.  sock_fd is the socket
+               that read the data.  parent_id (if > 0) is the socket id of the 
+               server that this socket spawned from (i.e. it's a new client connection) */
+       void (*data_received) 
+               (void* blob, struct socket_manager_struct*, 
+                int sock_fd, char* data, int parent_id);
+
+       void (*on_socket_closed) (void* blob, int sock_fd);
+
+       socket_node* socket;
+       void* blob;
+};
+typedef struct socket_manager_struct socket_manager;
+
+void socket_manager_free(socket_manager* mgr);
+
+/* creates a new server socket node and adds it to the socket set.
+       returns socket id on success.  -1 on failure.
+       socket_type is one of INET or UNIX  */
+int socket_open_tcp_server(socket_manager*, int port, const char* listen_ip );
+
+int socket_open_unix_server(socket_manager* mgr, const char* path);
+
+int socket_open_udp_server( socket_manager* mgr, int port, const char* listen_ip );
+
+/* creates a client TCP socket and adds it to the socket set.
+       returns 0 on success.  -1 on failure.  */
+int socket_open_tcp_client(socket_manager*, int port, const char* dest_addr);
+
+/* creates a client UNIX socket and adds it to the socket set.
+       returns 0 on success.  -1 on failure.  */
+int socket_open_unix_client(socket_manager*, const char* sock_path);
+
+int socket_open_udp_client( socket_manager* mgr, int port, const char* dest_addr);
+
+/* sends the given data to the given socket. returns 0 on success, -1 otherwise */
+int socket_send(int sock_fd, const char* data);
+
+/* waits at most usecs microseconds for the socket buffer to
+ * be available */
+int socket_send_timeout( int sock_fd, const char* data, int usecs );
+
+/* disconnects the node with the given sock_fd and removes
+       it from the socket set */
+void socket_disconnect(socket_manager*, int sock_fd);
+
+/* XXX This only works if 'sock_fd' is a client socket... */
+int socket_wait(socket_manager* mgr, int timeout, int sock_fd);
+
+/* waits on all sockets for incoming data.  
+       timeout == -1   | block indefinitely
+       timeout == 0    | don't block, just read any available data off all sockets
+       timeout == x    | block for at most x seconds */
+int socket_wait_all(socket_manager* mgr, int timeout);
+
+/* utility function for displaying the currently attached sockets */
+void _socket_print_list(socket_manager* mgr);
+
+int socket_connected(int sock_fd);
+
+#endif
diff --git a/trunk/include/opensrf/string_array.h b/trunk/include/opensrf/string_array.h
new file mode 100644 (file)
index 0000000..70b0038
--- /dev/null
@@ -0,0 +1,44 @@
+#include <stdio.h>
+
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+#include <opensrf/osrf_list.h>
+
+#define STRING_ARRAY_MAX_SIZE 4096
+
+#ifndef STRING_ARRAY_H
+#define STRING_ARRAY_H
+
+#define OSRF_STRING_ARRAY_FREE(arr)\
+    if(arr) {osrfListFree(arr->list); free(arr);}
+        
+
+struct string_array_struct {
+    osrfList* list;
+    int size;
+};
+typedef struct string_array_struct string_array;
+typedef struct string_array_struct osrfStringArray;
+
+osrfStringArray* init_string_array(int size);
+osrfStringArray* osrfNewStringArray(int size);
+
+void string_array_add(osrfStringArray*, char* string);
+void osrfStringArrayAdd(osrfStringArray*, char* string);
+
+char* string_array_get_string(osrfStringArray* arr, int index);
+char* osrfStringArrayGetString(osrfStringArray* arr, int index);
+
+/* returns true if this array contains the given string */
+int osrfStringArrayContains( osrfStringArray* arr, char* string );
+
+
+void string_array_destroy(osrfStringArray*);
+void osrfStringArrayFree(osrfStringArray*);
+
+/* total size of all included strings */
+int string_array_get_total_size(osrfStringArray* arr);
+
+void osrfStringArrayRemove( osrfStringArray* arr, char* str);
+
+#endif
diff --git a/trunk/include/opensrf/transport_client.h b/trunk/include/opensrf/transport_client.h
new file mode 100644 (file)
index 0000000..89a51e3
--- /dev/null
@@ -0,0 +1,92 @@
+#include <opensrf/transport_session.h>
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+
+#include <time.h>
+
+#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;
+       int error;
+};
+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.
+// if port > 0 => connect via TCP
+// else if unix_path != NULL => connect via UNIX socket
+// ---------------------------------------------------------------------------
+transport_client* client_init( const char* server, int port, const char* unix_path, int component );
+
+
+// ---------------------------------------------------------------------------
+// Connects to the Jabber server with the provided information. Returns 1 on
+// success, 0 otherwise.
+// ---------------------------------------------------------------------------
+int client_connect( transport_client* client, 
+               const char* username, const char* password, const char* resource,
+               int connect_timeout, enum TRANSPORT_AUTH_TYPE auth_type );
+
+
+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( const transport_client* client );
+
+// ---------------------------------------------------------------------------
+// This is the message handler required by transport_session.  This handler
+// takes all incoming messages and puts them into the back of a linked list
+// of messages.  
+// ---------------------------------------------------------------------------
+void client_message_handler( void* client, transport_message* msg );
+
+// ---------------------------------------------------------------------------
+// If there are any message in the message list, the 'oldest' message is
+// returned.  If not, this function will wait at most 'timeout' seconds 
+// for a message to arrive.  Specifying -1 means that this function will not
+// return unless a message arrives.
+// ---------------------------------------------------------------------------
+transport_message* client_recv( transport_client* client, int timeout );
+
+
+#endif
diff --git a/trunk/include/opensrf/transport_message.h b/trunk/include/opensrf/transport_message.h
new file mode 100644 (file)
index 0000000..6cf3a49
--- /dev/null
@@ -0,0 +1,100 @@
+#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>
+
+#include <opensrf/utils.h>
+#include <opensrf/xml_utils.h>
+#include <opensrf/log.h>
+
+#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;
+       char* osrf_xid;
+       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( const char* body, const char* subject, 
+               const char* thread, const char* recipient, const char* sender );
+
+transport_message* new_message_from_xml( const char* msg_xml );
+
+
+void message_set_router_info( transport_message* msg, const char* router_from,
+               const char* router_to, const char* router_class, const char* router_command,
+               int broadcast_enabled );
+
+void message_set_osrf_xid( transport_message* msg, const char* osrf_xid );
+
+// ---------------------------------------------------------------------------------
+// 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[], int size );
+
+// ---------------------------------------------------------------------------------
+// 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[], int size );
+
+/** Puts the domain portion of the given jid into the pre-allocated buffer */
+void jid_get_domain( const char* jid, char buf[], int size );
+
+void set_msg_error( transport_message*, const char* error_type, int error_code);
+
+
+#endif
diff --git a/trunk/include/opensrf/transport_session.h b/trunk/include/opensrf/transport_session.h
new file mode 100644 (file)
index 0000000..a5420c6
--- /dev/null
@@ -0,0 +1,226 @@
+// ---------------------------------------------------------------------------------
+// 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 <opensrf/transport_message.h>
+
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+#include <opensrf/socket_bundle.h>
+
+#include "sha.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 
+
+// ---------------------------------------------------------------------------------
+// Takes data from the socket handler and pushes it directly into the push parser
+// ---------------------------------------------------------------------------------
+//void grab_incoming( void * session, char* data );
+void grab_incoming(void* blob, socket_manager* mgr, int sockid, char* data, int parent);
+
+// ---------------------------------------------------------------------------------
+// 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;
+
+
+enum TRANSPORT_AUTH_TYPE { AUTH_PLAIN, AUTH_DIGEST };
+
+// ---------------------------------------------------------------------------------
+// Transport session.  This maintains all the various parts of a session
+// ---------------------------------------------------------------------------------
+struct transport_session_struct {
+
+       /* our socket connection */
+       //transport_socket* sock_obj;
+       socket_manager* sock_mgr;
+
+       /* 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;
+       growing_buffer* session_id;
+       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;
+       growing_buffer* osrf_xid_buffer;
+       int router_broadcast;
+
+       /* this can be anything.  It will show up in the 
+               callbacks for your convenience. Otherwise, it's
+               left untouched.  */
+       void* user_data;
+
+       char* server;
+       char* unix_path;
+       int     port;
+       int sock_id;
+
+       int component; /* true if we're a component */
+
+       /* 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.
+// If port > 0, then this session uses  TCP connection.  Otherwise,
+// if unix_path != NULL, it uses a UNIX domain socket.
+// ------------------------------------------------------------------
+transport_session* init_transport( const char* server, int port, 
+       const char* unix_path, void* user_data, int component );
+
+// ------------------------------------------------------------------
+// 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, 
+               enum TRANSPORT_AUTH_TYPE auth_type );
+
+int session_disconnect( transport_session* session );
+
+int reset_session_buffers( transport_session* session );
+
+#endif
diff --git a/trunk/include/opensrf/utils.h b/trunk/include/opensrf/utils.h
new file mode 100644 (file)
index 0000000..f6cb272
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+Bill Erickson <highfalutin@gmail.com>
+Mike Rylander <mrylander@gmail.com>
+
+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.
+*/
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+//#include <sys/timeb.h>
+
+#include "md5.h"
+
+#define OSRF_MALLOC(ptr, size) \
+       do {\
+               ptr = (void*) malloc( size ); \
+               if( ptr == NULL ) { \
+                       perror("OSRF_MALLOC(): Out of Memory" );\
+                       exit(99); \
+               } \
+               memset( ptr, 0, size );\
+       } while(0)
+
+#ifdef NDEBUG
+// The original ... replace with noop once no more errors occur in NDEBUG mode
+#define osrf_clearbuf( s, n ) memset( s, 0, n )
+#else
+#define osrf_clearbuf( s, n ) \
+       do { \
+               char * clearbuf_temp_s = (s); \
+               size_t clearbuf_temp_n = (n); \
+               memset( clearbuf_temp_s, '!', clearbuf_temp_n ); \
+               clearbuf_temp_s[ clearbuf_temp_n - 1 ] = '\0'; \
+       } while( 0 )
+#endif
+
+#define OSRF_BUFFER_ADD(gb, data) \
+       do {\
+               int __tl; \
+               if(gb && data) {\
+                       __tl = strlen(data) + gb->n_used;\
+                       if( __tl < gb->size ) {\
+                               strcat(gb->buf, data);\
+                               gb->n_used = __tl; \
+                       } else { buffer_add(gb, data); }\
+               }\
+       } while(0)
+
+#define OSRF_BUFFER_ADD_CHAR(gb, c)\
+       do {\
+               if(gb) {\
+                       if(gb->n_used < gb->size - 1)\
+                               gb->buf[gb->n_used++] = c;\
+                       else\
+                               buffer_add_char(gb, c);\
+               }\
+       }while(0)
+
+#define OSRF_BUFFER_RESET(gb) \
+    memset(gb->buf, 0, gb->size);\
+    gb->n_used = 0;
+
+       
+
+
+/* turns a va_list into a string */
+#define VA_LIST_TO_STRING(x) \
+       unsigned long __len = 0;\
+       va_list args; \
+       va_list a_copy;\
+       va_copy(a_copy, args); \
+       va_start(args, x); \
+       __len = vsnprintf(NULL, 0, x, args); \
+       va_end(args); \
+       __len += 2; \
+       char _b[__len]; \
+       bzero(_b, __len); \
+       va_start(a_copy, x); \
+       vsnprintf(_b, __len - 1, x, a_copy); \
+       va_end(a_copy); \
+       char* VA_BUF = _b; \
+
+/* turns a long into a string */
+#define LONG_TO_STRING(l) \
+       unsigned int __len = snprintf(NULL, 0, "%ld", l) + 2;\
+       char __b[__len]; \
+       bzero(__b, __len); \
+       snprintf(__b, __len - 1, "%ld", l); \
+       char* LONGSTR = __b;
+
+#define DOUBLE_TO_STRING(l) \
+       unsigned int __len = snprintf(NULL, 0, "%f", l) + 2; \
+       char __b[__len]; \
+       bzero(__b, __len); \
+       snprintf(__b, __len - 1, "%f", l); \
+       char* DOUBLESTR = __b;
+
+#define LONG_DOUBLE_TO_STRING(l) \
+       unsigned int __len = snprintf(NULL, 0, "%Lf", l) + 2; \
+       char __b[__len]; \
+       bzero(__b, __len); \
+       snprintf(__b, __len - 1, "%Lf", l); \
+       char* LONGDOUBLESTR = __b;
+
+
+#define INT_TO_STRING(l) \
+       unsigned int __len = snprintf(NULL, 0, "%d", l) + 2; \
+       char __b[__len]; \
+       bzero(__b, __len); \
+       snprintf(__b, __len - 1, "%d", l); \
+       char* INTSTR = __b;
+
+
+/*
+#define MD5SUM(s) \
+       struct md5_ctx ctx; \
+       unsigned char digest[16];\
+       MD5_start (&ctx);\
+       int i;\
+       for ( i=0 ; i != strlen(text) ; i++ ) MD5_feed (&ctx, text[i]);\
+       MD5_stop (&ctx, digest);\
+       char buf[16];\
+       memset(buf,0,16);\
+       char final[256];\
+       memset(final,0,256);\
+       for ( i=0 ; i<16 ; i++ ) {\
+               sprintf(buf, "%02x", digest[i]);\
+               strcat( final, buf );\
+       }\
+       char* MD5STR = final;
+       */
+
+
+       
+
+
+#define BUFFER_MAX_SIZE 10485760 
+
+/* these are evil and should be condemned 
+       ! Only use these if you are done with argv[].
+       call init_proc_title() first, then call
+       set_proc_title. 
+       the title is only allowed to be as big as the
+       initial process name of the process (full size of argv[]).
+       truncation may occurr.
+ */
+int init_proc_title( int argc, char* argv[] );
+int set_proc_title( const char* format, ... );
+
+
+int daemonize( void );
+
+void* safe_malloc(int size);
+void* safe_calloc(int size);
+
+// ---------------------------------------------------------------------------------
+// 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);
+
+// XXX This isn't defined in utils.c!! removing for now...
+//int buffer_addchar(growing_buffer* gb, char c);
+
+int buffer_add(growing_buffer* gb, const char* c);
+int buffer_fadd(growing_buffer* gb, const char* format, ... );
+int buffer_reset( growing_buffer* gb);
+char* buffer_data( const growing_buffer* gb);
+char* buffer_release( growing_buffer* gb );
+int buffer_free( growing_buffer* gb );
+int buffer_add_char(growing_buffer* gb, char c);
+int buffer_chomp(growing_buffer* gb); // removes the last character from the buffer
+
+/* returns the size needed to fill in the vsnprintf buffer.  
+       * ! this calls va_end on the va_list argument*
+       */
+long va_list_size(const char* format, va_list);
+
+/* turns a va list into a string, caller must free the 
+       allocated char */
+char* va_list_to_string(const char* format, ...);
+
+
+/* string escape utility method.  escapes unicode embeded characters.
+       escapes the usual \n, \t, etc. 
+       for example, if you provide a string like so:
+
+       hello,
+               you
+
+       you would get back:
+       hello,\n\tyou
+ */
+char* uescape( const char* string, int size, int full_escape );
+
+/* utility methods */
+int set_fl( int fd, int flags );
+int clr_fl( int fd, int flags );
+
+
+
+// Utility method
+double get_timestamp_millis( void );
+
+
+/* returns true if the whole string is a number */
+int stringisnum(const char* s);
+
+/* reads a file and returns the string version of the file
+       user is responsible for freeing the returned char*
+       */
+char* file_to_string(const char* filename);
+
+
+
+/** 
+  Calculates the md5 of the text provided.
+  The returned string must be freed by the caller.
+  */
+char* md5sum( const char* text, ... );
+
+
+/**
+  Checks the validity of the file descriptor
+  returns -1 if the file descriptor is invalid
+  returns 0 if the descriptor is OK
+  */
+int osrfUtilsCheckFileDescriptor( int fd );
+
+#endif
diff --git a/trunk/include/opensrf/xml_utils.h b/trunk/include/opensrf/xml_utils.h
new file mode 100644 (file)
index 0000000..42318f9
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef _XML_UTILS_H
+#define _XML_UTILS_H
+
+#include <opensrf/osrf_json.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+jsonObject* xmlDocToJSON(xmlDocPtr doc);
+
+/* debug function, prints each node and content */
+void recurse_doc( xmlNodePtr node );
+
+
+/* turns an XML doc into a char*.  
+       User is responsible for freeing the returned char*
+       if(full), then we return the whole doc (xml declaration, etc.)
+       else we return the doc from the root node down
+       */
+char* xmlDocToString(xmlDocPtr doc, int full);
+
+
+/* Takes an xmlChar** from a SAX callback and returns the value
+       for the attribute with name 'name'
+       */
+char* xmlSaxAttr( const xmlChar** atts, const char* name ); 
+
+/**
+  Sets the xml attributes from atts to the given dom node 
+ */
+int xmlAddAttrs( xmlNodePtr node, const xmlChar** atts );
+
+
+#endif
diff --git a/trunk/src/Makefile.am b/trunk/src/Makefile.am
new file mode 100644 (file)
index 0000000..c7f0cfd
--- /dev/null
@@ -0,0 +1,82 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+# Declare some directory variables
+
+export OPENSRF = opensrf
+export BINDIR  = @bindir@
+export LIBDIR  = @libdir@
+jsdir = $(LIBDIR)/javascript
+export OSRF_JAVA_DEPSDIR = @OSRF_JAVA_DEPSDIR@
+etcdir = $(ETCDIR)
+
+
+AM_LDFLAGS = $(DEF_LDFLAGS)
+AM_CFLAGS = $(DEF_CFLAGS)
+
+if BUILDPYTHON
+MAYBE_PY = python
+endif 
+
+if BUILDJAVA
+MAYBE_JA = java
+endif
+
+SUBDIRS = libopensrf c-apps router srfsh jserver gateway $(MAYBE_PY) $(MAYBE_JA)
+
+dist_bin_SCRIPTS = @top_srcdir@/bin/osrf_ctl.sh @top_srcdir@/bin/opensrf-perl.pl
+bin_SCRIPTS = @top_srcdir@/bin/osrf_config
+
+dist_sysconf_DATA = @top_srcdir@/examples/opensrf.xml.example @top_srcdir@/examples/opensrf_core.xml.example @top_srcdir@/examples/srfsh.xml.example 
+
+install-exec-local:
+       mkdir -p $(VAR)
+       mkdir -p $(PID)
+       mkdir -p $(LOG)
+       mkdir -p $(SOCK)
+       mkdir -p $(jsdir)
+       make install-perl
+
+install-exec-hook:
+       sed -i 's|LOCALSTATEDIR|$(VAR)|g' '$(DESTDIR)@sysconfdir@/opensrf.xml.example'
+       sed -i 's|SYSCONFDIR|$(ETCDIR)|g' '$(DESTDIR)@sysconfdir@/opensrf.xml.example'
+       sed -i 's|LOCALSTATEDIR|$(VAR)|g' '$(DESTDIR)@sysconfdir@/opensrf_core.xml.example'
+       sed -i 's|SYSCONFDIR|$(ETCDIR)|g' '$(DESTDIR)@sysconfdir@/opensrf_core.xml.example'
+       sed -i 's|LOCALSTATEDIR|$(VAR)|g' '$(DESTDIR)@sysconfdir@/srfsh.xml.example'
+       sed -i 's|SYSCONFDIR|$(ETCDIR)|g' '$(DESTDIR)@sysconfdir@/srfsh.xml.example'
+       sed -i 's|SYSCONFDIR|$(ETCDIR)|g' '@abs_top_srcdir@/examples/math_bench.pl'
+       sed -i 's|LIBDIR|$(LIBDIR)|g' '@abs_top_srcdir@/examples/multisession-test.pl'
+       sed -i 's|SYSCONFDIR|$(ETCDIR)|g' '@abs_top_srcdir@/doc/dokuwiki-doc-stubber.pl'
+       cp -r @srcdir@/javascript/* $(jsdir)/
+
+
+install-perl:
+       cd ./perl && perl Makefile.PL || make -s install-perl-fail
+       make -C perl
+       make -C perl test || make -s install-perl-fail
+       make -C perl install
+
+install-perl-fail:
+       echo
+       echo ">>> Installation of Perl modules has failed. The most likely"
+       echo ">>> possibility is that a dependency is not pre-installed"
+       echo ">>> or that a test has failed."
+       echo ">>> See the messages above this one for more information."
+       echo
+       exit 1
+
+uninstall-hook:
+       rm @includedir@/opensrf/apachetools.h
+       rm -R $(jsdir)
+
diff --git a/trunk/src/c-apps/Makefile.am b/trunk/src/c-apps/Makefile.am
new file mode 100644 (file)
index 0000000..80e0e3d
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright (C) 2007-2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+AM_CFLAGS = $(DEF_CFLAGS) -DORSF_LOG_PARAMS 
+AM_LDFLAGS = $(DEF_LDFLAGS) -L@top_builddir@/src/libopensrf
+
+noinst_PROGRAMS = timejson
+lib_LTLIBRARIES = osrf_dbmath.la osrf_math.la osrf_version.la
+
+timejson_SOURCES = timejson.c
+timejson_LDADD = -lopensrf
+osrf_dbmath_la_SOURCES = osrf_dbmath.c 
+osrf_dbmath_la_LDFLAGS = $(AM_LDFLAGS) -module
+osrf_dbmath_la_LIBADD = -lopensrf
+osrf_math_la_SOURCES = osrf_math.c
+osrf_math_la_LDFLAGS = $(AM_LDFLAGS) -module
+osrf_math_la_LIBADD =  -lopensrf
+osrf_version_la_SOURCES = osrf_version.c 
+osrf_version_la_LDFLAGS = $(AM_LDFLAGS) -module
+osrf_version_la_LIBADD = -lopensrf
diff --git a/trunk/src/c-apps/osrf_dbmath.c b/trunk/src/c-apps/osrf_dbmath.c
new file mode 100644 (file)
index 0000000..bb0c093
--- /dev/null
@@ -0,0 +1,86 @@
+#include <opensrf/osrf_app_session.h>
+#include <opensrf/osrf_application.h>
+#include <opensrf/osrf_json.h>
+#include <opensrf/log.h>
+
+#define MODULENAME "opensrf.dbmath"
+
+int osrfAppInitialize();
+int osrfAppChildInit();
+int osrfMathRun( osrfMethodContext* );
+
+
+int osrfAppInitialize() {
+
+       osrfAppRegisterMethod( 
+                       MODULENAME, 
+                       "add", 
+                       "osrfMathRun", 
+                       "Addss two numbers", 2, 0 );
+
+       osrfAppRegisterMethod( 
+                       MODULENAME, 
+                       "sub", 
+                       "osrfMathRun", 
+                       "Subtracts two numbers", 2, 0 );
+
+       osrfAppRegisterMethod( 
+                       MODULENAME, 
+                       "mult", 
+                       "osrfMathRun", 
+                       "Multiplies two numbers", 2, 0 );
+
+       osrfAppRegisterMethod( 
+                       MODULENAME, 
+                       "div", 
+                       "osrfMathRun", 
+                       "Divides two numbers", 2, 0 );
+
+       return 0;
+}
+
+int osrfAppChildInit() {
+       return 0;
+}
+
+int osrfMathRun( osrfMethodContext* ctx ) {
+
+       OSRF_METHOD_VERIFY_CONTEXT(ctx);        
+
+       const jsonObject* x = jsonObjectGetIndex(ctx->params, 0);
+       const jsonObject* y = jsonObjectGetIndex(ctx->params, 1);
+
+       if( x && y ) {
+
+               char* a = jsonObjectToSimpleString(x);
+               char* b = jsonObjectToSimpleString(y);
+
+               if( a && b ) {
+
+                       double i = strtod(a, NULL);
+                       double j = strtod(b, NULL);
+                       double r = 0;
+
+                       if(!strcmp(ctx->method->name, "add"))   r = i + j;
+                       if(!strcmp(ctx->method->name, "sub"))   r = i - j;
+                       if(!strcmp(ctx->method->name, "mult"))  r = i * j;
+                       if(!strcmp(ctx->method->name, "div"))   r = i / j;
+
+                       jsonObject* resp = jsonNewNumberObject(r);
+                       osrfAppRespondComplete( ctx, resp );
+                       jsonObjectFree(resp);
+
+                       free(a); free(b);
+                       return 0;
+               }
+               else {
+                       if(a) free(a);
+                       if(b) free(b);
+               }
+       }
+
+       return -1;
+}
+
+
+
diff --git a/trunk/src/c-apps/osrf_math.c b/trunk/src/c-apps/osrf_math.c
new file mode 100644 (file)
index 0000000..df44afb
--- /dev/null
@@ -0,0 +1,114 @@
+#include <opensrf/osrf_app_session.h>
+#include <opensrf/osrf_application.h>
+#include <opensrf/osrf_json.h>
+#include <opensrf/log.h>
+
+#define MODULENAME "opensrf.math"
+
+int osrfAppInitialize();
+int osrfAppChildInit();
+void osrfAppChildExit();
+int osrfMathRun( osrfMethodContext* );
+
+
+int osrfAppInitialize() {
+
+       osrfAppRegisterMethod( 
+                       MODULENAME,                             /* which application has this method */
+                       "add",                                  /* the name of the method */
+                       "osrfMathRun",                  /* the symbol that runs the method */
+                       "Adds two numbers",     /* description of the method */
+                       2,                                                      /* the minimum number of params required to run the method */
+                       0 );                                            /* method options, 0 for not special options */
+
+       osrfAppRegisterMethod( 
+                       MODULENAME, 
+                       "sub", 
+                       "osrfMathRun", 
+                       "Subtracts two numbers", 2, 0 );
+
+       osrfAppRegisterMethod( 
+                       MODULENAME, 
+                       "mult", 
+                       "osrfMathRun", 
+                       "Multiplies two numbers", 2, 0 );
+
+       osrfAppRegisterMethod( 
+                       MODULENAME, 
+                       "div", 
+                       "osrfMathRun", 
+                       "Divides two numbers", 2, 0 );
+
+       return 0;
+}
+
+/* called when this process is just coming into existence */
+int osrfAppChildInit() {
+       return 0;
+}
+
+/* called when this process is about to exit */
+void osrfAppChildExit() {
+   osrfLogDebug(OSRF_LOG_MARK, "Child is exiting...");
+}
+
+
+int osrfMathRun( osrfMethodContext* ctx ) {
+
+       OSRF_METHOD_VERIFY_CONTEXT(ctx); /* see osrf_application.h */
+
+       /* collect the request params */
+       const jsonObject* x = jsonObjectGetIndex(ctx->params, 0);
+       const jsonObject* y = jsonObjectGetIndex(ctx->params, 1);
+
+       if( x && y ) {
+
+               /* pull out the params as strings since they may be either
+                       strings or numbers depending on the client */
+               char* a = jsonObjectToSimpleString(x);
+               char* b = jsonObjectToSimpleString(y);
+
+               if( a && b ) {
+
+                       osrfLogActivity( OSRF_LOG_MARK, "Running opensrf.math %s [ %s : %s ]", 
+                                       ctx->method->name, a, b );
+
+                       /* construct a new params object to send to dbmath */
+                       jsonObject* newParams = jsonParseStringFmt( "[ %s, %s ]", a, b );
+                       free(a); free(b);
+
+                       /* connect to db math */
+                       osrfAppSession* ses = osrfAppSessionClientInit("opensrf.dbmath");
+
+                       /* forcing an explicit connect allows us to talk to one worker backend
+                        * regardless of "stateful" config settings for the server 
+                        * This buys us nothing here since we're only sending one request...
+                        * */
+                       /*osrfAppSessionConnect(ses);*/
+
+                       /* dbmath uses the same method names that math does */
+                       int req_id = osrfAppSessionMakeRequest( ses, newParams, ctx->method->name, 1, NULL );
+                       osrfMessage* omsg = osrfAppSessionRequestRecv( ses, req_id, 60 );
+                       jsonObjectFree(newParams);
+
+                       if(omsg) {
+                               /* return dbmath's response to the user */
+                               osrfAppRespondComplete( ctx, osrfMessageGetResult(omsg) ); 
+                               osrfMessageFree(omsg);
+                               osrfAppSessionFree(ses);
+                               return 0;
+                       }
+
+                       osrfAppSessionFree(ses);
+               }
+               else {
+                       if(a) free(a);
+                       if(b) free(b);
+               }
+       }
+
+       return -1;
+}
+
+
+
diff --git a/trunk/src/c-apps/osrf_version.c b/trunk/src/c-apps/osrf_version.c
new file mode 100644 (file)
index 0000000..dbe845b
--- /dev/null
@@ -0,0 +1,98 @@
+#include "opensrf/osrf_app_session.h"
+#include "opensrf/osrf_application.h"
+#include "opensrf/osrf_json.h"
+#include "opensrf/utils.h"
+#include "opensrf/log.h"
+
+#define OSRF_VERSION_CACHE_TIME 300
+
+int osrfAppInitialize();
+int osrfAppChildInit();
+int osrfVersion( osrfMethodContext* );
+
+
+int osrfAppInitialize() {
+
+       osrfAppRegisterMethod( 
+                       "opensrf.version", 
+                       "opensrf.version.verify", 
+                       "osrfVersion", 
+                       "The data for a service/method/params combination will be retrieved "
+                       "from the necessary server and the MD5 sum of the total values received "
+                       "will be returned. PARAMS( serviceName, methodName, [param1, ...] )", 
+                       2, 0 );
+       
+       return 0;
+}
+
+int osrfAppChildInit() {
+       return 0;
+}
+
+int osrfVersion( osrfMethodContext* ctx ) {
+
+       OSRF_METHOD_VERIFY_CONTEXT(ctx); 
+
+       /* First, see if the data is in the cache */
+       char* json = jsonObjectToJSON(ctx->params);
+       char* paramsmd5 = md5sum(json);
+       char* cachedmd5 = osrfCacheGetString(paramsmd5);
+       free(json); 
+
+       if( cachedmd5 ) {
+               osrfLogDebug(OSRF_LOG_MARK,  "Found %s object in cache, returning....", cachedmd5 );
+               jsonObject* resp = jsonNewObject(cachedmd5);
+               osrfAppRespondComplete( ctx, resp  );
+               jsonObjectFree(resp);
+               free(paramsmd5);
+               free(cachedmd5);
+               return 0;
+       }
+
+       const jsonObject* serv = jsonObjectGetIndex(ctx->params, 0);
+       const jsonObject* meth = jsonObjectGetIndex(ctx->params, 1);
+       const char* service = jsonObjectGetString(serv);
+       const char* methd = jsonObjectGetString(meth);
+
+       if( service && methd ) {
+               /* shove the additional params into an array */
+               jsonObject* tmpArray = jsonNewObject(NULL);
+               int i;
+               for( i = 2; i != ctx->params->size; i++ ) 
+                       jsonObjectPush( tmpArray, jsonObjectClone(jsonObjectGetIndex(ctx->params, i)));
+
+               osrfAppSession* ses = osrfAppSessionClientInit(service);
+               int reqid = osrfAppSessionMakeRequest( ses, tmpArray, methd, 1, NULL );
+               osrfMessage* omsg = osrfAppSessionRequestRecv( ses, reqid, 60 );
+               jsonObjectFree(tmpArray);
+
+               if( omsg ) {
+
+                       jsonObject* result = osrfMessageGetResult( omsg );
+                       char* resultjson = jsonObjectToJSON(result);
+                       char* resultmd5 = md5sum(resultjson);
+                       free(resultjson);
+                       osrfMessageFree(omsg);
+
+                       if( resultmd5 ) {
+                               jsonObject* resp = jsonNewObject(resultmd5);
+                               osrfAppRespondComplete( ctx, resp );
+                               jsonObjectFree(resp);
+                               osrfAppSessionFree(ses);
+                               osrfLogDebug(OSRF_LOG_MARK, "Found version string %s, caching and returning...", resultmd5 );
+                               osrfCachePutString( paramsmd5, resultmd5, OSRF_VERSION_CACHE_TIME );
+                               free(resultmd5);
+                               free(paramsmd5);
+                               return 0;
+                       } 
+               }
+               osrfAppSessionFree(ses);
+       }
+
+       free(paramsmd5);
+
+       return -1;
+}
+
+
+
diff --git a/trunk/src/c-apps/timejson.c b/trunk/src/c-apps/timejson.c
new file mode 100644 (file)
index 0000000..0ee1e94
--- /dev/null
@@ -0,0 +1,80 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <malloc.h>
+#include "opensrf/utils.h"
+#include "opensrf/osrf_json.h"
+
+struct timeval diff_timeval( const struct timeval * begin,
+       const struct timeval * end );
+
+static const char sample_json[] =
+       "{\"menu\": {\"id\": \"file\", \"value\": \"File\","
+       "\"popup\": { \"menuitem\": [ {\"value\": \"New\", "
+       "\"onclick\": \"CreateNewDoc()\"},"
+       "{\"value\": \"Open\", \"onclick\": \"OpenDoc()\"}, "
+       "{\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}]}}}";
+
+int main( void ) {
+       int rc = 0;
+
+       struct timezone tz = { 240, 1 };
+       struct timeval begin_timeval;
+       struct timeval end_timeval;
+
+       gettimeofday( &begin_timeval, &tz );
+
+       long i;
+       jsonObject * pObj = NULL;
+
+       for( i = 10000000; i; --i )
+       {
+//             pObj = jsonParseString( sample_json );
+               pObj = jsonNewObject( NULL );
+               jsonObject * p1 = jsonNewObject( NULL );
+               jsonObject * p2 = jsonNewObject( NULL );
+               jsonObjectFree( p1 );
+               jsonObjectFree( p2 );
+               jsonObjectFree( pObj );
+       }
+
+       jsonObjectFreeUnused();
+       
+       gettimeofday( &end_timeval, &tz );
+
+       struct timeval elapsed = diff_timeval( &begin_timeval, &end_timeval );
+
+       printf( "Elapsed time: %ld seconds, %ld microseconds\n",
+                       (long) elapsed.tv_sec, (long) elapsed.tv_usec );
+
+       struct rlimit rlim;
+       if( getrlimit( RLIMIT_DATA, &rlim ) )
+               printf( "Error calling getrlimit\n" );
+       else
+               printf( "Address space: %lu\n", (unsigned long) rlim.rlim_cur );
+
+       malloc_stats();
+
+       return rc;
+}
+
+struct timeval diff_timeval( const struct timeval * begin, const struct timeval * end )
+{
+       struct timeval diff;
+
+       diff.tv_sec = end->tv_sec - begin->tv_sec;
+       diff.tv_usec = end->tv_usec - begin->tv_usec;
+
+       if( diff.tv_usec < 0 )
+       {
+               diff.tv_usec += 1000000;
+               --diff.tv_sec;
+       }
+
+       return diff;
+
+}
diff --git a/trunk/src/extras/docgen.xsl b/trunk/src/extras/docgen.xsl
new file mode 100644 (file)
index 0000000..e9f107b
--- /dev/null
@@ -0,0 +1,384 @@
+<?xml-stylesheet type="text/xsl"  href="#"?> 
+<xsl:stylesheet
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+  xmlns:res="http://opensrf.org/-/namespaces/gateway/v1"
+  version="1.0"
+  >
+  <xsl:template match="xsl:stylesheet">
+    <html>
+      <head>
+        <style type="text/css">
+body { background-color:#F0F0F0; font: 9pt Verdana, Arial, "Arial Unicode MS", Helvetica, sans-serif;}
+input.button { font:8pt Verdana, Arail, "Arial Unicode MS", Helvetica, sans-serif;}
+input.text {}
+div.DDB { position:absolute; top:20pt; left:15pt; visibility:visible; }
+div.DLC { position:absolute; top:20pt; left:15pt; visibility:hidden; }
+div.numFound { position:absolute; top:0px; left:0pt; font-weight:bold;}
+
+table { background-color:lightgray; font-size:10pt; margin:10pt 0pt 15pt 0pt; width:90%; border-collapse: collapse; spacing:0; padding:0;}
+td { background-color:#f0f0f0; border: solid lightgray 1px; }
+td.fulltag { background-color:#f0f0f0;}
+td.fullind { background-color:#f0f0f0;  width:20pt;}
+td.fullfield{ background-color:#f0f0f0; width:100%;}
+
+table.signature { background-color:lightgray; font-size:10pt; margin:0; width:100%; border:none; padding:0;}
+table.params { background-color:lightgray; font-size:10pt; margin:3px 0px 3px 0px; width:100%; border: solid black 1px; padding:0;}
+td.params { background-color:lightgray; font-size:10pt; border: solid black 1px;}
+
+h1 { text-decoration: underline; }
+
+td.header { font-weight:bold; color:black; font-size:14pt; border-bottom: solid gray 2px}
+td.label { vertical-align:top; padding-left:10pt; width:120pt; font-weight:normal; color:darkblue;}
+td.value { vertical-align:top; text-align:left; font-weight: bold;}
+span.subcode { color:darkblue;}        </style>
+
+      </head>
+      <body>
+        <a name="top"/>
+
+<!--#if expr='$QUERY_STRING = /limit=([^&]+)/' -->
+  <!--#set var="limit" value="$1" -->
+<!--#else -->
+  <!--#set var="limit" value="25" -->
+<!--#endif -->
+
+<!--#if expr='$QUERY_STRING = /offset=([^&]+)/' -->
+  <!--#set var="offset" value="$1" -->
+<!--#else -->
+  <!--#set var="offset" value="0" -->
+<!--#endif -->
+
+<!--#if expr='$QUERY_STRING = /service=([^&]+)/' -->
+  <!--#set var="service" value="$1" -->
+<!--#else -->
+  <!--#set var="service" value="" -->
+<!--#endif -->
+
+<!--#if expr='$QUERY_STRING = /method=([^&]+)/' -->
+  <!--#set var="method" value="$1" -->
+<!--#endif -->
+
+<!--#if expr="$QUERY_STRING = /all=on/" -->
+  <!--#set var="all" value="on" -->
+  <!--#set var="method" value="opensrf.sysemt.method.all" -->
+<!--#else -->
+  <!--#set var="all" value="off" -->
+  <!--#set var="method" value="opensrf.sysemt.method" -->
+<!--#endif -->
+
+<!--#if expr='$QUERY_STRING = /param=%22(.+?)%22/' -->
+  <!--#set var="param" value="$1" -->
+<!--#else -->
+  <!--#set var="param" value="" -->
+<!--#endif -->
+
+        <xsl:if test="not(res:response)">
+         <br/><br/><br/><br/><br/><br/>
+         <br/><br/><br/><br/><br/><br/>
+       </xsl:if>
+
+        <form
+         method="GET"
+         action='<!--#echo var="DOCUMENT_URI" -->'
+         onsubmit='
+           this.param.value = "\"" + this.param.value + "\"";
+           if (this.all.checked) this.method.value = "opensrf.system.method.all";
+         '>
+          <xsl:if test="not(res:response)">
+           <xsl:attribute name="style">
+             <xsl:value-of select="'text-align:center;'"/>
+           </xsl:attribute>
+         </xsl:if>
+          Application:
+         <input name="service" type="text" value='<!--#echo var="service" -->'/>&#160;
+          API Method Name Regex:
+         <input name="param" type="text" value='<!--#echo var="param" -->'>
+            <xsl:if test="'<!--#echo var="all" -->' = 'on'">
+             <xsl:attribute name="disabled">
+               <xsl:value-of select="'true'"/>
+             </xsl:attribute>
+           </xsl:if>
+         </input>&#160;
+         All Methods (Use with care!)
+         <input
+           name="all"
+           type="checkbox"
+           value="on"
+           onclick='
+             if (this.checked) this.form.param.disabled = true;
+             else this.form.param.disabled = false;
+           '>
+           <xsl:if test="'<!--#echo var="all" -->' = 'on'">
+             <xsl:attribute name="checked">
+               <xsl:value-of select="'checked'"/>
+             </xsl:attribute>
+           </xsl:if>
+
+           </input>&#160;
+          <input type="hidden" name="offset" value="<!--#echo var="offset" -->"/>
+          <button name="limit" value="<!--#echo var="limit" -->">Find 'em</button>
+        </form>
+
+        <xsl:if test="res:response">
+         <hr/>
+
+          <xsl:apply-templates select="res:response"/>
+
+         <hr/>
+
+          <form
+           method="GET"
+           action='<!--#echo var="DOCUMENT_URI" -->'
+           onsubmit='
+             this.param.value = "\"" + this.param.value + "\"";
+             if (this.all.checked) this.method.value = "opensrf.system.method.all";
+           '>
+            <xsl:if test="not(res:response)">
+             <xsl:attribute name="style">
+               <xsl:value-of select="'text-align:center;'"/>
+             </xsl:attribute>
+           </xsl:if>
+            Application:
+           <input name="service" type="text" value='<!--#echo var="service" -->'/>&#160;
+            API Method Name Regex:
+           <input name="param" type="text" value='<!--#echo var="param" -->'>
+              <xsl:if test="'<!--#echo var="all" -->' = 'on'">
+               <xsl:attribute name="disabled">
+                 <xsl:value-of select="'true'"/>
+               </xsl:attribute>
+             </xsl:if>
+           </input>&#160;
+           All Methods (Use with care!)
+           <input
+             name="all"
+             type="checkbox"
+             value="on"
+             onclick='
+               if (this.checked) this.form.param.disabled = true;
+               else this.form.param.disabled = false;
+             '>
+             <xsl:if test="'<!--#echo var="all" -->' = 'on'">
+               <xsl:attribute name="checked">
+                 <xsl:value-of select="'checked'"/>
+               </xsl:attribute>
+             </xsl:if>
+  
+             </input>&#160;
+            <input type="hidden" name="offset" value="<!--#echo var="offset" -->"/>
+            <button name="limit" value="<!--#echo var="limit" -->">Find 'em</button>
+          </form>
+
+       </xsl:if>
+      </body>
+    </html>
+  </xsl:template>
+
+  <xsl:template name="apiNameLink">
+    API Level: <xsl:value-of select="../res:element[@key='api_level']/res:number"/> / Method: 
+    <a>
+      <xsl:attribute name="href">#<xsl:value-of select="../res:element[@key='api_level']/res:number"/>/<xsl:value-of select="res:string"/></xsl:attribute>
+      <xsl:value-of select="res:string"/>
+    </a>
+    <br/>
+  </xsl:template>
+
+  <xsl:template match="res:response">
+    <xsl:choose>
+      <xsl:when test="count(//res:element[@key='api_name']) > 1 or <!--#echo var="offset" --> > 0">
+        <h1>Matching Methods</h1>
+
+       <xsl:if test="<!--#echo var="offset" --> &gt; 0">
+         <span>
+           <a>
+              <xsl:attribute name="href">docgen.xsl?service=<!--#echo var="service" -->&amp;all=<!--#echo var="all" -->&amp;param="<!--#echo var="param" -->"&amp;limit=<!--#echo var="limit" -->&amp;offset=<xsl:value-of select='<!--#echo var="offset" --> - <!--#echo var="limit" -->'/></xsl:attribute>
+               Previous Page</a>
+           //
+         </span>
+       </xsl:if>
+
+
+        <span>
+         <xsl:value-of select='<!--#echo var="offset" --> + 1'/>
+           -
+         <xsl:value-of select='<!--#echo var="offset" --> + count(//res:element[@key="api_name"])'/>
+       </span>
+
+       <xsl:if test="count(//res:element[@key='api_name']) = <!--#echo var="limit" -->">
+         <span>
+           //
+           <a>
+              <xsl:attribute name="href">docgen.xsl?service=<!--#echo var="service" -->&amp;all=<!--#echo var="all" -->&amp;param="<!--#echo var="param" -->"&amp;limit=<!--#echo var="limit" -->&amp;offset=<xsl:value-of select='<!--#echo var="offset" --> + <!--#echo var="limit" -->'/></xsl:attribute>
+               Next Page</a>
+         </span>
+       </xsl:if>
+
+        <br/>
+        <br/>
+
+       <xsl:for-each select="//res:element[@key='api_name']">
+          <xsl:sort select="concat(../res:element[@key='api_level']/res:number/text(), res:string/text())"/>
+          <xsl:call-template name="apiNameLink"/>
+        </xsl:for-each>
+
+        <h1>Method Definitions</h1>
+      </xsl:when>
+      <xsl:when test="count(//res:element[@key='api_name']) = 0">
+        <h1><i>No Matching Methods Found</i></h1>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:for-each select="res:payload/res:object">
+      <xsl:sort select="concat(../res:element[@key='api_level']/res:number/text(), res:string/text())"/>
+      <xsl:call-template name="methodDefinition"/>
+    </xsl:for-each>
+  </xsl:template>
+
+
+  <xsl:template name="methodDefinition">
+    <xsl:if test="res:element[@key='remote']/res:number/text()='0'">
+
+      <xsl:if test="count(//res:element[@key='api_name']) > 1">
+        <a>
+          <xsl:attribute name="name"><xsl:value-of select="res:element[@key='api_level']/res:number"/>/<xsl:value-of select="res:element[@key='api_name']/res:string"/></xsl:attribute>
+        </a>
+        <a href="#top">Top</a>
+      </xsl:if>
+
+      <table>
+        <tr>
+          <td colspan="3" class="header"><xsl:value-of select="res:element[@key='api_name']/res:string"/></td>
+        </tr>
+        <tr>
+          <td class="label">API Level:</td>
+          <td colspan="2" class="value"><xsl:value-of select="res:element[@key='api_level']/res:number"/></td>
+        </tr>
+        <tr>
+          <td class="label">Package:</td>
+          <td colspan="2" class="value"><xsl:value-of select="res:element[@key='package']/res:string"/></td>
+        </tr>
+        <tr>
+          <td class="label">Packaged Method:</td>
+          <td colspan="2" class="value"><xsl:value-of select="res:element[@key='method']/res:string"/></td>
+        </tr>
+        <tr>
+          <td class="label">Required argument count:</td>
+          <td colspan="2" class="value"><xsl:value-of select="res:element[@key='argc']/res:number"/></td>
+        </tr>
+        <xsl:if test="normalize-space(res:element[@key='signature']/res:object/res:element[@key='desc']/res:string/text()) != normalize-space(res:element[@key='notes']/res:string/text())">
+          <tr>
+            <td class="label">
+              <xsl:attribute name='rowspan'>
+                <xsl:value-of select='
+                 count(res:element[@key="signature"]/res:object/res:element[@key="params"]/res:array/res:object) +
+                 count(res:element[@key="signature"]/res:object/res:element[@key="params"]/res:array[res:object]) +
+                 5
+               '/>
+              </xsl:attribute>
+              Signature:
+            </td>
+          </tr>
+         <xsl:for-each select="res:element[@key='signature']/res:object">
+            <xsl:call-template name="methodSignature"/>
+         </xsl:for-each>
+        </xsl:if>
+        <tr>
+          <td class="label">Streaming method:</td>
+          <td colspan="2" class="value">
+            <xsl:if test="res:element[@key='stream']/res:number/text()='1'">Yes</xsl:if>
+            <xsl:if test="res:element[@key='stream']/res:number/text()='0'">No</xsl:if>
+          </td>
+        </tr>
+        <xsl:if test="res:element[@key='notes']">
+          <tr>
+            <td class="label">Notes:</td>
+            <td colspan="2" class="value"><pre style="font-weight:normal;font-size:10px;"><xsl:value-of select="res:element[@key='notes']/res:string"/></pre></td>
+          </tr>
+        </xsl:if>
+      </table>
+    </xsl:if>
+  </xsl:template>
+
+
+  <xsl:template name="paramInfoLine">
+    <tr>
+      <td class="label params">
+        <xsl:if test="@key='name'">Name:</xsl:if>
+        <xsl:if test="@key='desc'">Description:</xsl:if>
+        <xsl:if test="@key='type'">Data type:</xsl:if>
+        <xsl:if test="@key='class'">Object class:</xsl:if>
+      </td>
+      <td class="value params"><xsl:value-of select="res:string"/></td>
+    </tr>
+  </xsl:template>
+
+
+  <xsl:template name="paramInfo">
+    <tr>
+      <td>
+        <table class="params">
+         <tr>
+           <td class="label params">Position:</td>
+           <td class="value params"><xsl:value-of select="position()"/></td>
+         </tr>
+         <xsl:for-each select="res:element">
+               <xsl:call-template name="paramInfoLine"/>
+         </xsl:for-each>
+        </table>
+      </td>
+    </tr>
+  </xsl:template>
+
+
+  <xsl:template name="methodSignature">
+      <xsl:if test="res:element[@key='desc']">
+        <tr>
+          <td class="label">Description:</td>
+          <td class="value"><xsl:value-of select="res:element[@key='desc']/res:string"/></td>
+        </tr>
+      </xsl:if>
+      <xsl:if test="res:element[@key='params']/res:array/res:object">
+        <tr>
+          <td class="label">
+            <xsl:attribute name='rowspan'>
+              <xsl:value-of select='count(res:element[@key="params"]/res:array/res:object) + 1'/>
+            </xsl:attribute>
+            Parameters:</td>
+        </tr>
+      </xsl:if>
+      <xsl:for-each select="res:element[@key='params']/res:array/res:object">
+        <xsl:sort select="position()"/>
+        <xsl:call-template name="paramInfo"/>
+      </xsl:for-each>
+      <xsl:if test="res:element[@key='return']">
+        <tr>
+          <td class="label">Returns:</td>
+          <td class="value"><xsl:value-of select="res:element[@key='return']/res:object/res:element[@key='desc']/res:string"/></td>
+        </tr>
+        <tr>
+          <td class="label">Return type:</td>
+          <td class="value"><xsl:value-of select="res:element[@key='return']/res:object/res:element[@key='type']/res:string"/></td>
+        </tr>
+        <tr>
+          <td class="label">Return type class:</td>
+          <td class="value"><xsl:value-of select="res:element[@key='return']/res:object/res:element[@key='class']/res:string"/></td>
+        </tr>
+      </xsl:if>
+  </xsl:template>
+
+
+  <!--#if expr="$QUERY_STRING = /service=[^&]+/" -->
+    <!--#if expr="$QUERY_STRING = /param=%22[^&]+%22/" -->
+      <!-- virtual="/gateway?format=xml&${QUERY_STRING}"-->
+      <!-- virtual="/restgateway?${QUERY_STRING}"-->
+      <!--#include virtual='/gateway?format=xml&input_format=json&service=$service&method=opensrf.system.method&param="$param"&param=$limit&param=$offset'-->
+    <!--#endif -->
+    <!--#if expr="$QUERY_STRING = /all=on/" -->
+      <!-- virtual="/gateway?format=xml&${QUERY_STRING}"-->
+      <!-- virtual="/restgateway?${QUERY_STRING}"-->
+      <!--#include virtual='/gateway?format=xml&input_format=json&service=$service&method=opensrf.system.method.all&param=$limit&param=$offset' -->
+    <!--#endif -->
+  <!--#endif -->
+
+
+</xsl:stylesheet>
+
diff --git a/trunk/src/gateway/Makefile.am b/trunk/src/gateway/Makefile.am
new file mode 100644 (file)
index 0000000..3fba17c
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+EXTRA_DIST = @srcdir@/apachetools.c @srcdir@/apachetools.h @srcdir@/osrf_json_gateway.c @srcdir@/osrf_http_translator.c
+
+AM_CFLAGS = -D_LARGEFILE64_SOURCE -Wall -I@abs_top_srcdir@/include/ -I$(LIBXML2_HEADERS) -I$(APACHE2_HEADERS) -I$(APR_HEADERS)
+AM_LDFLAGS = -L$(LIBDIR) -L@top_builddir@/src/libopensrf
+
+install-exec-local: 
+       if [ ! "$$(grep mod_placeholder `apxs2 -q SYSCONFDIR`/httpd.conf)" ]; \
+               then echo -e "#\n#LoadModule mod_placeholder /usr/lib/apache2/modules/mod_placeholder.so" \
+               >> `apxs2 -q SYSCONFDIR`/httpd.conf; \
+       fi
+       $(APXS2) -c $(DEF_LDLIBS) $(AM_CFLAGS) $(AM_LDFLAGS) @srcdir@/osrf_json_gateway.c apachetools.c apachetools.h libopensrf.so
+       $(APXS2) -c $(DEF_LDLIBS) $(AM_CFLAGS) $(AM_LDFLAGS) @srcdir@/osrf_http_translator.c apachetools.c apachetools.h libopensrf.so
+       $(APXS2) -i -a @srcdir@/osrf_json_gateway.la
+       $(APXS2) -i -a @srcdir@/osrf_http_translator.la
+
+clean-local:
+       rm -f @srcdir@/osrf_http_translator.la @srcdir@/osrf_http_translator.lo @srcdir@/osrf_http_translator.slo @srcdir@/osrf_json_gateway.la @srcdir@/osrf_json_gateway.lo @srcdir@/osrf_json_gateway.slo
diff --git a/trunk/src/gateway/apachetools.c b/trunk/src/gateway/apachetools.c
new file mode 100644 (file)
index 0000000..617ed0b
--- /dev/null
@@ -0,0 +1,202 @@
+#include "apachetools.h"
+
+osrfStringArray* apacheParseParms(request_rec* r) {
+
+       if( r == NULL ) return NULL;
+
+       char* arg = NULL;
+       apr_pool_t *p = r->pool;        /* memory pool */
+       growing_buffer* buffer = buffer_init(1025);
+
+       /* gather the post args and append them to the url query string */
+       if( !strcmp(r->method,"POST") ) {
+
+               ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
+               
+               osrfLogDebug(OSRF_LOG_MARK, "gateway reading post data..");
+
+               if(ap_should_client_block(r)) {
+
+
+                       /* Start with url query string, if any */
+                       
+                       if(r->args && r->args[0])
+                               buffer_add(buffer, r->args);
+
+                       char body[1025];
+
+                       osrfLogDebug(OSRF_LOG_MARK, "gateway client has post data, reading...");
+
+                       /* Append POST data */
+                       
+                       long bread;
+                       while( (bread = ap_get_client_block(r, body, sizeof(body) - 1)) ) {
+
+                               if(bread < 0) {
+                                       osrfLogInfo(OSRF_LOG_MARK, 
+                                               "ap_get_client_block(): returned error, exiting POST reader");
+                                       break;
+                               }
+
+                               body[bread] = '\0';
+                               buffer_add( buffer, body );
+
+                               osrfLogDebug(OSRF_LOG_MARK, 
+                                       "gateway read %ld bytes: %d bytes of data so far", bread, buffer->n_used);
+
+                               if(buffer->n_used > APACHE_TOOLS_MAX_POST_SIZE) {
+                                       osrfLogError(OSRF_LOG_MARK, "gateway received POST larger "
+                                               "than %d bytes. dropping request", APACHE_TOOLS_MAX_POST_SIZE);
+                                       buffer_free(buffer);
+                                       return NULL;
+                               }
+                       }
+
+                       osrfLogDebug(OSRF_LOG_MARK, "gateway done reading post data");
+               }
+
+       } else { /* GET */
+
+        if(r->args && r->args[0])
+            buffer_add(buffer, r->args);
+    }
+
+
+    if(buffer->n_used > 0)
+        arg = apr_pstrdup(p, buffer->buf);
+    else
+        arg = NULL; 
+    buffer_free(buffer);
+
+       if( !arg || !arg[0] ) { /* we received no request */
+               return NULL;
+       }
+
+       osrfLogDebug(OSRF_LOG_MARK, "parsing URL params from post/get request data: %s", arg);
+       
+       osrfStringArray* sarray         = osrfNewStringArray(12); /* method parameters */
+       int sanity = 0;
+       char* key                                       = NULL; /* query item name */
+       char* val                                       = NULL; /* query item value */
+
+       /* Parse the post/get request data into a series of name/value pairs.   */
+       /* Load each name into an even-numbered slot of an osrfStringArray, and */
+       /* the corresponding value into the following odd-numbered slot.        */
+
+       while( arg && (val = ap_getword(p, (const char**) &arg, '&'))) {
+
+               key = ap_getword(r->pool, (const char**) &val, '=');
+               if(!key || !key[0])
+                       break;
+
+               ap_unescape_url(key);
+               ap_unescape_url(val);
+
+               osrfLogDebug(OSRF_LOG_MARK, "parsed URL params %s=%s", key, val);
+
+               osrfStringArrayAdd(sarray, key);
+               osrfStringArrayAdd(sarray, val);
+
+               if( sanity++ > 1000 ) {
+                       osrfLogError(OSRF_LOG_MARK, 
+                               "Parsing URL params failed sanity check: 1000 iterations");
+                       osrfStringArrayFree(sarray);
+                       return NULL;
+               }
+
+       }
+
+       osrfLogDebug(OSRF_LOG_MARK,
+               "Apache tools parsed %d params key/values", sarray->size / 2 );
+
+       return sarray;
+}
+
+
+
+osrfStringArray* apacheGetParamKeys(osrfStringArray* params) {
+       if(params == NULL) return NULL; 
+       osrfStringArray* sarray = osrfNewStringArray(12);
+       int i;
+       osrfLogDebug(OSRF_LOG_MARK, "Fetching URL param keys");
+       for( i = 0; i < params->size; i++ ) 
+               osrfStringArrayAdd(sarray, osrfStringArrayGetString(params, i++));
+       return sarray;
+}
+
+osrfStringArray* apacheGetParamValues(osrfStringArray* params, char* key) {
+
+       if(params == NULL || key == NULL) return NULL;  
+       osrfStringArray* sarray = osrfNewStringArray(12);
+
+       osrfLogDebug(OSRF_LOG_MARK, "Fetching URL values for key %s", key);
+       int i;
+       for( i = 0; i < params->size; i++ ) {
+               char* nkey = osrfStringArrayGetString(params, i++);
+               if(key && !strcmp(nkey, key)) 
+                       osrfStringArrayAdd(sarray, osrfStringArrayGetString(params, i));
+       }
+       return sarray;
+}
+
+
+char* apacheGetFirstParamValue(osrfStringArray* params, char* key) {
+       if(params == NULL || key == NULL) return NULL;  
+
+       int i;
+       osrfLogDebug(OSRF_LOG_MARK, "Fetching first URL value for key %s", key);
+       for( i = 0; i < params->size; i++ ) {
+               char* nkey = osrfStringArrayGetString(params, i++);
+               if(key && !strcmp(nkey, key)) 
+                       return strdup(osrfStringArrayGetString(params, i));
+       }
+
+       return NULL;
+}
+
+
+int apacheDebug( char* msg, ... ) {
+       VA_LIST_TO_STRING(msg);
+       fprintf(stderr, "%s\n", VA_BUF);
+       fflush(stderr);
+       return 0;
+}
+
+
+int apacheError( char* msg, ... ) {
+       VA_LIST_TO_STRING(msg);
+       fprintf(stderr, "%s\n", VA_BUF);
+       fflush(stderr);
+       return HTTP_INTERNAL_SERVER_ERROR; 
+}
+
+
+/* taken more or less directly from O'Reillly - Writing Apache Modules in Perl and C */
+/* needs updating...
+apr_table_t* apacheParseCookies(request_rec *r) {
+
+   const char *data = apr_table_get(r->headers_in, "Cookie");
+       osrfLogDebug(OSRF_LOG_MARK, "Loaded cookies: %s", data);
+
+   apr_table_t* cookies;
+   const char *pair;
+   if(!data) return NULL;
+
+   cookies = apr_make_table(r->pool, 4);
+   while(*data && (pair = ap_getword(r->pool, &data, ';'))) {
+       const char *name, *value;
+       if(*data == ' ') ++data;
+       name = ap_getword(r->pool, &pair, '=');
+       while(*pair && (value = ap_getword(r->pool, &pair, '&'))) {
+           ap_unescape_url((char *)value);
+           apr_table_add(cookies, name, value);
+       }
+   }
+
+    return cookies;
+}
+
+*/ 
+
+
diff --git a/trunk/src/gateway/apachetools.h b/trunk/src/gateway/apachetools.h
new file mode 100644 (file)
index 0000000..4f951f8
--- /dev/null
@@ -0,0 +1,57 @@
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+//#include "apr_compat.h"
+#include "apr_strings.h"
+#include "apr_reslist.h"
+#include "http_log.h"
+
+
+#include "opensrf/string_array.h"
+#include "opensrf/utils.h"
+#include "opensrf/log.h"
+
+#ifndef APACHE_TOOLS_H
+#define APACHE_TOOLS_H
+
+#define APACHE_TOOLS_MAX_POST_SIZE 10485760 /* 10 MB */
+
+
+/* parses apache URL params (GET and POST).  
+       Returns a osrfStringArray of the form [ key, val, key, val, ...]
+       Returns NULL if there are no params */
+osrfStringArray* apacheParseParms(request_rec* r);
+
+/* provide the params string array, and this will generate a 
+       string of array of param keys 
+       the returned osrfStringArray most be freed by the caller
+       */
+osrfStringArray* apacheGetParamKeys(osrfStringArray* params);
+
+/* provide the params string array and a key name, and 
+       this will provide the value found for that key 
+       the returned osrfStringArray most be freed by the caller
+       */
+osrfStringArray* apacheGetParamValues(osrfStringArray* params, char* key);
+
+/* returns the first value found for the given param.  
+       char* must be freed by the caller */
+char* apacheGetFirstParamValue(osrfStringArray* params, char* key);
+
+/* Writes msg to stderr, flushes stderr, and returns 0 */
+int apacheDebug( char* msg, ... );
+
+/* Writes to stderr, flushe stderr, and returns HTTP_INTERNAL_SERVER_ERROR; 
+ */
+int apacheError( char* msg, ... );
+
+/*
+ * Creates an apache table* of cookie name / value pairs 
+ */
+/*
+apr_table_t* apacheParseCookies(request_rec *r);
+*/
+
+
+#endif
diff --git a/trunk/src/gateway/osrf_http_translator.c b/trunk/src/gateway/osrf_http_translator.c
new file mode 100644 (file)
index 0000000..5d2f60a
--- /dev/null
@@ -0,0 +1,486 @@
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <strings.h>
+#include "apachetools.h"
+#include <opensrf/osrf_app_session.h>
+#include <opensrf/osrf_system.h>
+#include <opensrf/osrfConfig.h>
+#include <opensrf/osrf_json.h>
+#include <opensrf/osrf_cache.h>
+
+#define MODULE_NAME "osrf_http_translator_module"
+#define OSRF_TRANSLATOR_CONFIG_FILE "OSRFTranslatorConfig"
+#define OSRF_TRANSLATOR_CONFIG_CTX "OSRFTranslatorConfigContext"
+#define OSRF_TRANSLATOR_CACHE_SERVER "OSRFTranslatorCacheServer"
+
+#define DEFAULT_TRANSLATOR_CONFIG_CTX "gateway"
+#define DEFAULT_TRANSLATOR_CONFIG_FILE "/openils/conf/opensrf_core.xml"
+#define DEFAULT_TRANSLATOR_TIMEOUT 1200
+#define DEFAULT_TRANSLATOR_CACHE_SERVERS "127.0.0.1:11211"
+
+#define MULTIPART_CONTENT_TYPE "multipart/x-mixed-replace;boundary=\"%s\""
+#define JSON_CONTENT_TYPE "text/plain"
+#define MAX_MSGS_PER_PACKET 256
+#define CACHE_TIME 300
+
+#define OSRF_HTTP_HEADER_TO "X-OpenSRF-to"
+#define OSRF_HTTP_HEADER_XID "X-OpenSRF-xid"
+#define OSRF_HTTP_HEADER_FROM "X-OpenSRF-from"
+#define OSRF_HTTP_HEADER_THREAD "X-OpenSRF-thread"
+#define OSRF_HTTP_HEADER_TIMEOUT "X-OpenSRF-timeout"
+#define OSRF_HTTP_HEADER_SERVICE "X-OpenSRF-service"
+#define OSRF_HTTP_HEADER_MULTIPART "X-OpenSRF-multipart"
+
+char* configFile = DEFAULT_TRANSLATOR_CONFIG_FILE;
+char* configCtx = DEFAULT_TRANSLATOR_CONFIG_CTX;
+char* cacheServers = DEFAULT_TRANSLATOR_CACHE_SERVERS;
+
+char* routerName = NULL;
+char* domainName = NULL;
+int osrfConnected = 0;
+char recipientBuf[128];
+char contentTypeBuf[80];
+
+// for development only, writes to apache error log
+static void _dbg(char* s, ...) {
+    VA_LIST_TO_STRING(s);
+    fprintf(stderr, "%s\n", VA_BUF);
+    fflush(stderr);
+}
+
+// Translator struct
+typedef struct {
+    request_rec* apreq;
+    transport_client* handle;
+    osrfList* messages;
+    char* body;
+    char* delim;
+    const char* recipient;
+    const char* service;
+    const char* thread;
+    const char* remoteHost;
+    int complete;
+    int timeout;
+    int multipart;
+    int connectOnly;
+    int disconnectOnly;
+    int localXid;
+} osrfHttpTranslator;
+
+
+static const char* osrfHttpTranslatorGetConfigFile(cmd_parms *parms, void *config, const char *arg) {
+    configFile = (char*) arg;
+       return NULL;
+}
+static const char* osrfHttpTranslatorGetConfigFileCtx(cmd_parms *parms, void *config, const char *arg) {
+    configCtx = (char*) arg;
+       return NULL;
+}
+static const char* osrfHttpTranslatorGetCacheServer(cmd_parms *parms, void *config, const char *arg) {
+    cacheServers = (char*) arg;
+       return NULL;
+}
+
+/** set up the configuratoin handlers */
+static const command_rec osrf_json_gateway_cmds[] = {
+       AP_INIT_TAKE1( OSRF_TRANSLATOR_CONFIG_FILE, osrfHttpTranslatorGetConfigFile,
+                       NULL, RSRC_CONF, "osrf translator config file"),
+       AP_INIT_TAKE1( OSRF_TRANSLATOR_CONFIG_CTX, osrfHttpTranslatorGetConfigFileCtx,
+                       NULL, RSRC_CONF, "osrf translator config file context"),
+       AP_INIT_TAKE1( OSRF_TRANSLATOR_CACHE_SERVER, osrfHttpTranslatorGetCacheServer,
+                       NULL, RSRC_CONF, "osrf translator cache server"),
+    {NULL}
+};
+
+
+// there can only be one, so use a global static one
+static osrfHttpTranslator globalTranslator;
+
+/*
+ * Constructs a new translator object based on the current apache 
+ * request_rec.  Reads the request body and headers.
+ */
+static osrfHttpTranslator* osrfNewHttpTranslator(request_rec* apreq) {
+    osrfHttpTranslator* trans = &globalTranslator;
+    trans->apreq = apreq;
+    trans->complete = 0;
+    trans->connectOnly = 0;
+    trans->disconnectOnly = 0;
+    trans->remoteHost = apreq->connection->remote_ip;
+    trans->messages = NULL;
+
+    /* load the message body */
+       osrfStringArray* params = apacheParseParms(apreq);
+    trans->body = apacheGetFirstParamValue(params, "osrf-msg");
+    osrfStringArrayFree(params);
+
+    /* load the request headers */
+    if (apr_table_get(apreq->headers_in, OSRF_HTTP_HEADER_XID)) // force our log xid to match the caller
+           osrfLogForceXid(strdup(apr_table_get(apreq->headers_in, OSRF_HTTP_HEADER_XID)));
+
+    trans->handle = osrfSystemGetTransportClient();
+    trans->recipient = apr_table_get(apreq->headers_in, OSRF_HTTP_HEADER_TO);
+    trans->service = apr_table_get(apreq->headers_in, OSRF_HTTP_HEADER_SERVICE);
+    trans->thread = apr_table_get(apreq->headers_in, OSRF_HTTP_HEADER_THREAD); /* XXX create thread if necessary */
+
+    const char* timeout = apr_table_get(apreq->headers_in, OSRF_HTTP_HEADER_TIMEOUT);
+    if(timeout) 
+        trans->timeout = atoi(timeout);
+    else 
+        trans->timeout = DEFAULT_TRANSLATOR_TIMEOUT;
+
+    const char* multipart = apr_table_get(apreq->headers_in, OSRF_HTTP_HEADER_MULTIPART);
+    if(multipart && !strcasecmp(multipart, "true"))
+        trans->multipart = 1;
+    else
+        trans->multipart = 0;
+
+    char buf[32];
+    snprintf(buf, sizeof(buf), "%d%ld", getpid(), time(NULL));
+    trans->delim = md5sum(buf);
+
+    return trans;
+}
+
+static void osrfHttpTranslatorFree(osrfHttpTranslator* trans) {
+    if(!trans) return;
+    if(trans->body)
+        free(trans->body);
+    if(trans->delim)
+        free(trans->delim);
+    osrfListFree(trans->messages);
+}
+
+static void osrfHttpTranslatorDebug(osrfHttpTranslator* trans) {
+    _dbg("-----------------------------------");
+    _dbg("body = %s", trans->body);
+    _dbg("service = %s", trans->service);
+    _dbg("thread = %s", trans->thread);
+    _dbg("multipart = %d", trans->multipart);
+    _dbg("recipient = %s", trans->recipient);
+}
+
+/**
+ * Determines the correct recipient address based on the requested 
+ * service or recipient address.  
+ */
+static int osrfHttpTranslatorSetTo(osrfHttpTranslator* trans) {
+    int stat = 0;
+    jsonObject* sessionCache = NULL;
+
+    if(trans->service) {
+        if(trans->recipient) {
+            osrfLogError(OSRF_LOG_MARK, "Specifying both SERVICE and TO are not allowed");
+
+        } else {
+            // service is specified, build a recipient address 
+            // from the router, domain, and service
+            int size = snprintf(recipientBuf, 128, "%s@%s/%s", routerName, domainName, trans->service);
+            recipientBuf[size] = '\0';
+            osrfLogDebug(OSRF_LOG_MARK, "Set recipient to %s", recipientBuf);
+            trans->recipient = recipientBuf;
+            stat = 1;
+        }
+
+    } else {
+
+        if(trans->recipient) {
+            sessionCache = osrfCacheGetObject(trans->thread);
+
+            if(sessionCache) {
+                char* ipAddr = jsonObjectGetString(jsonObjectGetKey(sessionCache, "ip"));
+                char* recipient = jsonObjectGetString(jsonObjectGetKey(sessionCache, "jid"));
+
+                // choosing a specific recipient address requires that the recipient and 
+                // thread be cached on the server (so drone processes cannot be hijacked)
+                if(!strcmp(ipAddr, trans->remoteHost) && !strcmp(recipient, trans->recipient)) {
+                    osrfLogDebug(OSRF_LOG_MARK, "Found cached session from host %s and recipient %s", 
+                        trans->remoteHost, trans->recipient);
+                    stat = 1;
+                    trans->service = jsonObjectGetString(jsonObjectGetKey(sessionCache, "service"));
+
+                } else {
+                    osrfLogError(OSRF_LOG_MARK, 
+                        "Session cache for thread %s does not match request", trans->thread);
+                }
+            }  else {
+                osrfLogError(OSRF_LOG_MARK, 
+                    "attempt to send directly to %s without a session", trans->recipient);
+            }
+        } else {
+            osrfLogError(OSRF_LOG_MARK, "No SERVICE or RECIPIENT defined");
+        } 
+    }
+
+    jsonObjectFree(sessionCache);
+    return stat;
+}
+
+/**
+ * Parses the request body and logs any REQUEST messages to the activity log
+ */
+static int osrfHttpTranslatorParseRequest(osrfHttpTranslator* trans) {
+    osrfMessage* msg;
+    osrfMessage* msgList[MAX_MSGS_PER_PACKET];
+    int numMsgs = osrf_message_deserialize(trans->body, msgList, MAX_MSGS_PER_PACKET);
+    osrfLogDebug(OSRF_LOG_MARK, "parsed %d opensrf messages in this packet", numMsgs);
+
+    if(numMsgs == 0)
+        return 0;
+
+    if(numMsgs == 1) {
+        msg = msgList[0];
+        if(msg->m_type == CONNECT) {
+            trans->connectOnly = 1;
+            return 1;
+        }
+        if(msg->m_type == DISCONNECT) {
+            trans->disconnectOnly = 1;
+            return 1;
+        }
+    }
+
+    // log request messages to the activity log
+    int i;
+    for(i = 0; i < numMsgs; i++) {
+        msg = msgList[i];
+        if(msg->m_type == REQUEST) {
+
+            jsonObject* params = msg->_params;
+            growing_buffer* act = buffer_init(128);    
+            buffer_fadd(act, "[%s] [%s] %s %s", trans->remoteHost, "", trans->service, msg->method_name);
+
+            char* str; 
+            int i = 0;
+            while((str = jsonObjectGetString(jsonObjectGetIndex(params, i++)))) {
+                if( i == 1 )
+                    OSRF_BUFFER_ADD(act, " ");
+                else 
+                    OSRF_BUFFER_ADD(act, ", ");
+                OSRF_BUFFER_ADD(act, str);
+            }
+            osrfLogActivity(OSRF_LOG_MARK, act->buf);
+            buffer_free(act);
+        }
+    }
+
+    return 1;
+}
+
+static int osrfHttpTranslatorCheckStatus(osrfHttpTranslator* trans, transport_message* msg) {
+    osrfMessage* omsgList[MAX_MSGS_PER_PACKET];
+    int numMsgs = osrf_message_deserialize(msg->body, omsgList, MAX_MSGS_PER_PACKET);
+    osrfLogDebug(OSRF_LOG_MARK, "parsed %d response messages", numMsgs);
+    if(numMsgs == 0) return 0;
+
+    osrfMessage* last = omsgList[numMsgs-1];
+    if(last->m_type == STATUS) {
+        if(last->status_code == OSRF_STATUS_TIMEOUT) {
+            osrfLogDebug(OSRF_LOG_MARK, "removing cached session on request timeout");
+            osrfCacheRemove(trans->thread);
+            return 0;
+        }
+        // XXX hm, check for explicit status=COMPLETE message instead??
+        if(last->status_code != OSRF_STATUS_CONTINUE)
+            trans->complete = 1;
+    }
+
+    return 1;
+}
+
+static void osrfHttpTranslatorInitHeaders(osrfHttpTranslator* trans, transport_message* msg) {
+    apr_table_set(trans->apreq->headers_out, OSRF_HTTP_HEADER_FROM, msg->sender);
+    apr_table_set(trans->apreq->headers_out, OSRF_HTTP_HEADER_THREAD, trans->thread);
+    if(trans->multipart) {
+        sprintf(contentTypeBuf, MULTIPART_CONTENT_TYPE, trans->delim);
+        contentTypeBuf[79] = '\0';
+        osrfLogDebug(OSRF_LOG_MARK, "content type %s : %s : %s", MULTIPART_CONTENT_TYPE, trans->delim, contentTypeBuf);
+           ap_set_content_type(trans->apreq, contentTypeBuf);
+        ap_rprintf(trans->apreq, "--%s\n", trans->delim);
+    } else {
+           ap_set_content_type(trans->apreq, JSON_CONTENT_TYPE);
+    }
+}
+
+static void osrfHttpTranslatorCacheSession(osrfHttpTranslator* trans) {
+    jsonObject* cacheObj = jsonNewObject(NULL);
+    jsonObjectSetKey(cacheObj, "ip", jsonNewObject(trans->remoteHost));
+    jsonObjectSetKey(cacheObj, "jid", jsonNewObject(trans->recipient));
+    jsonObjectSetKey(cacheObj, "service", jsonNewObject(trans->service));
+    osrfCachePutObject((char*) trans->thread, cacheObj, CACHE_TIME);
+}
+
+           
+/**
+ * Writes a single chunk of multipart/x-mixed-replace content
+ */
+static void osrfHttpTranslatorWriteChunk(osrfHttpTranslator* trans, transport_message* msg) {
+    ap_rprintf(trans->apreq, 
+        "Content-type: %s\n\n%s\n\n", JSON_CONTENT_TYPE, msg->body);
+    if(trans->complete)
+        ap_rprintf(trans->apreq, "--%s--\n", trans->delim);
+    else
+        ap_rprintf(trans->apreq, "--%s\n", trans->delim);
+    ap_rflush(trans->apreq);
+}
+
+static int osrfHttpTranslatorProcess(osrfHttpTranslator* trans) {
+    if(trans->body == NULL)
+        return HTTP_BAD_REQUEST;
+
+    if(!osrfHttpTranslatorSetTo(trans))
+        return HTTP_BAD_REQUEST;
+
+    if(!osrfHttpTranslatorParseRequest(trans))
+        return HTTP_BAD_REQUEST;
+
+    while(client_recv(trans->handle, 0))
+        continue; // discard any old status messages in the recv queue
+
+    // send the message to the recipient
+    transport_message* tmsg = message_init(
+        trans->body, NULL, trans->thread, trans->recipient, NULL);
+    message_set_osrf_xid(tmsg, osrfLogGetXid());
+    client_send_message(trans->handle, tmsg);
+    message_free(tmsg); 
+
+    if(trans->disconnectOnly) {
+        osrfLogDebug(OSRF_LOG_MARK, "exiting early on disconnect");
+        return OK;
+    }
+
+    // process the response from the opensrf service
+    int firstWrite = 1;
+    while(!trans->complete) {
+        transport_message* msg = client_recv(trans->handle, trans->timeout);
+
+        if(trans->handle->error) {
+            osrfLogError(OSRF_LOG_MARK, "Transport error");
+            return HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if(msg == NULL)
+            return HTTP_GATEWAY_TIME_OUT;
+
+        if(msg->is_error) {
+            osrfLogError(OSRF_LOG_MARK, "XMPP message resulted in error code %d", msg->error_code);
+            return HTTP_NOT_FOUND;
+        }
+
+        if(!osrfHttpTranslatorCheckStatus(trans, msg))
+            continue;
+
+        if(firstWrite) {
+            osrfHttpTranslatorInitHeaders(trans, msg);
+            osrfHttpTranslatorCacheSession(trans);
+            firstWrite = 0;
+        }
+
+        if(trans->multipart) {
+            osrfHttpTranslatorWriteChunk(trans, msg);
+            if(trans->connectOnly)
+                break;
+        } else {
+            if(!trans->messages)
+                trans->messages = osrfNewList();
+            osrfListPush(trans->messages, msg->body);
+
+            if(trans->complete || trans->connectOnly) {
+                growing_buffer* buf = buffer_init(128);
+                int i;
+                OSRF_BUFFER_ADD(buf, osrfListGetIndex(trans->messages, 0));
+                for(i = 1; i < trans->messages->size; i++) {
+                    buffer_chomp(buf); // chomp off the closing array bracket
+                    char* body = osrfListGetIndex(trans->messages, i);
+                    char newbuf[strlen(body)];
+                    sprintf(newbuf, body+1); // chomp off the opening array bracket
+                    OSRF_BUFFER_ADD_CHAR(buf, ',');
+                    OSRF_BUFFER_ADD(buf, newbuf);
+                }
+                
+                ap_rputs(buf->buf, trans->apreq);
+                buffer_free(buf);
+            }
+        }
+    }
+
+    return OK;
+}
+
+static void testConnection(request_rec* r) {
+       if(!osrfConnected || !osrfSystemGetTransportClient()) {
+        osrfLogError(OSRF_LOG_MARK, "We're not connected to OpenSRF");
+               ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "We're not connected to OpenSRF");
+               usleep(100000); // .1 second to prevent process die/start overload
+               exit(1);
+       }
+}
+
+// it's dead, Jim
+static apr_status_t childExit(void* data) {
+    osrf_system_shutdown();
+    return OK;
+}
+
+static void childInit(apr_pool_t *p, server_rec *s) {
+       if(!osrfSystemBootstrapClientResc(configFile, configCtx, "translator")) {
+               ap_log_error( APLOG_MARK, APLOG_ERR, 0, s, 
+                       "Unable to Bootstrap OpenSRF Client with config %s..", configFile);
+               return;
+       }
+
+    routerName = osrfConfigGetValue(NULL, "/router_name");
+    domainName = osrfConfigGetValue(NULL, "/domain");
+    const char* servers[] = {cacheServers};
+    osrfCacheInit(servers, 1, 86400);
+       osrfConnected = 1;
+
+    // at pool destroy time (= child exit time), cleanup
+    apr_pool_cleanup_register(p, NULL, childExit, NULL);
+}
+
+static int handler(request_rec *r) {
+    int stat = OK;
+       if(strcmp(r->handler, MODULE_NAME)) return DECLINED;
+    if(r->header_only) return stat;
+
+       r->allowed |= (AP_METHOD_BIT << M_GET);
+       r->allowed |= (AP_METHOD_BIT << M_POST);
+
+       osrfLogSetAppname("osrf_http_translator");
+    testConnection(r);
+
+    osrfHttpTranslator* trans = osrfNewHttpTranslator(r);
+       osrfLogMkXid();
+    if(trans->body) {
+        stat = osrfHttpTranslatorProcess(trans);
+        //osrfHttpTranslatorDebug(trans);
+        osrfLogInfo(OSRF_LOG_MARK, "translator resulted in status %d", stat);
+    } else {
+        osrfLogWarning(OSRF_LOG_MARK, "no message body to process");
+    }
+    osrfHttpTranslatorFree(trans);
+       return stat;
+}
+
+
+static void registerHooks (apr_pool_t *p) {
+       ap_hook_handler(handler, NULL, NULL, APR_HOOK_MIDDLE);
+       ap_hook_child_init(childInit, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+
+module AP_MODULE_DECLARE_DATA osrf_http_translator_module = {
+       STANDARD20_MODULE_STUFF,
+    NULL,
+       NULL,
+    NULL,
+       NULL,
+    NULL,
+       registerHooks,
+};
+
+
+
+
diff --git a/trunk/src/gateway/osrf_json_gateway.c b/trunk/src/gateway/osrf_json_gateway.c
new file mode 100644 (file)
index 0000000..c260d81
--- /dev/null
@@ -0,0 +1,447 @@
+#include "apachetools.h"
+#include "opensrf/osrf_app_session.h"
+#include "opensrf/osrf_system.h"
+#include "opensrf/osrfConfig.h"
+#include <opensrf/osrf_json.h>
+#include <opensrf/osrf_json_xml.h>
+#include <opensrf/osrf_legacy_json.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <strings.h>
+
+
+#define MODULE_NAME "osrf_json_gateway_module"
+#define GATEWAY_CONFIG "OSRFGatewayConfig"
+#define DEFAULT_LOCALE "OSRFDefaultLocale"
+#define CONFIG_CONTEXT "gateway"
+#define JSON_PROTOCOL "OSRFGatewayLegacyJSON"
+#define GATEWAY_USE_LEGACY_JSON 0
+
+typedef struct { 
+       int legacyJSON;
+} osrf_json_gateway_dir_config;
+
+
+module AP_MODULE_DECLARE_DATA osrf_json_gateway_module;
+
+char* osrf_json_default_locale = "en-US";
+char* osrf_json_gateway_config_file = NULL;
+int bootstrapped = 0;
+int numserved = 0;
+osrfStringArray* allowedServices = NULL;
+
+static const char* osrf_json_gateway_set_default_locale(cmd_parms *parms, void *config, const char *arg) {
+       if (arg)
+               osrf_json_default_locale = (char*) arg;
+       return NULL;
+}
+
+static const char* osrf_json_gateway_set_config(cmd_parms *parms, void *config, const char *arg) {
+       osrf_json_gateway_config_file = (char*) arg;
+       return NULL;
+}
+
+static const char* osrf_json_gateway_set_json_proto(cmd_parms *parms, void *config, const char *arg) {
+       osrf_json_gateway_dir_config* cfg = (osrf_json_gateway_dir_config*) config;
+       cfg->legacyJSON = (!strcasecmp((char*) arg, "true")) ? 1 : 0;
+       return NULL;
+}
+
+/* tell apache about our commands */
+static const command_rec osrf_json_gateway_cmds[] = {
+       AP_INIT_TAKE1( GATEWAY_CONFIG, osrf_json_gateway_set_config, 
+                       NULL, RSRC_CONF, "osrf json gateway config file"),
+       AP_INIT_TAKE1( DEFAULT_LOCALE, osrf_json_gateway_set_default_locale, 
+                       NULL, RSRC_CONF, "osrf json gateway default locale"),
+       AP_INIT_TAKE1( JSON_PROTOCOL, osrf_json_gateway_set_json_proto,
+                       NULL, ACCESS_CONF, "osrf json gateway config file"),
+       {NULL}
+};
+
+
+static void* osrf_json_gateway_create_dir_config( apr_pool_t* p, char* dir) {
+       osrf_json_gateway_dir_config* cfg = (osrf_json_gateway_dir_config*) 
+                       apr_palloc(p, sizeof(osrf_json_gateway_dir_config));
+       cfg->legacyJSON = GATEWAY_USE_LEGACY_JSON;
+       return (void*) cfg;
+}
+
+static apr_status_t child_exit(void* data) {
+    osrfLogInfo(OSRF_LOG_MARK, "Disconnecting on child cleanup...");
+    osrf_system_shutdown();
+    return OK;
+}
+
+static void osrf_json_gateway_child_init(apr_pool_t *p, server_rec *s) {
+
+       char* cfg = osrf_json_gateway_config_file;
+       char buf[32];
+       int t = time(NULL);
+       snprintf(buf, sizeof(buf), "%d", t);
+
+       if( ! osrfSystemBootstrapClientResc( cfg, CONFIG_CONTEXT, buf ) ) {
+               ap_log_error( APLOG_MARK, APLOG_ERR, 0, s, 
+                       "Unable to Bootstrap OpenSRF Client with config %s..", cfg);
+               return;
+       }
+
+       bootstrapped = 1;
+       allowedServices = osrfNewStringArray(8);
+       osrfLogInfo(OSRF_LOG_MARK, "Bootstrapping gateway child for requests");
+       osrfConfigGetValueList( NULL, allowedServices, "/services/service" );
+
+       int i;
+       for( i = 0; i < allowedServices->size; i++ ) {
+               ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, 
+                       "allowed service: %s\n", osrfStringArrayGetString(allowedServices, i));
+       }
+
+    // when this pool is cleaned up, it means the child 
+    // process is going away.  register some cleanup code
+    apr_pool_cleanup_register(p, NULL, child_exit, NULL);
+}
+
+static int osrf_json_gateway_method_handler (request_rec *r) {
+
+       /* make sure we're needed first thing*/
+       if (strcmp(r->handler, MODULE_NAME )) return DECLINED;
+
+
+       osrf_json_gateway_dir_config* dir_conf =  
+               ap_get_module_config(r->per_dir_config, &osrf_json_gateway_module);
+
+
+       /* provide 2 different JSON parsers and serializers to support legacy JSON */
+       jsonObject* (*parseJSONFunc) (const char*) = legacy_jsonParseString;
+       char* (*jsonToStringFunc) (const jsonObject*) = legacy_jsonObjectToJSON;
+
+       if(dir_conf->legacyJSON) {
+               ap_log_rerror( APLOG_MARK, APLOG_DEBUG, 0, r, "Using legacy JSON");
+
+       } else {
+               parseJSONFunc = jsonParseString;
+               jsonToStringFunc = jsonObjectToJSON;
+       }
+
+
+       osrfLogDebug(OSRF_LOG_MARK, "osrf gateway: entered request handler");
+
+       /* verify we are connected */
+       if( !bootstrapped || !osrfSystemGetTransportClient()) {
+               ap_log_rerror( APLOG_MARK, APLOG_ERR, 0, r, "Cannot process request "
+                               "because the OpenSRF JSON gateway has not been bootstrapped...");
+               usleep( 100000 ); /* 100 milliseconds */
+               exit(1);
+       }
+
+       osrfLogSetAppname("osrf_json_gw");
+
+       char* osrf_locale       = NULL;
+       char* param_locale      = NULL; /* locale for this call */
+       char* service           = NULL; /* service to connect to */
+       char* method            = NULL; /* method to perform */
+       char* format            = NULL; /* method to perform */
+       char* a_l               = NULL; /* request api level */
+       char* input_format      = NULL; /* POST data format, defaults to 'format' */
+       int   isXML             = 0;
+       int   api_level         = 1;
+
+       r->allowed |= (AP_METHOD_BIT << M_GET);
+       r->allowed |= (AP_METHOD_BIT << M_POST);
+
+       osrfLogDebug(OSRF_LOG_MARK, "osrf gateway: parsing URL params");
+       osrfStringArray* mparams        = NULL;
+       osrfStringArray* params = apacheParseParms(r); /* free me */
+       param_locale            = apacheGetFirstParamValue( params, "locale" );
+       service                 = apacheGetFirstParamValue( params, "service" );
+       method                  = apacheGetFirstParamValue( params, "method" ); 
+       format                  = apacheGetFirstParamValue( params, "format" ); 
+       input_format            = apacheGetFirstParamValue( params, "input_format" ); 
+       a_l                     = apacheGetFirstParamValue( params, "api_level" ); 
+       mparams                 = apacheGetParamValues( params, "param" ); /* free me */
+
+       if(format == NULL)
+               format = strdup( "json" );
+       if(input_format == NULL)
+               input_format = strdup( format );
+
+       /* set the user defined timeout value */
+       int timeout = 60;
+       char* tout = apacheGetFirstParamValue( params, "timeout" ); /* request timeout in seconds */
+       if( tout ) {
+               timeout = atoi(tout);
+               osrfLogDebug(OSRF_LOG_MARK, "Client supplied timeout of %d", timeout);
+               free( tout );
+       }
+
+       if (a_l) {
+               api_level = atoi(a_l);
+               free( a_l );
+       }
+
+       if (!strcasecmp(format, "xml")) {
+               isXML = 1;
+               ap_set_content_type(r, "application/xml");
+       } else {
+               ap_set_content_type(r, "text/plain");
+       }
+
+       free( format );
+       int ret = OK;
+
+       /* ----------------------------------------------------------------- */
+       /* Grab the requested locale using the Accept-Language header*/
+
+
+       if ( !param_locale ) {
+               if ( apr_table_get(r->headers_in, "X-OpenSRF-Language") ) {
+                       param_locale = strdup( apr_table_get(r->headers_in, "X-OpenSRF-Language") );
+               } else if ( apr_table_get(r->headers_in, "Accept-Language") ) {
+                       param_locale = strdup( apr_table_get(r->headers_in, "Accept-Language") );
+               }
+       }
+
+
+       if (param_locale) {
+               growing_buffer* osrf_locale_buf = buffer_init(16);      
+               if (index(param_locale, ',')) {
+                       int ind = index(param_locale, ',') - param_locale;
+                       int i;
+                       for ( i = 0; i < ind && i < 128; i++ )
+                               buffer_add_char( osrf_locale_buf, param_locale[i] );
+               } else {
+                       buffer_add( osrf_locale_buf, param_locale );
+               }
+
+               free(param_locale);
+               osrf_locale = buffer_release( osrf_locale_buf );
+       } else {
+               osrf_locale = strdup( osrf_json_default_locale );
+       }
+       /* ----------------------------------------------------------------- */
+
+
+       if(!(service && method) || 
+               !osrfStringArrayContains(allowedServices, service)) {
+
+               osrfLogError(OSRF_LOG_MARK, 
+                       "Service [%s] not found or not allowed", service);
+               ret = HTTP_NOT_FOUND;
+
+       } else {
+
+               /* This will log all heaers to the apache error log 
+               const apr_array_header_t* arr = apr_table_elts(r->headers_in);
+               const void* ptr;
+
+               while( (ptr = apr_array_pop(arr)) ) {
+                       apr_table_entry_t* e = (apr_table_entry_t*) ptr;
+                       fprintf(stderr, "Table entry: %s : %s\n", e->key, e->val );
+               }
+               fflush(stderr);
+               */
+
+               osrfAppSession* session = osrfAppSessionClientInit(service);
+               osrf_app_session_set_locale(session, osrf_locale);
+
+               double starttime = get_timestamp_millis();
+               int req_id = -1;
+
+               if(!strcasecmp(input_format, "json")) {
+                       jsonObject * arr = jsonNewObject(NULL);
+
+                       char* str;
+                       int i = 0;
+
+                       while( (str = osrfStringArrayGetString(mparams, i++)) ) 
+                               jsonObjectPush(arr, parseJSONFunc(str));
+
+                       req_id = osrfAppSessionMakeRequest( session, arr, method, api_level, NULL );
+                       jsonObjectFree(arr);
+               } else {
+
+                       /**
+                       * If we receive XML method params, convert each param to a JSON object
+                       * and pass the array of JSON object params to the method */
+                       if(!strcasecmp(input_format, "xml")) {
+                               jsonObject* jsonParams = jsonNewObject(NULL);
+
+                               char* str;
+                               int i = 0;
+                               while( (str = osrfStringArrayGetString(mparams, i++)) ) {
+                                       jsonObjectPush(jsonParams, jsonXMLToJSONObject(str));
+                               }
+
+                               req_id = osrfAppSessionMakeRequest( session, jsonParams, method, api_level, NULL );
+                               jsonObjectFree(jsonParams);
+                       }
+               }
+
+
+               if( req_id == -1 ) {
+                       osrfLogError(OSRF_LOG_MARK, "I am unable to communicate with opensrf..going away...");
+                       osrfAppSessionFree(session);
+                       /* we don't want to spawn an intense re-forking storm 
+                        * if there is no jabber server.. so give it some time before we die */
+                       usleep( 100000 ); /* 100 milliseconds */
+                       exit(1);
+               }
+
+
+               /* ----------------------------------------------------------------- */
+               /* log all requests to the activity log */
+               const char* authtoken = apr_table_get(r->headers_in, "X-OILS-Authtoken");
+               if(!authtoken) authtoken = "";
+               growing_buffer* act = buffer_init(128); 
+               buffer_fadd(act, "[%s] [%s] [%s] %s %s", r->connection->remote_ip, authtoken, osrf_locale, service, method );
+               char* str; int i = 0;
+               while( (str = osrfStringArrayGetString(mparams, i++)) ) {
+                       if( i == 1 ) {
+                               OSRF_BUFFER_ADD(act, " ");
+                               OSRF_BUFFER_ADD(act, str);
+                       } else {
+                               OSRF_BUFFER_ADD(act, ", ");
+                               OSRF_BUFFER_ADD(act, str);
+                       }
+               }
+
+               osrfLogActivity( OSRF_LOG_MARK, act->buf );
+               buffer_free(act);
+               /* ----------------------------------------------------------------- */
+
+
+               osrfMessage* omsg = NULL;
+
+               int statuscode = 200;
+
+               /* kick off the object */
+               if (isXML)
+                       ap_rputs("<response xmlns=\"http://opensrf.org/-/namespaces/gateway/v1\"><payload>", r);
+               else
+                       ap_rputs("{\"payload\":[", r);
+
+               int morethan1           = 0;
+               char* statusname        = NULL;
+               char* statustext        = NULL;
+               char* output            = NULL;
+
+               while((omsg = osrfAppSessionRequestRecv( session, req_id, timeout ))) {
+       
+                       statuscode = omsg->status_code;
+                       jsonObject* res;        
+
+                       if( ( res = osrfMessageGetResult(omsg)) ) {
+
+                               if (isXML) {
+                                       output = jsonObjectToXML( res );
+                               } else {
+                                       output = jsonToStringFunc( res );
+                                       if( morethan1 ) ap_rputs(",", r); /* comma between JSON array items */
+                               }
+                               ap_rputs(output, r);
+                               free(output);
+                               morethan1 = 1;
+               
+                       } else {
+       
+                               if( statuscode > 299 ) { /* the request returned a low level error */
+                                       statusname = omsg->status_name ? strdup(omsg->status_name) : strdup("Unknown Error");
+                                       statustext = omsg->status_text ? strdup(omsg->status_text) : strdup("No Error Message");
+                                       osrfLogError( OSRF_LOG_MARK,  "Gateway received error: %s", statustext );
+                               }
+                       }
+       
+                       osrfMessageFree(omsg);
+                       if(statusname) break;
+               }
+
+               double duration = get_timestamp_millis() - starttime;
+               osrfLogDebug(OSRF_LOG_MARK, "gateway request took %f seconds", duration);
+
+
+               if (isXML)
+                       ap_rputs("</payload>", r);
+               else
+                       ap_rputs("]",r); /* finish off the payload array */
+
+               if(statusname) {
+
+                       /* add a debug field if the request died */
+                       ap_log_rerror( APLOG_MARK, APLOG_INFO, 0, r, 
+                                       "OpenSRF JSON Request returned error: %s -> %s", statusname, statustext );
+                       int l = strlen(statusname) + strlen(statustext) + 32;
+                       char buf[l];
+
+                       if (isXML)
+                               snprintf( buf, sizeof(buf), "<debug>\"%s : %s\"</debug>", statusname, statustext );
+
+                       else {
+                               char bb[l];
+                               snprintf(bb, sizeof(bb),  "%s : %s", statusname, statustext);
+                               jsonObject* tmp = jsonNewObject(bb);
+                               char* j = jsonToStringFunc(tmp);
+                               snprintf( buf, sizeof(buf), ",\"debug\": %s", j);
+                               free(j);
+                               jsonObjectFree(tmp);
+                       }
+
+                       ap_rputs(buf, r);
+
+                       free(statusname);
+                       free(statustext);
+               }
+
+               /* insert the status code */
+               char buf[32];
+
+               if (isXML)
+                       snprintf(buf, sizeof(buf), "<status>%d</status>", statuscode );
+               else
+                       snprintf(buf, sizeof(buf), ",\"status\":%d", statuscode );
+
+               ap_rputs( buf, r );
+
+               if (isXML)
+                       ap_rputs("</response>", r);
+               else
+                       ap_rputs( "}", r ); /* finish off the object */
+
+               osrfAppSessionFree(session);
+       }
+
+       osrfLogInfo(OSRF_LOG_MARK, "Completed processing service=%s, method=%s", service, method);
+       osrfStringArrayFree(params);
+       osrfStringArrayFree(mparams);
+       free( osrf_locale );
+       free( input_format );
+       free( method );
+       free( service );
+
+       osrfLogDebug(OSRF_LOG_MARK, "Gateway served %d requests", ++numserved);
+       osrfLogClearXid();
+
+       return ret;
+}
+
+
+
+static void osrf_json_gateway_register_hooks (apr_pool_t *p) {
+       ap_hook_handler(osrf_json_gateway_method_handler, NULL, NULL, APR_HOOK_MIDDLE);
+       ap_hook_child_init(osrf_json_gateway_child_init,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+
+module AP_MODULE_DECLARE_DATA osrf_json_gateway_module = {
+       STANDARD20_MODULE_STUFF,
+       osrf_json_gateway_create_dir_config,
+       NULL,
+    NULL,
+       NULL,
+       osrf_json_gateway_cmds,
+       osrf_json_gateway_register_hooks,
+};
+
+
+
+
diff --git a/trunk/src/java/Makefile.am b/trunk/src/java/Makefile.am
new file mode 100644 (file)
index 0000000..07d5fa1
--- /dev/null
@@ -0,0 +1,57 @@
+JAVAC=javac -J-Xmx256m
+JAVA=java -Xmx256m 
+JAVA_LIBDIR = .lib
+JAVA_LIBS = .:$(OSRF_JAVA_DEPSDIR)/$(WSTX):$(OSRF_JAVA_DEPSDIR)/$(STAX):$(OSRF_JAVA_DEPSDIR)/$(MEMCACHE):$(OSRF_JAVA_DEPSDIR)/$(JSON)
+JAVA_SRC = \
+       org/opensrf/net/xmpp/*.java \
+       org/opensrf/util/*.java \
+       org/opensrf/*.java \
+       org/opensrf/test/*.java 
+
+#------------------------------------------------------------------
+
+all-local:     verify_deps dirs jar
+
+verify_deps:
+       @if [ ! -e "$(OSRF_JAVA_DEPSDIR)/$(WSTX)" ]; then echo -e "\nmissing dependency $(WSTX)!\n" && exit 1; fi
+       @if [ ! -e "$(OSRF_JAVA_DEPSDIR)/$(STAX)" ]; then echo -e "\nmissing dependency $(STAX)!\n" && exit 1; fi
+       @if [ ! -e "$(OSRF_JAVA_DEPSDIR)/$(MEMCACHE)" ]; then echo -e "\nmissing dependency $(MEMCACHE)!\n" && exit 1; fi
+       @if [ ! -e "$(OSRF_JAVA_DEPSDIR)/$(JSON)" ]; then echo -e "\nmissing dependency $(JSON)!\n" && exit 1; fi
+
+dirs:
+       mkdir -p $(JAVA_LIBDIR)
+
+opensrf:
+       $(JAVAC) -d $(JAVA_LIBDIR) -cp $(JAVA_LIBS) $(JAVA_SRC) 2>&1 
+
+jar:   opensrf
+       rm -f opensrf.jar
+       jar cf opensrf.jar -C $(JAVA_LIBDIR) org
+
+# only prints the first 30 lines of errors
+slim:
+       mkdir -p $(JAVA_LIBDIR)
+       $(JAVAC) -d $(JAVA_LIBDIR) -cp $(JAVA_LIBS) $(JAVA_SRC) 2>&1 | head -n 30
+       @echo -e "\nTruncating at 30 lines"
+
+check:
+       mkdir -p $(JAVA_LIBDIR)
+       $(JAVAC) -Xlint:unchecked -d $(JAVA_LIBDIR) -cp $(JAVA_LIBS) $(JAVA_SRC) 2>&1 | head -n 30
+       @echo -e "\nTruncating at 30 lines"
+
+run:
+       $(JAVA) -cp $(JAVA_LIBS):opensrf.jar $(JAVA_EXE) $(JAVA_ARGS)
+
+docs:
+       find . -name *.java > files;
+       javadoc -classpath $(JAVA_LIBS) -d doc @files;
+       rm files;
+
+install-data-local:
+       mkdir -p $(LIBDIR)/java
+       cp opensrf.jar $(LIBDIR)/java
+
+dep_clean:
+       rm -rf deps
+
+       
diff --git a/trunk/src/java/deps.inc b/trunk/src/java/deps.inc
new file mode 100644 (file)
index 0000000..dc21f6c
--- /dev/null
@@ -0,0 +1,5 @@
+export STAX="stax-api-1.0.1.jar"
+export WSTX="wstx-lgpl-3.2.1.jar"
+export MEMCACHE="java_memcached-release_1.5.1.jar"
+export JSON="json.jar"
+
diff --git a/trunk/src/java/deps.sh b/trunk/src/java/deps.sh
new file mode 100755 (executable)
index 0000000..0d7a7bf
--- /dev/null
@@ -0,0 +1,40 @@
+# ----------------------------------------------------------------
+# Utility script for fetching the OpenSRF Java dependencies
+# ----------------------------------------------------------------
+
+. deps.inc
+STAX=stax-api-1.0.1.jar
+WSTX=wstx-lgpl-3.2.1.jar
+MEMCACHE=java_memcached-release_1.5.1.jar
+JSON=json.zip
+JSON_ZIP=json.zip
+
+STAX_URL=http://woodstox.codehaus.org/$STAX
+WSTX_URL=http://woodstox.codehaus.org/3.2.1/$WSTX
+MEMCACHE_URL=http://img.whalin.com/memcached/jdk5/standard/$MEMCACHE
+JSON_URL=http://www.json.org/java/$JSON
+
+JAVAC="javac -J-Xmx256m"
+JAVA="java -Xmx256m"
+
+mkdir -p deps
+if [ ! -f deps/$STAX ]; then wget $STAX_URL -O deps/$STAX; fi 
+if [ ! -f deps/$WSTX ]; then wget $WSTX_URL -O deps/$WSTX; fi
+if [ ! -f deps/$MEMCACHE ]; then wget $MEMCACHE_URL -O deps/$MEMCACHE; fi
+if [ ! -f deps/$JSON ]; then 
+    mkdir -p deps 
+    cd deps 
+    wget "$JSON_URL"
+    unzip $JSON && $JAVAC org/json/*.java; 
+    jar cf json.jar org
+fi
+
+
+if [ -n "$INSTALLDIR" ]; then
+    cp deps/*.jar "$INSTALLDIR"/;
+else
+    echo ""
+    echo "if you provide an INSTALLDIR setting, the script will go ahead and copy the jars into place"
+    echo "example: INSTALLDIR=/path/to/java $0"
+    echo ""
+fi
diff --git a/trunk/src/java/org/opensrf/ClientSession.java b/trunk/src/java/org/opensrf/ClientSession.java
new file mode 100644 (file)
index 0000000..3ed4908
--- /dev/null
@@ -0,0 +1,175 @@
+package org.opensrf;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.Arrays;
+
+import org.opensrf.util.*;
+import org.opensrf.net.xmpp.*;
+
+
+/**
+ * Models an OpenSRF client session.
+ */
+public class ClientSession extends Session {
+
+    /** The remote service to communicate with */
+    private String service;
+    /** OpenSRF domain */
+    private String domain;
+    /** Router name */
+    private String router;
+
+    /** 
+     * original remote node.  The current remote node will change based 
+     * on server responses.  This is used to reset the remote node to 
+     * its original state.
+     */
+    private String origRemoteNode;
+    /** The next request id */
+    private int nextId;
+    /** The requests this session has sent */
+    private Map<Integer, Request> requests;
+
+    /**
+     * Creates a new client session.  Initializes the 
+     * @param service The remote service.
+     */
+    public ClientSession(String service) throws ConfigException {
+        this(service, null);
+    }
+
+    /**
+     * Creates a new client session.  Initializes the 
+     * @param service The remote service.
+     * @param locale The locale for this session.
+     */
+    public ClientSession(String service, String locale) throws ConfigException {
+        this.service = service;
+        if(locale != null) 
+            setLocale(locale);
+
+        /** generate the remote node string */
+        domain = (String) Config.global().getFirst("/domain");
+        router = Config.global().getString("/router_name");
+        setRemoteNode(router + "@" + domain + "/" + service);
+        origRemoteNode = getRemoteNode();
+
+
+        /** create a random thread */
+        long time = new Date().getTime();
+        Random rand = new Random(time);
+        setThread(rand.nextInt()+""+rand.nextInt()+""+time+Thread.currentThread().getId());
+
+        nextId = 0;
+        requests = new HashMap<Integer, Request>();
+        cacheSession();
+    }
+
+    /**
+     * Creates a new request to send to our remote service.
+     * @param method The method API name
+     * @param params The list of method parameters
+     * @return The request object.
+     */
+    public Request request(String method, List<Object> params) throws SessionException {
+        return request(new Request(this, nextId++, method, params));
+    }
+
+    /**
+     * Creates a new request to send to our remote service.
+     * @param method The method API name
+     * @param params The list of method parameters
+     * @return The request object.
+     */
+    public Request request(String method, Object[] params) throws SessionException {
+        return request(new Request(this, nextId++, method, Arrays.asList(params)));
+    }
+
+
+    /**
+     * Creates a new request to send to our remote service.
+     * @param method The method API name
+     * @return The request object.
+     */
+    public Request request(String method) throws SessionException {
+        return request(new Request(this, nextId++, method));
+    }
+
+
+    private Request request(Request req) throws SessionException {
+        if(getConnectState() != ConnectState.CONNECTED)
+            resetRemoteId();
+        requests.put(new Integer(req.getId()), req);
+        req.send();
+        return req;
+    }
+
+
+    /**
+     * Resets the remoteNode to its original state.
+     */
+    public void resetRemoteId() {
+        setRemoteNode(origRemoteNode);
+    }
+
+
+    /**
+     * Pushes a response onto the result queue of the appropriate request.
+     * @param msg The received RESULT Message
+     */
+    public void pushResponse(Message msg) {
+
+        Request req = findRequest(msg.getId());
+        if(req == null) {
+            /** LOG that we've received a result to a non-existant request */
+            System.err.println(msg.getId() +" has no corresponding request");
+            return;
+        }
+        OSRFObject payload = (OSRFObject) msg.get("payload");
+
+        /** build a result and push it onto the request's result queue */
+        req.pushResponse(
+            new Result( 
+                payload.getString("status"), 
+                payload.getInt("statusCode"),
+                payload.get("content")
+            )
+        );
+    }
+
+    public Request findRequest(int reqId) {
+        return requests.get(new Integer(reqId));
+    }
+
+    /**
+     * Removes a request for this session's request set
+     */
+    public void cleanupRequest(int reqId) {
+        requests.remove(new Integer(reqId));
+    }
+
+     public void setRequestComplete(int reqId) {
+        Request req = findRequest(reqId);
+        if(req == null) return;
+        req.setComplete();
+    }
+
+    public static Object atomicRequest(String service, String method, Object[] params) throws MethodException {
+        try {
+            ClientSession session = new ClientSession(service);
+            Request osrfRequest = session.request(method, params);
+            Result result = osrfRequest.recv(600000);
+            if(result.getStatusCode() != 200) 
+                throw new MethodException( 
+                    "Request "+service+":"+method+":"+" failed with status code " + result.getStatusCode());
+            return result.getContent();
+        } catch(Exception e) {
+            throw new MethodException(e);
+        }
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/Message.java b/trunk/src/java/org/opensrf/Message.java
new file mode 100644 (file)
index 0000000..6bfe1ea
--- /dev/null
@@ -0,0 +1,110 @@
+package org.opensrf;
+import org.opensrf.util.*;
+
+
+public class Message implements OSRFSerializable {
+
+    /** Message types */
+    public static final String REQUEST = "REQUEST";
+    public static final String STATUS = "STATUS";
+    public static final String RESULT = "RESULT";
+    public static final String CONNECT = "CONNECT";
+    public static final String DISCONNECT = "DISCONNECT";
+
+    /** Message ID.  This number is used to relate requests to responses */
+    private int id;
+    /** type of message. */
+    private String type;
+    /** message payload */
+    private Object payload;
+    /** message locale */
+    private String locale;
+
+    /** Create a registry for the osrfMessage object */
+    private static OSRFRegistry registry = 
+        OSRFRegistry.registerObject(
+            "osrfMessage", 
+            OSRFRegistry.WireProtocol.HASH, 
+            new String[] {"threadTrace", "type", "payload", "locale"});
+
+    /**
+     * @param id This message's ID
+     * @param type The type of message
+     */
+    public Message(int id, String type) {
+        setId(id);
+        setString(type);
+    }
+
+    /**
+     * @param id This message's ID
+     * @param type The type of message
+     * @param payload The message payload
+     */
+    public Message(int id, String type, Object payload) {
+        this(id, type);
+        setPayload(payload);
+    }
+
+    /**
+     * @param id This message's ID
+     * @param type The type of message
+     * @param payload The message payload
+     * @param locale The message locale
+     */
+    public Message(int id, String type, Object payload, String locale) {
+        this(id, type, payload);
+        setPayload(payload);
+        setLocale(locale);
+    }
+
+
+    public int getId() {
+        return id;
+    }   
+    public String getType() {
+        return type;
+    }
+    public Object getPayload() {
+        return payload;
+    }
+    public String getLocale() {
+        return locale;
+    }
+    public void setId(int id) {
+        this.id = id;
+    }
+    public void setString(String type) {
+        this.type = type;
+    }
+    public void setPayload(Object p) {
+        payload = p;
+    }
+    public void setLocale(String l) {
+        locale = l;
+    }
+
+    /**
+     * Implements the generic get() API required by OSRFSerializable
+     */
+    public Object get(String field) {
+        if("threadTrace".equals(field))
+            return getId();
+        if("type".equals(field))
+            return getType().toString();
+        if("payload".equals(field))
+            return getPayload();
+        if("locale".equals(field))
+            return getLocale();
+        return null;
+    }
+
+    /**
+     * @return The osrfMessage registry.
+     */
+    public OSRFRegistry getRegistry() {
+        return registry;
+    }
+}
+
+
diff --git a/trunk/src/java/org/opensrf/Method.java b/trunk/src/java/org/opensrf/Method.java
new file mode 100644 (file)
index 0000000..b708d4f
--- /dev/null
@@ -0,0 +1,78 @@
+package org.opensrf;
+import java.util.List;
+import java.util.ArrayList;
+import org.opensrf.util.*;
+
+
+public class Method extends OSRFObject {
+
+    /** The method API name */
+    private String name;
+    /** The ordered list of method params */
+    private List<Object> params;
+
+    /** Create a registry for the osrfMethod object */
+    private static OSRFRegistry registry = 
+        OSRFRegistry.registerObject(
+            "osrfMethod", 
+            OSRFRegistry.WireProtocol.HASH, 
+            new String[] {"method", "params"});
+
+    /**
+     * @param name The method API name 
+     */
+    public Method(String name) {
+        this.name = name;
+        this.params = new ArrayList<Object>(8);
+    }
+
+    /**
+     * @param name The method API name
+     * @param params The ordered list of params
+     */
+    public Method(String name, List<Object> params) {
+        this.name = name;
+        this.params = params;
+    }
+
+    /**
+     * @return The method API name
+     */
+    public String getName() {
+        return name;
+    }
+    /**
+     * @return The ordered list of params
+     */
+    public List<Object> getParams() {
+       return params; 
+    }
+
+    /**
+     * Pushes a new param object onto the set of params 
+     * @param p The new param to add to the method.
+     */
+    public void addParam(Object p) {
+        this.params.add(p);
+    }
+
+    /**
+     * Implements the generic get() API required by OSRFSerializable
+     */
+    public Object get(String field) {
+        if("method".equals(field))
+            return getName();
+        if("params".equals(field))
+            return getParams();
+        return null;
+    }
+
+    /**
+     * @return The osrfMethod registry.
+     */
+    public OSRFRegistry getRegistry() {
+        return registry;
+    }
+
+}
+
diff --git a/trunk/src/java/org/opensrf/MethodException.java b/trunk/src/java/org/opensrf/MethodException.java
new file mode 100644 (file)
index 0000000..f87e638
--- /dev/null
@@ -0,0 +1,14 @@
+package org.opensrf;
+
+/**
+ * Thrown when the server responds with a method exception.
+ */
+public class MethodException extends Exception {
+    public MethodException(String info) {
+        super(info);
+    }
+    public MethodException(Throwable cause) {
+        super(cause);
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/MultiSession.java b/trunk/src/java/org/opensrf/MultiSession.java
new file mode 100644 (file)
index 0000000..a312aff
--- /dev/null
@@ -0,0 +1,123 @@
+package org.opensrf;
+import java.util.List;
+import java.util.ArrayList;
+import org.opensrf.util.ConfigException;
+
+public class MultiSession {
+
+    class RequestContainer {
+        Request request;
+        int id;
+        RequestContainer(Request r) {
+            request = r; 
+        }
+    }
+
+    private boolean complete;
+    private List<RequestContainer> requests;
+    private int lastId;
+
+    public MultiSession() {
+        requests = new ArrayList<RequestContainer>();
+    }
+
+    public boolean isComplete() {
+        return complete;
+    }
+
+    public int lastId() {
+        return lastId;
+    }
+
+    /**
+     * Adds a new request to the set of requests.
+     * @param service The OpenSRF service
+     * @param method The OpenSRF method
+     * @param params The array of method params
+     * @return The request ID, which is used to map results from recv() to the original request.
+     */
+    public int request(String service, String method, Object[] params) throws SessionException, ConfigException {
+        ClientSession ses = new ClientSession(service);
+        return request(ses.request(method, params));
+    }
+
+
+    public int request(String service, String method) throws SessionException, ConfigException {
+        ClientSession ses = new ClientSession(service);
+        return request(ses.request(method));
+    }
+
+    private int request(Request req) {
+        RequestContainer c = new RequestContainer(req);
+        c.id = requests.size();
+        requests.add(c);
+        return c.id;
+    }
+
+
+    /**
+     * Calls recv on all pending requests until there is data to return.  The ID which
+     * maps the received object to the request can be retrieved by calling lastId().
+     * @param millis Number of milliseconds to wait for some data to arrive.
+     * @return The object result or null if all requests are complete
+     * @throws MethodException Thrown if no response is received within 
+     * the given timeout or the method fails.
+     */
+    public Object recv(int millis) throws MethodException {
+        if(complete) return null;
+
+        Request req = null;
+        Result res = null;
+        RequestContainer cont = null;
+
+        long duration = 0;
+        long blockTime = 100;
+
+        /* if there is only 1 outstanding request, don't poll */
+        if(requests.size() == 1)
+            blockTime = millis;
+
+        while(true) {
+            for(int i = 0; i < requests.size(); i++) {
+
+                cont = requests.get(i);
+                req = cont.request;
+
+                try {
+                    if(i == 0) {
+                        res = req.recv(blockTime);
+                    } else {
+                        res = req.recv(0);
+                    }
+                } catch(SessionException e) {
+                    throw new MethodException(e);
+                }
+
+                if(res != null) break;
+            }
+
+            if(res != null) break;
+            duration += blockTime;
+
+            if(duration >= millis) {
+                System.out.println("duration = " + duration + " millis = " + millis);
+                throw new MethodException("No request received within " + millis + " milliseconds");
+            }
+        }
+
+        if(res.getStatusCode() != 200) {
+            throw new MethodException("Request " + cont.id + " failed  with status code " + 
+                res.getStatusCode() + " and status message " + res.getStatus());
+        }
+
+        if(req.isComplete())
+            requests.remove(requests.indexOf(cont));
+
+        if(requests.size() == 0)
+            complete = true;
+
+        lastId = cont.id;
+        return res.getContent();
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/Request.java b/trunk/src/java/org/opensrf/Request.java
new file mode 100644 (file)
index 0000000..2d72e2d
--- /dev/null
@@ -0,0 +1,138 @@
+package org.opensrf;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.List;
+import java.util.Date;
+import org.opensrf.net.xmpp.XMPPException;
+import org.opensrf.util.Logger;
+
+public class Request {
+    
+    /** This request's controlling session */
+    private ClientSession session;
+    /** The method */
+    private Method method;
+    /** The ID of this request */
+    private int id;
+    /** Queue of Results */
+    private Queue<Result> resultQueue;
+    /** If true, the receive timeout for this request should be reset */
+    private boolean resetTimeout;
+
+    /** If true, the server has indicated that this request is complete. */
+    private boolean complete;
+
+    /**
+     * @param ses The controlling session for this request.
+     * @param id This request's ID.
+     * @param method The requested method.
+     */
+    public Request(ClientSession ses, int id, Method method) {
+        this.session = ses;
+        this.id = id;
+        this.method = method;
+        resultQueue = new ConcurrentLinkedQueue<Result>();
+        complete = false;
+        resetTimeout = false;
+    }
+
+    /**
+     * @param ses The controlling session for this request.
+     * @param id This request's ID.
+     * @param methodName The requested method's API name.
+     */
+    public Request(ClientSession ses, int id, String methodName) {
+        this(ses, id, new Method(methodName));
+    }
+
+    /**
+     * @param ses The controlling session for this request.
+     * @param id This request's ID.
+     * @param methodName The requested method's API name.
+     * @param params The list of request params
+     */
+    public Request(ClientSession ses, int id, String methodName, List<Object> params) {
+        this(ses, id, new Method(methodName, params));
+    }
+
+    /**
+     * Sends the request to the server.
+     */
+    public void send() throws SessionException {
+        session.send(new Message(id, Message.REQUEST, method, session.getLocale()));
+    }
+
+    /**
+     * Receives the next result for this request.  This method
+     * will wait up to the specified number of milliseconds for 
+     * a response. 
+     * @param millis Number of milliseconds to wait for a result.  If
+     * negative, this method will wait indefinitely.
+     * @return The result or null if none arrives in time
+     */
+    public Result recv(long millis) throws SessionException, MethodException {
+
+        Result result = null;
+
+        if((result = resultQueue.poll()) != null)
+            return result;
+
+        if(millis < 0 && !complete) {
+            /** wait potentially forever for a result to arrive */
+            while(!complete) {
+                session.waitForMessage(millis);
+                if((result = resultQueue.poll()) != null)
+                    return result;
+            }
+
+        } else {
+
+            while(millis >= 0 && !complete) {
+
+                /** wait up to millis milliseconds for a result.  waitForMessage() 
+                 * will return if a response to any request arrives, so we keep track
+                 * of how long we've been waiting in total for a response to 
+                 * this request */
+
+                long start = new Date().getTime();
+                session.waitForMessage(millis);
+                millis -= new Date().getTime() - start;
+                if((result = resultQueue.poll()) != null)
+                    return result;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Pushes a result onto the result queue 
+     * @param result The result to push
+     */
+    public void pushResponse(Result result) {
+        resultQueue.offer(result);
+    }
+
+    /**
+     * @return This request's ID
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Removes this request from the controlling session's request set
+     */
+    public void cleanup() {
+        session.cleanupRequest(id);
+    }
+
+    /** Sets this request as complete */
+    public void setComplete() {
+        complete = true;
+    }
+
+    public boolean isComplete() {
+        return complete;
+    }
+}
diff --git a/trunk/src/java/org/opensrf/Result.java b/trunk/src/java/org/opensrf/Result.java
new file mode 100644 (file)
index 0000000..80b71dd
--- /dev/null
@@ -0,0 +1,106 @@
+package org.opensrf;
+import org.opensrf.util.*;
+
+
+/**
+ * Models a single result from a method request.
+ */
+public class Result implements OSRFSerializable {
+
+    /** Method result content */
+    private Object content;
+    /** Name of the status */
+    private String status;
+    /** Status code number */
+    private int statusCode;
+
+
+    /** Register this object */
+    private static OSRFRegistry registry = 
+        OSRFRegistry.registerObject(
+            "osrfResult", 
+            OSRFRegistry.WireProtocol.HASH, 
+            new String[] {"status", "statusCode", "content"});
+
+
+    /**
+     * @param status The status message for this result
+     * @param statusCode The status code
+     * @param content The content of the result
+     */
+    public Result(String status, int statusCode, Object content) {
+        this.status = status;
+        this.statusCode = statusCode;
+        this.content = content;
+    }
+    
+    /**
+     * Get status.
+     * @return status as String.
+     */
+    public String getStatus() {
+        return status;
+    }
+    
+    /**
+     * Set status.
+     * @param status the value to set.
+     */
+    public void setStatus(String status) {
+        this.status = status;
+    }
+    
+    /**
+     * Get statusCode.
+     * @return statusCode as int.
+     */
+    public int getStatusCode() {
+        return statusCode;
+    }
+    
+    /**
+     * Set statusCode.
+     * @param statusCode the value to set.
+     */
+    public void setStatusCode(int statusCode) {
+        this.statusCode = statusCode;
+    }
+    
+    /**
+     * Get content.
+     * @return content as Object.
+     */
+    public Object getContent() {
+        return content;
+    }
+    
+    /**
+     * Set content.
+     * @param content the value to set.
+     */
+    public void setContent(Object content) {
+        this.content = content;
+    }
+
+    /**
+     * Implements the generic get() API required by OSRFSerializable
+     */
+    public Object get(String field) {
+        if("status".equals(field))
+            return getStatus();
+        if("statusCode".equals(field))
+            return getStatusCode();
+        if("content".equals(field))
+            return getContent();
+        return null;
+    }
+
+    /**
+     * @return The osrfMethod registry.
+     */
+    public OSRFRegistry getRegistry() {
+        return registry;
+    }
+
+}
+
diff --git a/trunk/src/java/org/opensrf/ServerSession.java b/trunk/src/java/org/opensrf/ServerSession.java
new file mode 100644 (file)
index 0000000..62e5133
--- /dev/null
@@ -0,0 +1,8 @@
+package org.opensrf;
+
+/**
+ * Models an OpenSRF server session.
+ */
+public class ServerSession extends Session {
+}
+
diff --git a/trunk/src/java/org/opensrf/Session.java b/trunk/src/java/org/opensrf/Session.java
new file mode 100644 (file)
index 0000000..15fd352
--- /dev/null
@@ -0,0 +1,180 @@
+package org.opensrf;
+import org.opensrf.util.JSONWriter;
+import org.opensrf.net.xmpp.*;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Arrays;
+
+public abstract class Session {
+
+    /** Represents the different connection states for a session */
+    public enum ConnectState {
+        DISCONNECTED,
+        CONNECTING,
+        CONNECTED
+    };
+
+    /** local cache of existing sessions */
+    private static Map<String, Session> 
+        sessionCache = new HashMap<String, Session>();
+
+    /** the current connection state */
+    private ConnectState connectState;
+
+    /** The address of the remote party we are communicating with */
+    private String remoteNode;
+
+    /** Session locale */
+    protected String locale;
+    /** Default session locale */
+    protected static String defaultLocale = "en-US";
+
+    /** 
+     * The thread is used to link messages to a given session. 
+     * In other words, each session has a unique thread, and all messages 
+     * in that session will carry this thread around as an indicator.
+     */
+    private String thread;
+
+    public Session() {
+        connectState = ConnectState.DISCONNECTED;
+    }
+    
+    /**
+     * Sends a Message to our remoteNode.
+     */
+    public void send(Message omsg) throws SessionException {
+
+        /** construct the XMPP message */
+        XMPPMessage xmsg = new XMPPMessage();
+        xmsg.setTo(remoteNode);
+        xmsg.setThread(thread);
+        xmsg.setBody(new JSONWriter(Arrays.asList(new Message[] {omsg})).write());
+
+        try {
+            XMPPSession.getThreadSession().send(xmsg);
+        } catch(XMPPException e) {
+            connectState = ConnectState.DISCONNECTED;
+            throw new SessionException("Error sending message to " + remoteNode, e);
+        }
+    }
+
+    /**
+     * Waits for a message to arrive over the network and passes
+     * all received messages to the stack for processing
+     * @param millis The number of milliseconds to wait for a message to arrive
+     */
+    public static void waitForMessage(long millis) throws SessionException, MethodException {
+        try {
+            Stack.processXMPPMessage(
+                XMPPSession.getThreadSession().recv(millis));
+        } catch(XMPPException e) {
+            throw new SessionException("Error waiting for message", e);
+        }
+    }
+
+    /**
+     * Removes this session from the session cache.
+     */
+    public void cleanup() {
+        sessionCache.remove(thread);
+    }
+
+    /**
+     * Searches for the cached session with the given thread.
+     * @param thread The session thread.
+     * @return The found session or null.
+     */
+    public static Session findCachedSession(String thread) {
+        return sessionCache.get(thread);
+    }
+
+    /**
+     * Puts this session into session cache.
+     */
+    protected void cacheSession() {
+        sessionCache.put(thread, this);
+    }
+
+    /**
+     * Sets the remote address
+     * @param nodeName The name of the remote node.
+     */
+    public void setRemoteNode(String nodeName) {
+        remoteNode = nodeName;
+    }
+    /**
+     * @return The remote node
+     */
+    public String getRemoteNode() {
+        return remoteNode;
+    }
+
+
+    /**
+     * Get thread.
+     * @return thread as String.
+     */
+    public String getThread() {
+        return thread;
+    }
+    
+    /**
+     * Set thread.
+     * @param thread the value to set.
+     */
+    public void setThread(String thread) {
+        this.thread = thread;
+    }
+
+    /**
+     * Get locale.
+     * @return locale as String.
+     */
+    public String getLocale() {
+        if(locale == null)
+            return defaultLocale;
+        return locale;
+    }
+    
+    /**
+     * Set locale.
+     * @param locale the value to set.
+     */
+    public void setLocale(String locale) {
+        this.locale = locale;
+    }
+
+    /**
+     * Get defaultLocale.
+     * @return defaultLocale as String.
+     */
+    public String getDefaultLocale() {
+        return defaultLocale;
+    }
+    
+    /**
+     * Set defaultLocale.
+     * @param defaultLocale the value to set.
+     */
+    public void setDefaultLocale(String defaultLocale) {
+        this.defaultLocale = defaultLocale;
+    }
+
+    
+    /**
+     * Get connectState.
+     * @return connectState as ConnectState.
+     */
+    public ConnectState getConnectState() {
+        return connectState;
+    }
+    
+    /**
+     * Set connectState.
+     * @param connectState the value to set.
+     */
+    public void setConnectState(ConnectState connectState) {
+        this.connectState = connectState;
+    }
+}
diff --git a/trunk/src/java/org/opensrf/SessionException.java b/trunk/src/java/org/opensrf/SessionException.java
new file mode 100644 (file)
index 0000000..bd90a76
--- /dev/null
@@ -0,0 +1,13 @@
+package org.opensrf;
+/**
+ * Used by sessions to indicate communication errors
+ */
+public class SessionException extends Exception {
+    public SessionException(String info) {
+        super(info);
+    }
+    public SessionException(String info, Throwable cause) {
+        super(info, cause);
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/Stack.java b/trunk/src/java/org/opensrf/Stack.java
new file mode 100644 (file)
index 0000000..3e7e606
--- /dev/null
@@ -0,0 +1,105 @@
+package org.opensrf;
+import org.opensrf.net.xmpp.XMPPMessage;
+import org.opensrf.util.*;
+import java.util.Date;
+import java.util.List;
+import java.util.Iterator;
+
+
+public class Stack {
+
+    public static void processXMPPMessage(XMPPMessage msg) throws MethodException {
+
+        if(msg == null) return;
+
+        //System.out.println(msg.getBody());
+
+        /** fetch this session from the cache */
+        Session ses = Session.findCachedSession(msg.getThread());
+
+        if(ses == null) {
+            /** inbound client request, create a new server session */
+            return;
+        }
+
+        /** parse the JSON message body, which should result in a list of OpenSRF messages */
+        List msgList; 
+
+        try {
+            msgList = new JSONReader(msg.getBody()).readArray();
+        } catch(JSONException e) {
+            /** XXX LOG error */
+            e.printStackTrace();
+            return;
+        }
+
+        Iterator itr = msgList.iterator();
+
+        OSRFObject obj = null;
+        long start = new Date().getTime();
+
+        /** cycle through the messages and push them up the stack */
+        while(itr.hasNext()) {
+
+            /** Construct a Message object from the parsed generic OSRFObject */
+            obj = (OSRFObject) itr.next();
+
+            processOSRFMessage(
+                ses, 
+                new Message(
+                    obj.getInt("threadTrace"),
+                    obj.getString("type"),
+                    obj.get("payload")
+                )
+            );
+        }
+
+        /** LOG the duration */
+    }
+
+    private static void processOSRFMessage(Session ses, Message msg) throws MethodException {
+
+        Logger.debug("received id=" + msg.getId() + 
+            " type=" + msg.getType() + " payload=" + msg.getPayload());
+
+        if( ses instanceof ClientSession ) 
+            processResponse((ClientSession) ses, msg);
+        else
+            processRequest((ServerSession) ses, msg);
+    }
+
+    /** 
+     * Process a server response
+     */
+    private static void processResponse(ClientSession session, Message msg) throws MethodException {
+        String type = msg.getType();
+
+        if(msg.RESULT.equals(type)) {
+            session.pushResponse(msg);
+            return;
+        }
+
+        if(msg.STATUS.equals(type)) {
+
+            OSRFObject obj = (OSRFObject) msg.getPayload();
+            Status stat = new Status(obj.getString("status"), obj.getInt("statusCode"));
+            int statusCode = stat.getStatusCode();
+            String status = stat.getStatus();
+
+            switch(statusCode) {
+                case Status.COMPLETE:
+                    session.setRequestComplete(msg.getId());
+                    break;
+                case Status.NOTFOUND: 
+                    session.setRequestComplete(msg.getId());
+                    throw new MethodException(status);
+            }
+        }
+    }
+
+    /**
+     * Process a client request
+     */
+    private static void processRequest(ServerSession session, Message msg) {
+    }
+}
diff --git a/trunk/src/java/org/opensrf/Status.java b/trunk/src/java/org/opensrf/Status.java
new file mode 100644 (file)
index 0000000..8026c7b
--- /dev/null
@@ -0,0 +1,63 @@
+package org.opensrf;
+import org.opensrf.util.*;
+
+public class Status {
+
+    public static final int CONTINUE            = 100;
+    public static final int OK                  = 200;
+    public static final int ACCEPTED            = 202;
+    public static final int COMPLETE            = 205;
+    public static final int REDIRECTED          = 307;
+    public static final int EST                 = 400;
+    public static final int STATUS_UNAUTHORIZED = 401;
+    public static final int FORBIDDEN           = 403;
+    public static final int NOTFOUND            = 404;
+    public static final int NOTALLOWED          = 405;
+    public static final int TIMEOUT             = 408;
+    public static final int EXPFAILED           = 417;
+    public static final int INTERNALSERVERERROR = 500;
+    public static final int NOTIMPLEMENTED      = 501;
+    public static final int VERSIONNOTSUPPORTED = 505;
+
+    private OSRFRegistry registry = OSRFRegistry.registerObject(
+        "osrfConnectStatus",
+        OSRFRegistry.WireProtocol.HASH,
+        new String[] {"status", "statusCode"});
+
+    /** The name of the status */
+    String status;
+    /** The status code */
+    int statusCode;
+
+    public Status(String status, int statusCode) {
+        this.status = status;
+        this.statusCode = statusCode;
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+    public String getStatus() {
+        return status;
+    }
+
+    /**
+     * Implements the generic get() API required by OSRFSerializable
+     */
+    public Object get(String field) {
+        if("status".equals(field))
+            return getStatus();
+        if("statusCode".equals(field))
+            return new Integer(getStatusCode());
+        return null;
+    }
+
+    /**
+     * @return The osrfMessage registry.
+     */
+    public OSRFRegistry getRegistry() {
+        return registry;
+    }
+}
+
+
diff --git a/trunk/src/java/org/opensrf/Sys.java b/trunk/src/java/org/opensrf/Sys.java
new file mode 100644 (file)
index 0000000..d65f8a4
--- /dev/null
@@ -0,0 +1,86 @@
+package org.opensrf;
+
+import org.opensrf.util.*;
+import org.opensrf.net.xmpp.*;
+import java.util.Random;
+import java.util.Date;
+import java.net.InetAddress;
+
+
+public class Sys {
+
+    private static void initLogger(Config config) {
+        if(Logger.instance() == null) {
+            try {
+                String logFile = config.getString("/logfile");
+                int logLevel = config.getInt("/loglevel");
+                Logger.init( (short) config.getInt("/loglevel"), new FileLogger(logFile));
+                /** add syslog support... */
+            } catch(Exception e) {
+                /* by default, log to stderr at WARN level */
+                Logger.init(Logger.WARN, new Logger()); 
+            }
+        }
+    }
+
+    /**
+     * Connects to the OpenSRF network so that client sessions may communicate.
+     * @param configFile The OpenSRF config file 
+     * @param configContext Where in the XML document the config chunk lives.  This
+     * allows an OpenSRF client config chunk to live in XML files where other config
+     * information lives.
+     */
+    public static void bootstrapClient(String configFile, String configContext) 
+            throws ConfigException, SessionException  {
+
+
+        /** see if the current thread already has a connection */
+        XMPPSession existing = XMPPSession.getThreadSession();
+        if(existing != null && existing.connected())
+            return;
+
+        /** create the config parser */
+        Config config = new Config(configContext);
+        config.parse(configFile);
+        Config.setConfig(config); /* set this as the global config */
+
+        initLogger(config);
+
+        /** Collect the network connection info from the config */
+        String username = config.getString("/username");
+        String passwd = config.getString("/passwd");
+        String host = (String) config.getFirst("/domain");
+        int port = config.getInt("/port");
+
+
+        /** Create a random login resource string */
+        String res = "java_";
+        try {
+            res += InetAddress.getLocalHost().getHostAddress();
+        } catch(java.net.UnknownHostException e) {}
+        res += "_"+Math.abs(new Random(new Date().getTime()).nextInt()) 
+            + "_t"+ Thread.currentThread().getId();
+
+
+
+        try {
+
+            /** Connect to the Jabber network */
+            Logger.info("attempting to create XMPP session "+username+"@"+host+"/"+res);
+            XMPPSession xses = new XMPPSession(host, port);
+            xses.connect(username, passwd, res);
+            XMPPSession.setThreadSession(xses);
+
+        } catch(XMPPException e) {
+            throw new SessionException("Unable to bootstrap client", e);
+        }
+    }
+
+    /**
+     * Shuts down the connection to the opensrf network
+     */
+    public static void shutdown() {
+        XMPPSession.getThreadSession().disconnect();
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/net/xmpp/XMPPException.java b/trunk/src/java/org/opensrf/net/xmpp/XMPPException.java
new file mode 100644 (file)
index 0000000..8c20ab7
--- /dev/null
@@ -0,0 +1,10 @@
+package org.opensrf.net.xmpp;
+
+/**
+ * Used for XMPP stream/authentication errors
+ */
+public class XMPPException extends Exception {
+    public XMPPException(String info) {
+        super(info);
+    }
+}
diff --git a/trunk/src/java/org/opensrf/net/xmpp/XMPPMessage.java b/trunk/src/java/org/opensrf/net/xmpp/XMPPMessage.java
new file mode 100644 (file)
index 0000000..b6e2c76
--- /dev/null
@@ -0,0 +1,101 @@
+package org.opensrf.net.xmpp;
+
+import java.io.*;
+
+/**
+ * Models a single XMPP message.
+ */
+public class XMPPMessage {
+
+    /** Message body */
+    private String body;
+    /** Message recipient */
+    private String to;
+    /** Message sender */
+    private String from;
+    /** Message thread */
+    private String thread;
+    /** Message xid */
+    private String xid;
+
+    public XMPPMessage() {
+    }
+
+    public String getBody() {
+        return body;
+    }
+    public String getTo() { 
+        return to; 
+    }
+    public String getFrom() { 
+        return from;
+    }
+    public String getThread() { 
+        return thread; 
+    }
+    public String getXid() {
+        return xid;
+    }
+    public void setBody(String body) {
+        this.body = body;
+    }
+    public void setTo(String to) { 
+        this.to = to; 
+    }
+    public void setFrom(String from) { 
+        this.from = from; 
+    }
+    public void setThread(String thread) { 
+        this.thread = thread; 
+    }
+    public void setXid(String xid) {
+        this.xid = xid; 
+    }
+
+
+    /**
+     * Generates the XML representation of this message.
+     */
+    public String toXML() {
+        StringBuffer sb = new StringBuffer("<message to='");
+        escapeXML(to, sb);
+        sb.append("' osrf_xid='");
+        escapeXML(xid, sb);
+        sb.append("'><thread>");
+        escapeXML(thread, sb);
+        sb.append("</thread><body>");
+        escapeXML(body, sb);
+        sb.append("</body></message>");
+        return sb.toString();
+    }
+
+
+    /**
+     * Escapes non-valid XML characters.
+     * @param s The string to escape.
+     * @param sb The StringBuffer to append new data to.
+     */
+    private void escapeXML(String s, StringBuffer sb) {
+        if( s == null ) return;
+        char c;
+        int l = s.length();
+        for( int i = 0; i < l; i++ ) {
+            c = s.charAt(i);
+            switch(c) {
+                case '<': 
+                    sb.append("&lt;");
+                    break;
+                case '>': 
+                    sb.append("&gt;");
+                    break;
+                case '&': 
+                    sb.append("&amp;");
+                    break;
+                default:
+                    sb.append(c);
+            }
+        }
+    }
+}
+
+
diff --git a/trunk/src/java/org/opensrf/net/xmpp/XMPPReader.java b/trunk/src/java/org/opensrf/net/xmpp/XMPPReader.java
new file mode 100644 (file)
index 0000000..dcaf8bd
--- /dev/null
@@ -0,0 +1,296 @@
+package org.opensrf.net.xmpp;
+
+import javax.xml.stream.*;
+import javax.xml.stream.events.* ;
+import javax.xml.namespace.QName;
+import java.util.Queue;
+import java.io.InputStream;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.Date;
+import org.opensrf.util.Logger;
+
+import com.ctc.wstx.stax.WstxInputFactory;
+
+/**
+ * Slim XMPP Stream reader.  This reader only understands enough XMPP
+ * to handle logins and recv messages.  It's implemented as a StAX parser.
+ * @author Bill Erickson, Georgia Public Library Systems
+ */
+public class XMPPReader implements Runnable {
+
+    /** Queue of received messages. */
+    private Queue<XMPPMessage> msgQueue;
+    /** Incoming XMPP XML stream */
+    private InputStream inStream;
+    /** Current message body */
+    private StringBuffer msgBody;
+    /** Current message thread */
+    private StringBuffer msgThread;
+    /** Current message status */
+    private StringBuffer msgStatus;
+    /** Current message error type */
+    private StringBuffer msgErrType;
+    /** Current message sender */
+    private String msgFrom;
+    /** Current message recipient */
+    private String msgTo;
+    /** Current message error code */
+    private int msgErrCode;
+
+    /** Where this reader currently is in the document */
+    private XMLState xmlState;
+
+    /** The current connect state to the XMPP server */
+    private XMPPStreamState streamState;
+
+
+    /** Used to represent out connection state to the XMPP server */
+    public static enum XMPPStreamState {
+        DISCONNECTED,   /* not connected to the server */
+        CONNECT_SENT,   /* we've sent the initial connect message */
+        CONNECT_RECV,   /* we've received a response to our connect message */
+        AUTH_SENT,      /* we've sent an authentication request */
+        CONNECTED       /* authentication is complete */
+    };
+
+
+    /** Used to represents where we are in the XML document stream. */
+    public static enum XMLState {
+        IN_NOTHING,
+        IN_BODY,
+        IN_THREAD,
+        IN_STATUS
+    };
+
+
+    /**
+     * Creates a new reader. Initializes the message queue.
+     * Sets the stream state to disconnected, and the xml
+     * state to in_nothing.
+     * @param inStream the inbound XML stream
+     */
+    public XMPPReader(InputStream inStream) {
+        msgQueue = new ConcurrentLinkedQueue<XMPPMessage>();
+        this.inStream = inStream;
+        resetBuffers();
+        xmlState = XMLState.IN_NOTHING;
+        streamState = XMPPStreamState.DISCONNECTED;
+    }
+
+    /**
+     * Change the connect state and notify that a core 
+     * event has occurred.
+     */
+    protected void setXMPPStreamState(XMPPStreamState state) {
+        streamState = state;
+        notifyCoreEvent();
+    }
+
+    /**
+     * @return The current stream state of the reader 
+     */
+    public XMPPStreamState getXMPPStreamState() {
+        return streamState;
+    }
+
+
+    /**
+     * @return The next message in the queue, or null
+     */
+    public XMPPMessage popMessageQueue() {
+        return (XMPPMessage) msgQueue.poll();
+    }
+
+
+    /**
+     * Initializes the message buffers 
+     */
+    private void resetBuffers() {
+        msgBody = new StringBuffer();
+        msgThread = new StringBuffer();
+        msgStatus = new StringBuffer(); 
+        msgErrType = new StringBuffer();
+        msgFrom = "";
+        msgTo = "";
+    }
+
+
+    /**
+     * Notifies the waiting thread that a core event has occurred.
+     * Each reader should have exactly one dependent session thread. 
+     */
+    private synchronized void notifyCoreEvent() {
+        notifyAll();
+    }
+
+
+    /**
+     * Waits up to timeout milliseconds for a core event to occur. 
+     * Also, having a message already waiting in the queue 
+     * constitutes a core event.
+     * @param timeout The number of milliseconds to wait.  If 
+     * timeout is negative, waits potentially forever.
+     * @return The number of milliseconds in wait
+     */
+    public synchronized long waitCoreEvent(long timeout) {
+
+        if(msgQueue.peek() != null || timeout == 0) return 0;
+        long start = new Date().getTime();
+
+        try{
+            if(timeout < 0) 
+                wait();
+            else 
+                wait(timeout);
+        } catch(InterruptedException ie) {}
+
+        return new Date().getTime() - start;
+    }
+
+
+
+    /** Kickoff the thread */
+    public void run() {
+        read();
+    }
+
+
+    /**
+     * Parses XML data from the provided XMPP stream.
+     */
+    public void read() {
+
+        try {
+
+            //XMLInputFactory factory = XMLInputFactory.newInstance();
+            XMLInputFactory factory = new com.ctc.wstx.stax.WstxInputFactory();
+
+            /** disable as many unused features as possible to speed up the parsing */
+            factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE);
+            factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
+            factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
+            factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE);
+            factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+
+            /** create the stream reader */
+            XMLStreamReader reader = factory.createXMLStreamReader(inStream);
+            int eventType;
+
+            while(reader.hasNext()) {
+                /** cycle through the XML events */
+
+                eventType = reader.next();
+
+                switch(eventType) {
+
+                    case XMLEvent.START_ELEMENT:
+                        handleStartElement(reader);
+                        break;
+
+                    case XMLEvent.CHARACTERS:
+                        switch(xmlState) {
+                            case IN_BODY:
+                                msgBody.append(reader.getText());
+                                break;
+                            case IN_THREAD:
+                                msgThread.append(reader.getText());
+                                break;
+                            case IN_STATUS:
+                                msgStatus.append(reader.getText());
+                                break;
+                        }
+                        break;
+
+                    case XMLEvent.END_ELEMENT: 
+                        xmlState = XMLState.IN_NOTHING;
+                        if("message".equals(reader.getName().toString())) {
+
+                           /** build a message and add it to the message queue */
+                           XMPPMessage msg = new XMPPMessage();
+                           msg.setFrom(msgFrom);
+                           msg.setTo(msgTo);
+                           msg.setBody(msgBody.toString());
+                           msg.setThread(msgThread.toString());
+
+                           Logger.internal("xmpp message from="+msgFrom+" " + msg.getBody());
+
+                           msgQueue.offer(msg);
+                           resetBuffers(); 
+                           notifyCoreEvent();
+                        }
+                        break;
+                }
+            }
+
+        } catch(javax.xml.stream.XMLStreamException se) {
+            /* XXX log an error */
+            xmlState = XMLState.IN_NOTHING;
+            streamState = XMPPStreamState.DISCONNECTED;
+            notifyCoreEvent();
+        }
+    }
+
+
+    /**
+     * Handles the start_element event.
+     */
+    private void handleStartElement(XMLStreamReader reader) {
+
+        String name = reader.getName().toString();
+
+        if("message".equals(name)) {
+            xmlState = XMLState.IN_BODY;
+
+            /** add a special case for the opensrf "router_from" attribute */
+            String rf = reader.getAttributeValue(null, "router_from");
+            if( rf != null )
+                msgFrom = rf;
+            else
+                msgFrom = reader.getAttributeValue(null, "from");
+            msgTo = reader.getAttributeValue(null, "to");
+            return;
+        }
+
+        if("body".equals(name)) {
+            xmlState = XMLState.IN_BODY;
+            return;
+        }
+
+        if("thread".equals(name)) {
+            xmlState = XMLState.IN_THREAD;
+            return;
+        }
+
+        if("stream:stream".equals(name)) {
+            setXMPPStreamState(XMPPStreamState.CONNECT_RECV);
+            return;
+        }
+
+        if("iq".equals(name)) {
+            if("result".equals(reader.getAttributeValue(null, "type")))
+                setXMPPStreamState(XMPPStreamState.CONNECTED);
+            return;
+        }
+
+        if("status".equals(name)) {
+            xmlState = XMLState.IN_STATUS;
+            return;
+        }
+
+        if("stream:error".equals(name)) {
+            setXMPPStreamState(XMPPStreamState.DISCONNECTED);
+            return;
+        }
+
+        if("error".equals(name)) {
+            msgErrType.append(reader.getAttributeValue(null, "type"));
+            msgErrCode = Integer.parseInt(reader.getAttributeValue(null, "code"));
+            setXMPPStreamState(XMPPStreamState.DISCONNECTED);
+            return;
+        }
+    }
+}
+
+
+
+
diff --git a/trunk/src/java/org/opensrf/net/xmpp/XMPPSession.java b/trunk/src/java/org/opensrf/net/xmpp/XMPPSession.java
new file mode 100644 (file)
index 0000000..f9be7d2
--- /dev/null
@@ -0,0 +1,263 @@
+package org.opensrf.net.xmpp;
+
+import java.io.*;
+import java.net.Socket;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * Represents a single XMPP session.  Sessions are responsible for writing to
+ * the stream and for managing a stream reader.
+ */
+public class XMPPSession {
+
+    /** Initial jabber message */
+    public static final String JABBER_CONNECT = 
+        "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+
+    /** Basic auth message */
+    public static final String JABBER_BASIC_AUTH =  
+        "<iq id='123' type='set'><query xmlns='jabber:iq:auth'>" +
+        "<username>%s</username><password>%s</password><resource>%s</resource></query></iq>";
+
+    public static final String JABBER_DISCONNECT = "</stream:stream>";
+
+    private static Map threadConnections = new ConcurrentHashMap();
+
+    /** jabber domain */
+    private String host;
+    /** jabber port */
+    private int port;
+    /** jabber username */
+    private String username;
+    /** jabber password */
+    private String password;
+    /** jabber resource */
+    private String resource;
+
+    /** XMPP stream reader */
+    XMPPReader reader;
+    /** Fprint-capable socket writer */
+    PrintWriter writer;
+    /** Raw socket output stream */
+    OutputStream outStream;
+    /** The raw socket */
+    Socket socket;
+
+    /** The process-wide session.  All communication occurs
+     * accross this single connection */
+    private static XMPPSession globalSession;
+
+
+    /**
+     * Creates a new session.
+     * @param host The jabber domain
+     * @param port The jabber port
+     */
+    public XMPPSession( String host, int port ) {
+        this.host = host;
+        this.port = port;
+    }
+
+    /**
+     * Returns the global, process-wide session
+     */
+    /*
+    public static XMPPSession getGlobalSession() {
+        return globalSession;
+    }
+    */
+
+    public static XMPPSession getThreadSession() {
+        return (XMPPSession) threadConnections.get(new Long(Thread.currentThread().getId()));
+    }
+
+    /**
+     * Sets the given session as the global session for the current thread
+     * @param ses The session
+     */
+    public static void setThreadSession(XMPPSession ses) {
+        /* every time we create a new connection, clean up any dead threads. 
+         * this is cheaper than cleaning up the dead threads at every access. */
+        cleanupThreadSessions();
+        threadConnections.put(new Long(Thread.currentThread().getId()), ses);
+    }
+
+    /**
+     * Analyzes the threadSession data to see if there are any sessions
+     * whose controlling thread has gone away.  
+     */
+    private static void cleanupThreadSessions() {
+        Thread threads[] = new Thread[Thread.activeCount()]; 
+        Thread.enumerate(threads);
+        for(Iterator i = threadConnections.keySet().iterator(); i.hasNext(); ) {
+            boolean found = false;
+            Long id = (Long) i.next();
+            for(Thread t : threads) {
+                if(t.getId() == id.longValue()) {
+                    found = true;
+                    break;
+                }
+            }
+            if(!found) 
+                threadConnections.remove(id);
+        }
+    }
+
+    /**
+     * Sets the global, process-wide section
+     */
+    /*
+    public static void setGlobalSession(XMPPSession ses) {
+        globalSession = ses;
+    }
+    */
+
+
+    /** true if this session is connected to the server */
+    public boolean connected() {
+        return (
+                reader != null && 
+                reader.getXMPPStreamState() == XMPPReader.XMPPStreamState.CONNECTED &&
+                !socket.isClosed()
+            );
+    }
+
+
+    /**
+     * Connects to the network.
+     * @param username The jabber username
+     * @param password The jabber password
+     * @param resource The Jabber resource
+     */
+    public void connect(String username, String password, String resource) throws XMPPException {
+
+        this.username = username;
+        this.password = password;
+        this.resource = resource;
+
+        try { 
+            /* open the socket and associated streams */
+            socket = new Socket(host, port);
+
+            /** the session maintains control over the output stream */
+            outStream = socket.getOutputStream();
+            writer = new PrintWriter(outStream, true);
+
+            /** pass the input stream to the reader */
+            reader = new XMPPReader(socket.getInputStream());
+
+        } catch(IOException ioe) {
+            throw new 
+                XMPPException("unable to communicate with host " + host + " on port " + port);
+        }
+
+        /* build the reader thread */
+        Thread thread = new Thread(reader);
+        thread.setDaemon(true);
+        thread.start();
+
+        synchronized(reader) {
+            /* send the initial jabber message */
+            sendConnect();
+            reader.waitCoreEvent(10000);
+        }
+        if( reader.getXMPPStreamState() != XMPPReader.XMPPStreamState.CONNECT_RECV ) 
+            throw new XMPPException("unable to connect to jabber server");
+
+        synchronized(reader) {
+            /* send the basic auth message */
+            sendBasicAuth(); 
+            reader.waitCoreEvent(10000);
+        }
+        if(!connected()) 
+            throw new XMPPException("Authentication failed");
+    }
+
+    /** Sends the initial jabber message */
+    private void sendConnect() {
+        reader.setXMPPStreamState(XMPPReader.XMPPStreamState.CONNECT_SENT);
+        writer.printf(JABBER_CONNECT, host);
+    }
+
+    /** Send the basic auth message */
+    private void sendBasicAuth() {
+        reader.setXMPPStreamState(XMPPReader.XMPPStreamState.AUTH_SENT);
+        writer.printf(JABBER_BASIC_AUTH, username, password, resource);
+    }
+
+
+    /**
+     * Sends an XMPPMessage.
+     * @param msg The message to send.
+     */
+    public synchronized void send(XMPPMessage msg) throws XMPPException {
+        checkConnected();
+        try {
+            String xml = msg.toXML();
+            outStream.write(xml.getBytes()); 
+        } catch (Exception e) {
+            throw new XMPPException(e.toString());
+        }
+    }
+
+
+    /**
+     * @throws XMPPException if we are no longer connected.
+     */
+    private void checkConnected() throws XMPPException {
+        if(!connected())
+            throw new XMPPException("Disconnected stream");
+    }
+
+
+    /**
+     * Receives messages from the network.  
+     * @param timeout Maximum number of milliseconds to wait for a message to arrive.
+     * If timeout is negative, this method will wait indefinitely.
+     * If timeout is 0, this method will not block at all, but will return a 
+     * message if there is already a message available.
+     */
+    public XMPPMessage recv(long timeout) throws XMPPException {
+
+        XMPPMessage msg;
+
+        if(timeout < 0) {
+
+            while(true) { /* wait indefinitely for a message to arrive */
+                reader.waitCoreEvent(timeout);
+                msg = reader.popMessageQueue();
+                if( msg != null ) return msg;
+                checkConnected();
+            }
+
+        } else {
+
+            while(timeout >= 0) { /* wait at most 'timeout' milleseconds for a message to arrive */
+                msg = reader.popMessageQueue();
+                if( msg != null ) return msg;
+                timeout -= reader.waitCoreEvent(timeout);
+                msg = reader.popMessageQueue();
+                if( msg != null ) return msg;
+                checkConnected();
+                if(timeout == 0) break;
+            }
+        }
+
+        return reader.popMessageQueue();
+    }
+
+
+    /**
+     * Disconnects from the jabber server and closes the socket
+     */
+    public void disconnect() {
+        try {
+            outStream.write(JABBER_DISCONNECT.getBytes());
+            socket.close();
+        } catch(Exception e) {}
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/test/MathBench.java b/trunk/src/java/org/opensrf/test/MathBench.java
new file mode 100644 (file)
index 0000000..b6e67f9
--- /dev/null
@@ -0,0 +1,79 @@
+package org.opensrf.test;
+import org.opensrf.*;
+import org.opensrf.util.*;
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.PrintStream;
+
+
+public class MathBench {
+
+    public static void main(String args[]) throws Exception {
+
+        PrintStream out = System.out;
+
+        if(args.length < 2) {
+            out.println("usage: java org.opensrf.test.MathBench <osrfConfig> <numIterations>");
+            return;
+        }
+
+        /** connect to the opensrf network */
+        Sys.bootstrapClient(args[0], "/config/opensrf");
+
+        /** how many iterations */
+        int count = Integer.parseInt(args[1]);
+
+        /** create the client session */
+        ClientSession session = new ClientSession("opensrf.math");
+
+        /** params are 1,2 */
+        List<Object> params = new ArrayList<Object>();
+        params.add(new Integer(1));
+        params.add(new Integer(2));
+
+        Request request;
+        Result result;
+        long start;
+        double total = 0;
+
+        for(int i = 0; i < count; i++) {
+
+            start = new Date().getTime();
+
+            /** create (and send) the request */
+            request = session.request("add", params);
+
+            /** wait up to 3 seconds for a response */
+            result = request.recv(3000);
+
+            /** collect the round-trip time */
+            total += new Date().getTime() - start;
+
+            if(result.getStatusCode() == Status.OK) {
+                out.print("+");
+            } else {
+                out.println("\nrequest failed");
+                out.println("status = " + result.getStatus());
+                out.println("status code = " + result.getStatusCode());
+            }
+
+            /** remove this request from the session's request set */
+            request.cleanup();
+
+            if((i+1) % 100 == 0) /** print 100 responses per line */
+                out.println(" [" + (i+1) + "]");
+        }
+
+        out.println("\nAverage request time is " + (total/count) + " ms");
+        
+        /** remove this session from the global session cache */
+        session.cleanup();
+
+        /** disconnect from the opensrf network */
+        Sys.shutdown();
+    }
+}
+
+
+
diff --git a/trunk/src/java/org/opensrf/test/TestCache.java b/trunk/src/java/org/opensrf/test/TestCache.java
new file mode 100644 (file)
index 0000000..1e47c32
--- /dev/null
@@ -0,0 +1,27 @@
+package org.opensrf.test;
+import org.opensrf.*;
+import org.opensrf.util.*;
+import java.util.List;
+import java.util.ArrayList;
+
+public class TestCache {
+    public static void main(String args[]) throws Exception {
+
+        /**
+         * args is a list of string like so:  server:port server2:port server3:port ...
+         */
+
+        Cache.initCache(args);
+        Cache cache = new Cache();
+
+        cache.set("key1", "HI, MA!");
+        cache.set("key2", "HI, MA! 2");
+        cache.set("key3", "HI, MA! 3");
+
+        System.out.println("got key1 = " + (String) cache.get("key1"));
+        System.out.println("got key2 = " + (String) cache.get("key2"));
+        System.out.println("got key3 = " + (String) cache.get("key3"));
+    }
+}
+
+
diff --git a/trunk/src/java/org/opensrf/test/TestClient.java b/trunk/src/java/org/opensrf/test/TestClient.java
new file mode 100644 (file)
index 0000000..a1136cd
--- /dev/null
@@ -0,0 +1,80 @@
+package org.opensrf.test;
+import org.opensrf.*;
+import org.opensrf.util.*;
+import java.util.Map;
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.PrintStream;
+
+public class TestClient {
+
+    public static void main(String args[]) throws Exception {
+
+        /** which opensrf service are we sending our request to */
+        String service; 
+        /** which opensrf method we're calling */
+        String method;
+        /** method params, captures from command-line args */
+        List<Object> params;
+        /** knows how to read JSON */
+        JSONReader reader;
+        /** opensrf request */
+        Request request;
+        /** request result */
+        Result result;
+        /** start time for the request */
+        long start;
+        /** for brevity */
+        PrintStream out = System.out;
+
+        if(args.length < 3) {
+            out.println( "usage: org.opensrf.test.TestClient "+
+                "<osrfConfigFile> <service> <method> [<JSONparam1>, <JSONparam2>]");
+            return;
+        }
+
+        /** connect to the opensrf network,  default config context 
+         * for opensrf_core.xml is /config/opensrf */
+        Sys.bootstrapClient(args[0], "/config/opensrf");
+
+        /* grab the server, method, and any params from the command line */
+        service = args[1];
+        method = args[2];
+        params = new ArrayList<Object>();
+        for(int i = 3; i < args.length; i++) 
+            params.add(new JSONReader(args[i]).read());
+
+
+        /** build the client session */
+        ClientSession session = new ClientSession(service);
+
+        /** kick off the timer */
+        start = new Date().getTime();
+
+        /** Create the request object from the session, method and params */
+        request = session.request(method, params);
+
+        while( (result = request.recv(60000)) != null ) { 
+            /** loop over the results and print the JSON version of the content */
+
+            if(result.getStatusCode() != 200) { 
+                /** make sure the request succeeded */
+                out.println("status = " + result.getStatus());
+                out.println("status code = " + result.getStatusCode());
+                continue;
+            }
+
+            /** JSON-ify the resulting object and print it */
+            out.println("\nresult JSON: " + new JSONWriter(result.getContent()).write());
+        }
+        
+        /** How long did the request take? */
+        out.println("Request round trip took: " + (new Date().getTime() - start) + " ms.");
+
+        Sys.shutdown();
+    }
+}
+
+
+
diff --git a/trunk/src/java/org/opensrf/test/TestConfig.java b/trunk/src/java/org/opensrf/test/TestConfig.java
new file mode 100644 (file)
index 0000000..f65a84f
--- /dev/null
@@ -0,0 +1,16 @@
+package org.opensrf.test;
+import org.opensrf.*;
+import org.opensrf.util.*;
+
+public class TestConfig {
+    public static void main(String args[]) throws Exception {
+        Config config = new Config("");
+        config.parse(args[0]);
+        Config.setConfig(config);
+        System.out.println(config);
+        System.out.println("");
+
+        for(int i = 1; i < args.length; i++) 
+            System.out.println("Found config value: " + args[i] + ": " + Config.global().get(args[i]));
+    }
+}
diff --git a/trunk/src/java/org/opensrf/test/TestJSON.java b/trunk/src/java/org/opensrf/test/TestJSON.java
new file mode 100644 (file)
index 0000000..b19d408
--- /dev/null
@@ -0,0 +1,51 @@
+package org.opensrf.test;
+
+import org.opensrf.*;
+import org.opensrf.util.*;
+import java.util.*;
+
+public class TestJSON {
+
+    public static void main(String args[]) throws Exception {
+        
+        Map<String,Object> map = new HashMap<String,Object>();
+        map.put("key1", "value1");
+        map.put("key2", "value2");
+        map.put("key3", "value3");
+        map.put("key4", "athe\u0301s");
+        map.put("key5", null);
+
+        List<Object> list = new ArrayList<Object>(16);
+        list.add(new Integer(1));
+        list.add(new Boolean(true));
+        list.add("WATER");
+        list.add(null);
+        map.put("key6", list);
+
+        System.out.println(new JSONWriter(map).write() + "\n");
+
+        String[] fields = {"isnew", "name", "shortname", "ill_address"};
+        OSRFRegistry.registerObject("aou", OSRFRegistry.WireProtocol.ARRAY, fields);
+
+        OSRFObject obj = new OSRFObject(OSRFRegistry.getRegistry("aou"));
+        obj.put("name", "athens clarke county");
+        obj.put("ill_address", new Integer(1));
+        obj.put("shortname", "ARL-ATH");
+
+        map.put("key7", obj);
+        list.add(obj);
+        System.out.println(new JSONWriter(map).write() + "\n");
+
+
+        Message m = new Message(1, Message.REQUEST);
+        Method method = new Method("opensrf.settings.host_config.get");
+        method.addParam("app07.dev.gapines.org");
+        m.setPayload(method);
+
+        String s = new JSONWriter(m).write();
+        System.out.println(s + "\n");
+
+        Object o = new JSONReader(s).read();
+        System.out.println("Read+Wrote: " + new JSONWriter(o).write());
+    }
+}
diff --git a/trunk/src/java/org/opensrf/test/TestLog.java b/trunk/src/java/org/opensrf/test/TestLog.java
new file mode 100644 (file)
index 0000000..1d60242
--- /dev/null
@@ -0,0 +1,15 @@
+package org.opensrf.test;
+import org.opensrf.util.Logger;
+import org.opensrf.util.FileLogger;
+
+
+/** Simple test class for tesing the logging functionality */
+public class TestLog {
+    public static void main(String args[]) {
+       Logger.init(Logger.DEBUG, new FileLogger("test.log")); 
+       Logger.error("Hello, world");
+       Logger.warn("Hello, world");
+       Logger.info("Hello, world");
+       Logger.debug("Hello, world");
+    }
+}
diff --git a/trunk/src/java/org/opensrf/test/TestMultiSession.java b/trunk/src/java/org/opensrf/test/TestMultiSession.java
new file mode 100644 (file)
index 0000000..bb0f1a1
--- /dev/null
@@ -0,0 +1,26 @@
+package org.opensrf.test;
+import org.opensrf.*;
+import org.opensrf.util.*;
+
+public class TestMultiSession {
+    public static void main(String[] args) {
+        try {
+            String config = args[0];
+
+            Sys.bootstrapClient(config, "/config/opensrf");
+            MultiSession ses = new MultiSession();
+
+            for(int i = 0; i < 40; i++) {
+                ses.request("opensrf.settings", "opensrf.system.time");
+            }
+
+            while(!ses.isComplete()) 
+                System.out.println("result = " + ses.recv(5000) + " and id = " + ses.lastId());
+
+            System.out.println("done");
+            Sys.shutdown();
+        } catch(Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/trunk/src/java/org/opensrf/test/TestSettings.java b/trunk/src/java/org/opensrf/test/TestSettings.java
new file mode 100644 (file)
index 0000000..116bbe1
--- /dev/null
@@ -0,0 +1,14 @@
+package org.opensrf.test;
+import org.opensrf.*;
+import org.opensrf.util.*;
+
+public class TestSettings {
+    public static void main(String args[]) throws Exception {
+        Sys.bootstrapClient(args[0], "/config/opensrf");
+        SettingsClient client = SettingsClient.instance();
+        String lang = client.getString("/apps/opensrf.settings/language");
+        String impl = client.getString("/apps/opensrf.settings/implementation");
+        System.out.println("opensrf.settings language = " + lang);
+        System.out.println("opensrf.settings implementation = " + impl);
+    }
+}
diff --git a/trunk/src/java/org/opensrf/test/TestThread.java b/trunk/src/java/org/opensrf/test/TestThread.java
new file mode 100644 (file)
index 0000000..bb4cf06
--- /dev/null
@@ -0,0 +1,68 @@
+package org.opensrf.test;
+import org.opensrf.*;
+import org.opensrf.util.*;
+import java.util.Map;
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.PrintStream;
+
+/**
+ * Connects to the opensrf network once per thread and runs
+ * and runs a series of request acccross all launched threads.
+ * The purpose is to verify that the java threaded client api 
+ * is functioning as expected
+ */
+public class TestThread implements Runnable {
+
+    String args[];
+
+    public TestThread(String args[]) {
+        this.args = args;
+    }
+
+    public void run() {
+
+        try {
+
+            Sys.bootstrapClient(args[0], "/config/opensrf");
+            ClientSession session = new ClientSession(args[3]);
+    
+            List params = new ArrayList<Object>();
+            for(int i = 5; i < args.length; i++) 
+                params.add(new JSONReader(args[3]).read());
+    
+            for(int i = 0; i < Integer.parseInt(args[2]); i++) {
+                System.out.println("thread " + Thread.currentThread().getId()+" sending request " + i);
+                Request request = session.request(args[4], params);
+                Result result = request.recv(3000);
+                if(result != null) {
+                    System.out.println("thread " + Thread.currentThread().getId()+ 
+                        " got result JSON: " + new JSONWriter(result.getContent()).write());
+                } else {
+                    System.out.println("* thread " + Thread.currentThread().getId()+ " got NO result");
+                }
+            }
+    
+            Sys.shutdown();
+        } catch(Exception e) {
+            System.err.println(e);
+        }
+    }
+
+    public static void main(String args[]) throws Exception {
+
+        if(args.length < 5) {
+            System.out.println( "usage: org.opensrf.test.TestClient "+
+                "<osrfConfigFile> <numthreads> <numiter> <service> <method> [<JSONparam1>, <JSONparam2>]");
+            return;
+        }
+
+        int numThreads = Integer.parseInt(args[1]);
+        for(int i = 0; i < numThreads; i++) 
+            new Thread(new TestThread(args)).start();
+    }
+}
+
+
+
diff --git a/trunk/src/java/org/opensrf/test/TestXMLFlattener.java b/trunk/src/java/org/opensrf/test/TestXMLFlattener.java
new file mode 100644 (file)
index 0000000..c1fa394
--- /dev/null
@@ -0,0 +1,11 @@
+package org.opensrf.test;
+import org.opensrf.util.XMLFlattener;
+import java.io.FileInputStream;
+
+public class TestXMLFlattener {
+    public static void main(String args[]) throws Exception {
+        FileInputStream fis = new FileInputStream(args[0]);
+        XMLFlattener f = new XMLFlattener(fis);
+        System.out.println(f.read());
+    }
+}
diff --git a/trunk/src/java/org/opensrf/test/TestXMLTransformer.java b/trunk/src/java/org/opensrf/test/TestXMLTransformer.java
new file mode 100644 (file)
index 0000000..9768372
--- /dev/null
@@ -0,0 +1,22 @@
+package org.opensrf.test;
+import org.opensrf.util.XMLTransformer;
+import java.io.File;
+
+public class TestXMLTransformer {
+    /**
+     * arg[0] path to an XML file
+     * arg[1] path to the XSL file to apply
+     */
+    public static void main(String[] args) {
+        try {
+            File xmlFile = new File(args[0]);
+            File xslFile = new File(args[1]);
+            XMLTransformer t = new XMLTransformer(xmlFile, xslFile);
+            System.out.println(t.apply());
+        } catch(Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
+
+
diff --git a/trunk/src/java/org/opensrf/test/TestXMPP.java b/trunk/src/java/org/opensrf/test/TestXMPP.java
new file mode 100644 (file)
index 0000000..2fba67f
--- /dev/null
@@ -0,0 +1,63 @@
+package org.opensrf.test;
+
+import org.opensrf.net.xmpp.XMPPReader;
+import org.opensrf.net.xmpp.XMPPMessage;
+import org.opensrf.net.xmpp.XMPPSession;
+
+public class TestXMPP {
+
+    /**
+     * Connects to the jabber server and waits for inbound messages.
+     * If a recipient is provided, a small message is sent to the recipient.
+     */
+    public static void main(String args[]) throws Exception {
+
+        String host;
+        int port;
+        String username;
+        String password;
+        String resource;
+        String recipient;
+
+        try {
+            host = args[0];
+            port = Integer.parseInt(args[1]);
+            username = args[2];
+            password = args[3];
+            resource = args[4];
+
+        } catch(ArrayIndexOutOfBoundsException e) {
+            System.err.println("usage: org.opensrf.test.TestXMPP <host> <port> <username> <password> <resource> [<recipient>]");
+            return;
+        }
+
+        XMPPSession session = new XMPPSession(host, port);
+        session.connect(username, password, resource);
+
+        XMPPMessage msg;
+
+        if( args.length == 6 ) {
+
+            /** they specified a recipient */
+
+            recipient = args[5];
+            msg = new XMPPMessage();
+            msg.setTo(recipient);
+            msg.setThread("test-thread");
+            msg.setBody("Hello, from java-xmpp");
+            System.out.println("Sending message to " + recipient);
+            session.send(msg);
+        }
+
+        while(true) {
+            System.out.println("waiting for message...");
+            msg = session.recv(-1); /* wait forever for a message to arrive */
+            System.out.println("got message: " + msg.toXML());
+        }
+    }
+}
+
+
+
+
+
diff --git a/trunk/src/java/org/opensrf/util/Cache.java b/trunk/src/java/org/opensrf/util/Cache.java
new file mode 100644 (file)
index 0000000..5303688
--- /dev/null
@@ -0,0 +1,38 @@
+package org.opensrf.util;
+import com.danga.MemCached.*;
+import java.util.List;
+
+/**
+ * Memcache client
+ */
+public class Cache extends MemCachedClient {
+
+    public Cache() {
+        super();
+        setCompressThreshold(4096); /* ?? */
+    }
+
+    /**
+     * Initializes the cache client
+     * @param serverList Array of server:port strings specifying the
+     * set of memcache servers this client will talk to
+     */
+    public static void initCache(String[] serverList) {
+        SockIOPool pool = SockIOPool.getInstance();
+        pool.setServers(serverList);
+        pool.initialize();      
+        com.danga.MemCached.Logger logger = 
+            com.danga.MemCached.Logger.getLogger(MemCachedClient.class.getName());
+        logger.setLevel(logger.LEVEL_ERROR);
+    }
+
+    /**
+     * Initializes the cache client
+     * @param serverList List of server:port strings specifying the
+     * set of memcache servers this client will talk to
+     */
+    public static void initCache(List<String> serverList) {
+        initCache(serverList.toArray(new String[]{}));
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/util/Config.java b/trunk/src/java/org/opensrf/util/Config.java
new file mode 100644 (file)
index 0000000..ddac9c0
--- /dev/null
@@ -0,0 +1,139 @@
+package org.opensrf.util;
+
+import org.json.*;
+import java.util.Map;
+import java.util.List;
+
+
+/**
+ * Config reader and accesor module.  This module reads an XML config file,
+ * then loads the file into an internal config, whose values may be accessed
+ * by xpath-style lookup paths.
+ */
+public class Config {
+
+    /** The globl config instance */
+    private static Config config;
+    /** The object form of the parsed config */
+    private Map configObject;
+    /** 
+     * The log parsing context.  This is used as a prefix to the
+     * config item search path.  This allows config XML chunks to 
+     * be inserted into arbitrary XML files.
+     */
+    private String context;
+
+    public static Config global() {
+        return config;
+    }
+
+
+    /**
+     * @param context The config context
+     */
+    public Config(String context) {
+        this.context = context;
+    }
+
+    /**
+     * Sets the global config object.
+     * @param c The config object to use.
+     */
+    public static void setGlobalConfig(Config c) {
+        config = c;
+    }
+
+    /**
+     * Parses an XML config file.
+     * @param filename The path to the file to parse.
+     */
+    public void parse(String filename) throws ConfigException {
+        try {
+            String xml = Utils.fileToString(filename);
+            JSONObject jobj = XML.toJSONObject(xml);
+            configObject = (Map) new JSONReader(jobj.toString()).readObject();
+        } catch(Exception e) {
+            throw new ConfigException("Error parsing config", e);
+        }
+    }
+
+    public static void setConfig(Config conf) {
+        config = conf;
+    }
+
+    public void setConfigObject(Map config) {
+        this.configObject = config;
+    }
+
+    protected Map getConfigObject() {
+        return this.configObject;
+    }
+
+
+    /**
+     * Returns the configuration value found at the requested path.
+     * @param path The search path
+     * @return The config value, or null if no value exists at the given path.  
+     * @throws ConfigException thrown if nothing is found at the path
+     */
+    public String getString(String path) throws ConfigException {
+        try {
+            return (String) get(path);
+        } catch(Exception e) {
+            throw new 
+                ConfigException("No config string found at " + path);
+        }
+    }
+
+    /**
+     * Gets the int value at the given path
+     * @param path The search path
+     */
+    public int getInt(String path) throws ConfigException {
+        try {
+            return Integer.parseInt(getString(path));
+        } catch(Exception e) {
+            throw new
+                ConfigException("No config int found at " + path);
+        }
+    }
+
+    /**
+     * Returns the configuration object found at the requested path.
+     * @param path The search path
+     * @return The config value
+     * @throws ConfigException thrown if nothing is found at the path
+     */
+    public Object get(String path) throws ConfigException {
+        try {
+            Object obj = Utils.findPath(configObject, context + path);
+            if(obj == null)
+                throw new ConfigException("");
+            return obj;
+        } catch(Exception e) {
+            e.printStackTrace();
+            throw new ConfigException("No config object found at " + path);
+        }
+    }
+
+    /**
+     * Returns the first item in the list found at the given path.  If
+     * no list is found, ConfigException is thrown.
+     * @param path The search path
+     */
+    public Object getFirst(String path) throws ConfigException {
+        Object obj = get(path); 
+        if(obj instanceof List) 
+            return ((List) obj).get(0);
+        return obj;
+    }
+
+
+    /**
+     * Returns the config as a JSON string
+     */
+    public String toString() {
+        return new JSONWriter(configObject).write();
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/util/ConfigException.java b/trunk/src/java/org/opensrf/util/ConfigException.java
new file mode 100644 (file)
index 0000000..c1c491e
--- /dev/null
@@ -0,0 +1,14 @@
+package org.opensrf.util;
+
+/**
+ * Thrown by the Config module when a user requests a configuration
+ * item that does not exist
+ */
+public class ConfigException extends Exception {
+    public ConfigException(String info) {
+        super(info);
+    }
+    public ConfigException(String info, Throwable t) {
+        super(info, t);
+    }
+}
diff --git a/trunk/src/java/org/opensrf/util/FileLogger.java b/trunk/src/java/org/opensrf/util/FileLogger.java
new file mode 100644 (file)
index 0000000..9eb838d
--- /dev/null
@@ -0,0 +1,44 @@
+package org.opensrf.util;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+
+public class FileLogger extends Logger {
+
+    /** File to log to */
+    private String filename;
+
+    /** 
+     * FileLogger constructor
+     * @param filename The path to the log file
+     */
+    public FileLogger(String filename) {
+        this.filename = filename;
+    }
+
+    /**
+     * Logs the mesage to a file.
+     * @param level The log level
+     * @param msg The mesage to log
+     */
+    protected synchronized void log(short level, String msg) {
+        if(level > logLevel) return;
+
+        BufferedWriter out = null;
+        try {
+            out = new BufferedWriter(new FileWriter(this.filename, true));
+            out.write(formatMessage(level, msg) + "\n");
+
+        } catch(Exception e) {
+            /** If we are unable to write our log message, go ahead and
+              * fall back to the default (stdout) logger */
+            Logger.init(logLevel, new Logger());
+            Logger.logByLevel(ERROR, "Unable to write to log file " + this.filename);
+            Logger.logByLevel(level, msg);
+        }
+
+        try {
+            out.close();
+        } catch(Exception e) {}
+    }
+}
diff --git a/trunk/src/java/org/opensrf/util/JSONException.java b/trunk/src/java/org/opensrf/util/JSONException.java
new file mode 100644 (file)
index 0000000..ec28e1d
--- /dev/null
@@ -0,0 +1,9 @@
+package org.opensrf.util;
+/**
+ * Used to indicate JSON parsing errors
+ */
+public class JSONException extends Exception {
+    public JSONException(String s) {
+        super(s);
+    }
+}
diff --git a/trunk/src/java/org/opensrf/util/JSONReader.java b/trunk/src/java/org/opensrf/util/JSONReader.java
new file mode 100644 (file)
index 0000000..b115140
--- /dev/null
@@ -0,0 +1,176 @@
+package org.opensrf.util;
+
+import java.io.*;
+import java.util.*;
+
+import org.json.JSONTokener;
+import org.json.JSONObject;
+import org.json.JSONArray;
+
+
+/**
+ * JSON utilities.
+ */
+public class JSONReader {
+
+    /** Special OpenSRF serializable object netClass key */
+    public static final String JSON_CLASS_KEY = "__c";
+
+    /** Special OpenSRF serializable object payload key */
+    public static final String JSON_PAYLOAD_KEY = "__p";
+
+    /** The JSON string to parser */
+    private String json;
+
+    /**
+     * @param json The JSON to parse
+     */
+    public JSONReader(String json) {
+        this.json = json;
+    }
+
+    /**
+     * Parses JSON and creates an object.
+     * @return The resulting object which may be a List, 
+     * Map, Number, String, Boolean, or null
+     */
+    public Object read() throws JSONException {
+        JSONTokener tk = new JSONTokener(json);
+        try {
+            return readSubObject(tk.nextValue());
+        } catch(org.json.JSONException e) {
+            throw new JSONException(e.toString());
+        }
+    }
+
+    /**
+     * Assumes that a JSON array will be read.  Returns
+     * the resulting array as a list.
+     */
+    public List<?> readArray() throws JSONException {
+        Object o = read();
+        try {
+            return (List<?>) o;
+        } catch(Exception e) {
+            throw new JSONException("readArray(): JSON cast exception");
+        }
+    }
+
+    /**
+     * Assumes that a JSON object will be read.  Returns 
+     * the resulting object as a map.
+     */
+    public Map<?,?> readObject() throws JSONException {
+        Object o = read();
+        try {
+            return (Map<?,?>) o;
+        } catch(Exception e) {
+            throw new JSONException("readObject(): JSON cast exception");
+        }
+    }
+
+
+    /**
+     * Recurse through the object and turn items into maps, lists, etc.
+     */
+    private Object readSubObject(Object obj) throws JSONException {
+
+        if( obj == null || 
+            obj instanceof String || 
+            obj instanceof Number ||
+            obj instanceof Boolean)
+                return obj;
+
+        try {
+
+            if( obj instanceof JSONObject ) {
+
+                /* read objects */
+                String key;
+                JSONObject jobj = (JSONObject) obj;
+                Map<String, Object> map = new HashMap<String, Object>();
+
+                for( Iterator e = jobj.keys(); e.hasNext(); ) {
+                    key = (String) e.next();
+
+                    /* we encoutered the special class key */
+                    if( JSON_CLASS_KEY.equals(key) ) 
+                        return buildRegisteredObject(
+                            (String) jobj.get(key), jobj.get(JSON_PAYLOAD_KEY));
+
+                    /* we encountered the data key */
+                    if( JSON_PAYLOAD_KEY.equals(key) ) 
+                        return buildRegisteredObject(
+                            (String) jobj.get(JSON_CLASS_KEY), jobj.get(key));
+
+                    map.put(key, readSubObject(jobj.get(key)));
+                }
+                return map;
+            } 
+            
+            if ( obj instanceof JSONArray ) {
+
+                JSONArray jarr = (JSONArray) obj;
+                int length = jarr.length();
+                List<Object> list = new ArrayList<Object>(length);
+
+                for( int i = 0; i < length; i++ ) 
+                    list.add(readSubObject(jarr.get(i)));   
+                return list;
+                
+            }
+
+        } catch(org.json.JSONException e) {
+
+            throw new JSONException(e.toString());
+        }
+
+        return null;
+    }
+
+
+
+    /**
+     * Builds an OSRFObject map registered OSRFHash object based on the JSON object data.
+     * @param netClass The network class hint for this object.
+     * @param paylaod The actual object on the wire.
+     */
+    private OSRFObject buildRegisteredObject(
+        String netClass, Object payload) throws JSONException {
+
+        OSRFRegistry registry = OSRFRegistry.getRegistry(netClass);
+        OSRFObject obj = new OSRFObject(registry);
+
+        try {
+            if( payload instanceof JSONArray ) {
+                JSONArray jarr = (JSONArray) payload;
+
+                /* for each array item, instert the item into the hash.  the hash 
+                 * key is found by extracting the fields array from the registered 
+                 * object at the current array index */
+                String fields[] = registry.getFields();
+                for( int i = 0; i < jarr.length(); i++ ) {
+                    obj.put(fields[i], readSubObject(jarr.get(i)));   
+                }
+
+            } else if( payload instanceof JSONObject ) {
+
+                /* since this is a hash, simply copy the data over */
+                JSONObject jobj = (JSONObject) payload;
+                String key;
+                for( Iterator e = jobj.keys(); e.hasNext(); ) {
+                    key = (String) e.next();
+                    obj.put(key, readSubObject(jobj.get(key)));
+                }
+            }
+
+        } catch(org.json.JSONException e) {
+            throw new JSONException(e.toString());
+        }
+
+        return obj;
+    }
+}
+
+
+
diff --git a/trunk/src/java/org/opensrf/util/JSONWriter.java b/trunk/src/java/org/opensrf/util/JSONWriter.java
new file mode 100644 (file)
index 0000000..7cb2cca
--- /dev/null
@@ -0,0 +1,172 @@
+package org.opensrf.util;
+
+import java.io.*;
+import java.util.*;
+
+
+/**
+ * JSONWriter
+ */
+public class JSONWriter {
+
+    /** The object to serialize to JSON */
+    private Object obj;
+
+    /**
+     * @param obj The object to encode
+     */
+    public JSONWriter(Object obj) {
+        this.obj = obj;
+    }
+
+
+    /**
+     * Encodes a java object to JSON.
+     */
+    public String write() {
+        StringBuffer sb = new StringBuffer();
+        write(sb);
+        return sb.toString();
+    }
+
+
+
+    /**
+     * Encodes a java object to JSON.
+     * Maps (HashMaps, etc.) are encoded as JSON objects.  
+     * Iterable's (Lists, etc.) are encoded as JSON arrays
+     */
+    public void write(StringBuffer sb) {
+        write(obj, sb);
+    }
+
+    /**
+     * Encodes the object as JSON into the provided buffer
+     */
+    public void write(Object obj, StringBuffer sb) {
+
+        /** JSON null */
+        if(obj == null) {
+            sb.append("null");
+            return;
+        }
+
+        /** JSON string */
+        if(obj instanceof String) {
+            sb.append('"');
+            Utils.escape((String) obj, sb);
+            sb.append('"');
+            return;
+        }
+
+        /** JSON number */
+        if(obj instanceof Number) {
+            sb.append(obj.toString());
+            return;
+        }
+
+        /** JSON array */
+        if(obj instanceof Iterable) {
+            encodeJSONArray((Iterable) obj, sb);
+            return;
+        }
+
+        /** OpenSRF serializable objects */
+        if(obj instanceof OSRFSerializable) {
+            encodeOSRFSerializable((OSRFSerializable) obj, sb);
+            return;
+        }
+
+        /** JSON object */
+        if(obj instanceof Map) {
+            encodeJSONObject((Map) obj, sb);
+            return;
+        }
+
+        /** JSON boolean */
+        if(obj instanceof Boolean) {
+            sb.append((((Boolean) obj).booleanValue() ? "true" : "false"));
+            return;
+        }
+    }
+
+
+    /**
+     * Encodes a List as a JSON array
+     */
+    private void encodeJSONArray(Iterable iterable, StringBuffer sb) {
+        Iterator itr = iterable.iterator();
+        sb.append("[");
+        boolean some = false;
+
+        while(itr.hasNext()) {
+            some = true;
+            write(itr.next(), sb);
+            sb.append(',');
+        }
+
+        /* remove the trailing comma if the array has any items*/
+        if(some) 
+            sb.deleteCharAt(sb.length()-1); 
+        sb.append("]");
+    }
+
+
+    /**
+     * Encodes a Map as a JSON object
+     */
+    private void encodeJSONObject(Map map, StringBuffer sb) {
+        Iterator itr = map.keySet().iterator();
+        sb.append("{");
+        Object key = null;
+
+        while(itr.hasNext()) {
+            key = itr.next();
+            write(key, sb);
+            sb.append(':');
+            write(map.get(key), sb);
+            sb.append(',');
+        }
+
+        /* remove the trailing comma if the object has any items*/
+        if(key != null) 
+            sb.deleteCharAt(sb.length()-1); 
+        sb.append("}");
+    }
+
+
+    /**
+     * Encodes a network-serializable OpenSRF object
+     */
+    private void encodeOSRFSerializable(OSRFSerializable obj, StringBuffer sb) {
+
+        OSRFRegistry reg = obj.getRegistry();
+        String[] fields = reg.getFields();
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put(JSONReader.JSON_CLASS_KEY, reg.getNetClass());
+
+        if( reg.getWireProtocol() == OSRFRegistry.WireProtocol.ARRAY ) {
+
+            /** encode arrays as lists */
+            List<Object> list = new ArrayList<Object>(fields.length);
+            for(String s : fields)
+                list.add(obj.get(s));
+            map.put(JSONReader.JSON_PAYLOAD_KEY, list);
+
+        } else {
+
+            /** encode hashes as maps */
+            Map<String, Object> subMap = new HashMap<String, Object>();
+            for(String s : fields)
+                subMap.put(s, obj.get(s));
+            map.put(JSONReader.JSON_PAYLOAD_KEY, subMap);
+                
+        }
+
+        /** now serialize the encoded object */
+        write(map, sb);
+    }
+}
+
+
+
diff --git a/trunk/src/java/org/opensrf/util/Logger.java b/trunk/src/java/org/opensrf/util/Logger.java
new file mode 100644 (file)
index 0000000..2923c23
--- /dev/null
@@ -0,0 +1,144 @@
+package org.opensrf.util;
+import java.text.SimpleDateFormat;
+import java.text.FieldPosition;
+import java.util.Date;
+
+/**
+ * Basic OpenSRF logging API.  This default implementation
+ * logs to stderr.
+ */
+public class Logger {
+
+    /** Log levels */
+    public static final short ERROR = 1;
+    public static final short WARN  = 2;
+    public static final short INFO  = 3;
+    public static final short DEBUG = 4;
+    public static final short INTERNAL = 5;
+
+    /** The global log instance */
+    private static Logger instance;
+    /** The global log level */
+    protected static short logLevel;
+
+    public Logger() {}
+
+    /** Sets the global Logger instance
+     * @param level The global log level.
+     * @param l The Logger instance to use
+     */
+    public static void init(short level, Logger l) {
+        instance = l;
+        logLevel = level;
+    }
+
+    /** 
+     * @return The global Logger instance
+     */
+    public static Logger instance() {
+        return instance;
+    }
+
+    /**
+     * Logs an error message
+     * @param msg The message to log
+     */
+    public static void error(String msg) {
+        instance.log(ERROR, msg);
+    }
+
+    /**
+     * Logs an warning message
+     * @param msg The message to log
+     */
+    public static void warn(String msg) {
+        instance.log(WARN, msg);
+    }
+
+    /**
+     * Logs an info message
+     * @param msg The message to log
+     */
+    public static void info(String msg) {
+        instance.log(INFO, msg);
+    }
+
+    /**
+     * Logs an debug message
+     * @param msg The message to log
+     */
+    public static void debug(String msg) {
+        instance.log(DEBUG, msg);
+    }
+
+    /**
+     * Logs an internal message
+     * @param msg The message to log
+     */
+    public static void internal(String msg) {
+        instance.log(INTERNAL, msg);
+    }
+
+
+    /** 
+     * Appends the text representation of the log level
+     * @param sb The stringbuffer to append to
+     * @param level The log level
+     */
+    protected static void appendLevelString(StringBuffer sb, short level) {
+        switch(level) {
+            case DEBUG:
+                sb.append("DEBG"); break;
+            case INFO:
+                sb.append("INFO"); break;
+            case INTERNAL:
+                sb.append("INT "); break;
+            case WARN:
+                sb.append("WARN"); break;
+            case ERROR:
+                sb.append("ERR "); break;
+        }
+    }
+
+    /**
+     * Formats a message for logging.  Appends the current date+time
+     * and the log level string.
+     * @param level The log level
+     * @param msg The message to log
+     */
+    protected static String formatMessage(short level, String msg) {
+
+        StringBuffer sb = new StringBuffer();
+        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(
+            new Date(), sb, new FieldPosition(0));
+
+        sb.append(" [");
+        appendLevelString(sb, level);
+        sb.append(":");
+        sb.append(Thread.currentThread().getId());
+        sb.append("] ");
+        sb.append(msg);
+        return sb.toString();
+    }
+
+    /**
+     * Logs a message by passing the log level explicitly
+     * @param level The log level
+     * @param msg The message to log
+     */
+    public static void logByLevel(short level, String msg) {
+        instance.log(level, msg);
+    }
+
+    /**
+     * Performs the actual logging.  Subclasses should override 
+     * this method.
+     * @param level The log level
+     * @param msg The message to log
+     */
+    protected synchronized void log(short level, String msg) {
+        if(level > logLevel) return;
+        System.err.println(formatMessage(level, msg));
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/util/OSRFObject.java b/trunk/src/java/org/opensrf/util/OSRFObject.java
new file mode 100644 (file)
index 0000000..af28f4a
--- /dev/null
@@ -0,0 +1,63 @@
+package org.opensrf.util;
+
+import java.util.Map;
+import java.util.HashMap;
+
+
+/**
+ * Generic OpenSRF network-serializable object.  This allows
+ * access to object fields.  
+ */
+public class OSRFObject extends HashMap<String, Object> implements OSRFSerializable {
+    
+    /** This objects registry */
+    private OSRFRegistry registry;
+
+    public OSRFObject() {
+    }
+
+
+    /**
+     * Creates a new object with the provided registry
+     */
+    public OSRFObject(OSRFRegistry reg) {
+        this();
+        registry = reg;
+    }
+
+
+    /**
+     * Creates a new OpenSRF object based on the net class string
+     * */
+    public OSRFObject(String netClass) {
+        this(OSRFRegistry.getRegistry(netClass));
+    }
+
+
+    /**
+     * @return This object's registry
+     */
+    public OSRFRegistry getRegistry() {
+        return registry;
+    }
+
+    /**
+     * Implement get() to fulfill our contract with OSRFSerializable
+     */
+    public Object get(String field) {
+        return super.get(field);
+    }
+
+    /** Returns the string value found at the given field */
+    public String getString(String field) {
+        return (String) get(field);
+    }
+
+    /** Returns the int value found at the given field */
+    public int getInt(String field) {
+        Object o = get(field);
+        if(o instanceof String)
+            return Integer.parseInt((String) o);
+        return ((Integer) get(field)).intValue();
+    }
+}
diff --git a/trunk/src/java/org/opensrf/util/OSRFRegistry.java b/trunk/src/java/org/opensrf/util/OSRFRegistry.java
new file mode 100644 (file)
index 0000000..f5fe7cb
--- /dev/null
@@ -0,0 +1,107 @@
+package org.opensrf.util;
+
+import java.util.Map;
+import java.util.HashMap;
+
+
+/**
+ * Manages the registration of OpenSRF network-serializable objects.  
+ * A serializable object has a class "hint" (called netClass within) which
+ * describes the type of object.  Each object also has a set of field names
+ * for accessing/mutating object properties.  Finally, objects have a 
+ * serialization wire protocol.  Currently supported protocols are HASH
+ * and ARRAY.
+ */
+public class OSRFRegistry {
+
+
+    /**
+     * Global collection of registered net objects.  
+     * Maps netClass names to registries.
+     */
+    private static HashMap<String, OSRFRegistry> 
+        registry = new HashMap<String, OSRFRegistry>();
+
+
+    /** Serialization types for registered objects */
+    public enum WireProtocol {
+        ARRAY, HASH
+    };
+
+
+    /** Array of field names for this registered object */
+    String fields[];
+    /** The wire protocol for this object */
+    WireProtocol wireProtocol;
+    /** The network class for this object */
+    String netClass;
+
+    /**
+     * Returns the array of field names
+     */
+    public String[] getFields() {
+        return this.fields;
+    }
+
+
+    /**
+     * Registers a new object.
+     * @param netClass The net class for this object
+     * @param wireProtocol The object's wire protocol
+     * @param fields An array of field names.  For objects whose
+     * wire protocol is ARRAY, the positions of the field names 
+     * will be used as the array indices for the fields at serialization time
+     */
+    public static OSRFRegistry registerObject(String netClass, WireProtocol wireProtocol, String fields[]) {
+        OSRFRegistry r = new OSRFRegistry(netClass, wireProtocol, fields);
+        registry.put(netClass, r);
+        return r;
+    }
+
+    /**
+     * Returns the registry for the given netclass
+     * @param netClass The network class to lookup
+     */
+    public static OSRFRegistry getRegistry(String netClass) {
+        if( netClass == null ) return null;
+        return (OSRFRegistry) registry.get(netClass);
+    }
+
+
+    /**
+     * @param field The name of the field to lookup
+     * @return the index into the fields array of the given field name.
+     */
+    public int getFieldIndex(String field) {
+        for( int i = 0; i < fields.length; i++ )
+            if( fields[i].equals(field) ) 
+                return i;
+        return -1;
+    }
+
+    /** Returns the wire protocol of this object */
+    public WireProtocol getWireProtocol() {
+        return this.wireProtocol;
+    }
+
+    /** Returns the netClass ("hint") of this object */
+    public String getNetClass() {
+        return this.netClass;
+    }
+
+    /**
+     * Creates a new registry object.
+     * @param netClass The network class/hint
+     * @param wireProtocol The wire protocol
+     * @param fields The array of field names.  For array-based objects,
+     * the fields array must be sorted in accordance with the sorting
+     * of the objects in the array.
+     */ 
+    public OSRFRegistry(String netClass, WireProtocol wireProtocol, String fields[]) {
+        this.netClass = netClass;
+        this.wireProtocol = wireProtocol;
+        this.fields = fields;
+    }
+}
+
+
diff --git a/trunk/src/java/org/opensrf/util/OSRFSerializable.java b/trunk/src/java/org/opensrf/util/OSRFSerializable.java
new file mode 100644 (file)
index 0000000..64b5d6f
--- /dev/null
@@ -0,0 +1,19 @@
+package org.opensrf.util;
+
+/**
+ * All network-serializable OpenSRF object must implement this interface.
+ */
+public interface OSRFSerializable {
+
+    /**
+     * Returns the object registry object for the implementing class.
+     */
+    public abstract OSRFRegistry getRegistry();
+
+    /**
+     * Returns the object found at the given field
+     */
+    public abstract Object get(String field);
+}
+
+
diff --git a/trunk/src/java/org/opensrf/util/SettingsClient.java b/trunk/src/java/org/opensrf/util/SettingsClient.java
new file mode 100644 (file)
index 0000000..71e570a
--- /dev/null
@@ -0,0 +1,53 @@
+package org.opensrf.util;
+import org.opensrf.*;
+import java.util.Map;
+
+/**
+ * Connects to the OpenSRF Settings server to fetch the settings config.  
+ * Provides a Config interface for fetching settings via path
+ */
+public class SettingsClient extends Config {
+
+    /** Singleton SettingsClient instance */
+    private static SettingsClient client = new SettingsClient();
+
+    public SettingsClient() {
+        super("");
+    }
+
+    /**
+     * @return The global settings client instance 
+     */
+    public static SettingsClient instance() throws ConfigException {
+        if(client.getConfigObject() == null) 
+            client.fetchConfig();
+        return client;
+    }
+
+    /**
+     * Fetches the settings object from the settings server
+     */
+    private void fetchConfig() throws ConfigException {
+
+        ClientSession ses = new ClientSession("opensrf.settings");
+        try {
+
+            Request req = ses.request(
+                "opensrf.settings.host_config.get", 
+                new String[]{(String)Config.global().getFirst("/domain")});
+    
+            Result res = req.recv(12000);
+            if(res == null) {
+                /** throw exception */
+            }
+            setConfigObject((Map) res.getContent());
+
+        } catch(Exception e) {
+            throw new ConfigException("Error fetching settings config", e);
+
+        } finally {
+            ses.cleanup();
+        }
+    }
+}
+
diff --git a/trunk/src/java/org/opensrf/util/Utils.java b/trunk/src/java/org/opensrf/util/Utils.java
new file mode 100644 (file)
index 0000000..159d254
--- /dev/null
@@ -0,0 +1,106 @@
+package org.opensrf.util;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Collection of general, static utility methods
+ */
+public class Utils {
+
+    /**
+     * Returns the string representation of a given file.
+     * @param filename The file to turn into a string
+     */
+    public static String fileToString(String filename) 
+            throws FileNotFoundException, IOException {
+
+        StringBuffer sb = new StringBuffer();
+        BufferedReader in = new BufferedReader(new FileReader(filename));
+        String str;
+        while ((str = in.readLine()) != null) 
+            sb.append(str);
+        in.close();
+        return sb.toString();
+    }
+
+
+    /**
+     * Escapes a string.
+     */
+    public static String escape(String string) {
+        StringBuffer sb = new StringBuffer();
+        escape(string, sb);
+        return sb.toString();
+    }
+
+    /**
+     * Escapes a string.  Turns bare newlines into \n, etc.
+     * Escapes \n, \r, \t, ", \f
+     * Encodes non-ascii characters as UTF-8: \u0000
+     * @param string The string to escape
+     * @param sb The string buffer to write the escaped string into
+     */
+    public static void escape(String string, StringBuffer sb) {
+        int len = string.length();
+        String utf;
+        char c;
+        for( int i = 0; i < len; i++ ) {
+            c = string.charAt(i);
+            switch (c) {
+                case '\\':
+                    sb.append("\\\\");
+                    break;
+                case '"':
+                    sb.append("\\\"");
+                    break;
+                case '\b':
+                    sb.append("\\b");
+                    break;
+                case '\t':
+                    sb.append("\\t");
+                    break;
+                case '\n':
+                    sb.append("\\n");
+                    break;
+                case '\f':
+                    sb.append("\\f");
+                    break;
+                case '\r':
+                    sb.append("\\r");
+                    break;
+                default:
+                    if (c < 32 || c > 126 ) { 
+                        /* escape all non-ascii or control characters as UTF-8 */
+                        utf = "000" + Integer.toHexString(c);
+                        sb.append("\\u" + utf.substring(utf.length() - 4));
+                    } else {
+                        sb.append(c);
+                    }
+            }
+        }
+    }
+
+
+    /** 
+     * Descends into the map along the given XPATH-style path 
+     * and returns the object found there.
+     * @param path The XPATH-style path to search.  Path 
+     * components are separated by '/' characters.  
+     * Example:  /opensrf/loglevel
+     * @return The found object. 
+     */
+
+    public static Object findPath(Map map, String path) {
+        String keys[] = path.split("/", -1);
+        int i = 0;
+        if(path.charAt(0) == '/') i++;
+        for(; i < keys.length - 1; i++ ) 
+            map = (Map) map.get(keys[i]);
+
+        return map.get(keys[i]);
+    }
+}
+
+
+
diff --git a/trunk/src/java/org/opensrf/util/XMLFlattener.java b/trunk/src/java/org/opensrf/util/XMLFlattener.java
new file mode 100644 (file)
index 0000000..5a8561d
--- /dev/null
@@ -0,0 +1,131 @@
+package org.opensrf.util;
+
+import javax.xml.stream.*;
+import javax.xml.stream.events.* ;
+import javax.xml.namespace.QName;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.io.InputStream;
+import org.opensrf.util.JSONWriter;
+import org.opensrf.util.JSONReader;
+
+import com.ctc.wstx.stax.WstxInputFactory;
+
+/**
+ * Flattens an XML file into a properties map.  Values are stored as JSON strings or arrays.
+ * An array is created if more than one value resides at the same key.
+ * e.g. html.head.script = "alert('hello');"
+ */
+public class XMLFlattener {
+
+    /** Flattened properties map */
+    private Map<String, String> props;
+    /** Incoming XML stream */
+    private InputStream inStream;
+    /** Runtime list of encountered elements */
+    private List<String> elementList;
+
+    /**
+     * Creates a new reader. Initializes the message queue.
+     * Sets the stream state to disconnected, and the xml
+     * state to in_nothing.
+     * @param inStream the inbound XML stream
+     */
+    public XMLFlattener(InputStream inStream) {
+        props = new HashMap<String, String>();
+        this.inStream = inStream;
+        elementList = new ArrayList<String>();
+    }
+
+    /** Turns an array of strings into a dot-separated key string */
+    private String listToString() {
+        ListIterator itr = elementList.listIterator();
+        StringBuffer sb = new StringBuffer();
+        while(itr.hasNext()) {
+            sb.append(itr.next());
+            if(itr.hasNext())
+                sb.append(".");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Parses XML data from the provided stream.
+     */
+    public Map read() throws javax.xml.stream.XMLStreamException {
+
+        //XMLInputFactory factory = XMLInputFactory.newInstance();
+        XMLInputFactory factory = new com.ctc.wstx.stax.WstxInputFactory();
+
+        /** disable as many unused features as possible to speed up the parsing */
+        factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
+        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
+        factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
+        factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE);
+        factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+
+        /** create the stream reader */
+        XMLStreamReader reader = factory.createXMLStreamReader(inStream);
+        int eventType;
+
+        while(reader.hasNext()) {
+            /** cycle through the XML events */
+
+            eventType = reader.next();
+            if(reader.isWhiteSpace()) continue;
+
+            switch(eventType) {
+
+                case XMLEvent.START_ELEMENT:
+                    elementList.add(reader.getName().toString());
+                    break;
+
+                case XMLEvent.CHARACTERS:
+                    String text = reader.getText();
+                    String key = listToString();
+
+                    if(props.containsKey(key)) {
+
+                        /* something in the map already has this key */
+
+                        Object o = null;
+                        try {
+                            o = new JSONReader(props.get(key)).read();
+                        } catch(org.opensrf.util.JSONException e){}
+
+                        if(o instanceof List) {
+                            /* if the map contains a list, append to the list and re-encode */
+                            ((List) o).add(text);
+
+                        } else {
+                            /* if the map just contains a string, start building a new list
+                             * with the old string and append the new string */
+                            List<String> arr = new ArrayList<String>();
+                            arr.add((String) o);
+                            arr.add(text);
+                            o = arr;
+                        }
+
+                        props.put(key, new JSONWriter(o).write());
+
+                    } else {
+                        props.put(key, new JSONWriter(text).write());
+                    }
+                    break;
+
+                case XMLEvent.END_ELEMENT: 
+                    elementList.remove(elementList.size()-1);
+                    break;
+            }
+        }
+
+        return props;
+    }
+}
+
+
+
+
diff --git a/trunk/src/java/org/opensrf/util/XMLTransformer.java b/trunk/src/java/org/opensrf/util/XMLTransformer.java
new file mode 100644 (file)
index 0000000..f8bc0d3
--- /dev/null
@@ -0,0 +1,59 @@
+package org.opensrf.util;
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+import javax.xml.parsers.*;
+import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+
+
+/**
+ * Performs XSL transformations.  
+ * TODO: Add ability to pass in XSL variables
+ */
+public class XMLTransformer {
+
+    /** The XML to transform */
+    private Source xmlSource;
+    /** The stylesheet to apply */
+    private Source xslSource;
+
+    public XMLTransformer(Source xmlSource, Source xslSource) {
+        this.xmlSource = xmlSource;
+        this.xslSource = xslSource;
+    }
+
+    public XMLTransformer(String xmlString, File xslFile) {
+        this(
+            new StreamSource(new ByteArrayInputStream(xmlString.getBytes())),
+            new StreamSource(xslFile));
+    }
+
+    public XMLTransformer(File xmlFile, File xslFile) {
+        this(
+            new StreamSource(xmlFile),
+            new StreamSource(xslFile));
+    }
+
+    /** 
+     * Applies the transformation and puts the result into the provided output stream
+     */
+    public void apply(OutputStream outStream) throws TransformerException, TransformerConfigurationException {
+        Result result = new StreamResult(outStream);
+        Transformer trans = TransformerFactory.newInstance().newTransformer(xslSource);
+        trans.transform(xmlSource, result);
+    }
+
+    /**
+     * Applies the transformation and return the resulting string
+     * @return The String created by the XSL transformation
+     */
+    public String apply() throws TransformerException, TransformerConfigurationException {
+        OutputStream outStream = new ByteArrayOutputStream();
+        this.apply(outStream);
+        return outStream.toString();
+    }
+}
+
+
diff --git a/trunk/src/javascript/DojoSRF.js b/trunk/src/javascript/DojoSRF.js
new file mode 100644 (file)
index 0000000..6b52594
--- /dev/null
@@ -0,0 +1,24 @@
+if(!dojo._hasResource['DojoSRF']){
+
+       dojo._hasResource['DojoSRF'] = true;
+       dojo.provide('DojoSRF');
+
+       // Note: this file was renamed from OpenSRF.js to DojoSRF.js,
+       // but still provides resources with the OpenSRF namespace
+       dojo.require('opensrf.md5', true);
+       dojo.require('opensrf.JSON_v1', true);
+       dojo.require('opensrf.opensrf', true);
+       dojo.require('opensrf.opensrf_xhr', true);
+
+       OpenSRF.session_cache = {};
+       OpenSRF.CachedClientSession = function ( app ) {
+               if (this.session_cache[app]) return this.session_cache[app];
+               this.session_cache[app] = new OpenSRF.ClientSession ( app );
+               return this.session_cache[app];
+       }
+
+       OpenSRF.locale = dojo.config.locale;
+       if (!OpenSRF.locale) {
+               OpenSRF.locale = dojo.isIE ? navigator.userLanguage : navigator.language;
+       }
+}
diff --git a/trunk/src/javascript/JSON_v0.js b/trunk/src/javascript/JSON_v0.js
new file mode 100644 (file)
index 0000000..e52a439
--- /dev/null
@@ -0,0 +1,135 @@
+// in case we run on an implimentation that doesn't have "undefined";
+var undefined;
+
+function Cast (obj, class_constructor) {
+       try {
+               if (eval(class_constructor + '["_isfieldmapper"]')) {
+                       obj = eval("new " + class_constructor + "(obj)");
+               }
+       } catch( E ) {
+               alert( E + "\n");
+       } finally {
+               return obj;
+       }
+}
+
+function JSON2js (json) {
+
+       json = String(json).replace( /\/\*--\s*S\w*?\s*?\s+\w+\s*--\*\//g, 'Cast(');
+       json = String(json).replace( /\/\*--\s*E\w*?\s*?\s+(\w+)\s*--\*\//g, ', "$1")');
+
+       var obj;
+       if (json != '') {
+               try {
+                       eval( 'obj = ' + json );
+               } catch(E) {
+                       debug("Error building JSON object with string " + E + "\nString:\n" + json );
+                       return null;
+               }
+       }
+       return obj;
+}
+
+
+function object2Array(obj) {
+       if( obj == null ) return null;
+
+       var arr = new Array();
+       for( var i  = 0; i < obj.length; i++ ) {
+               arr[i] = obj[i];
+       }
+       return arr;
+}
+
+
+function js2JSON(arg) {
+       return _js2JSON(arg);
+}
+
+function _js2JSON(arg) {
+       var i, o, u, v;
+
+               switch (typeof arg) {
+                       case 'object':
+       
+                               if(arg) {
+       
+                                       if (arg._isfieldmapper) { /* magi-c-ast for fieldmapper objects */
+       
+                                               if( arg.a.constructor != Array ) {
+                                                       var arr = new Array();
+                                                       for( var i  = 0; i < arg.a.length; i++ ) {
+                                                               if( arg.a[i] == null ) {
+                                                                       arr[i] = null; continue;
+                                                               }
+       
+                                                               if( typeof arg.a[i] != 'object' ) { 
+                                                                       arr[i] = arg.a[i];
+       
+                                                               } else if( typeof arg.a[i] == 'object' 
+                                                                                       && arg.a[i]._isfieldmapper) {
+       
+                                                                       arr[i] = arg.a[i];
+       
+                                                               } else {
+                                                                       arr[i] = object2Array(arg.a[i]);                
+                                                               }
+                                                       }
+                                                       arg.a = arr;
+                                               }
+       
+                                               return "/*--S " + arg.classname + " --*/" + js2JSON(arg.a) + "/*--E " + arg.classname + " --*/";
+       
+                                       } else {
+       
+                                               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;
+                                                               }
+                                                       }
+       
+                                                       o = '{' + o + '}';
+                                                       return o;
+       
+                                               } else {
+                                                       return;
+                                               }
+                                       }
+                               }
+                               return 'null';
+       
+                       case 'unknown':
+                       case 'number':
+                               return arg;
+       
+                       case 'undefined':
+                       case 'function':
+                               return u;
+       
+                       case 'string':
+                       default:
+                               return '"' + String(arg).replace(/(["\\])/g, '\\$1') + '"';
+               }
+
+}
diff --git a/trunk/src/javascript/JSON_v1.js b/trunk/src/javascript/JSON_v1.js
new file mode 100644 (file)
index 0000000..7be5639
--- /dev/null
@@ -0,0 +1,209 @@
+var JSON_CLASS_KEY     = '__c';
+var JSON_DATA_KEY      = '__p';
+
+
+
+function JSON_version() { return 'wrapper' }
+
+function JSON2js(text) {
+       return decodeJS(JSON2jsRaw(text));
+}
+
+function JSON2jsRaw(text) {
+       var obj;
+       eval('obj = ' + text);
+       return obj;
+}
+
+
+/* iterates over object, arrays, or fieldmapper objects */
+function jsIterate( arg, callback ) {
+       if( arg && typeof arg == 'object' ) {
+               if( arg.constructor == Array ) {
+                       for( var i = 0; i < arg.length; i++ ) 
+                               callback(arg, i);
+
+               }  else if( arg.constructor == Object ) {
+                               for( var i in arg ) 
+                                       callback(arg, i);
+
+               } else if( arg._isfieldmapper && arg.a ) {
+                       for( var i = 0; i < arg.a.length; i++ ) 
+                               callback(arg.a, i);
+               }
+       }
+}
+
+
+/* removes the class/paylod wrapper objects */
+function decodeJS(arg) {
+
+       if(arg == null) return null;
+
+       if(     arg && typeof arg == 'object' &&
+                       arg.constructor == Object &&
+                       arg[JSON_CLASS_KEY] ) {
+               eval('arg = new ' + arg[JSON_CLASS_KEY] + '(arg[JSON_DATA_KEY])');      
+       }
+
+    if(arg._encodehash) {
+           jsIterate( arg.hash, 
+                   function(o, i) {
+                           o[i] = decodeJS(o[i]);
+                   }
+           );
+    } else {
+           jsIterate( arg, 
+                   function(o, i) {
+                           o[i] = decodeJS(o[i]);
+                   }
+           );
+    }
+
+       return arg;
+}
+
+
+function jsClone(obj) {
+       if( obj == null ) return null;
+       if( typeof obj != 'object' ) return obj;
+
+       var newobj;
+       if (obj.constructor == Array) {
+               newobj = [];
+               for( var i = 0; i < obj.length; i++ ) 
+                       newobj[i] = jsClone(obj[i]);
+
+       } else if( obj.constructor == Object ) {
+               newobj = {};
+               for( var i in obj )
+                       newobj[i] = jsClone(obj[i]);
+
+       } else if( obj._isfieldmapper && obj.a ) {
+               eval('newobj = new '+obj.classname + '();');
+               for( var i = 0; i < obj.a.length; i++ ) 
+                       newobj.a[i] = jsClone(obj.a[i]);
+       }
+
+       return newobj;
+}
+       
+
+/* adds the class/paylod wrapper objects */
+function encodeJS(arg) {
+       if( arg == null ) return null;  
+       if( typeof arg != 'object' ) return arg;
+
+       if( arg._isfieldmapper ) {
+      var newarr = []
+      if(!arg.a) arg.a = [];
+               for( var i = 0; i < arg.a.length; i++ ) 
+                       newarr[i] = encodeJS(arg.a[i]);
+
+               var a = {};
+               a[JSON_CLASS_KEY] = arg.classname;
+               a[JSON_DATA_KEY] = newarr;
+      return a;
+       }
+
+       var newobj;
+
+       if(arg.length != undefined) {
+               newobj = [];
+               for( var i = 0; i < arg.length; i++ ) 
+         newobj.push(encodeJS(arg[i]));
+      return newobj;
+       } 
+   
+       newobj = {};
+       for( var i in arg )
+               newobj[i] = encodeJS(arg[i]);
+       return newobj;
+}
+
+/* turns a javascript object into a JSON string */
+function js2JSON(arg) {
+       return js2JSONRaw(encodeJS(arg));
+}
+
+function js2JSONRaw(arg) {
+
+       if( arg == null ) 
+               return 'null';
+
+       var o;
+
+       switch (typeof arg) {
+
+               case 'object':
+
+                       if (arg.constructor == Array) {
+                               o = '';
+                               jsIterate( arg,
+                                       function(obj, i) {
+                                               if (o) o += ',';
+                                               o += js2JSONRaw(obj[i]);
+                                       }
+                               );
+                               return '[' + o + ']';
+
+                       } else if (typeof arg.toString != 'undefined') {
+                               o = '';
+                               jsIterate( arg,
+                                       function(obj, i) {
+                                               if (o) o += ',';
+                                               o = o + js2JSONRaw(i) + ':' + js2JSONRaw(obj[i]);
+                                       }
+                               );
+                               return '{' + o + '}';
+
+                       } else {
+                               return 'null';
+                       }
+
+               case 'number': return arg;
+
+               case 'string':
+                       var s = String(arg);
+                       s = s.replace(/\\/g, '\\\\');
+                       s = s.replace(/"/g, '\\"');
+                       s = s.replace(/\t/g, "\\t");
+                       s = s.replace(/\n/g, "\\n");
+                       s = s.replace(/\r/g, "\\r");
+                       s = s.replace(/\f/g, "\\f");
+                       return '"' + s + '"';
+
+               default: return 'null';
+       }
+}
+
+
+function __tabs(c) { 
+       var s = ''; 
+       for( i = 0; i < c; i++ ) s += '\t';
+       return s;
+}
+
+function jsonPretty(str) {
+       if(!str) return "";
+       var s = '';
+       var d = 0;
+       for( var i = 0; i < str.length; i++ ) {
+               var c = str.charAt(i);
+               if( c == '{' || c == '[' ) {
+                       s += c + '\n' + __tabs(++d);
+               } else if( c == '}' || c == ']' ) {
+                       s += '\n' + __tabs(--d) + '\n';
+                       if( str.charAt(i+1) == ',' ) {
+                               s += '\n' + __tabs(d);
+                       }
+               } else if( c == ',' ) {
+                       s += ',\n' + __tabs(d);
+               } else {
+                       s += c;
+               }
+       }
+       return s;
+}
+
+
diff --git a/trunk/src/javascript/md5.js b/trunk/src/javascript/md5.js
new file mode 100644 (file)
index 0000000..46d2aab
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}
diff --git a/trunk/src/javascript/opensrf.js b/trunk/src/javascript/opensrf.js
new file mode 100644 (file)
index 0000000..43632c1
--- /dev/null
@@ -0,0 +1,396 @@
+/* -----------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *  
+ * 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.
+ * ----------------------------------------------------------------------- */
+
+/* session states */
+var OSRF_APP_SESSION_CONNECTED = 0;
+var OSRF_APP_SESSION_CONNECTING = 1;
+var OSRF_APP_SESSION_DISCONNECTED = 2;
+
+/* types of transport layers */
+var OSRF_TRANSPORT_TYPE_XHR = 1;
+var OSRF_TRANSPORT_TYPE_XMPP = 2;
+
+/* message types */
+var OSRF_MESSAGE_TYPE_REQUEST = 'REQUEST';
+var OSRF_MESSAGE_TYPE_STATUS = 'STATUS';
+var OSRF_MESSAGE_TYPE_RESULT = 'RESULT';
+var OSRF_MESSAGE_TYPE_CONNECT = 'CONNECT';
+var OSRF_MESSAGE_TYPE_DISCONNECT = 'DISCONNECT';
+
+/* message statuses */
+var OSRF_STATUS_CONTINUE = 100;
+var OSRF_STATUS_OK = 200;
+var OSRF_STATUS_ACCEPTED = 202;
+var OSRF_STATUS_COMPLETE = 205;
+var OSRF_STATUS_REDIRECTED = 307;
+var OSRF_STATUS_BADREQUEST = 400;
+var OSRF_STATUS_UNAUTHORIZED = 401;
+var OSRF_STATUS_FORBIDDEN = 403;
+var OSRF_STATUS_NOTFOUND = 404;
+var OSRF_STATUS_NOTALLOWED = 405;
+var OSRF_STATUS_TIMEOUT = 408;
+var OSRF_STATUS_EXPFAILED = 417;
+var OSRF_STATUS_INTERNALSERVERERROR = 500;
+var OSRF_STATUS_NOTIMPLEMENTED = 501;
+var OSRF_STATUS_VERSIONNOTSUPPORTED = 505;
+
+var OpenSRF = {};
+OpenSRF.locale = null;
+
+/* makes cls a subclass of pcls */
+OpenSRF.set_subclass = function(cls, pcls) {
+    var str = cls+'.prototype = new '+pcls+'();';
+    str += cls+'.prototype.constructor = '+cls+';';
+    str += cls+'.baseClass = '+pcls+'.prototype.constructor;';
+    str += cls+'.prototype.super = '+pcls+'.prototype;';
+    eval(str);
+}
+
+
+/* general session superclass */
+OpenSRF.Session = function() {
+    this.remote_id = null;
+    this.state = OSRF_APP_SESSION_DISCONNECTED;
+}
+
+OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_XHR; /* default to XHR */
+OpenSRF.Session.cache = {};
+OpenSRF.Session.find_session = function(thread_trace) {
+    return OpenSRF.Session.cache[thread_trace];
+}
+OpenSRF.Session.prototype.cleanup = function() {
+    delete OpenSRF.Session.cache[this.thread];
+}
+
+OpenSRF.Session.prototype.send = function(osrf_msg, args) {
+    args = (args) ? args : {};
+    switch(OpenSRF.Session.transport) {
+        case OSRF_TRANSPORT_TYPE_XHR:
+            return this.send_xhr(osrf_msg, args);
+        case OSRF_TRANSPORT_TYPE_XMPP:
+            return this.send_xmpp(osrf_msg, args);
+    }
+}
+
+OpenSRF.Session.prototype.send_xhr = function(osrf_msg, args) {
+    args.thread = this.thread;
+    args.rcpt = this.remote_id;
+    args.rcpt_service = this.service;
+    new OpenSRF.XHRequest(osrf_msg, args).send();
+}
+
+OpenSRF.Session.prototype.send_xmpp = function(osrf_msg, args) {
+    alert('xmpp transport not yet implemented');
+}
+
+
+/* client sessions make requests */
+OpenSRF.ClientSession = function(service) {
+    this.service = service
+    this.remote_id = null;
+    this.locale = OpenSRF.locale || 'en-US';
+    this.last_id = 0;
+    this.thread = Math.random() + '' + new Date().getTime();
+    this.requests = [];
+    this.onconnect = null;
+    OpenSRF.Session.cache[this.thread] = this;
+}
+OpenSRF.set_subclass('OpenSRF.ClientSession', 'OpenSRF.Session');
+
+
+OpenSRF.ClientSession.prototype.connect = function(args) {
+    args = (args) ? args : {};
+
+    if(args.onconnect)
+        this.onconnect = args.onconnect;
+
+    /* if no handler is provided, make this a synchronous call */
+    if(!this.onconnect) 
+        this.timeout = (args.timeout) ? args.timeout : 5;
+
+    message = new osrfMessage({
+        'threadTrace' : this.reqid, 
+        'type' : OSRF_MESSAGE_TYPE_CONNECT,
+    });
+
+    this.send(message, {'timeout' : this.timeout});
+
+    if(this.onconnect || this.state == OSRF_APP_SESSION_CONNECTED)
+        return true;
+    return false;
+}
+
+OpenSRF.ClientSession.prototype.disconnect = function(args) {
+    this.send(
+        new osrfMessage({
+            'threadTrace' : this.reqid, 
+            'type' : OSRF_MESSAGE_TYPE_DISCONNECT,
+        })
+    );
+}
+
+
+OpenSRF.ClientSession.prototype.request = function(args) {
+
+    if(typeof args == 'string') { 
+        params = [];
+        for(var i = 1; i < arguments.length; i++)
+            params.push(arguments[i]);
+
+        args = {
+            method : args, 
+            params : params
+        };
+    } else {
+        if(typeof args == 'undefined')
+            args = {};
+    }
+
+    var req = new OpenSRF.Request(this, this.last_id++, args);
+    this.requests.push(req);
+    return req;
+}
+
+OpenSRF.ClientSession.prototype.find_request = function(reqid) {
+    for(var i = 0; i < this.requests.length; i++) {
+        var req = this.requests[i];
+        if(req.reqid == reqid)
+            return req;
+    }
+    return null;
+}
+
+OpenSRF.Request = function(session, reqid, args) {
+    this.session = session;
+    this.reqid = reqid;
+
+    /* callbacks */
+    this.onresponse = args.onresponse;
+    this.oncomplete = args.oncomplete;
+    this.onerror = args.onerror;
+    this.onmethoderror = args.onmethoderror;
+    this.ontransporterror = args.ontransporterror;
+
+    this.method = args.method;
+    this.params = args.params;
+    this.timeout = args.timeout;
+    this.response_queue = [];
+    this.complete = false;
+}
+
+OpenSRF.Request.prototype.recv = function(timeout) {
+    if(this.response_queue.length > 0)
+        return this.response_queue.shift();
+    return null;
+}
+
+OpenSRF.Request.prototype.send = function() {
+    method = new osrfMethod({'method':this.method, 'params':this.params});
+    message = new osrfMessage({
+        'threadTrace' : this.reqid, 
+        'type' : OSRF_MESSAGE_TYPE_REQUEST, 
+        'payload' : method, 
+        'locale' : this.session.locale
+    });
+
+    this.session.send(message, {
+        'timeout' : this.timeout,
+        'onresponse' : this.onresponse,
+        'oncomplete' : this.oncomplete,
+        'onerror' : this.onerror,
+        'onmethoderror' : this.onmethoderror,
+        'ontransporterror' : this.ontransporterror
+    });
+}
+
+OpenSRF.NetMessage = function(to, from, thread, body) {
+    this.to = to;
+    this.from = from;
+    this.thread = thread;
+    this.body = body;
+}
+
+OpenSRF.Stack = function() {
+}
+
+OpenSRF.Stack.push = function(net_msg, callbacks) {
+    var ses = OpenSRF.Session.find_session(net_msg.thread); 
+    if(!ses) return;
+    ses.remote_id = net_msg.sender;
+    osrf_msgs = JSON2js(net_msg.body);
+    for(var i = 0; i < osrf_msgs.length; i++) 
+        OpenSRF.Stack.handle_message(ses, osrf_msgs[i], callbacks);        
+}
+
+OpenSRF.Stack.handle_message = function(ses, osrf_msg, callbacks) {
+    
+    var req = null;
+
+    if(osrf_msg.type() == OSRF_MESSAGE_TYPE_STATUS) {
+
+        var payload = osrf_msg.payload();
+        var status = payload.statusCode();
+        var status_text = payload.status();
+
+        if(status == OSRF_STATUS_COMPLETE) {
+            req = ses.find_request(osrf_msg.threadTrace());
+            if(req) {
+                req.complete = true;
+                if(callbacks.oncomplete && !req.oncomplete_called) {
+                    req.oncomplete_called = true;
+                    return callbacks.oncomplete(req);
+                }
+            }
+        }
+
+        if(status == OSRF_STATUS_OK) {
+            ses.state = OSRF_APP_SESSION_CONNECTED;
+
+            /* call the connect callback */
+            if(ses.onconnect && !ses.onconnect_called) {
+                ses.onconnect_called = true;
+                return ses.onconnect();
+            }
+        }
+
+        if(status == OSRF_STATUS_NOTFOUND) {
+            req = ses.find_request(osrf_msg.threadTrace());
+            if(callbacks.onmethoderror) 
+                return callbacks.onmethoderror(req, status, status_text);
+        }
+    }
+
+    if(osrf_msg.type() == OSRF_MESSAGE_TYPE_RESULT) {
+        req = ses.find_request(osrf_msg.threadTrace());
+        if(req) {
+            req.response_queue.push(osrf_msg.payload());
+            if(callbacks.onresponse) 
+                return callbacks.onresponse(req);
+        }
+    }
+}
+
+/* The following classes map directly to network-serializable opensrf objects */
+
+function osrfMessage(hash) {
+    this.hash = hash;
+    this._encodehash = true;
+}
+osrfMessage.prototype.threadTrace = function(d) { 
+    if(arguments.length == 1) 
+        this.hash.threadTrace = d; 
+    return this.hash.threadTrace; 
+}
+osrfMessage.prototype.type = function(d) { 
+    if(arguments.length == 1) 
+        this.hash.type = d; 
+    return this.hash.type; 
+}
+osrfMessage.prototype.payload = function(d) { 
+    if(arguments.length == 1) 
+        this.hash.payload = d; 
+    return this.hash.payload; 
+}
+osrfMessage.prototype.locale = function(d) { 
+    if(arguments.length == 1) 
+        this.hash.locale = d; 
+    return this.hash.locale; 
+}
+osrfMessage.prototype.serialize = function() {
+    return {
+        "__c":"osrfMessage",
+        "__p": {
+            'threadTrace' : this.hash.threadTrace,
+            'type' : this.hash.type,
+            'payload' : (this.hash.payload) ? this.hash.payload.serialize() : 'null',
+            'locale' : this.hash.locale
+        }
+    };
+}
+
+function osrfMethod(hash) {
+    this.hash = hash;
+    this._encodehash = true;
+} 
+osrfMethod.prototype.method = function() {
+    if(arguments.length == 1) 
+        this.hash.method = d; 
+    return this.hash.method; 
+}
+osrfMethod.prototype.params = function() {
+    if(arguments.length == 1) 
+        this.hash.params = d; 
+    return this.hash.params; 
+}
+osrfMethod.prototype.serialize = function() {
+    return {
+        "__c":"osrfMethod",
+        "__p": {
+            'method' : this.hash.method,
+            'params' : this.hash.params
+        }
+    };
+}
+
+function osrfMethodException(hash) {
+    this.hash = hash;
+    this._encodehash = true;
+}
+osrfMethodException.prototype.status = function() {
+    if(arguments.length == 1) 
+        this.hash.status = d; 
+    return this.hash.status; 
+}
+osrfMethodException.prototype.statusCode = function() {
+    if(arguments.length == 1) 
+        this.hash.statusCode = d; 
+    return this.hash.statusCode; 
+}
+function osrfConnectStatus(hash) { 
+    this.hash = hash;
+    this._encodehash = true;
+}
+osrfConnectStatus.prototype.status = function() {
+    if(arguments.length == 1) 
+        this.hash.status = d; 
+    return this.hash.status; 
+}
+osrfConnectStatus.prototype.statusCode = function() {
+    if(arguments.length == 1) 
+        this.hash.statusCode = d; 
+    return this.hash.statusCode; 
+}
+function osrfResult(hash) {
+    this.hash = hash;
+    this._encodehash = true;
+}
+osrfResult.prototype.status = function() {
+    if(arguments.length == 1) 
+        this.hash.status = d; 
+    return this.hash.status; 
+}
+osrfResult.prototype.statusCode = function() {
+    if(arguments.length == 1) 
+        this.hash.statusCode = d; 
+    return this.hash.statusCode; 
+}
+osrfResult.prototype.content = function() {
+    if(arguments.length == 1) 
+        this.hash.content = d; 
+    return this.hash.content; 
+}
+
+
+
diff --git a/trunk/src/javascript/opensrf_xhr.js b/trunk/src/javascript/opensrf_xhr.js
new file mode 100644 (file)
index 0000000..76d8473
--- /dev/null
@@ -0,0 +1,123 @@
+/* -----------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *  
+ * 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.
+ * ----------------------------------------------------------------------- */
+
+var OSRF_HTTP_HEADER_TO = 'X-OpenSRF-to';
+var OSRF_HTTP_HEADER_XID = 'X-OpenSRF-xid';
+var OSRF_HTTP_HEADER_FROM = 'X-OpenSRF-from';
+var OSRF_HTTP_HEADER_THREAD = 'X-OpenSRF-thread';
+var OSRF_HTTP_HEADER_TIMEOUT = 'X-OpenSRF-timeout';
+var OSRF_HTTP_HEADER_SERVICE = 'X-OpenSRF-service';
+var OSRF_HTTP_HEADER_MULTIPART = 'X-OpenSRF-multipart';
+var OSRF_HTTP_TRANSLATOR = '/osrf-http-translator'; /* XXX config */
+var OSRF_POST_CONTENT_TYPE = 'application/x-www-form-urlencoded';
+
+
+OpenSRF.XHRequest = function(osrf_msg, args) {
+    this.message = osrf_msg;
+    this.args = args;
+    this.xreq = new XMLHttpRequest(); /* XXX browser check */
+}
+
+OpenSRF.XHRequest.prototype.send = function() {
+    var xhr_req = this;
+    var xreq = this.xreq
+    
+    if(this.args.timeout) {
+        /* this is a standard blocking (non-multipart) call */
+        xreq.open('POST', OSRF_HTTP_TRANSLATOR, false);
+
+    } else {
+
+        if(!navigator.userAgent.match(/mozilla/i)) {
+
+            /* standard asynchronous call */
+            xreq.onreadystatechange = function() {
+                if(xreq.readyState == 4)
+                    xhr_req.core_handler();
+            }
+            xreq.open('POST', OSRF_HTTP_TRANSLATOR, true);
+
+        } else {
+
+            /* asynchronous multipart call */
+            xreq.multipart = true;
+            xreq.onload = function(evt) {xhr_req.core_handler();}
+            xreq.open('POST', OSRF_HTTP_TRANSLATOR, true);
+            xreq.setRequestHeader(OSRF_HTTP_HEADER_MULTIPART, 'true');
+
+            /* multipart requests do not pass the status info to the onload if there 
+               is no new data to load.  Capture the status on the readystate handler */
+            xreq.onreadystatechange = function() {
+                if(xreq.readyState == 4 && xreq.status >= 400)
+                    xhr_req.transport_error_handler();
+            }
+        }
+    }
+
+    xreq.setRequestHeader('Content-Type', OSRF_POST_CONTENT_TYPE);
+    xreq.setRequestHeader(OSRF_HTTP_HEADER_THREAD, this.args.thread);
+    if(this.args.rcpt)
+        xreq.setRequestHeader(OSRF_HTTP_HEADER_TO, this.args.rcpt);
+    else
+        xreq.setRequestHeader(OSRF_HTTP_HEADER_SERVICE, this.args.rcpt_service);
+
+    var post = 'osrf-msg=' + encodeURIComponent(js2JSON([this.message.serialize()]));
+    xreq.send(post);
+
+    if(this.args.timeout) /* this was a blocking call, manually run the handler */
+        this.core_handler()
+
+    return this;
+}
+
+OpenSRF.XHRequest.prototype.core_handler = function() {
+    sender = this.xreq.getResponseHeader(OSRF_HTTP_HEADER_FROM);
+    thread = this.xreq.getResponseHeader(OSRF_HTTP_HEADER_THREAD);
+    json = this.xreq.responseText;
+    stat = this.xreq.status;
+
+    if(stat >= 400) 
+        return this.transport_error_handler();
+
+    OpenSRF.Stack.push(
+        new OpenSRF.NetMessage(null, sender, thread, json),
+        {
+            onresponse : this.args.onresponse,
+            oncomplete : this.args.oncomplete,
+            onerror : this.args.onerror,
+            onmethoderror : this.method_error_handler()
+        }
+    );
+}
+
+
+OpenSRF.XHRequest.prototype.method_error_handler = function() {
+    var xhr = this;
+    return function(req, status, status_text) {
+        if(xhr.args.onmethoderror) 
+            xhr.args.onmethoderror(req, status, status_text);
+        if(xhr.args.onerror)  
+            xhr.args.onerror(xhr.message, xhr.args.rcpt || xhr.args.rcpt_service, xhr.args.thread);
+    }
+}
+
+OpenSRF.XHRequest.prototype.transport_error_handler = function() {
+    if(this.args.ontransporterror) 
+        this.args.ontransporterror(this.xreq);
+    if(this.args.onerror) 
+        this.args.onerror(this.message, this.args.rcpt || this.args.rcpt_service, this.args.thread);
+}
+
+
diff --git a/trunk/src/javascript/opensrf_xmpp.js b/trunk/src/javascript/opensrf_xmpp.js
new file mode 100644 (file)
index 0000000..f72054b
--- /dev/null
@@ -0,0 +1,444 @@
+
+/**
+ * XXX
+ * XXX For reference only until this code is updated to match new opensrf.js layout XXX
+ * XXX
+ */
+
+
+
+// ------------------------------------------------------------------
+//             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 ) { 
+                       new Logger().transport(
+                               "before process data", Logger.DEBUG );
+
+                       this.process_data( this.instream.read( count ) );
+
+                       new Logger().transport(
+                               "after process data", Logger.DEBUG );
+
+                       count = this.instream.available();
+
+                       new Logger().transport(
+                               "received " + count + " bytes" , Logger.DEBUG );
+               }
+
+       } 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;
+
+       var first_pass = true;
+       while( ((now-then) <= timeout) ) {
+               
+               if( this.buffer.length == 0  || !first_pass ) {
+                       if( ! this.tcp_recv( timeout ) ) {
+                               return null;
+                       }
+               }
+               first_pass = false;
+
+               //new Logger().transport( "\n\nTCP Buffer Before: \n" + this.buffer, Logger.DEBUG );
+
+               var buf = this.buffer;
+               this.buffer = "";
+
+               new Logger().transport( "CURRENT BUFFER\n" + buf,
+                       Logger.DEBUG );
+
+               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;
+
+                       } else {
+                               this.buffer = buf;
+                       }
+
+               } 
+               now = new Date().getTime();
+       }
+
+       return null;
+}
+
+jabber_socket.prototype.process_iq_data = function( data ) {
+       new Logger().transport( "IQ Packet received... Not Implemented\n" + data, Logger.ERROR );
+}
+
+/** Disconnects from the jabber server and closes down shop */
+jabber_socket.prototype.disconnect = function() {
+       this.tcp_send( "</stream:stream>" );
+       this.instream.close();
+       this.outstream.close();
+}
+
+/** True if connected */
+jabber_socket.prototype.connected = function() {
+       return this.is_connected;
+}
+
+
+
+
+
diff --git a/trunk/src/jserver/Makefile.am b/trunk/src/jserver/Makefile.am
new file mode 100644 (file)
index 0000000..03eab79
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+LDADD = -lxml2 $(DEF_LDLIBS)
+AM_CFLAGS = $(DEF_CFLAGS) -D_GNU_SOURCE -L@top_builddir@/src/libopensrf
+AM_LDFLAGS = $(DEF_LDFLAGS)
+
+bin_PROGRAMS = chopchop
+chopchop_SOURCES = osrf_chat.c osrf_chat.h osrf_chat_main.c
+
diff --git a/trunk/src/jserver/osrf_chat.c b/trunk/src/jserver/osrf_chat.c
new file mode 100644 (file)
index 0000000..f09d636
--- /dev/null
@@ -0,0 +1,828 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+#include "osrf_chat.h"
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+static int osrfChatXMLErrorOcurred = 0;
+
+/* This is used by code in osrfChatPushData, but that code is
+   currently commented out.  Uncomment the next line if needed. */
+//static int osrfChatClientSentDisconnect = 0;
+
+/* shorter version of strcmp */
+static int eq(const char* a, const char* b) { return (a && b && !strcmp(a,b)); }
+//#define eq(a,b) ((a && b && !strcmp(a,b)) ? 1 : 0)
+
+/* gnarly debug function */
+static void chatdbg( osrfChatServer* server ) {
+
+       if(!server) return;
+       return; /* heavy logging, should only be used in heavy debug mode */
+
+       growing_buffer* buf = buffer_init(256);
+
+       buffer_add(buf, "---------------------------------------------------------------------\n");
+
+       buffer_fadd(buf, 
+               "ChopChop Debug:\n"
+               "Connections:           %lu\n"
+               "Named nodes in hash:   %lu\n"
+               "Domain:                %s\n"
+               "Port:                  %d\n"
+               "S2S Port:              %d\n"
+               "-------------------------------------------------------\n",
+               osrfListGetCount(server->nodeList), osrfHashGetCount(server->nodeHash),
+               server->domain, server->port, server->s2sport );
+
+       osrfListIterator* itr = osrfNewListIterator(server->nodeList);
+       osrfChatNode* node;
+
+       while( (node = osrfListIteratorNext(itr)) ) {
+
+               buffer_fadd( buf, 
+                       "sockid:    %d\n"
+                       "Remote:    %s\n"
+                       "State:     %d\n"
+                       "XMLState:  %d\n"
+                       "In Parse:  %d\n"
+                       "to:        %s\n"
+                       "Resource:  %s\n"
+                       "Username:  %s\n"
+                       "Domain:    %s\n"
+                       "Authkey:   %s\n"
+                       "type:          %d\n"
+                       "-------------------------------------------------------\n",
+                       node->sockid, node->remote, node->state, node->xmlstate, node->inparse,
+                       node->to, node->resource, node->username, node->domain, node->authkey, node->type );
+       }
+
+       osrfLogDebug( OSRF_LOG_MARK, "DEBUG:\n%s", buf->buf );
+       buffer_free(buf);
+       osrfListIteratorFree(itr);
+}
+
+osrfChatServer* osrfNewChatServer( char* domain, char* secret, int s2sport ) {
+       if(!(domain && secret)) return NULL;
+
+       osrfChatServer* server = safe_malloc(sizeof(osrfChatServer));
+
+       server->nodeHash        = osrfNewHash();
+       server->nodeList        = osrfNewList();
+       server->deadNodes = osrfNewList();
+       server->nodeList->freeItem = &osrfChatNodeFree;
+       server->domain          = strdup(domain);
+       server->secret      = strdup(secret);
+       server->s2sport = s2sport;
+       server->port        = 0;
+
+       // Build socket manager
+       server->mgr = safe_malloc(sizeof(socket_manager));
+       server->mgr->data_received = &osrfChatHandleData;
+       server->mgr->socket = NULL;
+       server->mgr->blob = server;
+       server->mgr->on_socket_closed = &osrfChatSocketClosed;
+
+       return server;
+}
+
+void osrfChatCleanupClients( osrfChatServer* server ) {
+       if(!server) return;
+       osrfListFree(server->deadNodes);
+       server->deadNodes = osrfNewList();
+}
+
+
+
+osrfChatNode* osrfNewChatNode( int sockid, char* domain ) {
+       if(sockid < 1 || !domain) return NULL;
+       osrfChatNode* node      = safe_malloc(sizeof(osrfChatNode));
+       node->sockid            = 0;
+       node->remote            = NULL;
+       node->state                             = OSRF_CHAT_STATE_NONE;
+       node->xmlstate          = 0;
+       node->inparse           = 0;
+       node->msgs                              = NULL; /* only s2s nodes cache messages */
+       node->parserCtx         = xmlCreatePushParserCtxt(osrfChatSaxHandler, node, "", 0, NULL);
+       node->msgDoc                    = xmlNewDoc(BAD_CAST "1.0");
+       node->domain = strdup(domain);
+       xmlKeepBlanksDefault(0);
+       node->authkey                   = NULL;
+       node->username                  = NULL;
+       node->resource                  = NULL;
+       node->to                                        = NULL;
+       node->type = 0;
+       node->parent            = NULL;
+       return node;
+}
+
+
+osrfChatNode* osrfNewChatS2SNode( char* domain, char* remote ) {
+       if(!(domain && remote)) return NULL;
+       osrfChatNode* n = osrfNewChatNode( 1, domain );
+       n->state                = OSRF_CHAT_STATE_S2S_CHALLENGE;
+       n->sockid       = -1;
+       n->remote       = strdup(remote);
+       n->msgs         = osrfNewList();
+       n->msgs->freeItem = &osrfChatS2SMessageFree;
+       n->type = 1;
+       return n;
+}
+
+void osrfChatS2SMessageFree(void* n) { free(n); }
+
+void osrfChatNodeFree( void* node ) {
+       if(!node) return;
+       osrfChatNode* n = (osrfChatNode*) node;
+
+       /* we can't free messages that are mid-parse because the
+               we can't free the parser context */
+       if(n->inparse) {
+               n->inparse = 0;
+               osrfListPush(n->parent->deadNodes, n);
+               return;
+       }
+
+       free(n->remote);
+       free(n->to);
+       free(n->username);
+       free(n->resource);
+       free(n->domain);
+       free(n->authkey);
+
+       osrfListFree(n->msgs);
+
+       if(n->parserCtx) {
+               xmlFreeDoc(n->parserCtx->myDoc);
+               xmlFreeParserCtxt(n->parserCtx);
+       }
+
+       xmlFreeDoc(n->msgDoc);
+       free(n);
+}
+
+
+
+int osrfChatServerConnect( osrfChatServer* cs,  int port, int s2sport, char* listenAddr ) {
+       if(!(cs && port && listenAddr)) return -1;
+       cs->port = port;
+       cs->s2sport = s2sport;
+       if( socket_open_tcp_server(cs->mgr, port, listenAddr ) < 0 )
+               return -1;
+       if( socket_open_tcp_server(cs->mgr, s2sport, listenAddr ) < 0 )
+               return -1;
+       return 0;
+}
+
+
+int osrfChatServerWait( osrfChatServer* server ) {
+       if(!server) return -1;
+       while(1) {
+               if(socket_wait_all(server->mgr, -1) < 0)
+                       osrfLogWarning( OSRF_LOG_MARK,  "jserver_wait(): socket_wait_all() returned error");
+       }
+       return -1;
+}
+
+
+void osrfChatServerFree(osrfChatServer* server ) {
+       if(!server) return;
+       osrfHashFree(server->nodeHash);
+       osrfListFree(server->nodeList);
+       osrfListFree(server->deadNodes);
+       socket_manager_free(server->mgr);
+       free(server->domain);
+       free(server->secret);
+
+       free(server);
+}
+
+
+void osrfChatHandleData( void* cs, 
+       socket_manager* mgr, int sockid, char* data, int parent_id ) {
+
+       if(!(cs && mgr && sockid && data)) return;
+
+       osrfChatServer* server = (osrfChatServer*) cs;
+
+       osrfChatNode* node = osrfListGetIndex( server->nodeList, sockid );
+
+       if(node)
+               osrfLogDebug( OSRF_LOG_MARK, "Found node for sockid %d with state %d", sockid, node->state);
+
+       if(!node) {
+               osrfLogDebug( OSRF_LOG_MARK, "Adding new connection for sockid %d", sockid );
+               node = osrfChatAddNode( server, sockid );
+       }
+
+       if(node) {
+               if( (osrfChatPushData( server, node, data ) == -1) ) {
+                       osrfLogError( OSRF_LOG_MARK, 
+                                       "Node at socket %d with remote address %s and destination %s, "
+                                       "received bad XML [%s], disconnecting...", sockid, node->remote, node->to, data );
+                       osrfChatSendRaw(  node, OSRF_CHAT_PARSE_ERROR );
+                       osrfChatRemoveNode( server, node );
+               }
+       }
+
+       osrfChatCleanupClients(server); /* clean up old dead clients */
+}
+
+
+void osrfChatSocketClosed( void* blob, int sockid ) {
+       if(!blob) return;
+       osrfChatServer* server = (osrfChatServer*) blob;
+       osrfChatNode* node = osrfListGetIndex(server->nodeList, sockid);
+       osrfChatRemoveNode( server, node );
+}
+
+osrfChatNode* osrfChatAddNode( osrfChatServer* server, int sockid ) {
+       if(!(server && sockid)) return NULL;
+       osrfChatNode* node = osrfNewChatNode(sockid, server->domain);
+       node->parent = server;
+       node->sockid = sockid;
+       osrfListSet( server->nodeList, node, sockid );
+       return node;
+}
+
+void osrfChatRemoveNode( osrfChatServer* server, osrfChatNode* node ) {
+       if(!(server && node)) return;
+       socket_disconnect(server->mgr, node->sockid);
+       if(node->remote) 
+               osrfHashRemove( server->nodeHash, node->remote );
+       osrfListRemove( server->nodeList, node->sockid ); /* this will free it */
+}
+
+int osrfChatSendRaw( osrfChatNode* node, char* msgXML ) {
+       if(!(node && msgXML)) return -1;
+       /* wait at most 3 second for this client to take our data */
+       return socket_send_timeout( node->sockid, msgXML, 3000000 ); 
+}
+
+void osrfChatNodeFinish( osrfChatServer* server, osrfChatNode* node ) {
+       if(!(server && node)) return;
+       osrfChatSendRaw( node, "</stream:stream>");
+       osrfChatRemoveNode( server, node );
+}
+
+
+int osrfChatSend( osrfChatServer* cs, osrfChatNode* node, char* toAddr, char* fromAddr, char* msgXML ) {
+       if(!(cs && node && toAddr && msgXML)) return -1;
+
+       int l = strlen(toAddr);
+       char dombuf[l];
+       memset(dombuf, 0, sizeof(dombuf));
+       jid_get_domain( toAddr, dombuf, l );    
+
+       if( eq( dombuf, cs->domain ) ) { /* this is to a user we host */
+
+               osrfLogInfo( OSRF_LOG_MARK, "Sending message on local connection\nfrom: %s\nto: %s", fromAddr, toAddr );
+               osrfChatNode* tonode = osrfHashGet(cs->nodeHash, toAddr);
+               if(tonode) {
+
+                       /* if we can't send to the recipient (recipient is gone or too busy, 
+                        * we drop the recipient and inform the sender that the recipient
+                        * is no more */
+                       if( osrfChatSendRaw( tonode, msgXML ) < 0 ) {
+
+                               osrfChatRemoveNode( cs, tonode );
+                               char* xml = va_list_to_string( OSRF_CHAT_NO_RECIPIENT, toAddr, fromAddr );
+
+                               osrfLogError( OSRF_LOG_MARK, "Node failed to function. "
+                                               "Responding to caller with error: %s", toAddr);
+
+
+                               if( osrfChatSendRaw( node, xml ) < 0 ) {
+                                       osrfLogError(OSRF_LOG_MARK, "Sending node is now gone..removing");
+                                       osrfChatRemoveNode( cs, node );
+                               }
+                               free(xml);
+                       }
+
+               } else {
+
+                       /* send an error message saying we don't have this connection */
+                       osrfLogInfo( OSRF_LOG_MARK, "We have no connection for %s", toAddr);
+                       char* xml = va_list_to_string( OSRF_CHAT_NO_RECIPIENT, toAddr, fromAddr );
+                       if( osrfChatSendRaw( node, xml ) < 0 ) 
+                               osrfChatRemoveNode( cs, node );
+                       free(xml);
+               }
+
+       } else {
+
+               osrfChatNode* tonode = osrfHashGet(cs->nodeHash, dombuf);
+               if(tonode) {
+                       if( tonode->state == OSRF_CHAT_STATE_CONNECTED ) {
+                               osrfLogDebug( OSRF_LOG_MARK, "Routing message to server %s", dombuf);
+
+                               if( osrfChatSendRaw( tonode, msgXML ) < 0 ) {
+                                       osrfLogError( OSRF_LOG_MARK, "Node failed to function: %s", toAddr);
+                                       char* xml = va_list_to_string( OSRF_CHAT_NO_RECIPIENT, toAddr, fromAddr );
+                                       if( osrfChatSendRaw( node, xml ) < 0 ) 
+                                               osrfChatRemoveNode( cs, node );
+                                       free(xml);
+                                       osrfChatRemoveNode( cs, tonode );
+                               }
+
+                       } else {
+                               osrfLogInfo( OSRF_LOG_MARK, "Received s2s message and we're still trying to connect...caching");
+                               osrfListPush( tonode->msgs, strdup(msgXML) );
+                       }
+
+               } else {
+
+                       if( osrfChatInitS2S( cs, dombuf, toAddr, msgXML ) != 0 ) {
+                               osrfLogWarning( OSRF_LOG_MARK, "We are unable to connect to remote server %s for recipient %s", dombuf, toAddr);
+                               char* xml = va_list_to_string( OSRF_CHAT_NO_RECIPIENT, toAddr, fromAddr );
+                               osrfChatSendRaw( node, xml );
+                               free(xml);
+                       }
+               }
+       }
+
+       return 0;
+}
+
+
+/*
+void osrfChatCacheS2SMessage( char* toAddr, char* msgXML, osrfChatNode* snode ) {
+       if(!(toAddr && msgXML)) return;
+       osrfChatS2SMessage* msg = safe_malloc(sizeof(osrfChatS2SMessage));
+       msg->toAddr = strdup(toAddr);
+       msg->msgXML = strdup(msgXML);
+       osrfLogInfo( OSRF_LOG_MARK, "Pushing client message onto s2s queue waiting for connect... ");
+       osrfListPush( snode->msgs, msgXML );
+}
+*/
+
+
+int osrfChatInitS2S( osrfChatServer* cs, char* remote, char* toAddr, char* msgXML ) {
+       if(!(cs && remote && toAddr && msgXML)) return -1;
+
+       osrfLogInfo( OSRF_LOG_MARK, "Initing server2server connection to domain %s", remote );
+       osrfChatNode* snode = osrfNewChatS2SNode( cs->domain, remote );
+       snode->parent = cs;
+
+       /* try to connect to the remote site */
+       snode->sockid = socket_open_tcp_client(cs->mgr, cs->s2sport, remote);
+       if(snode->sockid < 1) {
+               osrfLogWarning( OSRF_LOG_MARK, "Unable to connect to remote server at %s", remote );
+               osrfChatNodeFree( snode );
+               return -1;
+       }
+
+       /* store the message we were supposed to deliver until we're fully connected */
+       //osrfChatCacheS2SMessage( toAddr, msgXML, snode );
+       osrfListPush( snode->msgs, strdup(msgXML) );
+       osrfHashSet(cs->nodeHash, snode, remote );
+       osrfListSet(cs->nodeList, snode, snode->sockid );
+
+       /* send the initial s2s request */
+       osrfChatSendRaw( snode, OSRF_CHAT_S2S_INIT );
+
+       osrfLogDebug( OSRF_LOG_MARK, "Added new s2s node...");
+       chatdbg(cs);
+
+       return 0;
+}
+
+
+/* commence SAX handling code */
+
+int osrfChatPushData( osrfChatServer* server, osrfChatNode* node, char* data ) {
+       if(!(node && data)) return -1;
+
+       chatdbg(server);
+
+       osrfLogDebug( OSRF_LOG_MARK, "pushing data into xml parser for node %d with state %d:\n%s", 
+                                                node->sockid, node->state, data);
+       node->inparse = 1;
+       xmlParseChunk(node->parserCtx, data, strlen(data), 0);
+       node->inparse = 0;
+
+       if(osrfChatXMLErrorOcurred) {
+               osrfChatXMLErrorOcurred = 0;
+               return -1;
+       }
+
+       /* we can't do cleanup of the XML handlers while in the middle of a 
+               data push, so set flags in the data push and doe the cleanup here */
+       /*
+       if(osrfChatClientSentDisconnect) {
+               osrfChatClientSentDisconnect  = 0;
+               osrfChatNodeFinish( server, node );
+       }
+       */
+
+       return 0;
+}
+
+
+void osrfChatStartStream( void* blob ) {
+       osrfLogDebug( OSRF_LOG_MARK, "Starting new client stream...");
+}
+
+
+void osrfChatStartElement( void* blob, const xmlChar *name, const xmlChar **atts ) {
+       if(!(blob && name)) return;
+       osrfChatNode* node = (osrfChatNode*) blob;
+
+       int status = -1;
+       char* nm = (char*) name;
+
+       osrfLogDebug( OSRF_LOG_MARK, "Starting element %s with namespace %s and node state %d", 
+                                                nm, xmlSaxAttr(atts, "xmlns"), node->state );
+
+       switch( node->state ) {
+
+               case OSRF_CHAT_STATE_NONE:
+                       status = osrfChatHandleNewConnection( node, nm, atts );
+                       osrfLogDebug( OSRF_LOG_MARK, "After NewConnection we have state %d", node->state);
+                       break;
+
+               case OSRF_CHAT_STATE_CONNECTING:
+                       status = osrfChatHandleConnecting( node, nm, atts );
+                       break;
+
+               case OSRF_CHAT_STATE_CONNECTED:
+                       status = osrfChatHandleConnected( node, nm, atts );
+                       break;
+
+               case OSRF_CHAT_STATE_S2S_CHALLENGE:      
+                       status = osrfChatHandleS2SChallenge( node, nm, atts );
+                       break;
+
+               case OSRF_CHAT_STATE_S2S_RESPONSE: /* server waiting for client response to challenge */
+                       if(eq(nm, "db:result")) {
+                               char* remote = xmlSaxAttr(atts, "from");
+                               if(remote) {
+                                       if( node->remote) free( node->remote );
+                                       node->remote = strdup(remote); /* copy off the client's id */
+                               }
+                               status = 0;
+                               node->xmlstate |= OSRF_CHAT_STATE_INS2SRESULT;
+                       } else status = -1; 
+                       break;
+
+               case OSRF_CHAT_STATE_S2S_VERIFY:        /* client : waiting for server verify message */
+                       if(eq(nm, "db:verify")) {
+                               char* id = xmlSaxAttr( atts, "id" );
+                               if(id) {
+                                       char* xml = va_list_to_string( OSRF_CHAT_S2S_VERIFY_RESPONSE, 
+                                                       node->remote, node->domain, id );
+                                       osrfChatSendRaw( node, xml );
+                                       free(xml);
+                                       node->state = OSRF_CHAT_STATE_S2S_VERIFY_FINAL;
+                                       status = 0;
+                               }
+                       }
+                       break;
+
+               case OSRF_CHAT_STATE_S2S_VERIFY_RESPONSE:       /* server waiting for client verify response */
+               case OSRF_CHAT_STATE_S2S_VERIFY_FINAL: /* client waitig for final verify */
+                       status = osrfChatHandleS2SConnected( node, nm, atts );
+                       break;
+
+       }
+
+       if(status != 0) 
+               osrfChatParseError( node, "We don't know how to handle the XML data received" );
+}
+
+#define CHAT_CHECK_VARS(x,y,z) if(!(x && y)) return -1; if(z) osrfLogDebug( OSRF_LOG_MARK, z);
+
+
+
+int osrfChatHandleS2SConnected( osrfChatNode* node, const char* name, const xmlChar**atts ) {
+       CHAT_CHECK_VARS(node, name, "osrfChatHandleS2SConnected" );
+
+       int status = -1;
+
+       if(eq(name,"db:verify")) { /* server receives verify from client */
+               char* xml = va_list_to_string(OSRF_CHAT_S2S_VERIFY_FINAL, node->domain, node->remote ); 
+               osrfChatSendRaw(node, xml );
+               free(xml);
+               status = 0;
+       }
+
+       if(eq(name, "db:result")) {
+               /* send all the messages that we have queued for this server */
+               node->state = OSRF_CHAT_STATE_CONNECTED;
+               osrfListIterator* itr = osrfNewListIterator(node->msgs);
+
+               char* xml;
+               while( (xml = (char*) osrfListIteratorNext(itr)) ) {
+                       xmlDocPtr doc = xmlParseMemory(xml, strlen(xml));
+                       if(doc) {
+                               char* from = (char*) xmlGetProp(xmlDocGetRootElement(doc), BAD_CAST "from");
+                               char* to = (char*) xmlGetProp(xmlDocGetRootElement(doc), BAD_CAST "to");
+                               osrfChatSend( node->parent, node, to, from, xml );
+                               osrfLogDebug( OSRF_LOG_MARK, "Sending cached message from %s to %s", from, to);
+                               xmlFree(to); xmlFree(from);
+                               xmlFreeDoc(doc);
+                       }
+               }
+
+               osrfListIteratorFree(itr);
+               osrfListFree(node->msgs);
+               node->msgs = NULL;
+               status = 0;
+       }
+
+       if(status == 0) {
+               osrfLogInfo( OSRF_LOG_MARK, "Successfully made S2S connection to %s", node->remote );
+               node->state = OSRF_CHAT_STATE_CONNECTED;
+               node->xmlstate = 0;
+       }
+
+       return status;
+}
+
+
+/** check the namespace of the stream message to see if it's a server or client connection */
+int osrfChatHandleNewConnection( osrfChatNode* node, const char* name, const xmlChar** atts ) {
+       CHAT_CHECK_VARS(node, name, "osrfChatHandleNewConnection()");
+
+       if(!eq(name, "stream:stream")) return -1;
+
+       if( node->authkey ) free( node->authkey );
+       node->authkey = osrfChatMkAuthKey();
+       char* ns = xmlSaxAttr(atts, "xmlns");
+       if(!ns) return -1;
+
+       if(eq(ns, "jabber:client")) { /* client connection */
+
+               char* domain = xmlSaxAttr( atts, "to" );
+               if(!domain) return -1; 
+       
+               if(!eq(domain, node->domain)) {
+                       osrfLogWarning( OSRF_LOG_MARK, 
+                               "Client attempting to connect to invalid domain %s. Our domain is %s", domain, node->domain);
+                       return -1;
+               }
+       
+               char* buf = va_list_to_string( OSRF_CHAT_START_STREAM, domain, node->authkey );
+               node->state = OSRF_CHAT_STATE_CONNECTING;
+
+               osrfLogDebug( OSRF_LOG_MARK, "Server node %d setting state to OSRF_CHAT_STATE_CONNECTING[%d]",
+                                                        node->sockid, node->state );
+       
+               osrfLogDebug( OSRF_LOG_MARK, "Server responding to connect message with\n%s\n", buf );
+               osrfChatSendRaw( node, buf );
+               free(buf);
+               return 0;
+       }
+
+       /* server to server init */
+       if(eq(ns, "jabber:server")) { /* client connection */
+               osrfLogInfo( OSRF_LOG_MARK, "We received a new server 2 server connection, generating auth key...");
+               char* xml = va_list_to_string( OSRF_CHAT_S2S_CHALLENGE, node->authkey );
+               osrfChatSendRaw( node, xml );
+               free(xml);
+               node->state = OSRF_CHAT_STATE_S2S_RESPONSE; /* the next message should be the response */
+               node->type = 1;
+               return 0;
+       }
+
+       return -1;
+}
+
+
+
+char* osrfChatMkAuthKey() {
+       char hostname[HOST_NAME_MAX + 1] = "";
+       gethostname(hostname, sizeof(hostname) );
+       hostname[HOST_NAME_MAX] = '\0';
+       char keybuf[112];
+       snprintf(keybuf, sizeof(keybuf), "%d%ld%s", (int) time(NULL), (long) getpid(), hostname);
+       return strdup(shahash(keybuf));
+}
+
+int osrfChatHandleConnecting( osrfChatNode* node, const char* name, const xmlChar** atts ) {
+       CHAT_CHECK_VARS(node, name, "osrfChatHandleConnecting()");
+       osrfLogDebug( OSRF_LOG_MARK, "Handling connect node %s", name );
+
+       if(eq(name, "iq")) node->xmlstate |= OSRF_CHAT_STATE_INIQ;
+       else if(eq(name,"username")) node->xmlstate |= OSRF_CHAT_STATE_INUSERNAME;
+       else if(eq(name,"resource")) node->xmlstate |= OSRF_CHAT_STATE_INRESOURCE;
+       return 0;
+}
+
+int osrfChatHandleConnected( osrfChatNode* node, const char* name, const xmlChar** atts ) {
+       CHAT_CHECK_VARS(node, name, "osrfChatHandleConnected()");
+
+       if(eq(name,"message")) {
+
+               /* drop the old message and start with a new one */
+               xmlNodePtr root = xmlNewNode(NULL, BAD_CAST name);
+               xmlAddAttrs(root, atts);
+               xmlNodePtr oldRoot = xmlDocSetRootElement(node->msgDoc, root);
+               free(node->to);
+
+               char* to = xmlSaxAttr(atts, "to");
+               if(!to) to = "";
+
+               node->to = strdup(to);
+               if(oldRoot) xmlFreeNode(oldRoot);
+               node->xmlstate = OSRF_CHAT_STATE_INMESSAGE;
+
+       } else {
+
+               /* all non "message" nodes are simply added to the message */
+               xmlNodePtr nodep = xmlNewNode(NULL, BAD_CAST name);
+               xmlAddAttrs(nodep, atts);
+               xmlAddChild(xmlDocGetRootElement(node->msgDoc), nodep);
+       }
+
+       return 0;
+}
+
+/* takes s2s secret, hashdomain, and the s2s auth token */
+static char* osrfChatGenerateS2SKey( char* secret, char* hashdomain, char* authtoken ) {
+       if(!(secret && hashdomain && authtoken)) return NULL;
+       osrfLogInfo( OSRF_LOG_MARK, "Generating s2s key with auth token: %s", authtoken );
+       char* a = shahash(secret);
+       osrfLogDebug( OSRF_LOG_MARK, "S2S secret hash: %s", a);
+       char* b = va_list_to_string("%s%s", a, hashdomain);
+       char* c = shahash(b);
+       osrfLogDebug( OSRF_LOG_MARK, "S2S intermediate hash: %s", c);
+       char* d = va_list_to_string("%s%s", c, authtoken);
+       char* e = strdup(shahash(d));
+       free(b); free(d); 
+       return e;
+}
+
+int osrfChatHandleS2SChallenge( osrfChatNode* node, const char* name, const xmlChar** atts ) {
+       CHAT_CHECK_VARS(node, name, "osrfChatHandleS2SChallenge()");
+
+/* here we respond to the stream challenge */
+       if(eq(name, "stream:stream")) {
+               char* id = xmlSaxAttr(atts, "id");
+               if(id) {
+                       /* we use our domain in the s2s challenge hash */
+                       char* d = osrfChatGenerateS2SKey(node->parent->secret, node->domain, id );
+                       char* e = va_list_to_string(OSRF_CHAT_S2S_RESPONSE, node->remote, node->domain, d );
+                       osrfLogInfo( OSRF_LOG_MARK, "Answering s2s challenge with key:  %s", e );
+                       osrfChatSendRaw( node, e );
+                       free(d); free(e);
+                       node->state = OSRF_CHAT_STATE_S2S_VERIFY;
+                       return 0;
+               }
+       }
+
+       return -1;
+}
+
+/*
+int osrfChatHandleS2SResponse( osrfChatNode* node, const char* name, const xmlChar** atts ) {
+       CHAT_CHECK_VARS(node, name, "osrfChatHandleS2SResponse()");
+
+       if(eq(name, "db:result")) {
+               node->xmlstate |= OSRF_CHAT_STATE_INS2SRESULT;
+               return 0;
+       }
+
+       return -1;
+}
+*/
+
+
+
+void osrfChatEndElement( void* blob, const xmlChar* name ) {
+       if(!(blob && name)) return;
+       osrfChatNode* node = (osrfChatNode*) blob;
+
+       char* nm = (char*) name;
+
+       if(eq(nm,"stream:stream")) {
+               osrfChatNodeFinish( node->parent, node );
+               return;
+       }
+
+       if( node->state == OSRF_CHAT_STATE_CONNECTED ) {
+               if(eq(nm, "message")) {
+
+                       xmlNodePtr msg = xmlDocGetRootElement(node->msgDoc);
+                       if(msg && node->type == 0)
+                               xmlSetProp(msg, BAD_CAST "from", BAD_CAST node->remote );
+                       char* string = xmlDocToString(node->msgDoc, 0 );
+
+                       char* from = (char*) xmlGetProp(msg, BAD_CAST "from");
+                       osrfLogDebug( OSRF_LOG_MARK,  "Routing message to %s\n%s\n", node->to, from, string );
+                       osrfChatSend( node->parent, node, node->to, from, string ); 
+                       xmlFree(from);
+                       free(string);
+               }
+       }
+
+       if( node->state == OSRF_CHAT_STATE_CONNECTING ) {
+               if( node->xmlstate & OSRF_CHAT_STATE_INIQ ) {
+
+                       if(eq(nm, "iq")) {
+                               node->xmlstate &= ~OSRF_CHAT_STATE_INIQ;
+                               if( node->remote ) free( node->remote );
+                               node->remote = va_list_to_string( 
+                                               "%s@%s/%s", node->username, node->domain, node->resource );
+
+                               osrfLogInfo( OSRF_LOG_MARK, "%s successfully logged in", node->remote );
+
+                               osrfLogDebug( OSRF_LOG_MARK, "Setting remote address to %s", node->remote );
+                               osrfChatSendRaw( node, OSRF_CHAT_LOGIN_OK );
+                               if(osrfHashGet( node->parent->nodeHash, node->remote ) ) {
+                                       osrfLogWarning( OSRF_LOG_MARK, "New node replaces existing node for remote id %s", node->remote);
+                                       osrfHashRemove(node->parent->nodeHash, node->remote);
+                               }
+                               osrfHashSet( node->parent->nodeHash, node, node->remote );
+                               node->state = OSRF_CHAT_STATE_CONNECTED;
+                       }
+               }
+       }
+}
+
+
+void osrfChatHandleCharacter( void* blob, const xmlChar *ch, int len) {
+       if(!(blob && ch && len)) return;
+       osrfChatNode* node = (osrfChatNode*) blob;
+
+       /*
+       osrfLogDebug( OSRF_LOG_MARK, "Char Handler: state %d, xmlstate %d, chardata %s", 
+                       node->state, node->xmlstate, (char*) ch );
+                       */
+
+       if( node->state == OSRF_CHAT_STATE_CONNECTING ) {
+               if( node->xmlstate & OSRF_CHAT_STATE_INIQ ) {
+
+                       if( node->xmlstate & OSRF_CHAT_STATE_INUSERNAME ) {
+                               free(node->username);
+                               node->username = strndup((char*) ch, len);
+                               node->xmlstate &= ~OSRF_CHAT_STATE_INUSERNAME;
+                       }
+
+                       if( node->xmlstate & OSRF_CHAT_STATE_INRESOURCE ) {
+                               free(node->resource);
+                               node->resource = strndup((char*) ch, len);
+                               node->xmlstate &= ~OSRF_CHAT_STATE_INRESOURCE;
+                       }
+               }
+
+               return;
+       } 
+       
+       if( node->state == OSRF_CHAT_STATE_CONNECTED ) {
+               xmlNodePtr last = xmlGetLastChild(xmlDocGetRootElement(node->msgDoc));
+               xmlNodePtr txt = xmlNewTextLen(ch, len);
+               xmlAddChild(last, txt);
+               return;
+       }
+
+       if( node->state == OSRF_CHAT_STATE_S2S_RESPONSE &&
+                       (node->xmlstate & OSRF_CHAT_STATE_INS2SRESULT) ) {
+
+               char* key = strndup((char*) ch, len);
+               osrfLogDebug( OSRF_LOG_MARK, "Got s2s key from %s : %s", node->remote, key );
+               char* e = osrfChatGenerateS2SKey(node->parent->secret, node->remote, node->authkey );
+               osrfLogInfo( OSRF_LOG_MARK, "\nReceived s2s key from server: %s\nKey should be: %s", key, e );
+
+               if(eq(key, e)) {
+                       char* msg = va_list_to_string(OSRF_CHAT_S2S_VERIFY_REQUEST,  
+                                       node->authkey, node->domain, node->remote, e );
+                       osrfChatSendRaw(node, msg );
+                       free(msg);
+                       node->state = OSRF_CHAT_STATE_S2S_VERIFY_RESPONSE;
+                       node->xmlstate = 0;
+
+               } else {
+                       osrfLogWarning( OSRF_LOG_MARK, "Server2Server keys do not match!");
+               }
+
+               free( e );
+               free( key );
+
+               /* do the hash dance again */
+       }
+}
+
+
+void osrfChatParseError( void* blob, const char* msg, ... ) {
+
+       osrfChatXMLErrorOcurred = 1;
+}
+
+
+
+
diff --git a/trunk/src/jserver/osrf_chat.h b/trunk/src/jserver/osrf_chat.h
new file mode 100644 (file)
index 0000000..fb24340
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+#ifndef OSRF_CHAT_H
+#define OSRF_CHAT_H
+
+
+/* opensrf headers */
+#include "opensrf/utils.h"
+#include "opensrf/osrf_hash.h"
+#include "opensrf/osrf_list.h"
+#include "opensrf/log.h"
+#include "opensrf/xml_utils.h"
+#include "opensrf/socket_bundle.h"
+#include "opensrf/sha.h"
+#include "opensrf/transport_message.h"
+
+/* libxml2 headers */
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+
+/* client to server XML */
+#define OSRF_CHAT_START_STREAM "<?xml version='1.0'?><stream:stream "\
+       "xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' "\
+       "from='%s' version='1.0' id='%s'>" 
+
+#define OSRF_CHAT_PARSE_ERROR "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "\
+       "version='1.0'><stream:error xmlns:stream='http://etherx.jabber.org/streams'>"\
+       "<xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"    \
+       "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>syntax error</text></stream:error></stream:stream>" 
+
+#define OSRF_CHAT_LOGIN_OK "<iq xmlns='jabber:client' id='0123456789' type='result'/>"
+
+#define OSRF_CHAT_NO_RECIPIENT "<message xmlns='jabber:client' type='error' from='%s' to='%s'>"\
+       "<error type='cancel' code='404'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"\
+       "</error><body>NOT ADDING BODY</body></message>"
+
+/* ---------------------------------------------------------------------------------- */
+/* server to server XML */
+
+// client to server init
+#define OSRF_CHAT_S2S_INIT "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "\
+       "xmlns='jabber:server' xmlns:db='jabber:server:dialback'>"
+
+// server to client challenge 
+#define OSRF_CHAT_S2S_CHALLENGE "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "\
+       "xmlns='jabber:server' id='%s' xmlns:db='jabber:server:dialback'>"
+
+// client to server challenge response
+#define OSRF_CHAT_S2S_RESPONSE "<db:result xmlns:db='jabber:server:dialback' to='%s' from='%s'>%s</db:result>"
+
+// server to client verify
+#define OSRF_CHAT_S2S_VERIFY_REQUEST "<db:verify xmlns:db='jabber:server:dialback' id='%s' from='%s' to='%s'>%s</db:verify>"
+
+// client to server verify response
+#define OSRF_CHAT_S2S_VERIFY_RESPONSE "<db:verify xmlns:db='jabber:server:dialback' type='valid' to='%s' from='%s' id='%s'/>"
+
+//server to client final verification
+#define OSRF_CHAT_S2S_VERIFY_FINAL "<db:result xmlns:db='jabber:server:dialback' type='valid' from='%s' to ='%s'/>"
+
+
+/* c2s states */
+#define OSRF_CHAT_STATE_NONE                                           0               /* blank node */
+#define OSRF_CHAT_STATE_CONNECTING                             1               /* we have received the opening stream */
+#define OSRF_CHAT_STATE_CONNECTED                              2               /* we have sent the OK/result message */
+
+/* s2s states */
+#define OSRF_CHAT_STATE_S2S_CHALLENGE                  4               /* client : waiting for the challenge */
+#define OSRF_CHAT_STATE_S2S_RESPONSE                   5               /* server : waiting for the challenge response */
+#define OSRF_CHAT_STATE_S2S_VERIFY                             6               /* client : waiting for verify message */
+#define OSRF_CHAT_STATE_S2S_VERIFY_RESPONSE    7               /* server : waiting for verify response */
+#define OSRF_CHAT_STATE_S2S_VERIFY_FINAL               8               /* client : waiting for final verify response */
+
+/* xml parser states */
+#define OSRF_CHAT_STATE_INMESSAGE              1
+#define OSRF_CHAT_STATE_INIQ                           2
+#define OSRF_CHAT_STATE_INUSERNAME             4
+#define OSRF_CHAT_STATE_INRESOURCE             8
+#define OSRF_CHAT_STATE_INS2SRESULT            16
+#define OSRF_CHAT_STATE_INS2SVERIFY            32
+
+
+struct __osrfChatNodeStruct {
+
+       int sockid;                     /* our socket id */
+
+       int type;                       /* 0 for client, 1 for server */
+
+       /* for clients this is the full JID of the client that connected to this server.
+               for servers it's the domain (network id) of the server we're connected to */
+       char* remote;           
+
+
+       int state;                      /* for the various stages of connectivity and parsing */
+       int xmlstate;           /* what part of the message are we currently parsing */
+       int inparse;            /* true if we are currently parsing a chunk of XML.  If so, we can't 
+                                                                       free the node.  we have to cache it and free it later */
+
+       char* to;                       /* The JID where the current message is being routed */
+
+       char* domain;           /* the domain, resource, and username of our connecting entity. */ 
+       char* resource; /* for s2s nodes, resource and username will be empty . */
+       char* username;
+
+       char* authkey;          /* when doing any auth negotiation, this is the auth seed hash */
+       osrfList* msgs; /* if we're a server node we may have a pool of messages waiting to be delivered */
+
+       xmlParserCtxtPtr parserCtx; 
+       xmlDocPtr msgDoc;
+       struct __osrfChatServerStruct* parent;
+
+};
+typedef struct __osrfChatNodeStruct osrfChatNode;
+
+/*
+struct __osrfChatS2SMessageStruct {
+       char* toAddr;
+       char* msgXML;
+};
+typedef struct __osrfChatS2SMessageStruct osrfChatS2SMessage;
+*/
+
+struct __osrfChatServerStruct {
+       osrfHash* nodeHash; /* sometimes we need hash (remote id) lookup, sometimes we need socket id lookup */
+       osrfList* nodeList;
+       osrfList* deadNodes; /* collection of nodes to free when we get a chance */
+       socket_manager* mgr;
+       char* secret;                   /* shared S2S secret */
+       char* domain;                   /* the domain this server hosts */
+       int s2sport;
+       int port;
+};
+
+typedef struct __osrfChatServerStruct osrfChatServer;
+
+
+void osrfChatCacheS2SMessage( char* toAddr, char* msgXML, osrfChatNode* snode );
+
+osrfChatNode* osrfNewChatS2SNode( char* domain, char* remote );
+osrfChatNode* osrfNewChatNode( int sockid, char* domain );
+void osrfChatNodeFree( void* node );
+
+/* @param s2sSecret The Server to server secret.  OK to leave NULL if no 
+       server to server communication is expected
+       */
+osrfChatServer* osrfNewChatServer( char* domain, char* s2sSecret, int s2sport );
+
+int osrfChatServerConnect( osrfChatServer* cs,  int port, int s2sport, char* listenAddr );
+
+int osrfChatServerWait( osrfChatServer* server );
+void osrfChatServerFree(osrfChatServer* cs);
+
+void osrfChatHandleData( void* cs, 
+       socket_manager* mgr, int sockid, char* data, int parent_id );
+
+
+/* removes dead nodes that have been cached due to mid-parse removals */
+void osrfChatCleanupClients( osrfChatServer* server );
+
+
+osrfChatNode* osrfChatAddNode( osrfChatServer* server, int sockid );
+
+
+void osrfChatRemoveNode( osrfChatServer* server, osrfChatNode* node );
+
+/** pushes new data into the nodes parser */
+int osrfChatPushData( osrfChatServer* server, osrfChatNode* node, char* data );
+
+
+void osrfChatSocketClosed( void* blob, int sockid );
+
+/**
+  Sends msgXML to the client with remote 'toAddr'.  if we have no connection
+  to 'toAddr' and the domain for 'toAddr' is different than our hosted domain
+  we attempt to send the message to the domain found in 'toAddr'.
+  */
+int osrfChatSend( osrfChatServer* cs, osrfChatNode* node, char* toAddr, char* fromAddr, char* msgXML );
+
+int osrfChatSendRaw( osrfChatNode* node, char* xml );
+
+
+void osrfChatNodeFinish( osrfChatServer* server, osrfChatNode* node );
+
+/* initializes the negotiation of a server to server connection */
+int osrfChatInitS2S( osrfChatServer* cs, char* remote, char* toAddr, char* msgXML );
+
+
+void osrfChatStartStream( void* blob );
+void osrfChatStartElement( void* blob, const xmlChar *name, const xmlChar **atts );
+void osrfChatEndElement( void* blob, const xmlChar* name );
+void osrfChatHandleCharacter(void* blob, const xmlChar *ch, int len);
+void osrfChatParseError( void* blob, const char* msg, ... );
+
+int osrfChatHandleNewConnection( osrfChatNode* node, const char* name, const xmlChar** atts );
+int osrfChatHandleConnecting( osrfChatNode* node, const char* name, const xmlChar** atts );
+int osrfChatHandleConnected( osrfChatNode* node, const char* name, const xmlChar** atts );
+int osrfChatHandleS2SInit( osrfChatNode* node, const char* name, const xmlChar** atts );
+int osrfChatHandleS2SChallenge( osrfChatNode* node, const char* name, const xmlChar** atts );
+int osrfChatHandleS2SResponse( osrfChatNode* node, const char* name, const xmlChar** atts );
+
+int osrfChatHandleS2SConnected( osrfChatNode* node, const char* nm, const xmlChar**atts );
+
+void osrfChatS2SMessageFree(void* n);
+
+
+
+/* generates a random sha1 hex key */
+char* osrfChatMkAuthKey();
+
+static xmlSAXHandler osrfChatSaxHandlerStruct = {
+   NULL,                                                               /* internalSubset */
+   NULL,                                                               /* isStandalone */
+   NULL,                                                               /* hasInternalSubset */
+   NULL,                                                               /* hasExternalSubset */
+   NULL,                                                               /* resolveEntity */
+   NULL,                                                               /* getEntity */
+   NULL,                                                               /* entityDecl */
+   NULL,                                                               /* notationDecl */
+   NULL,                                                               /* attributeDecl */
+   NULL,                                                               /* elementDecl */
+   NULL,                                                               /* unparsedEntityDecl */
+   NULL,                                                               /* setDocumentLocator */
+   osrfChatStartStream,                        /* startDocument */
+   NULL,                                                               /* endDocument */
+       osrfChatStartElement,           /* startElement */
+       osrfChatEndElement,                     /* endElement */
+   NULL,                                                               /* reference */
+       osrfChatHandleCharacter,        /* characters */
+   NULL,                                                               /* ignorableWhitespace */
+   NULL,                                                               /* processingInstruction */
+   NULL,                                                               /* comment */
+   osrfChatParseError,                 /* xmlParserWarning */
+   osrfChatParseError,                 /* xmlParserError */
+   NULL,                                                               /* xmlParserFatalError : unused */
+   NULL,                                                               /* getParameterEntity */
+   NULL,                                                               /* cdataBlock; */
+   NULL,                                                               /* externalSubset; */
+   1,
+   NULL,
+   NULL,                                                               /* startElementNs */
+   NULL,                                                               /* endElementNs */
+       NULL                                                            /* xmlStructuredErrorFunc */
+};
+
+static const xmlSAXHandlerPtr osrfChatSaxHandler = &osrfChatSaxHandlerStruct;
+
+
+#endif
+
+
diff --git a/trunk/src/jserver/osrf_chat_main.c b/trunk/src/jserver/osrf_chat_main.c
new file mode 100644 (file)
index 0000000..8ac5298
--- /dev/null
@@ -0,0 +1,92 @@
+#include "osrf_chat.h"
+#include "opensrf/osrfConfig.h"
+#include <stdio.h>
+#include "opensrf/log.h"
+#include <syslog.h>
+
+
+int main( int argc, char* argv[] ) {
+
+       if( argc < 3 ) {
+               fprintf( stderr, "Usage: %s <config_file> <config_context>\n", argv[0] );
+               exit(0);
+       }
+
+       osrfConfig* cfg = osrfConfigInit( argv[1], argv[2] );
+       if( !cfg ) {
+               fprintf( stderr, "Unable to load configuration file %s\n", argv[1] );
+               return -1;
+       }
+
+       init_proc_title( argc, argv );
+       set_proc_title( "ChopChop" );
+
+       char* domain            = osrfConfigGetValue(cfg, "/domain");
+       char* secret            = osrfConfigGetValue(cfg, "/secret");
+       char* sport                     = osrfConfigGetValue(cfg, "/port");
+       char* s2sport           = osrfConfigGetValue(cfg, "/s2sport");
+       char* listenaddr        = osrfConfigGetValue(cfg, "/listen_address");
+       char* llevel            = osrfConfigGetValue(cfg, "/loglevel");
+       char* lfile                     = osrfConfigGetValue(cfg, "/logfile");
+       char* facility          = osrfConfigGetValue(cfg, "/syslog");
+
+       if(!domain)
+               fputs( "No domain specified in configuration file\n", stderr );
+       
+       if(!secret)
+               fputs( "No secret specified in configuration file\n", stderr );
+       
+       if(!sport)
+               fputs( "No port specified in configuration file\n", stderr );
+       
+       if(!listenaddr)
+               fputs( "No listen_address specified in configuration file\n", stderr );
+       
+       if(!llevel)
+               fputs( "No loglevel specified in configuration file\n", stderr );
+       
+       if(!lfile)
+               fputs( "No logfile specified in configuration file\n", stderr );
+       
+       if(!s2sport)
+               fputs( "No s2sport specified in configuration file\n", stderr );
+       
+       if(!(domain && secret && sport && listenaddr && llevel && lfile && s2sport)) {
+               fprintf(stderr, "Configuration error for ChopChop - missing key ingredient\n");
+               return -1;
+       }
+
+       int port = atoi(sport);
+       int s2port = atoi(s2sport);
+       int level = atoi(llevel);
+
+       if(!strcmp(lfile, "syslog")) {
+               osrfLogInit( OSRF_LOG_TYPE_SYSLOG, "chopchop", level );
+               osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
+
+       } else {
+               osrfLogInit( OSRF_LOG_TYPE_FILE, "chopchop", level );
+               osrfLogSetFile( lfile );
+       }
+
+       fprintf(stderr, "Attempting to launch ChopChop with:\n"
+                       "domain: %s\nport: %s\nlisten address: %s\nlog level: %s\nlog file: %s\n",
+                       domain, sport, listenaddr, llevel, lfile );
+
+       osrfChatServer* server = osrfNewChatServer(domain, secret, s2port);
+
+       if( osrfChatServerConnect( server, port, s2port, listenaddr ) != 0 ) {
+               osrfLogError( OSRF_LOG_MARK, "ChopChop unable to bind to port %d on %s", port, listenaddr);
+               return -1;
+       }
+
+       daemonize();
+       osrfChatServerWait( server );
+
+       osrfChatServerFree( server );
+       osrfConfigFree(cfg);
+
+       return 0;
+
+}
+
diff --git a/trunk/src/libopensrf/Makefile.am b/trunk/src/libopensrf/Makefile.am
new file mode 100644 (file)
index 0000000..0293205
--- /dev/null
@@ -0,0 +1,109 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+AM_CFLAGS = $(DEF_CFLAGS) -DASSUME_STATELESS  -DOSRF_STRICT_PARAMS -rdynamic -fno-strict-aliasing -DOSRF_JSON_ENABLE_XML_UTILS
+AM_LDFLAGS = $(DEF_LDFLAGS) -R $(libdir)
+LDADD = -lxml2 -ldl -lmemcache -lopensrf
+
+OSRF_INC = @top_srcdir@/include/opensrf
+
+TARGS =                osrf_message.c \
+                       osrf_app_session.c \
+                       osrf_stack.c \
+                       osrf_system.c \
+                       osrf_settings.c \
+                       osrf_prefork.c \
+                       osrfConfig.c \
+                       osrf_application.c \
+                       osrf_cache.c \
+                       osrf_transgroup.c \
+                       osrf_list.c \
+                       osrf_hash.c \
+                       xml_utils.c \
+                       transport_message.c\
+                       transport_session.c\
+                       transport_client.c\
+                       md5.c\
+                       log.c\
+                       utils.c\
+                       socket_bundle.c\
+                       sha.c\
+                       string_array.c
+
+TARGS_HEADS =   $(OSRF_INC)/transport_message.h \
+                $(OSRF_INC)/transport_session.h \
+                $(OSRF_INC)/transport_client.h \
+                $(OSRF_INC)/osrf_message.h \
+                $(OSRF_INC)/osrf_app_session.h \
+                $(OSRF_INC)/osrf_stack.h \
+                $(OSRF_INC)/osrf_system.h \
+                $(OSRF_INC)/osrf_settings.h \
+                $(OSRF_INC)/osrf_prefork.h \
+                $(OSRF_INC)/osrfConfig.h \
+                $(OSRF_INC)/osrf_application.h \
+                $(OSRF_INC)/osrf_cache.h \
+                $(OSRF_INC)/osrf_list.h \
+                $(OSRF_INC)/osrf_hash.h \
+                $(OSRF_INC)/md5.h \
+                $(OSRF_INC)/log.h \
+                $(OSRF_INC)/utils.h \
+                $(OSRF_INC)/socket_bundle.h \
+                $(OSRF_INC)/sha.h \
+                $(OSRF_INC)/string_array.h \
+                $(OSRF_INC)/osrf_json_utils.h \
+                $(OSRF_INC)/osrf_json_xml.h 
+
+JSON_TARGS =                   osrf_json_object.c\
+                               osrf_json_parser.c \
+                               osrf_json_tools.c \
+                               osrf_legacy_json.c \
+                               osrf_json_xml.c
+
+# use these when building the standalone JSON module
+JSON_DEP =             osrf_list.c\
+                       osrf_hash.c\
+                       utils.c\
+                       log.c\
+                       md5.c\
+                       string_array.c
+
+JSON_TARGS_HEADS =     $(OSRF_INC)/osrf_legacy_json.h \
+                       $(OSRF_INC)/osrf_json_xml.h
+
+JSON_DEP_HEADS =       $(OSRF_INC)/osrf_list.h \
+                       $(OSRF_INC)/osrf_hash.h \
+                       $(OSRF_INC)/utils.h \
+                       $(OSRF_INC)/log.h \
+                       $(OSRF_INC)/md5.h \
+                       $(OSRF_INC)/string_array.h
+
+noinst_PROGRAMS = osrf_json_test
+
+bin_PROGRAMS = opensrf-c
+opensrf_c_SOURCES = opensrf.c
+opensrf_c_DEPENDENCIES = libopensrf.la
+
+osrf_json_test_SOURCES = osrf_json_test.c $(JSON_TARGS) $(JSON_DEP) $(JSON_TARGS_HEADS) $(JSON_DEP_HEADS)
+
+noinst_LTLIBRARIES = libosrf_json.la
+lib_LTLIBRARIES = libopensrf.la
+
+libosrf_json_la_SOURCES = $(JSON_TARGS) $(JSON_DEP) $(JSON_TARGS_HEADS) $(JSON_DEP_HEADS)
+libosrf_json_la_CFLAGS = $(AM_CFLAGS)
+
+libopensrf_la_CFLAGS = $(AM_CFLAGS)
+libopensrf_la_DEPENDENCIES = libosrf_json.la
+
+libopensrf_la_SOURCES = $(TARGS) $(TARGS_HEADS) $(JSON_TARGS) $(JSON_TARGS_HEADS)
+
diff --git a/trunk/src/libopensrf/Makefile.json b/trunk/src/libopensrf/Makefile.json
new file mode 100644 (file)
index 0000000..8cbc312
--- /dev/null
@@ -0,0 +1,37 @@
+#-DOSRF_JSON_ALLOW_COMMENTS 
+
+# ------------------------------------------------------------------
+# To build a standalone version of libosrf_json, something 
+# like the following should work:
+# $ CFLAGS="-fPIC -I /usr/include/libxml2 -I ../../include" \
+#      OSRF_INC="../../include/opensrf" LDLIBS="-lxml2" \
+#      make -f Makefile.json standalone
+# ------------------------------------------------------------------
+TARGETS = osrf_json_object.o osrf_json_parser.o osrf_json_tools.o osrf_legacy_json.o osrf_json_xml.o
+
+# these are only needed when compiling the standalone version
+EXT_TARGETS = osrf_list.o osrf_hash.o utils.o log.o md5.o string_array.o
+
+all:   $(TARGETS)
+
+standalone: $(TARGETS) $(EXT_TARGETS)
+       $(CC) -shared -W1 $(CFLAGS) $(LDFLAGS) $(LDLIBS) $(TARGETS) $(EXT_TARGETS) -o libosrf_json.so
+
+osrf_json_object.o:    osrf_json_object.c $(OSRF_INC)/osrf_json.h $(OSRF_INC)/osrf_json_utils.h
+osrf_json_parser.o:    osrf_json_parser.c $(OSRF_INC)/osrf_json.h $(OSRF_INC)/osrf_json_utils.h
+osrf_json_tools.o:     osrf_json_tools.c $(OSRF_INC)/osrf_json.h $(OSRF_INC)/osrf_json_utils.h
+osrf_legacy_json.o:    osrf_legacy_json.c $(OSRF_INC)/osrf_json.h $(OSRF_INC)/osrf_json_utils.h
+osrf_json_xml.o:       osrf_json_xml.c $(OSRF_INC)/osrf_json.h $(OSRF_INC)/osrf_json_xml.h
+
+
+osrf_list.o:   osrf_list.c $(OSRF_INC)/osrf_list.h
+osrf_hash.o:   osrf_hash.c $(OSRF_INC)/osrf_hash.h
+utils.o:       utils.c $(OSRF_INC)/utils.h
+md5.o: md5.c $(OSRF_INC)/md5.h
+log.o: log.c $(OSRF_INC)/log.h
+string_array.o:        string_array.c $(OSRF_INC)/string_array.h
+
+
+clean:
+       rm -f osrf_json*.o osrf_legacy_json.o libosrf_json.so
+
diff --git a/trunk/src/libopensrf/basic_client.c b/trunk/src/libopensrf/basic_client.c
new file mode 100644 (file)
index 0000000..a477ab0
--- /dev/null
@@ -0,0 +1,81 @@
+#include <opensrf/transport_client.h>
+#include "signal.h"
+
+pid_t pid;
+void sig_int( int sig ) {
+       fprintf(stderr, "Killing child %d\n", pid );
+       kill( pid, SIGKILL );
+}
+
+/* connects and registers with the router */
+int main( int argc, char** argv ) {
+
+       if( argc < 5 ) {
+               osrfLogError( OSRF_LOG_MARK, "Usage: %s <username> <host> <resource> <recipient> \n", argv[0] );
+               return 99;
+       }
+
+       transport_message* send;
+       transport_client* client = client_init( argv[2], 5222, 0 );
+
+       // try to connect, allow 15 second connect timeout 
+       if( client_connect( client, argv[1], "jkjkasdf", argv[3], 15, AUTH_DIGEST ) ) 
+               osrfLogInfo(OSRF_LOG_MARK, "Connected...\n");
+        else { 
+               osrfLogError( OSRF_LOG_MARK, "NOT Connected...\n" ); 
+               return -1;
+        }
+       
+       if( (pid=fork()) ) { /* parent */
+
+               signal(SIGINT, sig_int);
+               fprintf(stderr, "Listener: %ld\n", (long) getpid() );   
+               char buf[300];
+               osrf_clearbuf(buf, sizeof(buf));
+               printf("=> ");
+
+               while( fgets( buf, sizeof(buf), 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=> ");
+                       osrf_clearbuf(buf, sizeof(buf));
+               }
+               fprintf(stderr, "Killing child %d\n", pid );
+               kill( pid, SIGKILL );
+               return 0;
+
+       } else {
+
+               fprintf(stderr, "Sender: %ld\n", (long) getpid() );     
+
+               transport_message* recv;
+               while( (recv=client_recv( client, -1)) ) {
+                       if( recv->is_error )
+                               fprintf( stderr, "\nReceived Error\t: ------------------\nFrom:\t\t"
+                                       "%s\nRouterFrom:\t%s\nBody:\t\t%s\nType %s\nCode %d\n=> ", 
+                                       recv->sender, recv->router_from, recv->body, recv->error_type, recv->error_code );
+                       else
+                               fprintf( stderr, "\nReceived\t: ------------------\nFrom:\t\t"
+                                       "%s\nRouterFrom:\t%s\nBody:\t\t%s\n=> ", recv->sender, recv->router_from, recv->body );
+
+                       message_free( recv );
+               }
+
+       }
+       return 0;
+
+}
+
+
+
+
diff --git a/trunk/src/libopensrf/log.c b/trunk/src/libopensrf/log.c
new file mode 100644 (file)
index 0000000..14fdf06
--- /dev/null
@@ -0,0 +1,290 @@
+#include <opensrf/log.h>
+
+#define OSRF_NO_LOG_TYPE -1
+
+static int _prevLogType             = OSRF_NO_LOG_TYPE;
+static int _osrfLogType                                = OSRF_LOG_TYPE_STDERR;
+static int _osrfLogFacility                    = LOG_LOCAL0;
+static int _osrfLogActFacility         = LOG_LOCAL1;
+static char* _osrfLogFile                      = NULL;
+static char* _osrfLogAppname           = NULL;
+static int _osrfLogLevel                       = OSRF_LOG_INFO;
+static int _osrfLogActivityEnabled     = 1;
+static int _osrfLogIsClient         = 0;
+
+static char* _osrfLogXid            = NULL; /* current xid */
+static char* _osrfLogXidPfx         = NULL; /* xid prefix string */
+
+static void osrfLogSetType( int logtype );
+static void _osrfLogDetail( int level, const char* filename, int line, char* msg );
+static void _osrfLogToFile( const char* msg, ... );
+static void _osrfLogSetXid( const char* xid );
+
+#define OSRF_LOG_GO(f,li,m,l)  \
+        if(!m) return;         \
+        VA_LIST_TO_STRING(m);  \
+        _osrfLogDetail( l, f, li, VA_BUF );
+
+void osrfLogCleanup( void ) {
+       free(_osrfLogAppname);
+       _osrfLogAppname = NULL;
+       free(_osrfLogFile);
+       _osrfLogFile = NULL;
+       _osrfLogType = OSRF_LOG_TYPE_STDERR;
+}
+
+
+void osrfLogInit( int type, const char* appname, int maxlevel ) {
+       osrfLogSetType(type);
+       if(appname) osrfLogSetAppname(appname);
+       osrfLogSetLevel(maxlevel);
+       if( type == OSRF_LOG_TYPE_SYSLOG ) 
+               openlog(_osrfLogAppname, 0, _osrfLogFacility );
+}
+
+static void _osrfLogSetXid( const char* xid ) {
+   if(xid) {
+      if(_osrfLogXid) free(_osrfLogXid);
+      _osrfLogXid = strdup(xid);
+   }
+}
+
+void osrfLogClearXid( void ) { _osrfLogSetXid(""); }
+void osrfLogSetXid(char* xid) {
+   if(!_osrfLogIsClient) _osrfLogSetXid(xid);
+}
+void osrfLogForceXid(char* xid) {
+   _osrfLogSetXid(xid);
+}
+
+void osrfLogMkXid( void ) {
+   if(_osrfLogIsClient) {
+      static int _osrfLogXidInc = 0; /* increments with each new xid for uniqueness */
+      char buf[32];
+      snprintf(buf, sizeof(buf), "%s%d", _osrfLogXidPfx, _osrfLogXidInc);
+      _osrfLogSetXid(buf);
+      _osrfLogXidInc++;
+   }
+}
+
+char* osrfLogGetXid( void ) {
+   return _osrfLogXid;
+}
+
+void osrfLogSetIsClient(int is) {
+   _osrfLogIsClient = is;
+   if(!is) return;
+   /* go ahead and create the xid prefix so it will be consistent later */
+   static char buff[32];
+   snprintf(buff, sizeof(buff), "%d%ld", (int)time(NULL), (long) getpid());
+   _osrfLogXidPfx = buff;
+}
+
+/** Sets the type of logging to perform.  See log types */
+static void osrfLogSetType( int logtype ) {
+
+       switch( logtype )
+       {
+               case OSRF_LOG_TYPE_FILE :
+               case OSRF_LOG_TYPE_SYSLOG :
+               case OSRF_LOG_TYPE_STDERR :
+                       _osrfLogType = logtype;
+                       break;
+               default :
+                       fprintf(stderr, "Unrecognized log type.  Logging to stderr\n");
+                       _osrfLogType = OSRF_LOG_TYPE_STDERR;
+                       break;
+       }
+}
+
+void osrfLogToStderr( void )
+{
+       if( OSRF_NO_LOG_TYPE == _prevLogType ) {
+               _prevLogType = _osrfLogType;
+               _osrfLogType = OSRF_LOG_TYPE_STDERR;
+       }
+}
+
+void osrfRestoreLogType( void )
+{
+       if( _prevLogType != OSRF_NO_LOG_TYPE ) {
+               _osrfLogType = _prevLogType;
+               _prevLogType = OSRF_NO_LOG_TYPE;
+       }
+}
+
+void osrfLogSetFile( const char* logfile ) {
+       if(!logfile) return;
+       if(_osrfLogFile) free(_osrfLogFile);
+       _osrfLogFile = strdup(logfile);
+}
+
+void osrfLogSetActivityEnabled( int enabled ) {
+       _osrfLogActivityEnabled = enabled;
+}
+
+void osrfLogSetAppname( const char* appname ) {
+       if(!appname) return;
+       if(_osrfLogAppname) free(_osrfLogAppname);
+       _osrfLogAppname = strdup(appname);
+
+       /* if syslogging, re-open the log with the appname */
+       if( _osrfLogType == OSRF_LOG_TYPE_SYSLOG) {
+               closelog();
+               openlog(_osrfLogAppname, 0, _osrfLogFacility);
+       }
+}
+
+void osrfLogSetSyslogFacility( int facility ) {
+       _osrfLogFacility = facility;
+}
+void osrfLogSetSyslogActFacility( int facility ) {
+       _osrfLogActFacility = facility;
+}
+
+/** Sets the global log level.  Any log statements with a higher level
+ * than "level" will not be logged */
+void osrfLogSetLevel( int loglevel ) {
+       _osrfLogLevel = loglevel;
+}
+
+/** Gets the current global log level. **/
+int osrfLogGetLevel( void ) {
+       return _osrfLogLevel;
+}
+
+void osrfLogError( const char* file, int line, const char* msg, ... ) 
+       { OSRF_LOG_GO(file, line, msg, OSRF_LOG_ERROR); }
+void osrfLogWarning( const char* file, int line, const char* msg, ... ) 
+       { OSRF_LOG_GO(file, line, msg, OSRF_LOG_WARNING); }
+void osrfLogInfo( const char* file, int line, const char* msg, ... ) 
+       { OSRF_LOG_GO(file, line, msg, OSRF_LOG_INFO); }
+void osrfLogDebug( const char* file, int line, const char* msg, ... ) 
+       { OSRF_LOG_GO(file, line, msg, OSRF_LOG_DEBUG); }
+void osrfLogInternal( const char* file, int line, const char* msg, ... ) 
+       { OSRF_LOG_GO(file, line, msg, OSRF_LOG_INTERNAL); }
+void osrfLogActivity( const char* file, int line, const char* msg, ... ) { 
+       OSRF_LOG_GO(file, line, msg, OSRF_LOG_ACTIVITY); 
+       _osrfLogDetail( OSRF_LOG_INFO, file, line, VA_BUF ); /* also log at info level */
+}
+
+/** Actually does the logging */
+static void _osrfLogDetail( int level, const char* filename, int line, char* msg ) {
+
+       if( level == OSRF_LOG_ACTIVITY && ! _osrfLogActivityEnabled ) return;
+       if( level > _osrfLogLevel ) return;
+       if(!msg) return;
+       if(!filename) filename = "";
+
+       char* label = "INFO";           /* level name */
+       int lvl = LOG_INFO;     /* syslog level */
+       int fac = _osrfLogFacility;
+
+       switch( level ) {
+               case OSRF_LOG_ERROR:            
+                       label = "ERR "; 
+                       lvl = LOG_ERR;
+                       break;
+
+               case OSRF_LOG_WARNING:  
+                       label = "WARN"; 
+                       lvl = LOG_WARNING;
+                       break;
+
+               case OSRF_LOG_INFO:             
+                       label = "INFO"; 
+                       lvl = LOG_INFO;
+                       break;
+
+               case OSRF_LOG_DEBUG:    
+                       label = "DEBG"; 
+                       lvl = LOG_DEBUG;
+                       break;
+
+               case OSRF_LOG_INTERNAL: 
+                       label = "INT "; 
+                       lvl = LOG_DEBUG;
+                       break;
+
+               case OSRF_LOG_ACTIVITY: 
+                       label = "ACT"; 
+                       lvl = LOG_INFO;
+                       fac = _osrfLogActFacility;
+                       break;
+       }
+
+   char* xid = (_osrfLogXid) ? _osrfLogXid : "";
+
+   int logtype = _osrfLogType;
+   if( logtype == OSRF_LOG_TYPE_FILE && !_osrfLogFile )
+   {
+          // No log file defined?  Temporarily reroute to stderr
+          logtype = OSRF_LOG_TYPE_STDERR;
+   }
+
+   if( logtype == OSRF_LOG_TYPE_SYSLOG ) {
+               char buf[1536];  
+               buf[0] = '\0';
+               /* give syslog some breathing room, and be cute about it */
+               strncat(buf, msg, 1535);
+               buf[1532] = '.';
+               buf[1533] = '.';
+               buf[1534] = '.';
+               buf[1535] = '\0';
+               syslog( fac | lvl, "[%s:%ld:%s:%d:%s] %s", label, (long) getpid(), filename, line, xid, buf );
+       }
+
+       else if( logtype == OSRF_LOG_TYPE_FILE )
+               _osrfLogToFile( "[%s:%ld:%s:%d:%s] %s", label, (long) getpid(), filename, line, xid, msg );
+
+       else if( logtype == OSRF_LOG_TYPE_STDERR )
+               fprintf( stderr, "[%s:%ld:%s:%d:%s] %s\n", label, (long) getpid(), filename, line, xid, msg );
+}
+
+
+static void _osrfLogToFile( const char* msg, ... ) {
+
+       if(!msg) return;
+       if(!_osrfLogFile) return;
+       VA_LIST_TO_STRING(msg);
+
+       if(!_osrfLogAppname) _osrfLogAppname = strdup("osrf");
+
+       char datebuf[36];
+       time_t t = time(NULL);
+       struct tm* tms = localtime(&t);
+       strftime(datebuf, sizeof( datebuf ), "%Y-%m-%d %H:%M:%S", tms);
+
+       FILE* file = fopen(_osrfLogFile, "a");
+       if(!file) {
+               fprintf(stderr,
+                       "Unable to fopen log file %s for writing; logging to standard error\n", _osrfLogFile);
+               fprintf(stderr, "%s %s %s\n", _osrfLogAppname, datebuf, VA_BUF );
+
+               return;
+       }
+
+       fprintf(file, "%s %s %s\n", _osrfLogAppname, datebuf, VA_BUF );
+       if( fclose(file) != 0 ) 
+               fprintf( stderr, "Error closing log file: %s", strerror(errno));
+
+}
+
+
+int osrfLogFacilityToInt( char* facility ) {
+       if(!facility) return LOG_LOCAL0;
+       if(strlen(facility) < 6) return LOG_LOCAL0;
+       switch( facility[5] ) {
+               case '0': return LOG_LOCAL0;
+               case '1': return LOG_LOCAL1;
+               case '2': return LOG_LOCAL2;
+               case '3': return LOG_LOCAL3;
+               case '4': return LOG_LOCAL4;
+               case '5': return LOG_LOCAL5;
+               case '6': return LOG_LOCAL6;
+               case '7': return LOG_LOCAL7;
+       }
+       return LOG_LOCAL0;
+}
+
+
diff --git a/trunk/src/libopensrf/md5.c b/trunk/src/libopensrf/md5.c
new file mode 100644 (file)
index 0000000..c8d7688
--- /dev/null
@@ -0,0 +1,367 @@
+/* --- The data --- */
+
+const char data[] =
+"/* --- The MD5 routines --- */\n\n/* MD5 routines, after Ron R"
+"ivest */\n/* Written by David Madore <david.madore@ens.fr>, w"
+"ith code taken in\n * part from Colin Plumb. */\n/* Public dom"
+"ain (1999/11/24) */\n\n/* Note: these routines do not depend o"
+"n endianness. */\n\n/* === The header === */\n\n/* Put this in m"
+"d5.h if you don't like having everything in one big\n * file."
+" */\n\n#ifndef _DMADORE_MD5_H\n#define _DMADORE_MD5_H\n\nstruct m"
+"d5_ctx {\n  /* The four chaining variables */\n  unsigned long"
+" buf[4];\n  /* Count number of message bits */\n  unsigned lon"
+"g bits[2];\n  /* Data being fed in */\n  unsigned long in[16];"
+"\n  /* Our position within the 512 bits (always between 0 and"
+" 63) */\n  int b;\n};\n\nvoid MD5_transform (unsigned long buf[4"
+"], const unsigned long in[16]);\nvoid MD5_start (struct md5_c"
+"tx *context);\nvoid MD5_feed (struct md5_ctx *context, unsign"
+"ed char inb);\nvoid MD5_stop (struct md5_ctx *context, unsign"
+"ed char digest[16]);\n\n#endif /* not defined _DMADORE_MD5_H *"
+"/\n\n/* === The implementation === */\n\n#define F1(x, y, z) (z "
+"^ (x & (y ^ z)))\n#define F2(x, y, z) F1(z, x, y)\n#define F3("
+"x, y, z) (x ^ y ^ z)\n#define F4(x, y, z) (y ^ (x | ~z))\n\n#de"
+"fine MD5STEP(f, w, x, y, z, data, s) \\\n\t{ w += f (x, y, z) +"
+" data;  w = w<<s | (w&0xffffffffUL)>>(32-s); \\\n\t  w += x; }\n"
+"\nvoid\nMD5_transform (unsigned long buf[4], const unsigned lo"
+"ng in[16])\n{\n  register unsigned long a, b, c, d;\n\n  a = buf"
+"[0];  b = buf[1];  c = buf[2];  d = buf[3];\n  MD5STEP(F1, a,"
+" b, c, d, in[0] + 0xd76aa478UL, 7);\n  MD5STEP(F1, d, a, b, c"
+", in[1] + 0xe8c7b756UL, 12);\n  MD5STEP(F1, c, d, a, b, in[2]"
+" + 0x242070dbUL, 17);\n  MD5STEP(F1, b, c, d, a, in[3] + 0xc1"
+"bdceeeUL, 22);\n  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0fafU"
+"L, 7);\n  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aUL, 12);\n"
+"  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613UL, 17);\n  MD5ST"
+"EP(F1, b, c, d, a, in[7] + 0xfd469501UL, 22);\n  MD5STEP(F1, "
+"a, b, c, d, in[8] + 0x698098d8UL, 7);\n  MD5STEP(F1, d, a, b,"
+" c, in[9] + 0x8b44f7afUL, 12);\n  MD5STEP(F1, c, d, a, b, in["
+"10] + 0xffff5bb1UL, 17);\n  MD5STEP(F1, b, c, d, a, in[11] + "
+"0x895cd7beUL, 22);\n  MD5STEP(F1, a, b, c, d, in[12] + 0x6b90"
+"1122UL, 7);\n  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193UL,"
+" 12);\n  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eUL, 17);\n"
+"  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821UL, 22);\n  MD5S"
+"TEP(F2, a, b, c, d, in[1] + 0xf61e2562UL, 5);\n  MD5STEP(F2, "
+"d, a, b, c, in[6] + 0xc040b340UL, 9);\n  MD5STEP(F2, c, d, a,"
+" b, in[11] + 0x265e5a51UL, 14);\n  MD5STEP(F2, b, c, d, a, in"
+"[0] + 0xe9b6c7aaUL, 20);\n  MD5STEP(F2, a, b, c, d, in[5] + 0"
+"xd62f105dUL, 5);\n  MD5STEP(F2, d, a, b, c, in[10] + 0x024414"
+"53UL, 9);\n  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681UL, 1"
+"4);\n  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8UL, 20);\n  M"
+"D5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6UL, 5);\n  MD5STEP(F"
+"2, d, a, b, c, in[14] + 0xc33707d6UL, 9);\n  MD5STEP(F2, c, d"
+", a, b, in[3] + 0xf4d50d87UL, 14);\n  MD5STEP(F2, b, c, d, a,"
+" in[8] + 0x455a14edUL, 20);\n  MD5STEP(F2, a, b, c, d, in[13]"
+" + 0xa9e3e905UL, 5);\n  MD5STEP(F2, d, a, b, c, in[2] + 0xfce"
+"fa3f8UL, 9);\n  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9UL,"
+" 14);\n  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aUL, 20);\n"
+"  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942UL, 4);\n  MD5STE"
+"P(F3, d, a, b, c, in[8] + 0x8771f681UL, 11);\n  MD5STEP(F3, c"
+", d, a, b, in[11] + 0x6d9d6122UL, 16);\n  MD5STEP(F3, b, c, d"
+", a, in[14] + 0xfde5380cUL, 23);\n  MD5STEP(F3, a, b, c, d, i"
+"n[1] + 0xa4beea44UL, 4);\n  MD5STEP(F3, d, a, b, c, in[4] + 0"
+"x4bdecfa9UL, 11);\n  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b"
+"60UL, 16);\n  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70UL, "
+"23);\n  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6UL, 4);\n  "
+"MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faUL, 11);\n  MD5STEP"
+"(F3, c, d, a, b, in[3] + 0xd4ef3085UL, 16);\n  MD5STEP(F3, b,"
+" c, d, a, in[6] + 0x04881d05UL, 23);\n  MD5STEP(F3, a, b, c, "
+"d, in[9] + 0xd9d4d039UL, 4);\n  MD5STEP(F3, d, a, b, c, in[12"
+"] + 0xe6db99e5UL, 11);\n  MD5STEP(F3, c, d, a, b, in[15] + 0x"
+"1fa27cf8UL, 16);\n  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac566"
+"5UL, 23);\n  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244UL, 6)"
+";\n  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97UL, 10);\n  MD5"
+"STEP(F4, c, d, a, b, in[14] + 0xab9423a7UL, 15);\n  MD5STEP(F"
+"4, b, c, d, a, in[5] + 0xfc93a039UL, 21);\n  MD5STEP(F4, a, b"
+", c, d, in[12] + 0x655b59c3UL, 6);\n  MD5STEP(F4, d, a, b, c,"
+" in[3] + 0x8f0ccc92UL, 10);\n  MD5STEP(F4, c, d, a, b, in[10]"
+" + 0xffeff47dUL, 15);\n  MD5STEP(F4, b, c, d, a, in[1] + 0x85"
+"845dd1UL, 21);\n  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4fU"
+"L, 6);\n  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0UL, 10);"
+"\n  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314UL, 15);\n  MD5S"
+"TEP(F4, b, c, d, a, in[13] + 0x4e0811a1UL, 21);\n  MD5STEP(F4"
+", a, b, c, d, in[4] + 0xf7537e82UL, 6);\n  MD5STEP(F4, d, a, "
+"b, c, in[11] + 0xbd3af235UL, 10);\n  MD5STEP(F4, c, d, a, b, "
+"in[2] + 0x2ad7d2bbUL, 15);\n  MD5STEP(F4, b, c, d, a, in[9] +"
+" 0xeb86d391UL, 21);\n  buf[0] += a;  buf[1] += b;  buf[2] += "
+"c;  buf[3] += d;\n}\n\n#undef F1\n#undef F2\n#undef F3\n#undef F4\n"
+"#undef MD5STEP\n\nvoid\nMD5_start (struct md5_ctx *ctx)\n{\n  int"
+" i;\n\n  ctx->buf[0] = 0x67452301UL;\n  ctx->buf[1] = 0xefcdab8"
+"9UL;\n  ctx->buf[2] = 0x98badcfeUL;\n  ctx->buf[3] = 0x1032547"
+"6UL;\n  ctx->bits[0] = 0;\n  ctx->bits[1] = 0;\n  for ( i=0 ; i"
+"<16 ; i++ )\n    ctx->in[i] = 0;\n  ctx->b = 0;\n}\n\nvoid\nMD5_fe"
+"ed (struct md5_ctx *ctx, unsigned char inb)\n{\n  int i;\n  uns"
+"igned long temp;\n\n  ctx->in[ctx->b/4] |= ((unsigned long)inb"
+") << ((ctx->b%4)*8);\n  if ( ++ctx->b >= 64 )\n    {\n      MD5"
+"_transform (ctx->buf, ctx->in);\n      ctx->b = 0;\n      for "
+"( i=0 ; i<16 ; i++ )\n\tctx->in[i] = 0;\n    }\n  temp = ctx->bi"
+"ts[0];\n  ctx->bits[0] += 8;\n  if ( (temp&0xffffffffUL) > (ct"
+"x->bits[0]&0xffffffffUL) )\n    ctx->bits[1]++;\n}\n\nvoid\nMD5_s"
+"top (struct md5_ctx *ctx, unsigned char digest[16])\n{\n  int "
+"i;\n  unsigned long bits[2];\n\n  for ( i=0 ; i<2 ; i++ )\n    b"
+"its[i] = ctx->bits[i];\n  MD5_feed (ctx, 0x80);\n  for ( ; ctx"
+"->b!=56 ; )\n    MD5_feed (ctx, 0);\n  for ( i=0 ; i<2 ; i++ )"
+"\n    {\n      MD5_feed (ctx, bits[i]&0xff);\n      MD5_feed (c"
+"tx, (bits[i]>>8)&0xff);\n      MD5_feed (ctx, (bits[i]>>16)&0"
+"xff);\n      MD5_feed (ctx, (bits[i]>>24)&0xff);\n    }\n  for "
+"( i=0 ; i<4 ; i++ )\n    {\n      digest[4*i] = ctx->buf[i]&0x"
+"ff;\n      digest[4*i+1] = (ctx->buf[i]>>8)&0xff;\n      diges"
+"t[4*i+2] = (ctx->buf[i]>>16)&0xff;\n      digest[4*i+3] = (ct"
+"x->buf[i]>>24)&0xff;\n    }\n}\n\f\n/* --- The core of the progra"
+"m --- */\n\n#include <stdio.h>\n#include <string.h>\n\n#define LA"
+"RGE_ENOUGH 16384\n\nchar buffer[LARGE_ENOUGH];\n\nint\nmain (int "
+"argc, char *argv[])\n{\n  unsigned int i;\n\n  buffer[0] = 0;\n  "
+"strcat (buffer, \"/* --- The data --- */\\n\\n\");\n  strcat (buf"
+"fer, \"const char data[] =\");\n  for ( i=0 ; data[i] ; i++ )\n "
+"   {\n      if ( i%60 == 0 )\n\tstrcat (buffer, \"\\n\\\"\");\n      "
+"switch ( data[i] )\n\t{\n\tcase '\\\\':\n\tcase '\"':\n\t  strcat (buff"
+"er, \"\\\\\");\n\t  buffer[strlen(buffer)+1] = 0;\n\t  buffer[strlen"
+"(buffer)] = data[i];\n\t  break;\n\tcase '\\n':\n\t  strcat (buffer"
+", \"\\\\n\");\n\t  break;\n\tcase '\\t':\n\t  strcat (buffer, \"\\\\t\");\n\t"
+"  break;\n\tcase '\\f':\n\t  strcat (buffer, \"\\\\f\");\n\t  break;\n\td"
+"efault:\n\t  buffer[strlen(buffer)+1] = 0;\n\t  buffer[strlen(bu"
+"ffer)] = data[i];\n\t}\n      if ( i%60 == 59 || !data[i+1] )\n\t"
+"strcat (buffer, \"\\\"\");\n    }\n  strcat (buffer, \";\\n\\f\\n\");\n "
+" strcat (buffer, data);\n  if ( argc >= 2 && strcmp (argv[1],"
+" \"xyzzy\") == 0 )\n    printf (\"%s\", buffer);\n  else\n    {\n   "
+"   struct md5_ctx ctx;\n      unsigned char digest[16];\n\n    "
+"  MD5_start (&ctx);\n      for ( i=0 ; buffer[i] ; i++ )\n\tMD5"
+"_feed (&ctx, buffer[i]);\n      MD5_stop (&ctx, digest);\n    "
+"  for ( i=0 ; i<16 ; i++ )\n\tprintf (\"%02x\", digest[i]);\n    "
+"  printf (\"\\n\");\n    }\n  return 0;\n}\n";
+
+
+//#include "md5.h"
+#include "opensrf/md5.h"
+
+
+/* --- The MD5 routines --- */
+
+/* MD5 routines, after Ron Rivest */
+/* Written by David Madore <david.madore@ens.fr>, with code taken in
+ * part from Colin Plumb. */
+/* Public domain (1999/11/24) */
+
+/* Note: these routines do not depend on endianness. */
+
+/* === The header === */
+
+/* Put this in md5.h if you don't like having everything in one big
+ * file. */
+
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+       { w += f (x, y, z) + data;  w = w<<s | (w&0xffffffffUL)>>(32-s); \
+         w += x; }
+
+void
+MD5_transform (unsigned long buf[4], const unsigned long in[16])
+{
+  register unsigned long a, b, c, d;
+
+  a = buf[0];  b = buf[1];  c = buf[2];  d = buf[3];
+  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478UL, 7);
+  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756UL, 12);
+  MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbUL, 17);
+  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeUL, 22);
+  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0fafUL, 7);
+  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aUL, 12);
+  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613UL, 17);
+  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501UL, 22);
+  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8UL, 7);
+  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7afUL, 12);
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1UL, 17);
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beUL, 22);
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122UL, 7);
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193UL, 12);
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eUL, 17);
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821UL, 22);
+  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562UL, 5);
+  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340UL, 9);
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51UL, 14);
+  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaUL, 20);
+  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dUL, 5);
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453UL, 9);
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681UL, 14);
+  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8UL, 20);
+  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6UL, 5);
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6UL, 9);
+  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87UL, 14);
+  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edUL, 20);
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905UL, 5);
+  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8UL, 9);
+  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9UL, 14);
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aUL, 20);
+  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942UL, 4);
+  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681UL, 11);
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122UL, 16);
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cUL, 23);
+  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44UL, 4);
+  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9UL, 11);
+  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60UL, 16);
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70UL, 23);
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6UL, 4);
+  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faUL, 11);
+  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085UL, 16);
+  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05UL, 23);
+  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039UL, 4);
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5UL, 11);
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8UL, 16);
+  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665UL, 23);
+  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244UL, 6);
+  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97UL, 10);
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7UL, 15);
+  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039UL, 21);
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3UL, 6);
+  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92UL, 10);
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dUL, 15);
+  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1UL, 21);
+  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4fUL, 6);
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0UL, 10);
+  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314UL, 15);
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1UL, 21);
+  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82UL, 6);
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235UL, 10);
+  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbUL, 15);
+  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391UL, 21);
+  buf[0] += a;  buf[1] += b;  buf[2] += c;  buf[3] += d;
+}
+
+#undef F1
+#undef F2
+#undef F3
+#undef F4
+#undef MD5STEP
+
+void
+MD5_start (struct md5_ctx *ctx)
+{
+  int i;
+
+  ctx->buf[0] = 0x67452301UL;
+  ctx->buf[1] = 0xefcdab89UL;
+  ctx->buf[2] = 0x98badcfeUL;
+  ctx->buf[3] = 0x10325476UL;
+  ctx->bits[0] = 0;
+  ctx->bits[1] = 0;
+  for ( i=0 ; i<16 ; i++ )
+    ctx->in[i] = 0;
+  ctx->b = 0;
+}
+
+void
+MD5_feed (struct md5_ctx *ctx, unsigned char inb)
+{
+  int i;
+  unsigned long temp;
+
+  ctx->in[ctx->b/4] |= ((unsigned long)inb) << ((ctx->b%4)*8);
+  if ( ++ctx->b >= 64 )
+    {
+      MD5_transform (ctx->buf, ctx->in);
+      ctx->b = 0;
+      for ( i=0 ; i<16 ; i++ )
+       ctx->in[i] = 0;
+    }
+  temp = ctx->bits[0];
+  ctx->bits[0] += 8;
+  if ( (temp&0xffffffffUL) > (ctx->bits[0]&0xffffffffUL) )
+    ctx->bits[1]++;
+}
+
+void
+MD5_stop (struct md5_ctx *ctx, unsigned char digest[16])
+{
+  int i;
+  unsigned long bits[2];
+
+  for ( i=0 ; i<2 ; i++ )
+    bits[i] = ctx->bits[i];
+  MD5_feed (ctx, 0x80);
+  for ( ; ctx->b!=56 ; )
+    MD5_feed (ctx, 0);
+  for ( i=0 ; i<2 ; i++ )
+    {
+      MD5_feed (ctx, bits[i]&0xff);
+      MD5_feed (ctx, (bits[i]>>8)&0xff);
+      MD5_feed (ctx, (bits[i]>>16)&0xff);
+      MD5_feed (ctx, (bits[i]>>24)&0xff);
+    }
+  for ( i=0 ; i<4 ; i++ )
+    {
+      digest[4*i] = ctx->buf[i]&0xff;
+      digest[4*i+1] = (ctx->buf[i]>>8)&0xff;
+      digest[4*i+2] = (ctx->buf[i]>>16)&0xff;
+      digest[4*i+3] = (ctx->buf[i]>>24)&0xff;
+    }
+}
+\f
+/* --- The core of the program --- */
+
+#include <stdio.h>
+#include <string.h>
+
+#define LARGE_ENOUGH 16384
+
+char buffer[LARGE_ENOUGH];
+
+/*
+int
+main (int argc, char *argv[])
+{
+  unsigned int i;
+
+  buffer[0] = 0;
+  strcat (buffer, \n\n");
+  strcat (buffer, "const char data[] =");
+  for ( i=0 ; data[i] ; i++ )
+    {
+      if ( i%60 == 0 )
+       strcat (buffer, "\n\"");
+      switch ( data[i] )
+       {
+       case '\\':
+       case '"':
+         strcat (buffer, "\\");
+         buffer[strlen(buffer)+1] = 0;
+         buffer[strlen(buffer)] = data[i];
+         break;
+       case '\n':
+         strcat (buffer, "\\n");
+         break;
+       case '\t':
+         strcat (buffer, "\\t");
+         break;
+       case '\f':
+         strcat (buffer, "\\f");
+         break;
+       default:
+         buffer[strlen(buffer)+1] = 0;
+         buffer[strlen(buffer)] = data[i];
+       }
+      if ( i%60 == 59 || !data[i+1] )
+       strcat (buffer, "\"");
+    }
+  strcat (buffer, ";\n\f\n");
+  strcat (buffer, data);
+  if ( argc >= 2 && strcmp (argv[1], "xyzzy") == 0 )
+    printf ("%s", buffer);
+  else
+    {
+      struct md5_ctx ctx;
+      unsigned char digest[16];
+
+      MD5_start (&ctx);
+      for ( i=0 ; buffer[i] ; i++ )
+       MD5_feed (&ctx, buffer[i]);
+      MD5_stop (&ctx, digest);
+      for ( i=0 ; i<16 ; i++ )
+       printf ("%02x", digest[i]);
+      printf ("\n");
+    }
+  return 0;
+}
+*/
diff --git a/trunk/src/libopensrf/opensrf.c b/trunk/src/libopensrf/opensrf.c
new file mode 100644 (file)
index 0000000..e7215d2
--- /dev/null
@@ -0,0 +1,37 @@
+#include <opensrf/osrf_system.h>
+
+int main( int argc, char* argv[] ) {
+
+       if( argc < 4 ) {
+               fprintf(stderr, "Usage: %s <host> <bootstrap_config> <config_context>\n", argv[0]);
+               return 1;
+       }
+
+       /* these must be strdup'ed because init_proc_title / set_proc_title 
+               are evil and overwrite the argv memory */
+       char* host              = strdup( argv[1] );
+       char* config    = strdup( argv[2] );
+       char* context   = strdup( argv[3] );
+
+       init_proc_title( argc, argv );
+       set_proc_title( "OpenSRF System-C" );
+
+       int ret = osrfSystemBootstrap( host, config, context );
+
+       if (ret != 0) {
+               osrfLogError(
+                       OSRF_LOG_MARK,
+                       "Server Loop returned an error condition, exiting with %d",
+                       ret
+               );
+       }
+
+
+       free(host);
+       free(config);
+       free(context);
+
+       return ret;
+}
+
+
diff --git a/trunk/src/libopensrf/osrfConfig.c b/trunk/src/libopensrf/osrfConfig.c
new file mode 100644 (file)
index 0000000..44ff14d
--- /dev/null
@@ -0,0 +1,142 @@
+/* defines the currently used bootstrap config file */
+#include <opensrf/osrfConfig.h>
+
+static osrfConfig* osrfConfigDefault = NULL;
+
+
+void osrfConfigSetDefaultConfig(osrfConfig* cfg) {
+       if(cfg) {
+               if( osrfConfigDefault )
+                       osrfConfigFree( osrfConfigDefault );
+               osrfConfigDefault = cfg;
+       }
+}
+
+void osrfConfigFree(osrfConfig* cfg) {
+       if(cfg) {
+               jsonObjectFree(cfg->config);
+               free(cfg->configContext);
+               free(cfg);
+       }       
+}
+
+
+int osrfConfigHasDefaultConfig() {
+       return ( osrfConfigDefault != NULL );
+}
+
+
+void osrfConfigCleanup() { 
+       osrfConfigFree(osrfConfigDefault);
+       osrfConfigDefault = NULL;
+}
+
+
+void osrfConfigReplaceConfig(osrfConfig* cfg, const jsonObject* obj) {
+       if(!cfg || !obj) return;
+       jsonObjectFree(cfg->config);
+       cfg->config = jsonObjectClone(obj);     
+}
+
+osrfConfig* osrfConfigInit(const char* configFile, const char* configContext) {
+       if(!configFile) return NULL;
+
+       // Load XML from the configuration file
+       
+       xmlDocPtr doc = xmlParseFile(configFile);
+       if(!doc) {
+               osrfLogWarning( OSRF_LOG_MARK, "Unable to parse XML config file %s", configFile);
+               return NULL;
+       }
+
+       // Translate it into a jsonObject
+       
+       jsonObject* json_config = xmlDocToJSON(doc);
+       xmlFreeDoc(doc);
+
+       if(!json_config ) {
+               osrfLogWarning( OSRF_LOG_MARK, "xmlDocToJSON failed for config %s", configFile);
+               return NULL;
+       }       
+
+       // Build an osrfConfig and return it by pointer
+       
+       osrfConfig* cfg = safe_malloc(sizeof(osrfConfig));
+
+       if(configContext) cfg->configContext = strdup(configContext);
+       else cfg->configContext = NULL;
+
+       cfg->config = json_config;
+       
+       return cfg;
+}
+
+
+char* osrfConfigGetValue(const osrfConfig* cfg, const char* path, ...) {
+       if(!path) return NULL;
+       if(!cfg) cfg = osrfConfigDefault;
+       if(!cfg) { 
+        osrfLogWarning( OSRF_LOG_MARK, "No Config object in osrfConfigGetValue()"); 
+        return NULL; 
+    }
+
+       VA_LIST_TO_STRING(path);
+       jsonObject* obj;
+
+       if(cfg->configContext) 
+               obj = jsonObjectGetIndex(
+            jsonObjectFindPath(cfg->config, "//%s%s", cfg->configContext, VA_BUF), 0);
+       else
+               obj = jsonObjectFindPath( cfg->config, VA_BUF);
+
+       char* val = jsonObjectToSimpleString(obj);
+    jsonObjectFree(obj);
+    return val;
+}
+
+jsonObject* osrfConfigGetValueObject(osrfConfig* cfg, char* path, ...) {
+       if(!path) return NULL;
+       if(!cfg) cfg = osrfConfigDefault;
+       VA_LIST_TO_STRING(path);
+       if(cfg->configContext) 
+        return jsonObjectFindPath(cfg->config, "//%s%s", cfg->configContext, VA_BUF);
+       else
+               return jsonObjectFindPath(cfg->config, VA_BUF);
+}
+
+int osrfConfigGetValueList(const osrfConfig* cfg, osrfStringArray* arr,
+               const char* path, ...) {
+
+       if(!arr || !path) return 0;
+       if(!cfg) cfg = osrfConfigDefault;
+       if(!cfg) { osrfLogWarning( OSRF_LOG_MARK, "No Config object!"); return -1;}
+
+       VA_LIST_TO_STRING(path);
+
+       jsonObject* obj;
+       if(cfg->configContext) {
+               obj = jsonObjectFindPath( cfg->config, "//%s%s", cfg->configContext, VA_BUF);
+       } else {
+               obj = jsonObjectFindPath( cfg->config, VA_BUF);
+       }
+
+       int count = 0;
+
+       if(obj && obj->type == JSON_ARRAY ) {
+
+               int i;
+               for( i = 0; i < obj->size; i++ ) {
+
+                       char* val = jsonObjectToSimpleString(jsonObjectGetIndex(obj, i));
+                       if(val) {
+                               count++;
+                               osrfStringArrayAdd(arr, val);
+                               free(val);
+                       }
+               }
+       }
+
+       jsonObjectFree(obj);
+       return count;
+}
+
diff --git a/trunk/src/libopensrf/osrf_app_session.c b/trunk/src/libopensrf/osrf_app_session.c
new file mode 100644 (file)
index 0000000..0b274f7
--- /dev/null
@@ -0,0 +1,735 @@
+#include <opensrf/osrf_app_session.h>
+#include <time.h>
+
+/** Send the given message */
+static int _osrf_app_session_send( osrfAppSession*, osrfMessage* msg );
+
+static int osrfAppSessionMakeLocaleRequest(
+               osrfAppSession* session, const jsonObject* params, const char* method_name,
+               int protocol, osrfStringArray* param_strings, char* locale );
+
+/* the global app_session cache */
+osrfHash* osrfAppSessionCache = NULL;
+
+// --------------------------------------------------------------------------
+// --------------------------------------------------------------------------
+// Request API
+// --------------------------------------------------------------------------
+
+/** Allocates and initializes a new app_request object */
+static osrfAppRequest* _osrf_app_request_init(
+               osrfAppSession* session, osrfMessage* msg ) {
+
+       osrfAppRequest* req =
+               (osrfAppRequest*) safe_malloc(sizeof(osrfAppRequest));
+
+       req->session            = session;
+       req->request_id = msg->thread_trace;
+       req->complete           = 0;
+       req->payload            = msg;
+       req->result                     = NULL;
+       req->reset_timeout  = 0;
+
+       return req;
+
+}
+
+
+void osrfAppSessionCleanup() {
+       osrfHashFree(osrfAppSessionCache);      
+}
+
+/** Frees memory used by an app_request object */
+static void _osrf_app_request_free( void * req ){
+       if( req == NULL ) return;
+       osrfAppRequest* r = (osrfAppRequest*) req;
+       if( r->payload ) osrfMessageFree( r->payload );
+       free( r );
+}
+
+/** Pushes the given message onto the list of 'responses' to this request */
+static void _osrf_app_request_push_queue( osrfAppRequest* req, osrfMessage* result ){
+       if(req == NULL || result == NULL) return;
+       osrfLogDebug( OSRF_LOG_MARK,  "App Session pushing request [%d] onto request queue", result->thread_trace );
+       if(req->result == NULL) {
+               req->result = result;
+
+       } else {
+               
+               osrfMessage* ptr = req->result;
+               osrfMessage* ptr2 = req->result->next;
+               while( ptr2 ) {
+                       ptr = ptr2;
+                       ptr2 = ptr2->next;
+               }
+               ptr->next = result;
+       }
+}
+
+/** Removes this app_request from our session request set */
+void osrf_app_session_request_finish( 
+               osrfAppSession* session, int req_id ){
+
+       if(session == NULL) return;
+       osrfAppRequest* req = OSRF_LIST_GET_INDEX( session->request_queue, req_id );
+       if(req == NULL) return;
+       osrfListRemove( req->session->request_queue, req->request_id );
+}
+
+
+void osrf_app_session_request_reset_timeout( osrfAppSession* session, int req_id ) {
+       if(session == NULL) return;
+       osrfLogDebug( OSRF_LOG_MARK, "Resetting request timeout %d", req_id );
+       osrfAppRequest* req = OSRF_LIST_GET_INDEX( session->request_queue, req_id );
+       if(req == NULL) return;
+       req->reset_timeout = 1;
+}
+
+/** Checks the receive queue for messages.  If any are found, the first
+  * is popped off and 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.
+  */
+static osrfMessage* _osrf_app_request_recv( osrfAppRequest* req, int timeout ) {
+
+       if(req == NULL) return NULL;
+
+       if( req->result != NULL ) {
+               /* pop off the first message in the list */
+               osrfMessage* tmp_msg = req->result;
+               req->result = req->result->next;
+               return tmp_msg;
+       }
+
+       time_t start = time(NULL);      
+       time_t remaining = (time_t) timeout;
+
+       while( remaining >= 0 ) {
+               /* tell the session to wait for stuff */
+               osrfLogDebug( OSRF_LOG_MARK,  "In app_request receive with remaining time [%d]", (int) remaining );
+
+               osrf_app_session_queue_wait( req->session, 0, NULL );
+        if(req->session->transport_error) {
+            osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
+            return NULL;
+        }
+
+               if( req->result != NULL ) { /* if we received anything */
+                       /* pop off the first message in the list */
+                       osrfLogDebug( OSRF_LOG_MARK,  "app_request_recv received a message, returning it");
+                       osrfMessage* ret_msg = req->result;
+                       osrfMessage* tmp_msg = ret_msg->next;
+                       req->result = tmp_msg;
+                       if (ret_msg->sender_locale) {
+                               if (req->session->session_locale)
+                                       free(req->session->session_locale);
+                               req->session->session_locale = strdup(ret_msg->sender_locale);
+                       }
+                       return ret_msg;
+               }
+
+               if( req->complete )
+                       return NULL;
+
+               osrf_app_session_queue_wait( req->session, (int) remaining, NULL );
+
+        if(req->session->transport_error) {
+            osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
+            return NULL;
+        }
+
+               if( req->result != NULL ) { /* if we received anything */
+                       /* pop off the first message in the list */
+                       osrfLogDebug( OSRF_LOG_MARK,  "app_request_recv received a message, returning it");
+                       osrfMessage* ret_msg = req->result;
+                       osrfMessage* tmp_msg = ret_msg->next;
+                       req->result = tmp_msg;
+                       if (ret_msg->sender_locale) {
+                               if (req->session->session_locale)
+                                       free(req->session->session_locale);
+                               req->session->session_locale = strdup(ret_msg->sender_locale);
+                       }
+                       return ret_msg;
+               }
+               if( req->complete )
+                       return NULL;
+
+               if(req->reset_timeout) {
+                       remaining = (time_t) timeout;
+                       req->reset_timeout = 0;
+                       osrfLogDebug( OSRF_LOG_MARK, "Received a timeout reset");
+               } else {
+                       remaining -= (int) (time(NULL) - start);
+               }
+       }
+
+    char* paramString = jsonObjectToJSON(req->payload->_params);
+       osrfLogInfo( OSRF_LOG_MARK, "Returning NULL from app_request_recv after timeout: %s %s",
+        req->payload->method_name, paramString);
+    free(paramString);
+
+       return NULL;
+}
+
+/** Resend this requests original request message */
+static int _osrf_app_request_resend( osrfAppRequest* req ) {
+       if(req == NULL) return 0;
+       if(!req->complete) {
+               osrfLogDebug( OSRF_LOG_MARK,  "Resending request [%d]", req->request_id );
+               return _osrf_app_session_send( req->session, req->payload );
+       }
+       return 1;
+}
+
+
+
+// --------------------------------------------------------------------------
+// --------------------------------------------------------------------------
+// Session API
+// --------------------------------------------------------------------------
+
+/** returns a session from the global session hash */
+char* osrf_app_session_set_locale( osrfAppSession* session, const char* locale ) {
+       if (!session || !locale)
+               return NULL;
+
+       if(session->session_locale)
+               free(session->session_locale);
+
+       session->session_locale = strdup( locale );
+       return session->session_locale;
+}
+
+/** returns a session from the global session hash */
+osrfAppSession* osrf_app_session_find_session( const char* session_id ) {
+       if(session_id) return osrfHashGet(osrfAppSessionCache, session_id);
+       return NULL;
+}
+
+
+/** adds a session to the global session cache */
+static void _osrf_app_session_push_session( osrfAppSession* session ) {
+       if(!session) return;
+       if( osrfAppSessionCache == NULL ) osrfAppSessionCache = osrfNewHash();
+       if( osrfHashGet( osrfAppSessionCache, session->session_id ) ) return;
+       osrfHashSet( osrfAppSessionCache, session, session->session_id );
+}
+
+/** Allocates and initializes a new app_session */
+
+osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) {
+
+       if (!remote_service) {
+               osrfLogWarning( OSRF_LOG_MARK, "No remote service specified in osrf_app_client_session_init");
+               return NULL;
+       }
+
+       osrfAppSession* session = safe_malloc(sizeof(osrfAppSession));
+
+       session->transport_handle = osrfSystemGetTransportClient();
+       if( session->transport_handle == NULL ) {
+               osrfLogWarning( OSRF_LOG_MARK, "No transport client for service 'client'");
+               free( session );
+               return NULL;
+       }
+
+       osrfStringArray* arr = osrfNewStringArray(8);
+       osrfConfigGetValueList(NULL, arr, "/domain");
+       char* domain = osrfStringArrayGetString(arr, 0);
+
+       if (!domain) {
+               osrfLogWarning( OSRF_LOG_MARK, "No domains specified in the OpenSRF config file");
+               free( session );
+               osrfStringArrayFree(arr);
+               return NULL;
+       }
+
+       char* router_name = osrfConfigGetValue(NULL, "/router_name");
+       if (!router_name) {
+               osrfLogWarning( OSRF_LOG_MARK, "No router name specified in the OpenSRF config file");
+               free( session );
+               osrfStringArrayFree(arr);
+               return NULL;
+       }
+
+       char target_buf[512];
+       target_buf[ 0 ] = '\0';
+
+       int len = snprintf( target_buf, sizeof(target_buf), "%s@%s/%s",
+                       router_name ? router_name : "(null)",
+                       domain ? domain : "(null)",
+                       remote_service ? remote_service : "(null)" );
+       osrfStringArrayFree(arr);
+       //free(domain);
+       free(router_name);
+
+       if( len >= sizeof( target_buf ) ) {
+               osrfLogWarning( OSRF_LOG_MARK, "Buffer overflow for remote_id");
+               free( session );
+               return NULL;
+       }
+
+       session->request_queue = osrfNewList();
+       session->request_queue->freeItem = &_osrf_app_request_free;
+       session->remote_id = strdup(target_buf);
+       session->orig_remote_id = strdup(session->remote_id);
+       session->remote_service = strdup(remote_service);
+       session->session_locale = NULL;
+    session->transport_error = 0;
+
+       #ifdef ASSUME_STATELESS
+       session->stateless = 1;
+       osrfLogDebug( OSRF_LOG_MARK, "%s session is stateless", remote_service );
+       #else
+       session->stateless = 0;
+       osrfLogDebug( OSRF_LOG_MARK, "%s session is NOT stateless", remote_service );
+       #endif
+
+       /* build a chunky, random session id */
+       char id[256];
+
+       snprintf(id, sizeof(id), "%f.%d%ld", get_timestamp_millis(), (int)time(NULL), (long) getpid());
+       session->session_id = strdup(id);
+       osrfLogDebug( OSRF_LOG_MARK,  "Building a new client session with id [%s] [%s]", 
+                       session->remote_service, session->session_id );
+
+       session->thread_trace = 0;
+       session->state = OSRF_SESSION_DISCONNECTED;
+       session->type = OSRF_SESSION_CLIENT;
+       //session->next = NULL;
+
+       session->userData = NULL;
+       session->userDataFree = NULL;
+       
+       _osrf_app_session_push_session( session );
+       return session;
+}
+
+osrfAppSession* osrf_app_server_session_init(
+               const char* session_id, const char* our_app, const char* remote_id ) {
+
+       osrfLogDebug( OSRF_LOG_MARK, "Initing server session with session id %s, service %s,"
+                       " and remote_id %s", session_id, our_app, remote_id );
+
+       osrfAppSession* session = osrf_app_session_find_session( session_id );
+       if(session) return session;
+
+       session = safe_malloc(sizeof(osrfAppSession));
+
+       session->transport_handle = osrfSystemGetTransportClient();
+       if( session->transport_handle == NULL ) {
+               osrfLogWarning( OSRF_LOG_MARK, "No transport client for service '%s'", our_app );
+               free(session);
+               return NULL;
+       }
+
+       int stateless = 0;
+       char* statel = osrf_settings_host_value("/apps/%s/stateless", our_app );
+       if(statel) stateless = atoi(statel);
+       free(statel);
+
+
+       session->request_queue = osrfNewList();
+       session->request_queue->freeItem = &_osrf_app_request_free;
+       session->remote_id = strdup(remote_id);
+       session->orig_remote_id = strdup(remote_id);
+       session->session_id = strdup(session_id);
+       session->remote_service = strdup(our_app);
+       session->stateless = stateless;
+
+       #ifdef ASSUME_STATELESS
+       session->stateless = 1;
+       #else
+       session->stateless = 0;
+       #endif
+
+       session->thread_trace = 0;
+       session->state = OSRF_SESSION_DISCONNECTED;
+       session->type = OSRF_SESSION_SERVER;
+       session->session_locale = NULL;
+
+       session->userData = NULL;
+       session->userDataFree = NULL;
+       
+       _osrf_app_session_push_session( session );
+       return session;
+
+}
+
+
+
+/** frees memory held by a session */
+static void _osrf_app_session_free( osrfAppSession* session ){
+       if(session==NULL)
+               return;
+
+       if( session->userDataFree && session->userData ) 
+               session->userDataFree(session->userData);
+       
+       if(session->session_locale)
+               free(session->session_locale);
+
+       free(session->remote_id);
+       free(session->orig_remote_id);
+       free(session->session_id);
+       free(session->remote_service);
+       osrfListFree(session->request_queue);
+       free(session);
+}
+
+int osrfAppSessionMakeRequest(
+               osrfAppSession* session, const jsonObject* params,
+               const char* method_name, int protocol, osrfStringArray* param_strings ) {
+
+       return osrfAppSessionMakeLocaleRequest( session, params,
+                       method_name, protocol, param_strings, NULL );
+}
+
+static int osrfAppSessionMakeLocaleRequest(
+               osrfAppSession* session, const jsonObject* params, const char* method_name,
+               int protocol, osrfStringArray* param_strings, char* locale ) {
+
+       if(session == NULL) return -1;
+
+       osrfLogMkXid();
+
+       osrfMessage* req_msg = osrf_message_init( REQUEST, ++(session->thread_trace), protocol );
+       osrf_message_set_method(req_msg, method_name);
+
+       if (locale) {
+               osrf_message_set_locale(req_msg, locale);
+       } else if (session->session_locale) {
+               osrf_message_set_locale(req_msg, session->session_locale);
+       }
+
+       if(params) {
+               osrf_message_set_params(req_msg, params);
+
+       } else {
+
+               if(param_strings) {
+                       int i;
+                       for(i = 0; i!= param_strings->size ; i++ ) {
+                               osrf_message_add_param(req_msg,
+                                       osrfStringArrayGetString(param_strings,i));
+                       }
+               }
+       }
+
+       osrfAppRequest* req = _osrf_app_request_init( session, req_msg );
+       if(_osrf_app_session_send( session, req_msg ) ) {
+               osrfLogWarning( OSRF_LOG_MARK,  "Error sending request message [%d]", session->thread_trace );
+               _osrf_app_request_free(req);
+               return -1;
+       }
+
+       osrfLogDebug( OSRF_LOG_MARK,  "Pushing [%d] onto request queue for session [%s] [%s]",
+                       req->request_id, session->remote_service, session->session_id );
+       osrfListSet( session->request_queue, req, req->request_id ); 
+       return req->request_id;
+}
+
+void osrf_app_session_set_complete( osrfAppSession* session, int request_id ) {
+       if(session == NULL)
+               return;
+
+       osrfAppRequest* req = OSRF_LIST_GET_INDEX( session->request_queue, request_id );
+       if(req) req->complete = 1;
+}
+
+int osrf_app_session_request_complete( const osrfAppSession* session, int request_id ) {
+       if(session == NULL)
+               return 0;
+       osrfAppRequest* req = OSRF_LIST_GET_INDEX( session->request_queue, request_id );
+       if(req)
+               return req->complete;
+       return 0;
+}
+
+
+/** Resets the remote connection id to that of the original*/
+void osrf_app_session_reset_remote( osrfAppSession* session ){
+       if( session==NULL )
+               return;
+
+       free(session->remote_id);
+       osrfLogDebug( OSRF_LOG_MARK,  "App Session [%s] [%s] resetting remote id to %s",
+                       session->remote_service, session->session_id, session->orig_remote_id );
+
+       session->remote_id = strdup(session->orig_remote_id);
+}
+
+void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id ) {
+       if(session == NULL)
+               return;
+       if( session->remote_id )
+               free(session->remote_id );
+       session->remote_id = strdup( remote_id );
+}
+
+/** pushes the given message into the result list of the app_request
+  with the given request_id */
+int osrf_app_session_push_queue( 
+               osrfAppSession* session, osrfMessage* msg ){
+       if(session == NULL || msg == NULL) return 0;
+
+       osrfAppRequest* req = OSRF_LIST_GET_INDEX( session->request_queue, msg->thread_trace );
+       if(req == NULL) return 0;
+       _osrf_app_request_push_queue( req, msg );
+
+       return 0;
+}
+
+int osrfAppSessionConnect( osrfAppSession* session ) { 
+       return osrf_app_session_connect(session);
+}
+
+
+/** Attempts to connect to the remote service */
+int osrf_app_session_connect(osrfAppSession* session){
+       
+       if(session == NULL)
+               return 0;
+
+       if(session->state == OSRF_SESSION_CONNECTED) {
+               return 1;
+       }
+
+       int timeout = 5; /* XXX CONFIG VALUE */
+
+       osrfLogDebug( OSRF_LOG_MARK,  "AppSession connecting to %s", session->remote_id );
+
+       /* defaulting to protocol 1 for now */
+       osrfMessage* con_msg = osrf_message_init( CONNECT, session->thread_trace, 1 );
+       osrf_app_session_reset_remote( session );
+       session->state = OSRF_SESSION_CONNECTING;
+       int ret = _osrf_app_session_send( session, con_msg );
+       osrfMessageFree(con_msg);
+       if(ret) return 0;
+
+       time_t start = time(NULL);      
+       time_t remaining = (time_t) timeout;
+
+       while( session->state != OSRF_SESSION_CONNECTED && remaining >= 0 ) {
+               osrf_app_session_queue_wait( session, remaining, NULL );
+        if(session->transport_error) {
+            osrfLogError(OSRF_LOG_MARK, "cannot communicate with %s", session->remote_service);
+            return 0;
+        }
+               remaining -= (int) (time(NULL) - start);
+       }
+
+       if(session->state == OSRF_SESSION_CONNECTED)
+               osrfLogDebug( OSRF_LOG_MARK, " * Connected Successfully to %s", session->remote_service );
+
+       if(session->state != OSRF_SESSION_CONNECTED)
+               return 0;
+
+       return 1;
+}
+
+
+
+/** Disconnects from the remote service */
+int osrf_app_session_disconnect( osrfAppSession* session){
+       if(session == NULL)
+               return 1;
+
+       if(session->state == OSRF_SESSION_DISCONNECTED)
+               return 1;
+
+       if(session->stateless && session->state != OSRF_SESSION_CONNECTED) {
+               osrfLogDebug( OSRF_LOG_MARK,  
+                               "Exiting disconnect on stateless session %s", 
+                               session->session_id);
+               return 1;
+       }
+
+       osrfLogDebug(OSRF_LOG_MARK,  "AppSession disconnecting from %s", session->remote_id );
+
+       osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
+       _osrf_app_session_send( session, dis_msg );
+       session->state = OSRF_SESSION_DISCONNECTED;
+
+       osrfMessageFree( dis_msg );
+       osrf_app_session_reset_remote( session );
+       return 1;
+}
+
+int osrf_app_session_request_resend( osrfAppSession* session, int req_id ) {
+       osrfAppRequest* req = OSRF_LIST_GET_INDEX( session->request_queue, req_id );
+       return _osrf_app_request_resend( req );
+}
+
+
+static int osrfAppSessionSendBatch( osrfAppSession* session, osrfMessage* msgs[], int size ) {
+
+       if( !(session && msgs && size > 0) ) return 0;
+       int retval = 0;
+
+       osrfMessage* msg = msgs[0];
+
+       if(msg) {
+
+               osrf_app_session_queue_wait( session, 0, NULL );
+
+               if(session->state != OSRF_SESSION_CONNECTED)  {
+
+                       if(session->stateless) { /* stateless session always send to the root listener */
+                               osrf_app_session_reset_remote(session);
+
+                       } else { 
+
+                               /* do an auto-connect if necessary */
+                               if( ! session->stateless &&
+                                       (msg->m_type != CONNECT) && 
+                                       (msg->m_type != DISCONNECT) &&
+                                       (session->state != OSRF_SESSION_CONNECTED) ) {
+
+                                       if(!osrf_app_session_connect( session )) 
+                                               return 0;
+                               }
+                       }
+               }
+       }
+
+       char* string = osrfMessageSerializeBatch(msgs, size);
+
+       if( string ) {
+
+               transport_message* t_msg = message_init( 
+                               string, "", session->session_id, session->remote_id, NULL );
+      message_set_osrf_xid( t_msg, osrfLogGetXid() );
+
+               retval = client_send_message( session->transport_handle, t_msg );
+
+               if( retval ) osrfLogError(OSRF_LOG_MARK, "client_send_message failed");
+
+               osrfLogInfo(OSRF_LOG_MARK, "[%s] sent %d bytes of data to %s",
+                       session->remote_service, strlen(string), t_msg->recipient );
+
+               osrfLogDebug(OSRF_LOG_MARK, "Sent: %s", string );
+
+               free(string);
+               message_free( t_msg );
+       }
+
+       return retval; 
+}
+
+
+
+static int _osrf_app_session_send( osrfAppSession* session, osrfMessage* msg ){
+       if( !(session && msg) ) return 0;
+       osrfMessage* a[1];
+       a[0] = msg;
+       return osrfAppSessionSendBatch( session, a, 1 );
+}
+
+
+
+
+/**  Waits up to 'timeout' seconds for some data to arrive.
+  * Any data that arrives will be processed according to its
+  * payload and message type.  This method will return after
+  * any data has arrived.
+  */
+int osrf_app_session_queue_wait( osrfAppSession* session, int timeout, int* recvd ){
+       if(session == NULL) return 0;
+       osrfLogDebug(OSRF_LOG_MARK,  "AppSession in queue_wait with timeout %d", timeout );
+       return osrf_stack_entry_point(session->transport_handle, timeout, recvd);
+}
+
+/** Disconnects (if client) and removes the given session from the global session cache 
+  * ! This free's all attached app_requests ! 
+  */
+void osrfAppSessionFree( osrfAppSession* session ){
+       if(session == NULL) return;
+
+       osrfLogDebug(OSRF_LOG_MARK,  "AppSession [%s] [%s] destroying self and deleting requests", 
+                       session->remote_service, session->session_id );
+       if(session->type == OSRF_SESSION_CLIENT 
+                       && session->state != OSRF_SESSION_DISCONNECTED ) { /* disconnect if we're a client */
+               osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
+               _osrf_app_session_send( session, dis_msg ); 
+               osrfMessageFree(dis_msg);
+       }
+
+       osrfHashRemove( osrfAppSessionCache, session->session_id );
+       _osrf_app_session_free( session );
+}
+
+osrfMessage* osrfAppSessionRequestRecv(
+               osrfAppSession* session, int req_id, int timeout ) {
+       if(req_id < 0 || session == NULL)
+               return NULL;
+       osrfAppRequest* req = OSRF_LIST_GET_INDEX( session->request_queue, req_id );
+       return _osrf_app_request_recv( req, timeout );
+}
+
+
+
+int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data ) {
+       if(!ses || ! data ) return -1;
+
+       osrfMessage* msg = osrf_message_init( RESULT, requestId, 1 );
+       osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
+       char* json = jsonObjectToJSON( data );
+
+       osrf_message_set_result_content( msg, json );
+       _osrf_app_session_send( ses, msg ); 
+
+       free(json);
+       osrfMessageFree( msg );
+
+       return 0;
+}
+
+
+int osrfAppRequestRespondComplete( 
+               osrfAppSession* ses, int requestId, const jsonObject* data ) {
+
+       osrfMessage* payload = osrf_message_init( RESULT, requestId, 1 );
+       osrf_message_set_status_info( payload, NULL, "OK", OSRF_STATUS_OK );
+
+       osrfMessage* status = osrf_message_init( STATUS, requestId, 1);
+       osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete", OSRF_STATUS_COMPLETE );
+       
+       if (data) {
+               char* json = jsonObjectToJSON( data );
+               osrf_message_set_result_content( payload, json );
+               free(json);
+
+               osrfMessage* ms[2];
+               ms[0] = payload;
+               ms[1] = status;
+
+               osrfAppSessionSendBatch( ses, ms, 2 );
+       } else {
+               osrfAppSessionSendBatch( ses, &status, 1 );
+       }
+
+       osrfMessageFree( payload );
+       osrfMessageFree( status );
+
+       return 0;
+}
+
+int osrfAppSessionStatus( osrfAppSession* ses, int type,
+               const char* name, int reqId, const char* message ) {
+
+       if(ses) {
+               osrfMessage* msg = osrf_message_init( STATUS, reqId, 1);
+               osrf_message_set_status_info( msg, name, message, type );
+               _osrf_app_session_send( ses, msg ); 
+               osrfMessageFree( msg );
+               return 0;
+       }
+       return -1;
+}
+
+
+
+
+
+
diff --git a/trunk/src/libopensrf/osrf_application.c b/trunk/src/libopensrf/osrf_application.c
new file mode 100644 (file)
index 0000000..5737787
--- /dev/null
@@ -0,0 +1,513 @@
+#include <opensrf/osrf_application.h>
+
+static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
+               const char* notes, int argc, int options, void* );
+static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
+static int _osrfAppRegisterSysMethods( const char* app );
+static osrfApplication* _osrfAppFindApplication( const char* name );
+static osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
+static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
+static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
+static int _osrfAppRunSystemMethod(osrfMethodContext* context);
+static int osrfAppIntrospect( osrfMethodContext* ctx );
+static int osrfAppIntrospectAll( osrfMethodContext* ctx );
+static int osrfAppEcho( osrfMethodContext* ctx );
+
+static osrfHash* _osrfAppHash = NULL;
+
+int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
+       if(!appName || ! soFile) return -1;
+       char* error;
+
+       if(!_osrfAppHash) _osrfAppHash = osrfNewHash();
+
+       osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
+
+       osrfApplication* app = safe_malloc(sizeof(osrfApplication));
+       app->handle = dlopen (soFile, RTLD_NOW);
+       app->onExit = NULL;
+
+       if(!app->handle) {
+               osrfLogWarning( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, dlerror() );
+               dlerror(); /* clear the error */
+               free(app);
+               return -1;
+       }
+
+       app->methods = osrfNewHash();
+       osrfHashSet( _osrfAppHash, app, appName );
+
+       /* see if we can run the initialize method */
+       int (*init) (void);
+       *(void **) (&init) = dlsym(app->handle, "osrfAppInitialize");
+
+       if( (error = dlerror()) != NULL ) {
+               osrfLogWarning( OSRF_LOG_MARK, 
+                       "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s", appName, error );
+
+       } else {
+
+               /* run the method */
+               int ret;
+               if( (ret = (*init)()) ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
+                                       "'osrfAppInitialize', not registering...", appName );
+                       //free(app->name); /* need a method to remove an application from the list */
+                       //free(app);
+                       return ret;
+               }
+       }
+
+       _osrfAppRegisterSysMethods(appName);
+
+       osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
+
+       osrfLogSetAppname(appName);
+
+       osrfAppSetOnExit(app, appName);
+
+       return 0;
+}
+
+
+static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
+       if(!(app && appName)) return;
+
+       /* see if we can run the initialize method */
+       char* error;
+       void (*onExit) (void);
+       *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
+
+       if( (error = dlerror()) != NULL ) {
+               osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
+               return;
+       }
+
+       osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
+       app->onExit = (*onExit);
+}
+
+
+int osrfAppRunChildInit(const char* appname) {
+       osrfApplication* app = _osrfAppFindApplication(appname);
+       if(!app) return -1;
+
+       char* error;
+       int ret;
+       int (*childInit) (void);
+
+       *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
+
+       if( (error = dlerror()) != NULL ) {
+               osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
+               return 0;
+       }
+
+       if( (ret = (*childInit)()) ) {
+               osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
+               return -1;
+       }
+
+       osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
+       return 0;
+}
+
+
+void osrfAppRunExitCode() { 
+       osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
+       osrfApplication* app;
+       while( (app = osrfHashIteratorNext(itr)) ) {
+               if( app->onExit ) {
+                       osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
+                               osrfHashIteratorKey(itr) );
+                       app->onExit();
+               }
+       }
+       osrfHashIteratorFree(itr);
+}
+
+
+int osrfAppRegisterMethod( const char* appName, const char* methodName, 
+               const char* symbolName, const char* notes, int argc, int options ) {
+
+       return osrfAppRegisterExtendedMethod(
+                       appName,
+                       methodName,
+                       symbolName,
+                       notes,
+                       argc,
+                       options,
+                       NULL
+       );
+
+}
+
+int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName, 
+               const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
+
+       if( !appName || ! methodName  ) return -1;
+
+       osrfApplication* app = _osrfAppFindApplication(appName);
+       if(!app) {
+               osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
+               return -1;
+       }
+
+       osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
+
+       osrfMethod* method = _osrfAppBuildMethod(
+               methodName, symbolName, notes, argc, options, user_data );              
+       method->options = options;
+
+       /* plug the method into the list of methods */
+       osrfHashSet( app->methods, method, method->name );
+
+       if( options & OSRF_METHOD_STREAMING ) { /* build the atomic counterpart */
+               int newops = options | OSRF_METHOD_ATOMIC;
+               osrfMethod* atomicMethod = _osrfAppBuildMethod(
+                       methodName, symbolName, notes, argc, newops, NULL );            
+               osrfHashSet( app->methods, atomicMethod, atomicMethod->name );
+               atomicMethod->userData = method->userData;
+       }
+
+       return 0;
+}
+
+
+
+static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
+               const char* notes, int argc, int options, void* user_data ) {
+
+       osrfMethod* method                                      = safe_malloc(sizeof(osrfMethod));
+
+       if(methodName) method->name             = strdup(methodName);
+       else method->name    = NULL;
+       if(symbolName) method->symbol           = strdup(symbolName);
+       else method->symbol  = NULL;
+       if(notes) method->notes                         = strdup(notes);
+       else method->notes   = NULL;
+       if(user_data) method->userData  = user_data;
+
+       method->argc                                                    = argc;
+       method->options                                         = options;
+
+       if(options & OSRF_METHOD_ATOMIC) { /* add ".atomic" to the end of the name */
+               char mb[strlen(method->name) + 8];
+               sprintf(mb, "%s.atomic", method->name);
+               free(method->name);
+               method->name = strdup(mb);
+               method->options |= OSRF_METHOD_STREAMING;
+       }
+
+       return method;
+}
+
+
+/**
+  Registers all of the system methods for this app so that they may be
+  treated the same as other methods */
+static int _osrfAppRegisterSysMethods( const char* app ) {
+
+       osrfAppRegisterMethod( 
+                       app, OSRF_SYSMETHOD_INTROSPECT, NULL, 
+                       "Return a list of methods whose names have the same initial "
+                       "substring as that of the provided method name PARAMS( methodNameSubstring )", 
+                       1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
+
+       osrfAppRegisterMethod( 
+                       app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL, 
+                       "Returns a complete list of methods. PARAMS()", 0, 
+                       OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
+
+       osrfAppRegisterMethod( 
+                       app, OSRF_SYSMETHOD_ECHO, NULL, 
+                       "Echos all data sent to the server back to the client. PARAMS([a, b, ...])", 0, 
+                       OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
+
+       return 0;
+}
+
+/**
+  Finds the given app in the list of apps
+  @param name The name of the application
+  @return The application pointer or NULL if there is no such application
+ */
+static osrfApplication* _osrfAppFindApplication( const char* name ) {
+       if(!name) return NULL;
+       return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
+}
+
+/**
+  Finds the given method for the given app
+  @param app The application object
+  @param methodName The method to find
+  @return A method pointer or NULL if no such method 
+  exists for the given application
+ */
+static osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
+       if(!app || ! methodName) return NULL;
+       return (osrfMethod*) osrfHashGet( app->methods, methodName );
+}
+
+osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
+       if(!appName || ! methodName) return NULL;
+       return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
+}
+
+
+int osrfAppRunMethod( const char* appName, const char* methodName, 
+               osrfAppSession* ses, int reqId, jsonObject* params ) {
+
+       if( !(appName && methodName && ses) ) return -1;
+
+       char* error;
+       osrfApplication* app;
+       osrfMethod* method;
+       osrfMethodContext context;
+
+       context.session = ses;
+       context.params = params;
+       context.request = reqId;
+       context.responses = NULL;
+
+       /* this is the method we're gonna run */
+       int (*meth) (osrfMethodContext*);       
+
+       if( !(app = _osrfAppFindApplication(appName)) )
+               return osrfAppRequestRespondException( ses, 
+                               reqId, "Application not found: %s", appName );
+       
+       if( !(method = osrfAppFindMethod( app, methodName )) )
+               return osrfAppRequestRespondException( ses, reqId, 
+                               "Method [%s] not found for service %s", methodName, appName );
+
+       context.method = method;
+
+       #ifdef OSRF_STRICT_PARAMS
+       if( method->argc > 0 ) {
+               if(!params || params->type != JSON_ARRAY || params->size < method->argc )
+                       return osrfAppRequestRespondException( ses, reqId, 
+                               "Not enough params for method %s / service %s", methodName, appName );
+       }
+       #endif
+
+       int retcode = 0;
+
+       if( method->options & OSRF_METHOD_SYSTEM ) {
+               retcode = _osrfAppRunSystemMethod(&context);
+
+       } else {
+
+               /* open and now run the method */
+               *(void **) (&meth) = dlsym(app->handle, method->symbol);
+
+               if( (error = dlerror()) != NULL ) {
+                       return osrfAppRequestRespondException( ses, reqId, 
+                               "Unable to execute method [%s]  for service %s", methodName, appName );
+               }
+
+               retcode = (*meth) (&context);
+       }
+
+       if(retcode < 0) 
+               return osrfAppRequestRespondException( 
+                               ses, reqId, "An unknown server error occurred" );
+
+       return _osrfAppPostProcess( &context, retcode );
+
+}
+
+
+int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
+       return _osrfAppRespond( ctx, data, 0 );
+}
+
+int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
+       return _osrfAppRespond( context, data, 1 );
+}
+
+static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
+       if(!(ctx && ctx->method)) return -1;
+
+       if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
+               osrfLogDebug( OSRF_LOG_MARK,   
+                       "Adding responses to stash for atomic method %s", ctx->method->name );
+
+               if( ctx->responses == NULL )                                                                                            
+                       ctx->responses = jsonParseString("[]");                                                 
+
+               if ( data != NULL )
+                       jsonObjectPush( ctx->responses, jsonObjectClone(data) );        
+       }
+
+
+       if(     !(ctx->method->options & OSRF_METHOD_ATOMIC) && 
+                       !(ctx->method->options & OSRF_METHOD_CACHABLE) ) {
+
+               if(complete) 
+                       osrfAppRequestRespondComplete( ctx->session, ctx->request, data );
+               else
+                       osrfAppRequestRespond( ctx->session, ctx->request, data );
+               return 0;
+       }
+
+       return 0;
+}
+
+
+
+
+static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
+       if(!(ctx && ctx->method)) return -1;
+
+       osrfLogDebug( OSRF_LOG_MARK,  "Postprocessing method %s with retcode %d",
+                       ctx->method->name, retcode );
+
+       if(ctx->responses) { /* we have cached responses to return (no responses have been sent) */
+
+               osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
+               jsonObjectFree(ctx->responses);
+               ctx->responses = NULL;
+
+       } else {
+
+               if( retcode > 0 ) 
+                       osrfAppSessionStatus( ctx->session, OSRF_STATUS_COMPLETE,  
+                                       "osrfConnectStatus", ctx->request, "Request Complete" );
+       }
+
+       return 0;
+}
+
+int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
+       if(!ses) return -1;
+       if(!msg) msg = "";
+       VA_LIST_TO_STRING(msg);
+       osrfLogWarning( OSRF_LOG_MARK,  "Returning method exception with message: %s", VA_BUF );
+       osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request,  VA_BUF );
+       return 0;
+}
+
+
+static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method, jsonObject* resp ) {
+       if(!(ctx && resp)) return;
+
+       jsonObjectSetKey(resp, "api_name",      jsonNewObject(method->name));
+       jsonObjectSetKey(resp, "method",        jsonNewObject(method->symbol));
+       jsonObjectSetKey(resp, "service",       jsonNewObject(ctx->session->remote_service));
+       jsonObjectSetKey(resp, "notes",         jsonNewObject(method->notes));
+       jsonObjectSetKey(resp, "argc",          jsonNewNumberObject(method->argc));
+
+       jsonObjectSetKey(resp, "sysmethod", 
+                       jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
+       jsonObjectSetKey(resp, "atomic",                
+                       jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
+       jsonObjectSetKey(resp, "cachable",      
+                       jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
+
+       jsonObjectSetClass(resp, "method");
+}
+
+/**
+  Trys to run the requested method as a system method.
+  A system method is a well known method that all
+  servers implement.  
+  @param context The current method context
+  @return 0 if the method is run successfully, return < 0 means
+  the method was not run, return > 0 means the method was run
+  and the application code now needs to send a 'request complete' 
+  message
+ */
+static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
+       OSRF_METHOD_VERIFY_CONTEXT(ctx);
+
+       if(     !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) || 
+                       !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
+
+               return osrfAppIntrospectAll(ctx);
+       }
+
+
+       if(     !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
+                       !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
+
+               return osrfAppIntrospect(ctx);
+       }
+
+       if(     !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
+                       !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
+
+               return osrfAppEcho(ctx);
+       }
+
+
+       osrfAppRequestRespondException( ctx->session, 
+                       ctx->request, "System method implementation not found");
+
+       return 0;
+}
+
+
+static int osrfAppIntrospect( osrfMethodContext* ctx ) {
+
+       jsonObject* resp = NULL;
+       char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
+       osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
+       int len = 0;
+
+       if(!methodSubstring) return 1; /* respond with no methods */
+
+       if(app) {
+
+               osrfHashIterator* itr = osrfNewHashIterator(app->methods);
+               osrfMethod* method;
+
+               while( (method = osrfHashIteratorNext(itr)) ) {
+                       if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
+                               if( !strncmp( method->name, methodSubstring, len) ) {
+                                       resp = jsonNewObject(NULL);
+                                       _osrfAppSetIntrospectMethod( ctx, method, resp );
+                                       osrfAppRespond(ctx, resp);
+                                       jsonObjectFree(resp);
+                               }
+                       }
+               }
+               osrfHashIteratorFree(itr);
+               return 1;
+       }
+
+       return -1;
+
+}
+
+
+static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
+       jsonObject* resp = NULL;
+       osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
+
+       if(app) {
+               osrfHashIterator* itr = osrfNewHashIterator(app->methods);
+               osrfMethod* method;
+               while( (method = osrfHashIteratorNext(itr)) ) {
+                       resp = jsonNewObject(NULL);
+                       _osrfAppSetIntrospectMethod( ctx, method, resp );
+                       osrfAppRespond(ctx, resp);
+                       jsonObjectFree(resp);
+               }
+               osrfHashIteratorFree(itr);
+               return 1;
+       }
+
+       return -1;
+}
+
+static int osrfAppEcho( osrfMethodContext* ctx ) {
+       OSRF_METHOD_VERIFY_CONTEXT(ctx);
+       int i;
+       for( i = 0; i < ctx->params->size; i++ ) {
+               const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
+               osrfAppRespond(ctx, str);
+       }
+       return 1;
+}
+
diff --git a/trunk/src/libopensrf/osrf_big_hash.c b/trunk/src/libopensrf/osrf_big_hash.c
new file mode 100644 (file)
index 0000000..3e69f2f
--- /dev/null
@@ -0,0 +1,173 @@
+#include <opensrf/osrf_big_hash.h>
+
+osrfBigHash* osrfNewBigHash() {
+       osrfBigHash* hash = safe_malloc(sizeof(osrfBigHash));
+       hash->hash = (Pvoid_t) NULL;
+       hash->freeItem = NULL;
+       return hash;
+}
+
+void* osrfBigHashSet( osrfBigHash* hash, void* item, const char* key, ... ) {
+       if(!(hash && item && key )) return NULL;
+
+       Word_t* value;
+       VA_LIST_TO_STRING(key);
+       uint8_t idx[strlen(VA_BUF) + 1];
+       strcpy( idx, VA_BUF );
+
+       void* olditem = osrfBigHashRemove( hash, VA_BUF );
+
+       JSLI(value, hash->hash, idx);
+       if(value) *value = (Word_t) item;
+       return olditem;
+       
+}
+
+void* osrfBigHashRemove( osrfBigHash* hash, const char* key, ... ) {
+       if(!(hash && key )) return NULL;
+
+       VA_LIST_TO_STRING(key);
+
+       Word_t* value;
+       uint8_t idx[strlen(VA_BUF) + 1];
+       strcpy( idx, VA_BUF );
+       void* item = NULL;
+       int retcode;
+
+       JSLG( value, hash->hash,  idx);
+
+       if( value ) {
+               item = (void*) *value;
+               if(item) {
+                       if( hash->freeItem ) {
+                               hash->freeItem( (char*) idx, item ); 
+                               item = NULL;
+                       }
+               }
+       }
+
+
+       JSLD( retcode, hash->hash, idx );
+
+       return item;
+}
+
+
+void* osrfBigHashGet( osrfBigHash* hash, const char* key, ... ) {
+       if(!(hash && key )) return NULL;
+
+       VA_LIST_TO_STRING(key);
+
+       Word_t* value;
+       uint8_t idx[strlen(VA_BUF) + 1];
+       strcpy( idx, VA_BUF );
+
+       JSLG( value, hash->hash, idx );
+       if(value) return (void*) *value;
+       return NULL;
+}
+
+
+osrfStringArray* osrfBigHashKeys( osrfBigHash* hash ) {
+       if(!hash) return NULL;
+
+       Word_t* value;
+       uint8_t idx[OSRF_HASH_MAXKEY];
+       strcpy(idx, "");
+       char* key;
+       osrfStringArray* strings = osrfNewStringArray(8);
+
+       JSLF( value, hash->hash, idx );
+
+       while( value ) {
+               key = (char*) idx;
+               osrfStringArrayAdd( strings, key );
+               JSLN( value, hash->hash, idx );
+       }
+
+       return strings;
+}
+
+
+unsigned long osrfBigHashGetCount( osrfBigHash* hash ) {
+       if(!hash) return -1;
+
+       Word_t* value;
+       unsigned long count = 0;
+       uint8_t idx[OSRF_HASH_MAXKEY];
+
+       strcpy( (char*) idx, "");
+       JSLF(value, hash->hash, idx);
+
+       while(value) {
+               count++;
+               JSLN( value, hash->hash, idx );
+       }
+
+       return count;
+}
+
+void osrfBigHashFree( osrfBigHash* hash ) {
+       if(!hash) return;
+
+       int i;
+       osrfStringArray* keys = osrfBigHashKeys( hash );
+
+       for( i = 0; i != keys->size; i++ )  {
+               char* key = (char*) osrfStringArrayGetString( keys, i );
+               osrfBigHashRemove( hash, key );
+       }
+
+       osrfStringArrayFree(keys);
+       free(hash);
+}
+
+
+
+osrfBigHashIterator* osrfNewBigHashIterator( osrfBigHash* hash ) {
+       if(!hash) return NULL;
+       osrfBigHashIterator* itr = safe_malloc(sizeof(osrfBigHashIterator));
+       itr->hash = hash;
+       itr->current = NULL;
+       return itr;
+}
+
+void* osrfBigHashIteratorNext( osrfBigHashIterator* itr ) {
+       if(!(itr && itr->hash)) return NULL;
+
+       Word_t* value;
+       uint8_t idx[OSRF_HASH_MAXKEY];
+
+       if( itr->current == NULL ) { /* get the first item in the list */
+               strcpy(idx, "");
+               JSLF( value, itr->hash->hash, idx );
+
+       } else {
+               strcpy(idx, itr->current);
+               JSLN( value, itr->hash->hash, idx );
+       }
+
+       if(value) {
+               free(itr->current);
+               itr->current = strdup((char*) idx);
+               return (void*) *value;
+       }
+
+       return NULL;
+
+}
+
+void osrfBigHashIteratorFree( osrfBigHashIterator* itr ) {
+       if(!itr) return;
+       free(itr->current);
+       free(itr);
+}
+
+void osrfBigHashIteratorReset( osrfBigHashIterator* itr ) {
+       if(!itr) return;
+       free(itr->current);
+       itr->current = NULL;
+}
+
+
+
diff --git a/trunk/src/libopensrf/osrf_big_list.c b/trunk/src/libopensrf/osrf_big_list.c
new file mode 100644 (file)
index 0000000..26ff7b6
--- /dev/null
@@ -0,0 +1,174 @@
+#include <opensrf/osrf_big_list.h>
+
+
+osrfBigList* osrfNewBigList() {
+       osrfBigList* list = safe_malloc(sizeof(osrfBigList));
+       list->list = (Pvoid_t) NULL;
+       list->size = 0;
+       list->freeItem = NULL;
+       return list;
+}
+
+
+int osrfBigListPush( osrfBigList* list, void* item ) {
+       if(!(list && item)) return -1;
+       Word_t* value;
+       unsigned long index = -1;
+       JLL(value, list->list, index );
+       osrfBigListSet( list, item, index+1 );
+       return 0;
+}
+
+
+void* osrfBigListSet( osrfBigList* list, void* item, unsigned long position ) {
+       if(!list || position < 0) return NULL;
+
+       Word_t* value;
+       void* olditem = osrfBigListRemove( list, position );
+
+       JLI( value, list->list, position ); 
+       *value = (Word_t) item;
+       __osrfBigListSetSize( list );
+
+       return olditem;
+}
+
+
+void* osrfBigListGetIndex( osrfBigList* list, unsigned long position ) {
+       if(!list) return NULL;
+
+       Word_t* value;
+       JLG( value, list->list, position );
+       if(value) return (void*) *value;
+       return NULL;
+}
+
+void osrfBigListFree( osrfBigList* list ) {
+       if(!list) return;
+
+       Word_t* value;
+       unsigned long index = -1;
+       JLL(value, list->list, index );
+       int retcode;
+
+       while (value != NULL) {
+               if(list->freeItem) 
+                       list->freeItem( (void*) *value );
+               JLD(retcode, list->list, index);
+               JLP(value, list->list, index);
+       }               
+
+       free(list);
+}
+
+void* osrfBigListRemove( osrfBigList* list, int position ) {
+       if(!list) return NULL;
+
+       int retcode;
+       Word_t* value;
+       JLG( value, list->list, position );
+       void* olditem = NULL;
+
+       if( value ) {
+
+               olditem = (void*) *value;
+               if( olditem ) {
+                       JLD(retcode, list->list, position );
+                       if(retcode == 1) {
+                               if(list->freeItem) {
+                                       list->freeItem( olditem );
+                                       olditem = NULL;
+                               }
+                               __osrfBigListSetSize( list );
+                       }
+               }
+       }
+
+       return olditem;
+}
+
+
+int osrfBigListFind( osrfBigList* list, void* addr ) {
+       if(!(list && addr)) return -1;
+
+       Word_t* value;
+       unsigned long index = -1;
+       JLL(value, list->list, index );
+
+       while (value != NULL) {
+               if( (void*) *value == addr )
+                       return index;
+               JLP(value, list->list, index);
+       }
+
+       return -1;
+}
+
+
+
+void __osrfBigListSetSize( osrfBigList* list ) {
+       if(!list) return;
+
+       Word_t* value;
+       unsigned long index = -1;
+       JLL(value, list->list, index );
+       list->size = index + 1;
+}
+
+
+unsigned long osrfBigListGetCount( osrfBigList* list ) {
+       if(!list) return -1;
+       unsigned long retcode = -1;
+       JLC( retcode, list->list, 0, -1 );
+       return retcode;
+}
+
+
+void* osrfBigListPop( osrfBigList* list ) {
+       if(!list) return NULL;
+       return osrfBigListRemove( list, list->size - 1 );
+}
+
+
+osrfBigBigListIterator* osrfNewBigListIterator( osrfBigList* list ) {
+       if(!list) return NULL;
+       osrfBigBigListIterator* itr = safe_malloc(sizeof(osrfBigBigListIterator));
+       itr->list = list;
+       itr->current = 0;
+       return itr;
+}
+
+void* osrfBigBigListIteratorNext( osrfBigBigListIterator* itr ) {
+       if(!(itr && itr->list)) return NULL;
+
+       Word_t* value;
+       if(itr->current >= itr->list->size) return NULL;
+       JLF( value, itr->list->list, itr->current );
+       if(value) {
+               itr->current++;
+               return (void*) *value;
+       }
+       return NULL;
+}
+
+void osrfBigBigListIteratorFree( osrfBigBigListIterator* itr ) {
+       if(!itr) return;
+       free(itr);
+}
+
+
+
+void osrfBigBigListIteratorReset( osrfBigBigListIterator* itr ) {
+       if(!itr) return;
+       itr->current = 0;
+}
+
+
+void osrfBigListVanillaFree( void* item ) {
+       free(item);
+}
+
+void osrfBigListSetDefaultFree( osrfBigList* list ) {
+       if(!list) return;
+       list->freeItem = osrfBigListVanillaFree;
+}
diff --git a/trunk/src/libopensrf/osrf_cache.c b/trunk/src/libopensrf/osrf_cache.c
new file mode 100644 (file)
index 0000000..3626b80
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+Bill Erickson <highfalutin@gmail.com>
+
+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.
+*/
+
+#include <opensrf/osrf_cache.h>
+
+static struct memcache* _osrfCache = NULL;
+static time_t _osrfCacheMaxSeconds = -1;
+
+int osrfCacheInit( const char* serverStrings[], int size, time_t maxCacheSeconds ) {
+       if( !(serverStrings && size > 0) ) return -1;
+    osrfCacheCleanup(); /* in case we've already been init-ed */
+
+       int i;
+       _osrfCache = mc_new();
+       _osrfCacheMaxSeconds = maxCacheSeconds;
+
+       for( i = 0; i < size && serverStrings[i]; i++ ) 
+               mc_server_add4( _osrfCache, serverStrings[i] );
+
+       return 0;
+}
+
+int osrfCachePutObject( char* key, const jsonObject* obj, time_t seconds ) {
+       if( !(key && obj) ) return -1;
+       char* s = jsonObjectToJSON( obj );
+       osrfLogInternal( OSRF_LOG_MARK, "osrfCachePut(): Putting object: %s", s);
+    osrfCachePutString(key, s, seconds);
+       free(s);
+       return 0;
+}
+
+int osrfCachePutString( char* key, const char* value, time_t seconds ) {
+       if( !(key && value) ) return -1;
+    seconds = (seconds <= 0 || seconds > _osrfCacheMaxSeconds) ? _osrfCacheMaxSeconds : seconds;
+       osrfLogInternal( OSRF_LOG_MARK, "osrfCachePutString(): Putting string: %s", value);
+       mc_set(_osrfCache, key, strlen(key), value, strlen(value), seconds, 0);
+       return 0;
+}
+
+jsonObject* osrfCacheGetObject( const char* key, ... ) {
+       jsonObject* obj = NULL;
+       if( key ) {
+               VA_LIST_TO_STRING(key);
+               const char* data = (const char*) mc_aget( _osrfCache, VA_BUF, strlen(VA_BUF) );
+               if( data ) {
+                       osrfLogInternal( OSRF_LOG_MARK, "osrfCacheGetObject(): Returning object: %s", data);
+                       obj = jsonParseString( data );
+                       return obj;
+               }
+               osrfLogDebug(OSRF_LOG_MARK, "No cache data exists with key %s", VA_BUF);
+       }
+       return NULL;
+}
+
+char* osrfCacheGetString( const char* key, ... ) {
+       if( key ) {
+               VA_LIST_TO_STRING(key);
+               char* data = (char*) mc_aget(_osrfCache, VA_BUF, strlen(VA_BUF) );
+               osrfLogInternal( OSRF_LOG_MARK, "osrfCacheGetString(): Returning object: %s", data);
+               if(!data) osrfLogDebug(OSRF_LOG_MARK, "No cache data exists with key %s", VA_BUF);
+               return data;
+       }
+       return NULL;
+}
+
+
+int osrfCacheRemove( const char* key, ... ) {
+       if( key ) {
+               VA_LIST_TO_STRING(key);
+               return mc_delete(_osrfCache, VA_BUF, strlen(VA_BUF), 0 );
+       }
+       return -1;
+}
+
+
+int osrfCacheSetExpire( time_t seconds, const char* key, ... ) {
+       if( key ) {
+               VA_LIST_TO_STRING(key);
+               jsonObject* o = osrfCacheGetObject( VA_BUF );
+               //osrfCacheRemove(VA_BUF);
+               int rc = osrfCachePutObject( VA_BUF, o, seconds );
+               jsonObjectFree(o);
+               return rc;
+       }
+       return -1;
+}
+
+void osrfCacheCleanup() {
+    if(_osrfCache)
+        mc_free(_osrfCache);
+}
+
+
diff --git a/trunk/src/libopensrf/osrf_hash.c b/trunk/src/libopensrf/osrf_hash.c
new file mode 100644 (file)
index 0000000..2a0a341
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+Copyright (C) 2007, 2008  Georgia Public Library Service
+Bill Erickson <erickson@esilibrary.com>
+
+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.
+
+-----------
+
+An osrfHash is a hybrid between a hash table and a doubly linked
+list.  The hash table supports random lookups by key.  The list
+supports iterative traversals.  The sequence of entries in the
+list reflects the sequence in which the entries were added.
+
+osrfHashIterators are somewhat unusual in that, if an iterator
+is positioned on a given entry, deletion of that entry does
+not invalidate the iterator.  The entry to which it points is
+logically but not physically deleted.  You can still advance
+the iterator to the next entry in the list.
+
+*/
+
+#include <opensrf/osrf_hash.h>
+
+struct _osrfHashNodeStruct {
+       char* key;
+       void* item;
+       struct _osrfHashNodeStruct* prev;
+       struct _osrfHashNodeStruct* next;
+};
+typedef struct _osrfHashNodeStruct osrfHashNode;
+
+struct _osrfHashStruct {
+       osrfList* hash; /* this hash */
+       void (*freeItem) (char* key, void* item);       /* callback for freeing stored items */
+       unsigned int size;
+       osrfHashNode* first_key;
+       osrfHashNode* last_key;
+};
+
+struct _osrfHashIteratorStruct {
+       osrfHash* hash;
+       osrfHashNode* curr_node;
+};
+
+/* 0x100 is a good size for small hashes */
+//#define OSRF_HASH_LIST_SIZE 0x100  /* size of the main hash list */
+#define OSRF_HASH_LIST_SIZE 0x10  /* size of the main hash list */
+
+
+/* used internally */
+#define OSRF_HASH_NODE_FREE(h, n) \
+       if(h && n) { \
+               if(h->freeItem && n->key) h->freeItem(n->key, n->item);\
+               free(n->key); free(n); \
+}
+
+osrfHash* osrfNewHash() {
+       osrfHash* hash;
+       OSRF_MALLOC(hash, sizeof(osrfHash));
+       hash->hash              = osrfNewList();
+       hash->first_key = NULL;
+       hash->last_key  = NULL;
+       return hash;
+}
+
+static osrfHashNode* osrfNewHashNode(char* key, void* item);
+
+/* algorithm proposed by Donald E. Knuth 
+ * in The Art Of Computer Programming Volume 3 (more or less..)*/
+/*
+static unsigned int osrfHashMakeKey(char* str) {
+       if(!str) return 0;
+       unsigned int len = strlen(str);
+       unsigned int h = len;
+       unsigned int i = 0;
+       for(i = 0; i < len; str++, i++)
+               h = ((h << 5) ^ (h >> 27)) ^ (*str);
+       return (h & (OSRF_HASH_LIST_SIZE-1));
+}
+*/
+
+/* macro version of the above function */
+#define OSRF_HASH_MAKE_KEY(str,num) \
+   do {\
+      const char* k__ = str;\
+      unsigned int len__ = strlen(k__); \
+      unsigned int h__ = len__;\
+      unsigned int i__ = 0;\
+      for(i__ = 0; i__ < len__; k__++, i__++)\
+         h__ = ((h__ << 5) ^ (h__ >> 27)) ^ (*k__);\
+      num = (h__ & (OSRF_HASH_LIST_SIZE-1));\
+   } while(0)
+
+/* Installs a callback function for freeing stored items */
+void osrfHashSetCallback( osrfHash* hash, void (*callback) (char* key, void* item) )
+{
+       if( hash ) hash->freeItem = callback;
+}
+
+/* Returns a pointer to the item's node if found; otherwise returns NULL. */
+static osrfHashNode* find_item( const osrfHash* hash,
+               const char* key, unsigned int* bucketkey ) {
+
+       // Find the sub-list in the hash table
+
+       if( hash->size < 6 && !bucketkey )
+       {
+               // For only a few entries, when we don't need to identify
+               // the hash bucket, it's probably faster to search the
+               // linked list instead of hashing
+
+               osrfHashNode* currnode = hash->first_key;
+               while( currnode && strcmp( currnode->key, key ) )
+                        currnode = currnode->next;
+
+               return currnode;
+       }
+                               
+       unsigned int i = 0;
+       OSRF_HASH_MAKE_KEY(key,i);
+
+       // If asked, report which slot the key hashes to
+       if( bucketkey ) *bucketkey = i;
+
+       osrfList* list = OSRF_LIST_GET_INDEX( hash->hash, i );
+       if( !list ) { return NULL; }
+
+       // Search the sub-list
+       
+       int k;
+       osrfHashNode* node = NULL;
+       for( k = 0; k < list->size; k++ ) {
+               node = OSRF_LIST_GET_INDEX(list, k);
+               if( node && node->key && !strcmp(node->key, key) )
+                       return node;
+       }
+
+       return NULL;
+}
+
+static osrfHashNode* osrfNewHashNode(char* key, void* item) {
+       if(!(key && item)) return NULL;
+       osrfHashNode* n;
+       OSRF_MALLOC(n, sizeof(osrfHashNode));
+       n->key = strdup(key);
+       n->item = item;
+       n->prev = NULL;
+       n->prev = NULL;
+       return n;
+}
+
+/* If an entry exists for a given key, update it; otherwise create it.
+   If an entry exists, and there is no callback function to destroy it,
+   return a pointer to it so that the calling code has the option of
+   destroying it.  Otherwise return NULL.
+*/
+void* osrfHashSet( osrfHash* hash, void* item, const char* key, ... ) {
+       if(!(hash && item && key )) return NULL;
+
+       void* olditem = NULL;
+       unsigned int bucketkey;
+       
+       VA_LIST_TO_STRING(key);
+       osrfHashNode* node = find_item( hash, VA_BUF, &bucketkey );
+       if( node ) {
+
+               // We already have an item for this key.  Update it in place.
+               if( hash->freeItem ) {
+                       hash->freeItem( node->key, node->item );
+               }
+               else
+                       olditem = node->item;
+
+               node->item = item;
+               return olditem;
+       }
+       
+       osrfList* bucket;
+       if( !(bucket = OSRF_LIST_GET_INDEX(hash->hash, bucketkey)) ) {
+               bucket = osrfNewList();
+               osrfListSet( hash->hash, bucket, bucketkey );
+       }
+
+       node = osrfNewHashNode(VA_BUF, item);
+       osrfListPushFirst( bucket, node );
+
+       hash->size++;
+
+       // Add the new hash node to the end of the linked list
+
+       if( NULL == hash->first_key )
+               hash->first_key = hash->last_key = node;
+       else {
+               node->prev = hash->last_key;
+               hash->last_key->next = node;
+               hash->last_key = node;
+       }
+       
+       return olditem;
+}
+
+/* Delete the entry for a specified key.  If the entry exists,
+   and there is no callback function to destroy the associated
+   item, return a pointer to the formerly associated item.
+   Otherwise return NULL.
+*/
+void* osrfHashRemove( osrfHash* hash, const char* key, ... ) {
+       if(!(hash && key )) return NULL;
+
+       VA_LIST_TO_STRING(key);
+
+       osrfHashNode* node = find_item( hash, VA_BUF, NULL );
+       if( !node ) return NULL;
+
+       hash->size--;
+
+       void* item = NULL;  // to be returned
+       if( hash->freeItem )
+               hash->freeItem( node->key, node->item );
+       else
+               item = node->item;
+
+       // Mark the node as logically deleted
+       
+       free(node->key);
+       node->key = NULL;
+       node->item = NULL;
+
+       // Make the node unreachable from the rest of the linked list.
+       // We leave the next and prev pointers in place so that an
+       // iterator parked here can find its way to an adjacent node.
+
+       if( node->prev )
+               node->prev->next = node->next;
+       else
+               hash->first_key = node->next;
+
+       if( node->next )
+               node->next->prev = node->prev;
+       else
+               hash->last_key = node->prev;
+       
+       return item;
+}
+
+
+void* osrfHashGet( osrfHash* hash, const char* key, ... ) {
+       if(!(hash && key )) return NULL;
+       VA_LIST_TO_STRING(key);
+
+       osrfHashNode* node = find_item( hash, (char*) VA_BUF, NULL );
+       if( !node ) return NULL;
+       return node->item;
+}
+
+osrfStringArray* osrfHashKeys( osrfHash* hash ) {
+       if(!hash) return NULL;
+       
+       osrfHashNode* node;
+       osrfStringArray* strings = osrfNewStringArray( hash->size );
+
+       // Add every key on the linked list
+       
+       node = hash->first_key;
+       while( node ) {
+               if( node->key )  // should always be true
+                       osrfStringArrayAdd( strings, node->key );
+               node = node->next;
+       }
+       
+       return strings;
+}
+
+
+unsigned long osrfHashGetCount( osrfHash* hash ) {
+       if(!hash) return -1;
+       return hash->size;
+}
+
+void osrfHashFree( osrfHash* hash ) {
+       if(!hash) return;
+
+       int i, j;
+       osrfList* list;
+       osrfHashNode* node;
+
+       for( i = 0; i != hash->hash->size; i++ ) {
+               if( ( list = OSRF_LIST_GET_INDEX( hash->hash, i )) ) {
+                       for( j = 0; j != list->size; j++ ) {
+                               if( (node = OSRF_LIST_GET_INDEX( list, j )) ) {
+                                       OSRF_HASH_NODE_FREE(hash, node);
+                               }
+                       }
+                       osrfListFree(list);
+               }
+       }
+
+       osrfListFree(hash->hash);
+       free(hash);
+}
+
+osrfHashIterator* osrfNewHashIterator( osrfHash* hash ) {
+       if(!hash) return NULL;
+       osrfHashIterator* itr;
+       OSRF_MALLOC(itr, sizeof(osrfHashIterator));
+       itr->hash = hash;
+       itr->curr_node = NULL;
+       return itr;
+}
+
+void* osrfHashIteratorNext( osrfHashIterator* itr ) {
+       if(!(itr && itr->hash)) return NULL;
+
+       // Advance to the next node in the linked list
+       
+       if( NULL == itr->curr_node )
+               itr->curr_node = itr->hash->first_key;
+       else
+               itr->curr_node = itr->curr_node->next;
+
+       if( itr->curr_node )
+               return itr->curr_node->item;
+       else
+               return NULL;
+}
+
+const char* osrfHashIteratorKey( const osrfHashIterator* itr ) {
+       if( itr && itr->curr_node )
+               return itr->curr_node->key;
+       else
+               return NULL;
+}
+
+void osrfHashIteratorFree( osrfHashIterator* itr ) {
+       if(itr)
+               free(itr);
+}
+
+void osrfHashIteratorReset( osrfHashIterator* itr ) {
+       if(!itr) return;
+       itr->curr_node = NULL;
+}
+
+
+int osrfHashIteratorHasNext( osrfHashIterator* itr ) {
+       if( !itr )
+               return 0;
+       else if( itr->curr_node )
+               return itr->curr_node->next ? 1 : 0;
+       else
+               return itr->hash->first_key ? 1 : 0;
+}
diff --git a/trunk/src/libopensrf/osrf_json_object.c b/trunk/src/libopensrf/osrf_json_object.c
new file mode 100644 (file)
index 0000000..335d70e
--- /dev/null
@@ -0,0 +1,771 @@
+/*
+Copyright (C) 2006  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <opensrf/log.h>
+#include <opensrf/osrf_json.h>
+#include <opensrf/osrf_json_utils.h>
+
+/* cleans up an object if it is morphing another object, also
+ * verifies that the appropriate storage container exists where appropriate */
+#define JSON_INIT_CLEAR(_obj_, newtype)                \
+       if( _obj_->type == JSON_HASH && newtype != JSON_HASH ) {                        \
+               osrfHashFree(_obj_->value.h);                   \
+               _obj_->value.h = NULL;                                  \
+} else if( _obj_->type == JSON_ARRAY && newtype != JSON_ARRAY ) {      \
+               osrfListFree(_obj_->value.l);                   \
+               _obj_->value.l = NULL;                                  \
+} else if( _obj_->type == JSON_STRING || _obj_->type == JSON_NUMBER ) { \
+               free(_obj_->value.s);                                           \
+               _obj_->value.s = NULL;                                  \
+} \
+       _obj_->type = newtype;\
+       if( newtype == JSON_HASH && _obj_->value.h == NULL ) {  \
+               _obj_->value.h = osrfNewHash();         \
+               osrfHashSetCallback( _obj_->value.h, _jsonFreeHashItem ); \
+} else if( newtype == JSON_ARRAY && _obj_->value.l == NULL ) { \
+               _obj_->value.l = osrfNewList();         \
+               _obj_->value.l->freeItem = _jsonFreeListItem;\
+}
+
+static int unusedObjCapture = 0;
+static int unusedObjRelease = 0;
+static int mallocObjCreate = 0;
+static int currentListLen = 0;
+
+union unusedObjUnion{
+
+       union unusedObjUnion* next;
+       jsonObject obj;
+};
+typedef union unusedObjUnion unusedObj;
+
+// We maintain a free list of jsonObjects that are available
+// for use, in order to reduce the churning through
+// malloc() and free().
+
+static unusedObj* freeObjList = NULL;
+
+static void add_json_to_buffer( const jsonObject* obj, growing_buffer * buf );
+
+/**
+ * Return all unused jsonObjects to the heap
+ * @return Nothing
+ */
+void jsonObjectFreeUnused( void ) {
+
+       unusedObj* temp;
+       while( freeObjList ) {
+               temp = freeObjList->next;
+               free( freeObjList );
+               freeObjList = temp;
+       }
+}
+
+jsonObject* jsonNewObject(const char* data) {
+
+       jsonObject* o;
+
+       if( freeObjList ) {
+               o = (jsonObject*) freeObjList;
+               freeObjList = freeObjList->next;
+        unusedObjRelease++;
+        currentListLen--;
+       } else {
+               OSRF_MALLOC( o, sizeof(jsonObject) );
+        mallocObjCreate++;
+    }
+
+       o->size = 0;
+       o->classname = NULL;
+       o->parent = NULL;
+
+       if(data) {
+               o->type = JSON_STRING;
+               o->value.s = strdup(data);
+       } else {
+               o->type = JSON_NULL;
+               o->value.s = NULL;
+       }
+
+       return o;
+}
+
+jsonObject* jsonNewObjectFmt(const char* data, ...) {
+
+       jsonObject* o;
+
+       if( freeObjList ) {
+               o = (jsonObject*) freeObjList;
+               freeObjList = freeObjList->next;
+        unusedObjRelease++;
+        currentListLen--;
+       } else {
+               OSRF_MALLOC( o, sizeof(jsonObject) );
+        mallocObjCreate++;
+    }
+
+       o->size = 0;
+       o->classname = NULL;
+       o->parent = NULL;
+
+       if(data) {
+               VA_LIST_TO_STRING(data);
+               o->type = JSON_STRING;
+               o->value.s = strdup(VA_BUF);
+       }
+       else {
+               o->type = JSON_NULL;
+               o->value.s = NULL;
+       }
+       
+       return o;
+}
+
+jsonObject* jsonNewNumberObject( double num ) {
+       jsonObject* o = jsonNewObject(NULL);
+       o->type = JSON_NUMBER;
+       o->value.s = doubleToString( num );
+       return o;
+}
+
+/**
+ * Creates a new number object from a numeric string
+ */
+jsonObject* jsonNewNumberStringObject( const char* numstr ) {
+       if( !numstr )
+               numstr = "0";
+       else if( !jsonIsNumeric( numstr ) )
+               return NULL;
+
+       jsonObject* o = jsonNewObject(NULL);
+       o->type = JSON_NUMBER;
+       o->value.s = strdup( numstr );
+       return o;
+}
+
+jsonObject* jsonNewBoolObject(int val) {
+    jsonObject* o = jsonNewObject(NULL);
+    o->type = JSON_BOOL;
+    jsonSetBool(o, val);
+    return o;
+}
+
+jsonObject* jsonNewObjectType(int type) {
+       jsonObject* o = jsonNewObject(NULL);
+       o->type = type;
+       return o;
+}
+
+void jsonObjectFree( jsonObject* o ) {
+
+       if(!o || o->parent) return;
+       free(o->classname);
+
+       switch(o->type) {
+               case JSON_HASH          : osrfHashFree(o->value.h); break;
+               case JSON_ARRAY : osrfListFree(o->value.l); break;
+               case JSON_STRING        : free(o->value.s); break;
+               case JSON_NUMBER        : free(o->value.s); break;
+       }
+
+       // Stick the old jsonObject onto a free list
+       // for potential reuse
+       
+       unusedObj* unused = (unusedObj*) o;
+       unused->next = freeObjList;
+       freeObjList = unused;
+
+    unusedObjCapture++;
+    currentListLen++;
+    if (unusedObjCapture > 1 && !(unusedObjCapture % 1000))
+        osrfLogDebug( OSRF_LOG_MARK, "Objects malloc()'d: %d, Reusable objects captured: %d, Objects reused: %d, Current List Length: %d", mallocObjCreate, unusedObjCapture, unusedObjRelease, currentListLen );
+}
+
+static void _jsonFreeHashItem(char* key, void* item){
+       if(!item) return;
+       jsonObject* o = (jsonObject*) item;
+       o->parent = NULL; /* detach the item */
+       jsonObjectFree(o);
+}
+static void _jsonFreeListItem(void* item){
+       if(!item) return;
+       jsonObject* o = (jsonObject*) item;
+       o->parent = NULL; /* detach the item */
+       jsonObjectFree(o);
+}
+
+void jsonSetBool(jsonObject* bl, int val) {
+    if(!bl) return;
+    JSON_INIT_CLEAR(bl, JSON_BOOL);
+    bl->value.b = val;
+}
+
+unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo) {
+    if(!o) return -1;
+    if(!newo) newo = jsonNewObject(NULL);
+       JSON_INIT_CLEAR(o, JSON_ARRAY);
+       newo->parent = o;
+       osrfListPush( o->value.l, newo );
+       o->size = o->value.l->size;
+       return o->size;
+}
+
+unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObject* newObj) {
+    if(!dest) return -1;
+    if(!newObj) newObj = jsonNewObject(NULL);
+       JSON_INIT_CLEAR(dest, JSON_ARRAY);
+       newObj->parent = dest;
+       osrfListSet( dest->value.l, newObj, index );
+       dest->size = dest->value.l->size;
+       return dest->value.l->size;
+}
+
+unsigned long jsonObjectSetKey( jsonObject* o, const char* key, jsonObject* newo) {
+    if(!o) return -1;
+    if(!newo) newo = jsonNewObject(NULL);
+       JSON_INIT_CLEAR(o, JSON_HASH);
+       newo->parent = o;
+       osrfHashSet( o->value.h, newo, key );
+       o->size = osrfHashGetCount(o->value.h);
+       return o->size;
+}
+
+jsonObject* jsonObjectGetKey( jsonObject* obj, const char* key ) {
+       if(!(obj && obj->type == JSON_HASH && obj->value.h && key)) return NULL;
+       return osrfHashGet( obj->value.h, key);
+}
+
+const jsonObject* jsonObjectGetKeyConst( const jsonObject* obj, const char* key ) {
+       if(!(obj && obj->type == JSON_HASH && obj->value.h && key)) return NULL;
+       return osrfHashGet( obj->value.h, key);
+}
+
+char* jsonObjectToJSON( const jsonObject* obj ) {
+       jsonObject* obj2 = jsonObjectEncodeClass( obj );
+       char* json = jsonObjectToJSONRaw(obj2);
+       jsonObjectFree(obj2);
+       return json;
+}
+
+char* jsonObjectToJSONRaw( const jsonObject* obj ) {
+       if(!obj) return NULL;
+       growing_buffer* buf = buffer_init(32);
+       add_json_to_buffer( obj, buf );
+       return buffer_release( buf );
+}
+
+static void add_json_to_buffer( const jsonObject* obj, growing_buffer * buf ) {
+
+       switch(obj->type) {
+
+               case JSON_BOOL :
+                       if(obj->value.b) OSRF_BUFFER_ADD(buf, "true"); 
+                       else OSRF_BUFFER_ADD(buf, "false"); 
+                       break;
+
+               case JSON_NUMBER: {
+                       if(obj->value.s) OSRF_BUFFER_ADD( buf, obj->value.s );
+                       else OSRF_BUFFER_ADD_CHAR( buf, '0' );
+                       break;
+               }
+
+               case JSON_NULL:
+                       OSRF_BUFFER_ADD(buf, "null");
+                       break;
+
+               case JSON_STRING:
+                       OSRF_BUFFER_ADD_CHAR(buf, '"');
+                       char* data = obj->value.s;
+                       int len = strlen(data);
+                       
+                       char* output = uescape(data, len, 1);
+                       OSRF_BUFFER_ADD(buf, output);
+                       free(output);
+                       OSRF_BUFFER_ADD_CHAR(buf, '"');
+                       break;
+                       
+               case JSON_ARRAY: {
+                       OSRF_BUFFER_ADD_CHAR(buf, '[');
+                       if( obj->value.l ) {
+                               int i;
+                               for( i = 0; i != obj->value.l->size; i++ ) {
+                                       if(i > 0) OSRF_BUFFER_ADD(buf, ",");
+                                       add_json_to_buffer( OSRF_LIST_GET_INDEX(obj->value.l, i), buf );
+                               }
+                       }
+                       OSRF_BUFFER_ADD_CHAR(buf, ']');
+                       break;
+               }
+
+               case JSON_HASH: {
+
+                       OSRF_BUFFER_ADD_CHAR(buf, '{');
+                       osrfHashIterator* itr = osrfNewHashIterator(obj->value.h);
+                       jsonObject* item;
+                       int i = 0;
+
+                       while( (item = osrfHashIteratorNext(itr)) ) {
+                               if(i++ > 0) OSRF_BUFFER_ADD(buf, ",");
+                               buffer_fadd(buf, "\"%s\":", osrfHashIteratorKey(itr));
+                               add_json_to_buffer( item, buf );
+                       }
+
+                       osrfHashIteratorFree(itr);
+                       OSRF_BUFFER_ADD_CHAR(buf, '}');
+                       break;
+               }
+       }
+}
+
+
+jsonIterator* jsonNewIterator(const jsonObject* obj) {
+       if(!obj) return NULL;
+       jsonIterator* itr;
+       OSRF_MALLOC(itr, sizeof(jsonIterator));
+
+       itr->obj                = (jsonObject*) obj;
+       itr->index      = 0;
+       itr->key                = NULL;
+
+       if( obj->type == JSON_HASH )
+               itr->hashItr = osrfNewHashIterator(obj->value.h);
+       
+       return itr;
+}
+
+void jsonIteratorFree(jsonIterator* itr) {
+       if(!itr) return;
+       free(itr->key);
+       osrfHashIteratorFree(itr->hashItr);
+       free(itr);
+}
+
+jsonObject* jsonIteratorNext(jsonIterator* itr) {
+       if(!(itr && itr->obj)) return NULL;
+       if( itr->obj->type == JSON_HASH ) {
+               if(!itr->hashItr) return NULL;
+               jsonObject* item = osrfHashIteratorNext(itr->hashItr);
+        if(!item) return NULL;
+               free(itr->key);
+               itr->key = strdup( osrfHashIteratorKey(itr->hashItr) );
+               return item;
+       } else {
+               return jsonObjectGetIndex( itr->obj, itr->index++ );
+       }
+}
+
+int jsonIteratorHasNext(const jsonIterator* itr) {
+       if(!(itr && itr->obj)) return 0;
+       if( itr->obj->type == JSON_HASH )
+               return osrfHashIteratorHasNext( itr->hashItr );
+       return (itr->index < itr->obj->size) ? 1 : 0;
+}
+
+jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index ) {
+       if(!obj) return NULL;
+       return (obj->type == JSON_ARRAY) ? 
+        (OSRF_LIST_GET_INDEX(obj->value.l, index)) : NULL;
+}
+
+
+
+unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index) {
+       if( dest && dest->type == JSON_ARRAY ) {
+               osrfListRemove(dest->value.l, index);
+               return dest->value.l->size;
+       }
+       return -1;
+}
+
+
+unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key) {
+       if( dest && key && dest->type == JSON_HASH ) {
+               osrfHashRemove(dest->value.h, key);
+               return 1;
+       }
+       return -1;
+}
+
+/**
+ Allocate a buffer and format a specified numeric value into it.
+ Caller is responsible for freeing the buffer.
+**/
+char* doubleToString( double num ) {
+       
+       char buf[ 64 ];
+       size_t len = snprintf(buf, sizeof( buf ), "%.30g", num) + 1;
+       if( len < sizeof( buf ) )
+               return strdup( buf );
+       else
+       {
+               // Need a bigger buffer (should never be necessary)
+               
+               char* bigger_buff = safe_malloc( len + 1 );
+               (void) snprintf(bigger_buff, len + 1, "%.30g", num);
+               return bigger_buff;
+       }
+}
+
+char* jsonObjectGetString(const jsonObject* obj) {
+       if(obj)
+       {
+               if( obj->type == JSON_STRING )
+                       return obj->value.s;
+               else if( obj->type == JSON_NUMBER )
+                       return obj->value.s ? obj->value.s : "0";
+               else
+                       return NULL;
+       }
+       else
+               return NULL;
+}
+
+double jsonObjectGetNumber( const jsonObject* obj ) {
+       return (obj && obj->type == JSON_NUMBER && obj->value.s)
+                       ? strtod( obj->value.s, NULL ) : 0;
+}
+
+void jsonObjectSetString(jsonObject* dest, const char* string) {
+       if(!(dest && string)) return;
+       JSON_INIT_CLEAR(dest, JSON_STRING);
+       dest->value.s = strdup(string);
+}
+
+/**
+ Turn a jsonObject into a JSON_NUMBER (if it isn't already one) and store
+ a specified numeric string in it.  If the string is not numeric,
+ store the equivalent of zero, and return an error status.
+**/
+int jsonObjectSetNumberString(jsonObject* dest, const char* string) {
+       if(!(dest && string)) return -1;
+       JSON_INIT_CLEAR(dest, JSON_NUMBER);
+
+       if( jsonIsNumeric( string ) ) {
+               dest->value.s = strdup(string);
+               return 0;
+       }
+       else {
+               dest->value.s = NULL;  // equivalent to zero
+               return -1;
+       }
+}
+
+void jsonObjectSetNumber(jsonObject* dest, double num) {
+       if(!dest) return;
+       JSON_INIT_CLEAR(dest, JSON_NUMBER);
+       dest->value.s = doubleToString( num );
+}
+
+void jsonObjectSetClass(jsonObject* dest, const char* classname ) {
+       if(!(dest && classname)) return;
+       free(dest->classname);
+       dest->classname = strdup(classname);
+}
+const char* jsonObjectGetClass(const jsonObject* dest) {
+    if(!dest) return NULL;
+    return dest->classname;
+}
+
+jsonObject* jsonObjectClone( const jsonObject* o ) {
+    if(!o) return jsonNewObject(NULL);
+
+    int i;
+    jsonObject* arr; 
+    jsonObject* hash; 
+    jsonIterator* itr;
+    jsonObject* tmp;
+    jsonObject* result = NULL;
+
+    switch(o->type) {
+        case JSON_NULL:
+            result = jsonNewObject(NULL);
+            break;
+        case JSON_STRING:
+            result = jsonNewObject(jsonObjectGetString(o));
+            break;
+        case JSON_NUMBER:
+                       result = jsonNewObject( o->value.s );
+                       result->type = JSON_NUMBER;
+            break;
+        case JSON_BOOL:
+            result = jsonNewBoolObject(jsonBoolIsTrue((jsonObject*) o));
+            break;
+        case JSON_ARRAY:
+            arr = jsonNewObject(NULL);
+            arr->type = JSON_ARRAY;
+            for(i=0; i < o->size; i++) 
+                jsonObjectPush(arr, jsonObjectClone(jsonObjectGetIndex(o, i)));
+            result = arr;
+            break;
+        case JSON_HASH:
+            hash = jsonNewObject(NULL);
+            hash->type = JSON_HASH;
+            itr = jsonNewIterator(o);
+            while( (tmp = jsonIteratorNext(itr)) )
+                jsonObjectSetKey(hash, itr->key, jsonObjectClone(tmp));
+            jsonIteratorFree(itr);
+            result = hash;
+            break;
+    }
+
+    jsonObjectSetClass(result, jsonObjectGetClass(o));
+    return result;
+}
+
+int jsonBoolIsTrue( const jsonObject* boolObj ) {
+    if( boolObj && boolObj->type == JSON_BOOL && boolObj->value.b )
+        return 1;
+    return 0;
+}
+
+
+char* jsonObjectToSimpleString( const jsonObject* o ) {
+       if(!o) return NULL;
+
+       char* value = NULL;
+
+       switch( o->type ) {
+
+               case JSON_NUMBER:
+                       value = strdup( o->value.s ? o->value.s : "0" );
+                       break;
+
+               case JSON_STRING:
+                       value = strdup(o->value.s);
+       }
+
+       return value;
+}
+
+/**
+ Return 1 if the string is numeric, otherwise return 0.
+ This validation follows the rules defined by the grammar at:
+ http://www.json.org/
+ **/
+int jsonIsNumeric( const char* s ) {
+
+       if( !s || !*s ) return 0;
+
+       const char* p = s;
+
+       // skip leading minus sign, if present (leading plus sign not allowed)
+
+       if( '-' == *p )
+               ++p;
+
+       // There must be at least one digit to the left of the decimal
+
+       if( isdigit( (unsigned char) *p ) ) {
+               if( '0' == *p++ ) {
+
+                       // If the first digit is zero, it must be the
+                       // only digit to the lerft of the decimal
+
+                       if( isdigit( (unsigned char) *p ) )
+                               return 0;
+               }
+               else {
+
+                       // Skip oer the following digits
+
+                       while( isdigit( (unsigned char) *p ) ) ++p;
+               }
+       }
+       else
+               return 0;
+
+       if( !*p )
+               return 1;             // integer
+
+       if( '.' == *p ) {
+
+               ++p;
+
+               // If there is a decimal point, there must be
+               // at least one digit to the right of it
+
+               if( isdigit( (unsigned char) *p ) )
+                       ++p;
+               else
+                       return 0;
+
+               // skip over contiguous digits
+
+               while( isdigit( (unsigned char) *p ) ) ++p;
+       }
+
+       if( ! *p )
+               return 1;  // decimal fraction, no exponent
+       else if( *p != 'e' && *p != 'E' )
+               return 0;  // extra junk, no exponent
+       else
+               ++p;
+
+       // If we get this far, we have the beginnings of an exponent.
+       // Skip over optional sign of exponent.
+
+       if( '-' == *p || '+' == *p )
+               ++p;
+
+       // There must be at least one digit in the exponent
+       
+       if( isdigit( (unsigned char) *p ) )
+               ++p;
+       else
+               return 0;
+
+       // skip over contiguous digits
+
+       while( isdigit( (unsigned char) *p ) ) ++p;
+
+       if( *p )
+               return 0;  // extra junk
+       else
+               return 1;  // number with exponent
+}
+
+/**
+ Allocate and reformat a numeric string into one that is valid
+ by JSON rules.  If the string is not numeric, return NULL.
+ Caller is responsible for freeing the buffer.
+ **/
+char* jsonScrubNumber( const char* s ) {
+       if( !s || !*s ) return NULL;
+
+       growing_buffer* buf = buffer_init( 64 );
+
+       // Skip leading white space, if present
+
+       while( isspace( (unsigned char) *s ) ) ++s;
+
+       // Skip leading plus sign, if present, but keep a minus
+
+       if( '-' == *s )
+       {
+               buffer_add_char( buf, '-' );
+               ++s;
+       }
+       else if( '+' == *s )
+               ++s;
+
+       if( '\0' == *s ) {
+               // No digits found
+
+               buffer_free( buf );
+               return NULL;
+       }
+       // Skip any leading zeros
+
+       while( '0' == *s ) ++s;
+
+       // Capture digits to the left of the decimal,
+       // and note whether there are any.
+
+       int left_digit = 0;  // boolean
+
+       if( isdigit( (unsigned char) *s ) ) {
+               buffer_add_char( buf, *s++ );
+               left_digit = 1;
+       }
+       
+       while( isdigit( (unsigned char) *s  ) )
+               buffer_add_char( buf, *s++ );
+
+       // Now we expect to see a decimal point,
+       // an exponent, or end-of-string.
+
+       switch( *s )
+       {
+               case '\0' :
+                       break;
+               case '.' :
+               {
+                       // Add a single leading zero, if we need to
+
+                       if( ! left_digit )
+                               buffer_add_char( buf, '0' );
+                       buffer_add_char( buf, '.' );
+                       ++s;
+
+                       if( ! left_digit && ! isdigit( (unsigned char) *s ) )
+                       {
+                               // No digits on either side of decimal
+
+                               buffer_free( buf );
+                               return NULL;
+                       }
+
+                       // Collect digits to right of decimal
+
+                       while( isdigit( (unsigned char) *s ) )
+                               buffer_add_char( buf, *s++ );
+
+                       break;
+               }
+               case 'e' :
+               case 'E' :
+
+                       // Exponent; we'll deal with it later, but
+                       // meanwhile make sure we have something
+                       // to its left
+
+                       if( ! left_digit )
+                               buffer_add_char( buf, '1' );
+                       break;
+               default :
+
+                       // Unexpected character; bail out
+
+                       buffer_free( buf );
+                       return NULL;
+       }
+
+       if( '\0' == *s )    // Are we done yet?
+               return buffer_release( buf );
+
+       if( 'e' != *s && 'E' != *s ) {
+
+               // Unexpected character: bail out
+
+               buffer_free( buf );
+               return NULL;
+       }
+
+       // We have an exponent.  Load the e or E,
+       // and the sign if there is one.
+
+       buffer_add_char( buf, *s++ );
+
+       if( '+' == *s || '-' == *s )
+               buffer_add_char( buf, *s++ );
+
+       // Collect digits of the exponent
+
+       while( isdigit( (unsigned char) *s ) )
+               buffer_add_char( buf, *s++ );
+
+       // There better not be anything left
+
+       if( *s ) {
+               buffer_free( buf );
+               return NULL;
+       }
+
+       return buffer_release( buf );
+}
diff --git a/trunk/src/libopensrf/osrf_json_parser.c b/trunk/src/libopensrf/osrf_json_parser.c
new file mode 100644 (file)
index 0000000..fb6e61d
--- /dev/null
@@ -0,0 +1,692 @@
+/*
+Copyright (C) 2006  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+#include <opensrf/osrf_json.h>
+#include <opensrf/osrf_json_utils.h>
+#include <ctype.h>
+
+
+/* if the client sets a global error handler, this will point to it */
+static void (*jsonClientErrorCallback) (const char*) = NULL;
+
+/* these are the handlers for our internal parser */
+static const jsonParserHandler jsonInternalParserHandler = {
+       _jsonHandleStartObject,
+       _jsonHandleObjectKey,
+       _jsonHandleEndObject,
+       _jsonHandleStartArray,
+       _jsonHandleEndArray,
+       _jsonHandleNull,
+       _jsonHandleString,
+       _jsonHandleBool,
+       _jsonHandleNumber,
+       _jsonHandleError
+};
+
+static jsonParserContext staticContext;
+static int staticContextInUse = 0;       // boolean
+
+static jsonInternalParser staticParser;
+static int staticParserInUse = 0;        // boolean
+
+jsonParserContext* jsonNewParser( const jsonParserHandler* handler, void* userData) {
+       jsonParserContext* ctx;
+
+       // Use the static instance of jsonParserContext,
+       // if it's available
+       
+       if( staticContextInUse )
+               OSRF_MALLOC(ctx, sizeof(jsonParserContext));
+       else {
+               ctx = &staticContext;
+               staticContextInUse = 1;
+       }
+
+       ctx->stateStack                 = osrfNewList();
+       ctx->buffer                                     = buffer_init(512);
+       ctx->utfbuf                                     = buffer_init(5);
+       ctx->handler                            = handler;
+       ctx->state                                      = 0;
+       ctx->index                                      = 0;
+       ctx->chunksize                          = 0;
+       ctx->flags                                      = 0;
+       ctx->chunk                                      = NULL;
+       ctx->userData                           = userData;
+       return ctx;
+}
+
+void jsonParserFree( jsonParserContext* ctx ) {
+       if(!ctx) return;
+       buffer_free(ctx->buffer);
+       buffer_free(ctx->utfbuf);
+       osrfListFree(ctx->stateStack);
+
+       // if the jsonParserContext was allocated
+       // dynamically, then free it
+       
+       if( &staticContext == ctx )
+               staticContextInUse = 0;
+       else
+               free(ctx);
+}
+
+
+void jsonSetGlobalErrorHandler(void (*errorHandler) (const char*)) {
+       jsonClientErrorCallback = errorHandler;
+}
+
+
+int _jsonParserError( jsonParserContext* ctx, char* err, ... ) {
+       if( ctx->handler->handleError ) {
+               VA_LIST_TO_STRING(err);
+
+               // Determine the beginning and ending points of a JSON
+               // fragment to display, from the vicinity of the error
+
+               int pre = ctx->index - 15;
+               if( pre < 0 ) pre = 0;
+               int post= ctx->index + 15;
+               if( post >= ctx->chunksize ) post = ctx->chunksize - 1;
+
+               // Copy the fragment into a buffer
+               
+               int len = post - pre + 1;  // length of fragment
+               char buf[len + 1];
+               memcpy( buf, ctx->chunk + pre, len );
+               buf[ len ] = '\0';
+
+               // Issue an error message
+
+               ctx->handler->handleError( ctx->userData,
+                       "*JSON Parser Error\n - char  = %c\n "
+                       "- index = %d\n - near  => %s\n - %s", 
+                       ctx->chunk[ctx->index], ctx->index, buf, VA_BUF );
+       }
+       JSON_STATE_SET(ctx, JSON_STATE_IS_INVALID);
+       return -1;
+}
+
+
+int _jsonParserHandleUnicode( jsonParserContext* ctx ) {
+
+       /* collect as many of the utf characters as we can in this chunk */
+       JSON_CACHE_DATA(ctx, ctx->utfbuf, 4);
+
+       /* we ran off the end of the chunk */
+       if( ctx->utfbuf->n_used < 4 ) {
+               JSON_STATE_SET(ctx, JSON_STATE_IN_UTF);
+               return 1;
+       }
+
+       ctx->index--; /* push it back to index of the final utf char */
+
+       /* ----------------------------------------------------------------------- */
+       /* We have all of the escaped unicode data.  Write it to the buffer */
+       /* The following chunk is used with permission from 
+        * json-c http://oss.metaparadigm.com/json-c/ 
+        */
+       #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
+       unsigned char utf_out[4];
+       memset(utf_out, 0, sizeof(utf_out));
+       char* buf = ctx->utfbuf->buf;
+
+       unsigned int ucs_char =
+               (hexdigit(buf[0] ) << 12) +
+               (hexdigit(buf[1]) << 8) +
+               (hexdigit(buf[2]) << 4) +
+               hexdigit(buf[3]);
+
+       if (ucs_char < 0x80) {
+               utf_out[0] = ucs_char;
+               OSRF_BUFFER_ADD(ctx->buffer, (char*)utf_out);
+
+       } else if (ucs_char < 0x800) {
+               utf_out[0] = 0xc0 | (ucs_char >> 6);
+               utf_out[1] = 0x80 | (ucs_char & 0x3f);
+               OSRF_BUFFER_ADD(ctx->buffer, (char*)utf_out);
+
+       } else {
+               utf_out[0] = 0xe0 | (ucs_char >> 12);
+               utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
+               utf_out[2] = 0x80 | (ucs_char & 0x3f);
+               OSRF_BUFFER_ADD(ctx->buffer, (char*)utf_out);
+       }
+       /* ----------------------------------------------------------------------- */
+       /* ----------------------------------------------------------------------- */
+
+       JSON_STATE_REMOVE(ctx, JSON_STATE_IN_UTF);
+       JSON_STATE_REMOVE(ctx, JSON_STATE_IN_ESCAPE);
+       OSRF_BUFFER_RESET(ctx->utfbuf);
+       return 0;
+}
+
+
+
+/* type : 0=null, 1=true, 2=false */
+int _jsonParserHandleMatch( jsonParserContext* ctx, int type ) {
+
+       switch(type) {
+
+               case 0: /* JSON null */
+
+                       /* first see if we have it all first */
+                       if( ctx->chunksize > (ctx->index + 3) ) {
+                               if( strncasecmp(ctx->chunk + ctx->index, "null", 4) ) 
+                                       return _jsonParserError(ctx, "Invalid JSON 'null' sequence");
+                               if( ctx->handler->handleNull ) 
+                                       ctx->handler->handleNull(ctx->userData);
+                               ctx->index += 4;
+                               break;
+                       }
+
+                       JSON_CACHE_DATA(ctx, ctx->buffer, 4);
+                       if( ctx->buffer->n_used < 4 ) {
+                               JSON_STATE_SET(ctx, JSON_STATE_IN_NULL);
+                               return 1;
+                       } 
+
+                       if( strncasecmp(ctx->buffer->buf, "null", 4) ) 
+                               return _jsonParserError(ctx, "Invalid JSON 'null' sequence");
+                       if( ctx->handler->handleNull ) 
+                               ctx->handler->handleNull(ctx->userData);
+                       break;
+
+               case 1: /* JSON true */
+
+                       /* see if we have it all first */
+                       if( ctx->chunksize > (ctx->index + 3) ) {
+                               if( strncasecmp(ctx->chunk + ctx->index, "true", 4) ) 
+                                       return _jsonParserError(ctx, "Invalid JSON 'true' sequence");
+                               if( ctx->handler->handleBool ) 
+                                       ctx->handler->handleBool(ctx->userData, 1);
+                               ctx->index += 4;
+                               break;
+                       }
+
+                       JSON_CACHE_DATA(ctx, ctx->buffer, 4);
+                       if( ctx->buffer->n_used < 4 ) {
+                               JSON_STATE_SET(ctx, JSON_STATE_IN_TRUE);
+                               return 1;
+                       } 
+                       if( strncasecmp( ctx->buffer->buf, "true", 4 ) ) {
+                               return _jsonParserError(ctx, "Invalid JSON 'true' sequence");
+                       }
+                       if( ctx->handler->handleBool ) 
+                               ctx->handler->handleBool(ctx->userData, 1);
+                       break;
+
+               case 2: /* JSON false */
+
+                       /* see if we have it all first */
+                       if( ctx->chunksize > (ctx->index + 4) ) {
+                               if( strncasecmp(ctx->chunk + ctx->index, "false", 5) ) 
+                                       return _jsonParserError(ctx, "Invalid JSON 'false' sequence");
+                               if( ctx->handler->handleBool ) 
+                                       ctx->handler->handleBool(ctx->userData, 0);
+                               ctx->index += 5;
+                               break;
+                       }
+
+                       JSON_CACHE_DATA(ctx, ctx->buffer, 5);
+                       if( ctx->buffer->n_used < 5 ) {
+                               JSON_STATE_SET(ctx, JSON_STATE_IN_FALSE);
+                               return 1;
+                       }
+                       if( strncasecmp( ctx->buffer->buf, "false", 5 ) ) 
+                               return _jsonParserError(ctx, "Invalid JSON 'false' sequence");
+                       if( ctx->handler->handleBool ) 
+                               ctx->handler->handleBool(ctx->userData, 0);
+                       break;
+
+               default: 
+                       fprintf(stderr, "Invalid type flag\n");
+                       return -1;
+
+       }
+
+       ctx->index--; /* set it back to the index of the final sequence character */
+       OSRF_BUFFER_RESET(ctx->buffer);
+       JSON_STATE_REMOVE(ctx, JSON_STATE_IN_NULL);
+       JSON_STATE_REMOVE(ctx, JSON_STATE_IN_TRUE);
+       JSON_STATE_REMOVE(ctx, JSON_STATE_IN_FALSE);
+
+       return 0;
+}
+
+
+int _jsonParserHandleString( jsonParserContext* ctx ) {
+
+       char c = ctx->chunk[ctx->index];
+
+       if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_ESCAPE) ) {
+
+               if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_UTF) ) {
+
+                       return _jsonParserHandleUnicode( ctx );
+                                               
+               } else {
+
+                       switch(c) {
+
+                               /* handle all of the escape chars */
+                               case '\\': OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\\' ); break;
+                               case '"'        : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\"' ); break;
+                               case 't'        : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\t' ); break;
+                               case 'b'        : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\b' ); break;
+                               case 'f'        : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\f' ); break;
+                               case 'r'        : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\r' ); break;
+                               case 'n'        : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\n' ); break;
+                               case 'u'        : 
+                                       ctx->index++; /* progress to the first utf char */
+                                       return _jsonParserHandleUnicode( ctx );
+                               default : OSRF_BUFFER_ADD_CHAR( ctx->buffer, c );
+                       }
+               }
+
+               JSON_STATE_REMOVE(ctx, JSON_STATE_IN_ESCAPE);
+               return 0;
+
+       } else {
+
+               switch(c) {
+
+                       case '"'        : /* this string is ending */
+                               if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_KEY) ) {
+
+                                       /* object key */
+                                       if(ctx->handler->handleObjectKey) {
+                                               ctx->handler->handleObjectKey( 
+                                                       ctx->userData, ctx->buffer->buf);
+                                       }
+
+                               } else { /* regular json string */
+
+                                       if(ctx->handler->handleString) {
+                                               ctx->handler->handleString( 
+                                                       ctx->userData, ctx->buffer->buf );
+                                       }
+
+                               }
+
+                               OSRF_BUFFER_RESET(ctx->buffer); /* flush the buffer and states */
+                               JSON_STATE_REMOVE(ctx, JSON_STATE_IN_STRING);
+                               JSON_STATE_REMOVE(ctx, JSON_STATE_IN_KEY);
+                               break;
+
+                       case '\\' : JSON_STATE_SET(ctx, JSON_STATE_IN_ESCAPE); break;
+                       default  : OSRF_BUFFER_ADD_CHAR( ctx->buffer, c );
+               }
+       }
+       return 0;
+}
+
+
+int _jsonParserHandleNumber( jsonParserContext* ctx ) {
+       char c = ctx->chunk[ctx->index];
+
+       do {
+               OSRF_BUFFER_ADD_CHAR(ctx->buffer, c);
+               c = ctx->chunk[++(ctx->index)];
+       } while( strchr(JSON_NUMBER_CHARS, c) && ctx->index < ctx->chunksize );
+
+       /* if we're run off the end of the chunk and we're not parsing the last chunk,
+        * save the number and the state */
+       if( ctx->index >= ctx->chunksize && 
+                       ! JSON_PARSE_FLAG_CHECK(ctx, JSON_PARSE_LAST_CHUNK) ) {
+               JSON_STATE_SET(ctx, JSON_STATE_IN_NUMBER);
+               return 1;
+       }
+
+       if(ctx->handler->handleNumber)
+       {
+               if( jsonIsNumeric( ctx->buffer->buf ) )
+                       ctx->handler->handleNumber( ctx->userData, ctx->buffer->buf );
+               else {
+                       // The number string is not numeric according to JSON rules.
+                       // Scrub it into an acceptable format.
+
+                       char* scrubbed = jsonScrubNumber( ctx->buffer->buf );
+                       if( !scrubbed )
+                               return _jsonParserError(ctx, "Invalid number sequence");
+                       else {
+                               ctx->handler->handleNumber( ctx->userData, scrubbed );
+                               free( scrubbed );
+                       }
+               }
+       }
+       
+       ctx->index--; /* scooch back to the first non-digit number */
+       JSON_STATE_REMOVE(ctx, JSON_STATE_IN_NUMBER);
+       OSRF_BUFFER_RESET(ctx->buffer);
+       return 0;
+}
+
+int jsonParseChunk( jsonParserContext* ctx, const char* data, int datalen, int flags ) {
+
+       if( !( ctx && ctx->handler && data && datalen > 0 )) return -1;
+       ctx->chunksize  = datalen;
+       ctx->chunk              = data;
+       ctx->flags              = flags;
+       char c;
+
+       if( JSON_STATE_CHECK(ctx, JSON_STATE_IS_INVALID) )
+               return _jsonParserError( ctx, "JSON Parser cannot continue after an error" );
+
+       if( JSON_STATE_CHECK(ctx, JSON_STATE_IS_DONE) )
+               return _jsonParserError( ctx, "Extra content at end of JSON data" );
+
+       for( ctx->index = 0; (ctx->index < ctx->chunksize) && 
+                               (c = ctx->chunk[ctx->index]); ctx->index++ ) {
+
+               /* middle of parsing a string */
+               if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_STRING)) {
+                       if( _jsonParserHandleString(ctx) == -1 )
+                               return -1;
+                       continue;
+               }
+
+               /* middle of parsing a number */
+               if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_NUMBER) ) {
+                       if( _jsonParserHandleNumber(ctx) == -1 )
+                               return -1;
+                       continue;
+               }
+
+
+#ifdef OSRF_JSON_ALLOW_COMMENTS
+               /* we just saw a bare '/' character */
+               if( JSON_STATE_CHECK(ctx, JSON_STATE_START_COMMENT) ) {
+                       if(c == '*') {
+                               JSON_STATE_REMOVE(ctx, JSON_STATE_START_COMMENT);
+                               JSON_STATE_SET(ctx, JSON_STATE_IN_COMMENT);
+                               continue;
+                       } else {
+                               return _jsonParserError( ctx, "Invalid comment initializer" );
+                       }
+               }
+
+               /* we're currently in the middle of a comment block */
+               if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_COMMENT) ) {
+                       if(c == '*') {
+                               JSON_STATE_REMOVE(ctx, JSON_STATE_IN_COMMENT);
+                               JSON_STATE_SET(ctx, JSON_STATE_END_COMMENT);
+                               continue;
+                       } else {
+                               continue;
+                       }
+               }
+
+               /* we're in a comment, and we just saw a '*' character */
+               if( JSON_STATE_CHECK(ctx, JSON_STATE_END_COMMENT) ) {
+                       if( c == '/' ) { /* comment is finished */
+                               JSON_STATE_REMOVE(ctx, JSON_STATE_END_COMMENT);
+                               continue;
+                       } else {
+                               /* looks like this isn't the end of the comment after all */
+                               JSON_STATE_SET(ctx, JSON_STATE_IN_COMMENT);
+                               JSON_STATE_REMOVE(ctx, JSON_STATE_END_COMMENT);
+                       }
+               }
+#endif
+
+               /* if we're in the middle of parsing a null/true/false sequence */
+               if( JSON_STATE_CHECK(ctx, (JSON_STATE_IN_NULL | 
+                                       JSON_STATE_IN_TRUE | JSON_STATE_IN_FALSE)) ) {
+
+                       int type = (JSON_STATE_CHECK(ctx, JSON_STATE_IN_NULL)) ? 0 :
+                               (JSON_STATE_CHECK(ctx, JSON_STATE_IN_TRUE)) ? 1 : 2;
+
+                       if( _jsonParserHandleMatch( ctx, type ) == -1 ) 
+                               return -1;
+                       continue;
+               }
+
+               JSON_EAT_WS(ctx);
+
+               /* handle all of the top level characters */
+               switch(c) {
+
+                       case '{' : /* starting an object */
+                               if( ctx->handler->handleStartObject) 
+                                       ctx->handler->handleStartObject( ctx->userData );
+                               JSON_STATE_PUSH(ctx, JSON_STATE_IN_OBJECT);
+                               JSON_STATE_SET(ctx, JSON_STATE_IN_KEY);
+                               break;
+
+                       case '}' : /* ending an object */
+                               if( ctx->handler->handleEndObject) 
+                                       ctx->handler->handleEndObject( ctx->userData ); 
+                JSON_STATE_REMOVE(ctx, JSON_STATE_IN_KEY);
+                               JSON_STATE_POP(ctx);
+                               if( JSON_STATE_PEEK(ctx) == NULL )
+                                       JSON_STATE_SET(ctx, JSON_STATE_IS_DONE);
+                               break;
+
+                       case '[' : /* starting an array */
+                               if( ctx->handler->handleStartArray )
+                                       ctx->handler->handleStartArray( ctx->userData );
+                               JSON_STATE_PUSH(ctx, JSON_STATE_IN_ARRAY);
+                               break;
+
+                       case ']': /* ending an array */
+                               if( ctx->handler->handleEndArray )
+                                       ctx->handler->handleEndArray( ctx->userData );
+                               JSON_STATE_POP(ctx);
+                               if( JSON_STATE_PEEK(ctx) == NULL )
+                                       JSON_STATE_SET(ctx, JSON_STATE_IS_DONE);
+                               break;
+                               
+                       case ':' : /* done with the object key */
+                               JSON_STATE_REMOVE(ctx, JSON_STATE_IN_KEY);
+                               break;
+
+                       case ',' : /* after object or array item */
+                               if( JSON_STATE_CHECK_STACK(ctx, JSON_STATE_IN_OBJECT) )
+                                       JSON_STATE_SET(ctx, JSON_STATE_IN_KEY);
+                               break;
+
+                       case 'n' :
+                       case 'N' : /* null */
+                               if( _jsonParserHandleMatch( ctx, 0 ) == -1)
+                                       return -1;
+                               break;
+
+                       case 't' :
+                       case 'T' :
+                               if( _jsonParserHandleMatch( ctx, 1 ) == -1 )
+                                       return -1;
+                               break;
+
+                       case 'f' :
+                       case 'F' :
+                               if( _jsonParserHandleMatch( ctx, 2 ) == -1)
+                                       return -1;
+                               break;
+
+                       case '"' : 
+                               JSON_STATE_SET(ctx, JSON_STATE_IN_STRING);
+                               break;
+
+#ifdef OSRF_JSON_ALLOW_COMMENTS
+                       case '/' :
+                               JSON_STATE_SET(ctx, JSON_STATE_START_COMMENT);
+                               break;
+#endif
+
+                       default:
+                               if( strchr(JSON_NUMBER_CHARS, c) ) {
+                                       if( _jsonParserHandleNumber( ctx ) == -1 )
+                                               return -1;
+                               } else {
+                                       return _jsonParserError( ctx, "Invalid Token" );
+                               }
+               }
+       }
+
+       return 0;
+}
+
+
+jsonInternalParser* _jsonNewInternalParser() {
+       jsonInternalParser* p;
+
+       // Use the static instance of jsonInternalParser,
+       // if it's available
+       
+       if( staticParserInUse )
+               OSRF_MALLOC(p, sizeof(jsonInternalParser));
+       else {
+               p = &staticParser;
+               staticParserInUse = 1;
+       }
+
+       p->ctx = jsonNewParser( &jsonInternalParserHandler, p );
+       p->obj          = NULL;
+       p->current  = NULL;
+       p->lastkey      = NULL;
+       p->handleError = NULL;
+       return p;
+}
+
+void _jsonInternalParserFree(jsonInternalParser* p) {
+       if(!p) return;
+       jsonParserFree(p->ctx);
+       free(p->lastkey);
+
+       // if the jsonInternalParser was allocated
+       // dynamically, then free it
+       
+       if( &staticParser == p )
+               staticParserInUse = 0;
+       else
+               free(p);
+}
+
+static jsonObject* _jsonParseStringImpl(const char* str, void (*errorHandler) (const char*) ) {
+       jsonInternalParser* parser = _jsonNewInternalParser();
+       parser->handleError = errorHandler;
+       jsonParseChunk( parser->ctx, str, strlen(str),  JSON_PARSE_LAST_CHUNK );
+       jsonObject* obj = parser->obj;
+       _jsonInternalParserFree(parser);
+       return obj;
+}
+
+jsonObject* jsonParseStringHandleError( 
+               void (*errorHandler) (const char*), char* str, ... ) {
+       if(!str) return NULL;
+       VA_LIST_TO_STRING(str);
+       return _jsonParseStringImpl(VA_BUF, errorHandler);
+}
+
+jsonObject* jsonParseString( const char* str ) {
+       if(!str) return NULL;
+       jsonObject* obj =  _jsonParseStringImpl(str, NULL);
+       jsonObject* obj2 = jsonObjectDecodeClass(obj);
+       jsonObjectFree(obj);
+       return obj2;
+}
+
+jsonObject* jsonParseStringRaw( const char* str ) {
+       if(!str) return NULL;
+       return _jsonParseStringImpl(str, NULL);
+}
+
+jsonObject* jsonParseStringFmt( const char* str, ... ) {
+       if(!str) return NULL;
+       VA_LIST_TO_STRING(str);
+       return _jsonParseStringImpl(VA_BUF, NULL);
+}
+
+
+#define JSON_SHOVE_ITEM(ctx,type)  \
+       jsonInternalParser* p = (jsonInternalParser*) ctx;\
+       _jsonInsertParserItem(p, jsonNewObjectType(type));
+
+void _jsonHandleStartObject(void* ctx) { JSON_SHOVE_ITEM(ctx, JSON_HASH); }
+void _jsonHandleStartArray(void* ctx) { JSON_SHOVE_ITEM(ctx, JSON_ARRAY); }
+void _jsonHandleNull(void* ctx) { JSON_SHOVE_ITEM(ctx, JSON_NULL); }
+
+void _jsonHandleObjectKey(void* ctx, char* key) {
+       jsonInternalParser* p = (jsonInternalParser*) ctx;
+       free(p->lastkey);
+       p->lastkey = strdup(key);
+}
+
+void _jsonHandleEndObject(void* ctx) {
+       jsonInternalParser* p = (jsonInternalParser*) ctx;
+       p->current = p->current->parent;
+}
+
+void _jsonHandleEndArray(void* ctx) {
+       jsonInternalParser* p = (jsonInternalParser*) ctx;
+       p->current = p->current->parent;
+}
+
+void _jsonHandleString(void* ctx, char* string) {
+       jsonInternalParser* p = (jsonInternalParser*) ctx;
+       _jsonInsertParserItem(p, jsonNewObject(string));
+}
+
+void _jsonHandleBool(void* ctx, int boolval) {
+       jsonInternalParser* p = (jsonInternalParser*) ctx;
+       jsonObject* obj = jsonNewObjectType(JSON_BOOL);
+       obj->value.b = boolval;
+       _jsonInsertParserItem(p, obj);
+}
+
+void _jsonHandleNumber(void* ctx, const char* numstr) {
+       jsonObject* obj = jsonNewNumberStringObject(numstr);
+       jsonInternalParser* p = (jsonInternalParser*) ctx;
+       _jsonInsertParserItem(p, obj);
+}
+
+void _jsonHandleError(void* ctx, char* str, ...) {
+       jsonInternalParser* p = (jsonInternalParser*) ctx;
+       VA_LIST_TO_STRING(str);
+
+       if( p->handleError ) 
+               p->handleError(VA_BUF);
+       else 
+               if( jsonClientErrorCallback ) 
+                       jsonClientErrorCallback(VA_BUF);
+
+       else fprintf(stderr, "%s\n", VA_BUF);
+       jsonObjectFree(p->obj);
+       p->obj = NULL;
+}
+
+
+void _jsonInsertParserItem( jsonInternalParser* p, jsonObject* newo ) {
+
+       if( !p->obj ) {
+
+               /* new parser, set the new object to our object */
+               p->obj = p->current = newo;
+
+       } else {
+
+               /* insert the new object into the current container object */
+               if(p->current->type == JSON_HASH)
+                       jsonObjectSetKey(p->current, p->lastkey, newo);
+               else  // assume it's a JSON_ARRAY; if it isn't, it'll become one
+                       jsonObjectPush(p->current, newo);
+
+               /* if the new object is a container object, make it our current container */
+               if( newo->type == JSON_ARRAY || newo->type == JSON_HASH )
+                       p->current = newo;      
+       }
+}
+
+
diff --git a/trunk/src/libopensrf/osrf_json_test.c b/trunk/src/libopensrf/osrf_json_test.c
new file mode 100644 (file)
index 0000000..4d1994d
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Basic JSON test module.  Needs more strenous tests....
+ *
+ */
+#include <stdio.h>
+#include <opensrf/osrf_json.h>
+
+static void speedTest();
+
+
+int main(int argc, char* argv[]) {
+    /* XXX add support for command line test type specification */
+    speedTest(); 
+    return 0; 
+}
+
+
+
+static void speedTest() {
+
+    /* creates a giant json object, generating JSON strings
+     * of subobjects as it goes. */
+
+    int i,k;
+    int count = 50;
+    char buf[16];
+    char* jsonString;
+
+    jsonObject* array;
+    jsonObject* dupe;
+    jsonObject* hash = jsonNewObject(NULL);
+
+    for(i = 0; i < count; i++) {
+        
+        snprintf(buf, sizeof(buf), "key_%d", i);
+
+        array = jsonNewObject(NULL);
+        for(k = 0; k < count + i; k++) {
+            jsonObjectPush(array, jsonNewNumberObject(k));
+            jsonObjectPush(array, jsonNewObject(NULL));
+            jsonObjectPush(array, jsonNewObjectFmt("str %d-%d", i, k));
+        }
+        jsonObjectSetKey(hash, buf, array);
+
+        jsonString = jsonObjectToJSON(hash);
+        printf("%s\n\n", jsonString);
+        dupe = jsonParseString(jsonString);
+
+        jsonObjectFree(dupe);
+        free(jsonString);
+    }
+
+    jsonObjectFree(hash);
+}
+
diff --git a/trunk/src/libopensrf/osrf_json_tools.c b/trunk/src/libopensrf/osrf_json_tools.c
new file mode 100644 (file)
index 0000000..0d33acf
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+Copyright (C) 2006  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+#include <opensrf/osrf_json.h>
+#include <opensrf/osrf_json_utils.h>
+
+static jsonObject* findMultiPath( const jsonObject* o,
+               const char* root, const char* path );
+static jsonObject* findMultiPathRecurse( const jsonObject* o, const char* root );
+static jsonObject* _jsonObjectEncodeClass( const jsonObject* obj, int ignoreClass );
+
+static char* _tabs(int count) {
+       growing_buffer* buf = buffer_init(24);
+       int i;
+       for(i=0;i<count;i++) OSRF_BUFFER_ADD(buf, "  ");
+    return buffer_release(buf);
+}
+
+char* jsonFormatString( const char* string ) {
+       if(!string) return strdup("");
+
+       growing_buffer* buf = buffer_init(64);
+       int i;
+       int depth = 0;
+       char* tab = NULL;
+
+       char c;
+       for(i=0; i!= strlen(string); i++) {
+               c = string[i];
+
+               if( c == '{' || c == '[' ) {
+
+                       tab = _tabs(++depth);
+                       buffer_fadd( buf, "%c\n%s", c, tab);
+                       free(tab);
+
+               } else if( c == '}' || c == ']' ) {
+
+                       tab = _tabs(--depth);
+                       buffer_fadd( buf, "\n%s%c", tab, c);
+                       free(tab);
+
+                       if(string[i+1] != ',') {
+                               tab = _tabs(depth);
+                               buffer_fadd( buf, "%s", tab );  
+                               free(tab);
+                       }
+
+               } else if( c == ',' ) {
+
+                       tab = _tabs(depth);
+                       buffer_fadd(buf, ",\n%s", tab);
+                       free(tab);
+
+               } else { buffer_add_char(buf, c); }
+
+       }
+
+    return buffer_release(buf);
+}
+
+
+
+jsonObject* jsonObjectDecodeClass( const jsonObject* obj ) {
+       if(!obj) return jsonNewObject(NULL);
+
+       jsonObject* newObj                       = NULL; 
+       const jsonObject* classObj       = NULL;
+       const jsonObject* payloadObj = NULL;
+       int i;
+
+       if( obj->type == JSON_HASH ) {
+
+               /* are we a special class object? */
+               if( (classObj = jsonObjectGetKeyConst( obj, JSON_CLASS_KEY )) ) {
+
+                       /* do we have a payload */
+                       if( (payloadObj = jsonObjectGetKeyConst( obj, JSON_DATA_KEY )) ) {
+                               newObj = jsonObjectDecodeClass( payloadObj ); 
+                               jsonObjectSetClass( newObj, jsonObjectGetString(classObj) );
+
+                       } else { /* class is defined but there is no payload */
+                               return NULL;
+                       }
+
+               } else { /* we're a regular hash */
+
+                       jsonIterator* itr = jsonNewIterator(obj);
+                       jsonObject* tmp;
+                       newObj = jsonNewObjectType(JSON_HASH);
+                       while( (tmp = jsonIteratorNext(itr)) ) {
+                               jsonObject* o = jsonObjectDecodeClass(tmp);
+                               jsonObjectSetKey( newObj, itr->key, o );
+                       }
+                       jsonIteratorFree(itr);
+               }
+
+       } else {
+
+               if( obj->type == JSON_ARRAY ) { /* we're an array */
+                       newObj = jsonNewObjectType(JSON_ARRAY);
+                       for( i = 0; i != obj->size; i++ ) {
+                               jsonObject* tmp = jsonObjectDecodeClass(jsonObjectGetIndex( obj, i ) );
+                               jsonObjectSetIndex( newObj, i, tmp );
+                       }
+
+               } else { /* not an aggregate type */
+                       newObj = jsonObjectClone(obj);
+               }
+       }
+               
+       return newObj;
+}
+
+jsonObject* jsonObjectEncodeClass( const jsonObject* obj ) {
+       return _jsonObjectEncodeClass( obj, 0 );
+}
+
+static jsonObject* _jsonObjectEncodeClass( const jsonObject* obj, int ignoreClass ) {
+
+       //if(!obj) return NULL;
+       if(!obj) return jsonNewObject(NULL);
+       jsonObject* newObj = NULL;
+
+       if( obj->classname && ! ignoreClass ) {
+               newObj = jsonNewObjectType(JSON_HASH);
+
+               jsonObjectSetKey( newObj, 
+                       JSON_CLASS_KEY, jsonNewObject(obj->classname) ); 
+
+               jsonObjectSetKey( newObj, 
+                       JSON_DATA_KEY, _jsonObjectEncodeClass(obj, 1));
+
+       } else if( obj->type == JSON_HASH ) {
+
+               jsonIterator* itr = jsonNewIterator(obj);
+               jsonObject* tmp;
+               newObj = jsonNewObjectType(JSON_HASH);
+
+               while( (tmp = jsonIteratorNext(itr)) ) {
+                       jsonObjectSetKey( newObj, itr->key, 
+                                       _jsonObjectEncodeClass(tmp, 0));
+               }
+               jsonIteratorFree(itr);
+
+       } else if( obj->type == JSON_ARRAY ) {
+
+               newObj = jsonNewObjectType(JSON_ARRAY);
+               int i;
+               for( i = 0; i != obj->size; i++ ) {
+                       jsonObjectSetIndex( newObj, i, 
+                               _jsonObjectEncodeClass(jsonObjectGetIndex( obj, i ), 0 ));
+               }
+
+       } else {
+               newObj = jsonObjectClone(obj);
+       }
+
+       return newObj;
+}
+
+jsonObject* jsonParseFile( const char* filename ) {
+       if(!filename) return NULL;
+       char* data = file_to_string(filename);
+       jsonObject* o = jsonParseString(data);
+       free(data);
+       return o;
+}
+
+
+
+jsonObject* jsonObjectFindPath( const jsonObject* obj, const char* format, ...) {
+       if(!obj || !format || strlen(format) < 1) return NULL;  
+
+       VA_LIST_TO_STRING(format);
+       char* buf = VA_BUF;
+       char* token = NULL;
+       char* tt; /* strtok storage */
+
+       /* special case where path starts with //  (start anywhere) */
+       if(buf[0] == '/' && buf[1] == '/' && buf[2] != '\0') {
+
+               /* copy the path before strtok_r destroys it */
+               char* pathcopy = strdup(buf);
+
+               /* grab the root of the path */
+               token = strtok_r(buf, "/", &tt);
+               if(!token) {
+                       free(pathcopy);
+                       return NULL;
+               }
+
+               jsonObject* it = findMultiPath(obj, token, pathcopy + 1);
+               free(pathcopy);
+               return it;
+       }
+       else
+       {
+               /* grab the root of the path */
+               token = strtok_r(buf, "/", &tt);
+               if(!token) return NULL;
+
+               do {
+                       obj = jsonObjectGetKeyConst(obj, token);
+               } while( (token = strtok_r(NULL, "/", &tt)) && obj);
+
+               return jsonObjectClone(obj);
+       }
+}
+
+/* --------------------------------------------------------------- */
+
+/* Utility method. finds any object in the tree that matches the path.  
+       Use this for finding paths that start with '//' */
+static jsonObject* findMultiPath(const jsonObject* obj,
+               const char* root, const char* path) {
+
+       if(!obj || ! root || !path) return NULL;
+
+       /* collect all of the potential objects */
+       jsonObject* arr = findMultiPathRecurse(obj, root);
+
+       /* path is just /root or /root/ */
+       if( strlen(root) + 2 >= strlen(path) ) {
+               return arr;
+
+       } else {
+
+               /* container for fully matching objects */
+               jsonObject* newarr = jsonNewObjectType(JSON_ARRAY);
+               int i;
+
+               /* gather all of the sub-objects that match the full path */
+               for( i = 0; i < arr->size; i++ ) {
+                       const jsonObject* a = jsonObjectGetIndex(arr, i);
+                       jsonObject* thing = jsonObjectFindPath(a , path + strlen(root) + 1); 
+
+                       if(thing) { //jsonObjectPush(newarr, thing);
+                               if(thing->type == JSON_ARRAY) {
+                       int i;
+                                       for( i = 0; i != thing->size; i++ )
+                                               jsonObjectPush(newarr, jsonObjectClone(jsonObjectGetIndex(thing,i)));
+                                       jsonObjectFree(thing);
+                               } else {
+                                       jsonObjectPush(newarr, thing);
+                               }
+                       }
+               }
+
+               jsonObjectFree(arr);
+               return newarr;
+       }
+}
+
+/* returns a list of object whose key is 'root'.  These are used as
+       potential objects when doing a // search */
+static jsonObject* findMultiPathRecurse(const jsonObject* obj, const char* root) {
+
+       jsonObject* arr = jsonNewObjectType(JSON_ARRAY);
+       if(!obj) return arr;
+
+       int i;
+
+       /* if the current object has a node that matches, add it */
+
+       const jsonObject* o = jsonObjectGetKeyConst(obj, root);
+       if(o) jsonObjectPush( arr, jsonObjectClone(o) );
+
+       jsonObject* tmp = NULL;
+       jsonObject* childarr;
+       jsonIterator* itr = jsonNewIterator(obj);
+
+       /* recurse through the children and find all potential nodes */
+       while( (tmp = jsonIteratorNext(itr)) ) {
+               childarr = findMultiPathRecurse(tmp, root);
+               if(childarr && childarr->size > 0) {
+                       for( i = 0; i!= childarr->size; i++ ) {
+                               jsonObjectPush( arr, jsonObjectClone(jsonObjectGetIndex(childarr, i)) );
+                       }
+               }
+               jsonObjectFree(childarr);
+       }
+
+       jsonIteratorFree(itr);
+
+       return arr;
+}
+
+
+
+
diff --git a/trunk/src/libopensrf/osrf_json_xml.c b/trunk/src/libopensrf/osrf_json_xml.c
new file mode 100644 (file)
index 0000000..ef5b306
--- /dev/null
@@ -0,0 +1,354 @@
+#include <opensrf/osrf_json_xml.h>
+
+#ifdef OSRF_JSON_ENABLE_XML_UTILS
+
+struct osrfXMLGatewayParserStruct {
+    osrfList* objStack;
+    osrfList* keyStack;
+    jsonObject* obj;
+    short inString;
+    short inNumber;
+    short error;
+};
+typedef struct osrfXMLGatewayParserStruct osrfXMLGatewayParser;
+
+/** returns the attribute value with the given attribute name */
+static char* getXMLAttr(const xmlChar** atts, const char* attr_name) {
+    int i;
+    if (atts != NULL) {
+        for(i = 0; (atts[i] != NULL); i++) {
+            if(strcmp((char*) atts[i++], attr_name) == 0) {
+                if(atts[i] != NULL) 
+                    return (char*) atts[i];
+            }
+        }
+    }
+    return NULL;
+}
+
+
+static void appendChild(osrfXMLGatewayParser* p, jsonObject* obj) {
+
+    if(p->obj == NULL) 
+        p->obj = obj;
+
+    if(p->objStack->size == 0)
+        return;
+    
+    jsonObject* parent = OSRF_LIST_GET_INDEX(p->objStack, p->objStack->size - 1);
+
+    if(parent->type == JSON_ARRAY) {
+        jsonObjectPush(parent, obj);
+    } else {
+        char* key = osrfListPop(p->keyStack);
+        jsonObjectSetKey(parent, key, obj);
+        free(key); /* the list is not setup for auto-freeing */
+    }
+}
+
+
+
+static void startElementHandler(
+    void *parser, const xmlChar *name, const xmlChar **atts) {
+
+    osrfXMLGatewayParser* p = (osrfXMLGatewayParser*) parser;
+    jsonObject* obj;
+
+    char* hint = getXMLAttr(atts, "class_hint");
+
+    if(!strcmp((char*) name, "null")) {
+        appendChild(p, jsonNewObject(NULL));
+        return;
+    }
+
+    if(!strcmp((char*) name, "string")) {
+        p->inString = 1;
+        return;
+    }
+
+    if(!strcmp((char*) name, "element")) {
+       osrfListPush(p->keyStack, strdup(getXMLAttr(atts, "key")));
+       return;
+    }
+
+    if(!strcmp((char*) name, "object")) {
+        obj = jsonNewObject(NULL);
+        jsonObjectSetClass(obj, hint); /* OK if hint is NULL */
+        obj->type = JSON_HASH;
+        appendChild(p, obj);
+        osrfListPush(p->objStack, obj);
+        return;
+    }
+
+    if(!strcmp((char*) name, "array")) {
+        obj = jsonNewObject(NULL);
+        jsonObjectSetClass(obj, hint); /* OK if hint is NULL */
+        obj->type = JSON_ARRAY;
+        appendChild(p, obj);
+        osrfListPush(p->objStack, obj);
+        return;
+    }
+
+
+    if(!strcmp((char*) name, "number")) {
+        p->inNumber = 1;
+        return;
+    }
+
+    if(!strcmp((char*) name, "boolean")) {
+        obj = jsonNewObject(NULL);
+        obj->type = JSON_BOOL;
+        char* val = getXMLAttr(atts, "value");
+        if(val && !strcmp(val, "true"))
+            obj->value.b = 1;
+        
+        appendChild(p, obj);
+        return;
+    }
+}
+
+static void endElementHandler( void *parser, const xmlChar *name) {
+    if(!strcmp((char*) name, "array") || !strcmp((char*) name, "object")) {
+        osrfXMLGatewayParser* p = (osrfXMLGatewayParser*) parser;
+        osrfListPop(p->objStack);
+    }
+}
+
+static void characterHandler(void *parser, const xmlChar *ch, int len) {
+
+    char data[len+1];
+    strncpy(data, (char*) ch, len);
+    data[len] = '\0';
+    osrfXMLGatewayParser* p = (osrfXMLGatewayParser*) parser;
+
+    if(p->inString) {
+        appendChild(p, jsonNewObject(data));
+        p->inString = 0;
+        return;
+    }
+
+    if(p->inNumber) {
+        appendChild(p, jsonNewNumberObject(atof(data)));
+        p->inNumber = 0;
+        return;
+    }
+}
+
+static void parseWarningHandler(void *parser, const char* msg, ...) {
+    VA_LIST_TO_STRING(msg);
+    fprintf(stderr, "Parser warning %s\n", VA_BUF);
+    fflush(stderr);
+}
+
+static void parseErrorHandler(void *parser, const char* msg, ...) {
+
+    VA_LIST_TO_STRING(msg);
+    fprintf(stderr, "Parser error %s\n", VA_BUF);
+    fflush(stderr);
+
+    osrfXMLGatewayParser* p = (osrfXMLGatewayParser*) parser;
+
+    /*  keyStack as strdup'ed strings.  The list may
+     *  not be empty, so tell it to free the items
+     *  when it's freed (from the main routine)
+     */
+    osrfListSetDefaultFree(p->keyStack);
+    jsonObjectFree(p->obj);
+
+    p->obj = NULL;
+    p->error = 1;
+}
+
+
+
+
+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 */
+};
+
+static const xmlSAXHandlerPtr SAXHandler = &SAXHandlerStruct;
+
+jsonObject* jsonXMLToJSONObject(const char* xml) {
+
+    osrfXMLGatewayParser parser;
+
+    /* don't define freeItem, since objects will be cleaned by freeing the parent */
+    parser.objStack = osrfNewList(); 
+    /* don't define freeItem, since the list eill end up empty if there are no errors*/
+    parser.keyStack = osrfNewList(); 
+    parser.obj = NULL;
+    parser.inString = 0;
+    parser.inNumber = 0;
+
+    xmlParserCtxtPtr ctxt = xmlCreatePushParserCtxt(SAXHandler, &parser, "", 0, NULL);
+    xmlParseChunk(ctxt, xml, strlen(xml), 1);
+
+    osrfListFree(parser.objStack);
+    osrfListFree(parser.keyStack);
+    xmlFreeParserCtxt(ctxt);
+    xmlCleanupCharEncodingHandlers();
+    xmlDictCleanup();
+    xmlCleanupParser();
+
+    return parser.obj;
+}
+
+
+
+
+
+
+static char* _escape_xml (const char*);
+static int _recurse_jsonObjectToXML(const jsonObject*, growing_buffer*);
+
+char* jsonObjectToXML(const jsonObject* obj) {
+
+       if (!obj)
+               return strdup("<null/>");
+       
+       growing_buffer * res_xml = buffer_init(1024);
+
+       _recurse_jsonObjectToXML( obj, res_xml );
+       return buffer_release(res_xml);
+
+}
+
+int _recurse_jsonObjectToXML(const jsonObject* obj, growing_buffer* res_xml) {
+
+       char * hint = NULL;
+       
+       if (obj->classname)
+               hint = strdup(obj->classname);
+
+       if(obj->type == JSON_NULL) {
+
+               if (hint)
+                       buffer_fadd(res_xml, "<null class_hint=\"%s\"/>",hint);
+               else
+                       buffer_add(res_xml, "<null/>");
+
+       } else if(obj->type == JSON_BOOL) {
+
+               const char* bool_val;
+               if (obj->value.b)
+                       bool_val = "true";
+               else
+                       bool_val = "false";
+
+               if (hint)
+                       buffer_fadd(res_xml, "<boolean value=\"%s\" class_hint=\"%s\"/>", bool_val, hint);
+               else
+                       buffer_fadd(res_xml, "<boolean value=\"%s\"/>", bool_val);
+
+       } else if (obj->type == JSON_STRING) {
+               if (hint) {
+                       char * t = _escape_xml(jsonObjectGetString(obj));
+                       buffer_fadd(res_xml,"<string class_hint=\"%s\">%s</string>", hint, t);
+                       free(t);
+               } else {
+                       char * t = _escape_xml(jsonObjectGetString(obj));
+                       buffer_fadd(res_xml,"<string>%s</string>", t);
+                       free(t);
+               }
+
+       } else if(obj->type == JSON_NUMBER) {
+               double x = jsonObjectGetNumber(obj);
+               if (hint) {
+                       if (x == (int)x)
+                               buffer_fadd(res_xml,"<number class_hint=\"%s\">%d</number>", hint, (int)x);
+                       else
+                               buffer_fadd(res_xml,"<number class_hint=\"%s\">%lf</number>", hint, x);
+               } else {
+                       if (x == (int)x)
+                               buffer_fadd(res_xml,"<number>%d</number>", (int)x);
+                       else
+                               buffer_fadd(res_xml,"<number>%lf</number>", x);
+               }
+
+       } else if (obj->type == JSON_ARRAY) {
+
+               if (hint) 
+                       buffer_fadd(res_xml,"<array class_hint=\"%s\">", hint);
+               else
+                               buffer_add(res_xml,"<array>");
+
+        int i;
+        for ( i = 0; i!= obj->size; i++ )
+                   _recurse_jsonObjectToXML(jsonObjectGetIndex(obj,i), res_xml);
+
+               buffer_add(res_xml,"</array>");
+
+       } else if (obj->type == JSON_HASH) {
+
+               if (hint)
+                       buffer_fadd(res_xml,"<object class_hint=\"%s\">", hint);
+               else
+                       buffer_add(res_xml,"<object>");
+
+               jsonIterator* itr = jsonNewIterator(obj);
+               const jsonObject* tmp;
+               while( (tmp = jsonIteratorNext(itr)) ) {
+                       buffer_fadd(res_xml,"<element key=\"%s\">",itr->key);
+                       _recurse_jsonObjectToXML(tmp, res_xml);
+                       buffer_add(res_xml,"</element>");
+               }
+               jsonIteratorFree(itr);
+
+               buffer_add(res_xml,"</object>");
+       }
+
+       if (hint)
+               free(hint);
+
+       return 1;
+}
+
+static char* _escape_xml (const char* text) {
+       growing_buffer* b = buffer_init(256);
+       int len = strlen(text);
+       int i;
+       for (i = 0; i < len; i++) {
+               if (text[i] == '&')
+                       buffer_add(b,"&amp;");
+               else if (text[i] == '<')
+                       buffer_add(b,"&lt;");
+               else if (text[i] == '>')
+                       buffer_add(b,"&gt;");
+               else
+                       buffer_add_char(b,text[i]);
+       }
+       return buffer_release(b);
+}
+
+#endif
diff --git a/trunk/src/libopensrf/osrf_legacy_json.c b/trunk/src/libopensrf/osrf_legacy_json.c
new file mode 100644 (file)
index 0000000..17dab25
--- /dev/null
@@ -0,0 +1,865 @@
+/*
+Copyright (C) 2006  Georgia Public Library Service 
+Bill Erickson <billserickson@gmail.com>
+
+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.
+*/
+
+
+#include <opensrf/osrf_legacy_json.h>
+
+/* keep a copy of the length of the current json string so we don't 
+ * have to calculate it in each function
+ */
+int current_strlen; 
+
+
+jsonObject* legacy_jsonParseString( const char* string) {
+       return json_parse_string( (char*) string );
+}
+
+jsonObject* legacy_jsonParseStringFmt( const char* string, ... ) {
+       VA_LIST_TO_STRING(string);
+       return json_parse_string( VA_BUF );
+}
+
+
+jsonObject* json_parse_string(char* string) {
+
+       if(string == NULL) return NULL;
+
+       current_strlen = strlen(string);
+
+       if(current_strlen == 0) 
+               return NULL;
+
+       unsigned long index = 0;
+
+       json_eat_ws(string, &index, 1, current_strlen); /* remove leading whitespace */
+       if(index == current_strlen) return NULL;
+
+       jsonObject* obj = jsonNewObject(NULL);
+
+       int status = _json_parse_string(string, &index, obj, current_strlen);
+       if(!status) return obj;
+
+       if(status == -2) {
+               jsonObjectFree(obj);
+               return NULL;
+       }
+
+       return NULL;
+}
+
+
+int _json_parse_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen) {
+       if( !string || !index || *index >= current_strlen) return -2;
+
+       int status = 0; /* return code from parsing routines */
+       char* classname = NULL; /* object class hint */
+       json_eat_ws(string, index, 1, current_strlen); /* remove leading whitespace */
+
+       char c = string[*index];
+
+       /* remove any leading comments */
+       if( c == '/' ) { 
+
+               while(1) {
+                       (*index)++; /* move to second comment char */
+                       status = json_eat_comment(string, index, &classname, 1, current_strlen);
+                       if(status) return status;
+
+                       json_eat_ws(string, index, 1, current_strlen);
+                       c = string[*index];
+                       if(c != '/')
+                               break;
+               }
+       }
+
+       json_eat_ws(string, index, 1, current_strlen); /* remove leading whitespace */
+
+       if(*index >= current_strlen)
+               return -2;
+
+       switch(c) {
+                               
+               /* json string */
+               case '"': 
+                       (*index)++;
+                       status = json_parse_json_string(string, index, obj, current_strlen);
+                       break;
+
+               /* json array */
+               case '[':
+                       (*index)++;
+                       status = json_parse_json_array(string, index, obj, current_strlen);
+                       break;
+
+               /* json object */
+               case '{':
+                       (*index)++;
+                       status = json_parse_json_object(string, index, obj, current_strlen);
+                       break;
+
+               /* NULL */
+               case 'n':
+               case 'N':
+                       status = json_parse_json_null(string, index, obj, current_strlen);
+                       break;
+                       
+
+               /* true, false */
+               case 'f':
+               case 'F':
+               case 't':
+               case 'T':
+                       status = json_parse_json_bool(string, index, obj, current_strlen);
+                       break;
+
+               default:
+                       if(isdigit(c) || c == '.' || c == '-') { /* are we a number? */
+                               status = json_parse_json_number(string, index, obj, current_strlen);
+                               if(status) return status;
+                               break;
+                       }
+
+                       (*index)--;
+                       /* we should never get here */
+                       return json_handle_error(string, index, "_json_parse_string() final switch clause");
+       }       
+
+       if(status) return status;
+
+       json_eat_ws(string, index, 1, current_strlen);
+
+       if( *index < current_strlen ) {
+               /* remove any trailing comments */
+               c = string[*index];
+               if( c == '/' ) { 
+                       (*index)++;
+                       status = json_eat_comment(string, index, NULL, 0, current_strlen);
+                       if(status) return status;
+               }
+       }
+
+       if(classname){
+               jsonObjectSetClass(obj, classname);
+               free(classname);
+       }
+
+       return 0;
+}
+
+
+int json_parse_json_null(char* string, unsigned long* index, jsonObject* obj, int current_strlen) {
+
+       if(*index >= (current_strlen - 3)) {
+               return json_handle_error(string, index, 
+                       "_parse_json_null(): invalid null" );
+       }
+
+       if(!strncasecmp(string + (*index), "null", 4)) {
+               (*index) += 4;
+               obj->type = JSON_NULL;
+               return 0;
+       } else {
+               return json_handle_error(string, index,
+                       "_parse_json_null(): invalid null" );
+       }
+}
+
+/* should be at the first character of the bool at this point */
+int json_parse_json_bool(char* string, unsigned long* index, jsonObject* obj, int current_strlen) {
+       if( ! string || ! obj || *index >= current_strlen ) return -1;
+
+       char* ret = "json_parse_json_bool(): truncated bool";
+
+       if( *index >= (current_strlen - 5))
+               return json_handle_error(string, index, ret);
+       
+       if(!strncasecmp( string + (*index), "false", 5)) {
+               (*index) += 5;
+               obj->value.b = 0;
+               obj->type = JSON_BOOL;
+               return 0;
+       }
+
+       if( *index >= (current_strlen - 4))
+               return json_handle_error(string, index, ret);
+
+       if(!strncasecmp( string + (*index), "true", 4)) {
+               (*index) += 4;
+               obj->value.b = 1;
+               obj->type = JSON_BOOL;
+               return 0;
+       }
+
+       return json_handle_error(string, index, ret);
+}
+
+
+/* expecting the first character of the number */
+int json_parse_json_number(char* string, unsigned long* index, jsonObject* obj, int current_strlen) {
+       if( ! string || ! obj || *index >= current_strlen ) return -1;
+
+       growing_buffer* buf = buffer_init(64);
+       char c = string[*index];
+
+       int done = 0;
+       int dot_seen = 0;
+
+       /* negative number? */
+       if(c == '-') { buffer_add(buf, "-"); (*index)++; }
+
+       c = string[*index];
+
+       while(*index < current_strlen) {
+
+               if(isdigit(c)) {
+                       buffer_add_char(buf, c);
+               }
+
+               else if( c == '.' ) {
+                       if(dot_seen) {
+                               buffer_free(buf);
+                               return json_handle_error(string, index, 
+                                       "json_parse_json_number(): malformed json number");
+                       }
+                       dot_seen = 1;
+                       buffer_add_char(buf, c);
+               } else {
+                       done = 1; break;
+               }
+
+               (*index)++;
+               c = string[*index];
+               if(done) break;
+       }
+
+       obj->type = JSON_NUMBER;
+       obj->value.s = buffer_release(buf);
+       return 0;
+}
+
+/* index should point to the character directly following the '['.  when done
+ * index will point to the character directly following the ']' character
+ */
+int json_parse_json_array(char* string, unsigned long* index, jsonObject* obj, int current_strlen) {
+
+       if( ! string || ! obj || ! index || *index >= current_strlen ) return -1;
+
+       int status = 0;
+       int in_parse = 0; /* true if this array already contains one item */
+       obj->type = JSON_ARRAY;
+       int set = 0;
+       int done = 0;
+
+       while(*index < current_strlen) {
+
+               json_eat_ws(string, index, 1, current_strlen);
+
+               if(string[*index] == ']') {
+                       (*index)++;
+                       done = 1;
+                       break;
+               }
+
+               if(in_parse) {
+                       json_eat_ws(string, index, 1, current_strlen);
+                       if(string[*index] != ',') {
+                               return json_handle_error(string, index,
+                                       "json_parse_json_array(): array item not followed by a ','");
+                       }
+                       (*index)++;
+                       json_eat_ws(string, index, 1, current_strlen);
+               }
+
+               jsonObject* item = jsonNewObject(NULL);
+
+               #ifndef STRICT_JSON_READ
+               if(*index < current_strlen) {
+                       if(string[*index] == ',' || string[*index] == ']') {
+                               status = 0;
+                               set = 1;
+                       }
+               }
+               if(!set) status = _json_parse_string(string, index, item, current_strlen);
+
+               #else
+               status = _json_parse_string(string, index, item, current_strlen);
+               #endif
+
+               if(status) { jsonObjectFree(item); return status; }
+               jsonObjectPush(obj, item);
+               in_parse = 1;
+               set = 0;
+       }
+
+       if(!done)
+               return json_handle_error(string, index,
+                       "json_parse_json_array(): array not closed");
+
+       return 0;
+}
+
+
+/* index should point to the character directly following the '{'.  when done
+ * index will point to the character directly following the '}'
+ */
+int json_parse_json_object(char* string, unsigned long* index, jsonObject* obj, int current_strlen) {
+       if( ! string || !obj || ! index || *index >= current_strlen ) return -1;
+
+       obj->type = JSON_HASH;
+       int status;
+       int in_parse = 0; /* true if we've already added one item to this object */
+       int set = 0;
+       int done = 0;
+
+       while(*index < current_strlen) {
+
+               json_eat_ws(string, index, 1, current_strlen);
+
+               if(string[*index] == '}') {
+                       (*index)++;
+                       done = 1;
+                       break;
+               }
+
+               if(in_parse) {
+                       if(string[*index] != ',') {
+                               return json_handle_error(string, index,
+                                       "json_parse_json_object(): object missing ',' between elements" );
+                       }
+                       (*index)++;
+                       json_eat_ws(string, index, 1, current_strlen);
+               }
+
+               /* first we grab the hash key */
+               jsonObject* key_obj = jsonNewObject(NULL);
+               status = _json_parse_string(string, index, key_obj, current_strlen);
+               if(status) return status;
+
+               if(key_obj->type != JSON_STRING) {
+                       return json_handle_error(string, index, 
+                               "_json_parse_json_object(): hash key not a string");
+               }
+
+               char* key = key_obj->value.s;
+
+               json_eat_ws(string, index, 1, current_strlen);
+
+               if(string[*index] != ':') {
+                       return json_handle_error(string, index, 
+                               "json_parse_json_object(): hash key not followed by ':' character");
+               }
+
+               (*index)++;
+
+               /* now grab the value object */
+               json_eat_ws(string, index, 1, current_strlen);
+               jsonObject* value_obj = jsonNewObject(NULL);
+
+#ifndef STRICT_JSON_READ
+               if(*index < current_strlen) {
+                       if(string[*index] == ',' || string[*index] == '}') {
+                               status = 0;
+                               set = 1;
+                       }
+               }
+               if(!set)
+                       status = _json_parse_string(string, index, value_obj, current_strlen);
+
+#else
+                status = _json_parse_string(string, index, value_obj, current_strlen);
+#endif
+
+               if(status) return status;
+
+               /* put the data into the object and continue */
+               jsonObjectSetKey(obj, key, value_obj);
+               jsonObjectFree(key_obj);
+               in_parse = 1;
+               set = 0;
+       }
+
+       if(!done)
+               return json_handle_error(string, index,
+                       "json_parse_json_object(): object not closed");
+
+       return 0;
+}
+
+
+
+/* when done, index will point to the character after the closing quote */
+int json_parse_json_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen) {
+       if( ! string || ! index || *index >= current_strlen ) return -1;
+
+       int in_escape = 0;      
+       int done = 0;
+       growing_buffer* buf = buffer_init(64);
+
+       while(*index < current_strlen) {
+
+               char c = string[*index]; 
+
+               switch(c) {
+
+                       case '\\':
+                               if(in_escape) {
+                                       buffer_add(buf, "\\");
+                                       in_escape = 0;
+                               } else 
+                                       in_escape = 1;
+                               break;
+
+                       case '"':
+                               if(in_escape) {
+                                       buffer_add(buf, "\"");
+                                       in_escape = 0;
+                               } else 
+                                       done = 1;
+                               break;
+
+                       case 't':
+                               if(in_escape) {
+                                       buffer_add(buf,"\t");
+                                       in_escape = 0;
+                               } else 
+                                       buffer_add_char(buf, c);
+                               break;
+
+                       case 'b':
+                               if(in_escape) {
+                                       buffer_add(buf,"\b");
+                                       in_escape = 0;
+                               } else 
+                                       buffer_add_char(buf, c);
+                               break;
+
+                       case 'f':
+                               if(in_escape) {
+                                       buffer_add(buf,"\f");
+                                       in_escape = 0;
+                               } else 
+                                       buffer_add_char(buf, c);
+                               break;
+
+                       case 'r':
+                               if(in_escape) {
+                                       buffer_add(buf,"\r");
+                                       in_escape = 0;
+                               } else 
+                                       buffer_add_char(buf, c);
+                               break;
+
+                       case 'n':
+                               if(in_escape) {
+                                       buffer_add(buf,"\n");
+                                       in_escape = 0;
+                               } else 
+                                       buffer_add_char(buf, c);
+                               break;
+
+                       case 'u':
+                               if(in_escape) {
+                                       (*index)++;
+
+                                       if(*index >= (current_strlen - 4)) {
+                                               buffer_free(buf);
+                                               return json_handle_error(string, index,
+                                                       "json_parse_json_string(): truncated escaped unicode"); }
+
+                                       /* ----------------------------------------------------------------------- */
+                                       /* ----------------------------------------------------------------------- */
+                                       /* The following chunk was borrowed with permission from 
+                                               json-c http://oss.metaparadigm.com/json-c/ */
+                                       unsigned char utf_out[3] = { '\0', '\0', '\0' };
+
+                                       #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
+
+                                       unsigned int ucs_char =
+                                               (hexdigit(string[*index] ) << 12) +
+                                               (hexdigit(string[*index + 1]) << 8) +
+                                               (hexdigit(string[*index + 2]) << 4) +
+                                               hexdigit(string[*index + 3]);
+       
+                                       if (ucs_char < 0x80) {
+                                               utf_out[0] = ucs_char;
+                                               buffer_add(buf, (char*) utf_out);
+
+                                       } else if (ucs_char < 0x800) {
+                                               utf_out[0] = 0xc0 | (ucs_char >> 6);
+                                               utf_out[1] = 0x80 | (ucs_char & 0x3f);
+                                               buffer_add(buf, (char*) utf_out);
+
+                                       } else {
+                                               utf_out[0] = 0xe0 | (ucs_char >> 12);
+                                               utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
+                                               utf_out[2] = 0x80 | (ucs_char & 0x3f);
+                                               buffer_add(buf, (char*) utf_out);
+                                       }
+                                       /* ----------------------------------------------------------------------- */
+                                       /* ----------------------------------------------------------------------- */
+
+                                       (*index) += 3;
+                                       in_escape = 0;
+
+                               } else {
+
+                                       buffer_add_char(buf, c);
+                               }
+
+                               break;
+
+                       default:
+                               buffer_add_char(buf, c);
+               }
+
+               (*index)++;
+               if(done) break;
+       }
+
+       jsonObjectSetString(obj, buf->buf);
+       buffer_free(buf);
+       return 0;
+}
+
+
+void json_eat_ws(char* string, unsigned long* index, int eat_all, int current_strlen) {
+       if( ! string || ! index ) return;
+       if(*index >= current_strlen)
+               return;
+
+       if( eat_all ) { /* removes newlines, etc */
+               while(string[*index] == ' '     || 
+                               string[*index] == '\n'  ||
+                               string[*index] == '\t') 
+                       (*index)++;
+       }
+
+       else    
+               while(string[*index] == ' ') (*index)++;
+}
+
+
+/* index should be at the '*' character at the beginning of the comment.
+ * when done, index will point to the first character after the final /
+ */
+int json_eat_comment(char* string, unsigned long* index, char** buffer, int parse_class, int current_strlen) {
+       if( ! string || ! index || *index >= current_strlen ) return -1;
+       
+
+       if(string[*index] != '*' && string[*index] != '/' )
+               return json_handle_error(string, index, 
+                       "json_eat_comment(): invalid character after /");
+
+       /* chop out any // style comments */
+       if(string[*index] == '/') {
+               (*index)++;
+               char c = string[*index];
+               while(*index < current_strlen) {
+                       (*index)++;
+                       if(c == '\n') 
+                               return 0;
+                       c = string[*index];
+               }
+               return 0;
+       }
+
+       (*index)++;
+
+       int on_star                     = 0; /* true if we just saw a '*' character */
+
+       /* we're just past the '*' */
+       if(!parse_class) { /* we're not concerned with class hints */
+               while(*index < current_strlen) {
+                       if(string[*index] == '/') {
+                               if(on_star) {
+                                       (*index)++;
+                                       return 0;
+                               }
+                       }
+
+                       if(string[*index] == '*') on_star = 1;
+                       else on_star = 0;
+
+                       (*index)++;
+               }
+               return 0;
+       }
+
+
+
+       growing_buffer* buf = buffer_init(64);
+
+       int first_dash          = 0;
+       int second_dash = 0;
+       int third_dash          = 0;
+       int fourth_dash = 0;
+
+       int in_hint                     = 0;
+       int done                                = 0;
+
+       /*--S hint--*/   /* <-- Hints  look like this */
+       /*--E hint--*/
+
+       while(*index < current_strlen) {
+               char c = string[*index];
+
+               switch(c) {
+
+                       case '-':
+                               on_star = 0;
+                               if(third_dash)                  fourth_dash = 1;
+                               else if(in_hint)                third_dash      = 1;
+                               else if(first_dash)     second_dash = 1;
+                               else                                            first_dash = 1;
+                               break;
+
+                       case 'S':
+                               on_star = 0;
+                               if(second_dash && !in_hint) {
+                                       (*index)++;
+                                       json_eat_ws(string, index, 1, current_strlen);
+                                       (*index)--; /* this will get incremented at the bottom of the loop */
+                                       in_hint = 1;
+                                       break;
+                               } 
+
+                               if(second_dash && in_hint) {
+                                       buffer_add_char(buf, c);
+                                       break;
+                               }
+
+                       case 'E':
+                               on_star = 0;
+                               if(second_dash && !in_hint) {
+                                       (*index)++;
+                                       json_eat_ws(string, index, 1, current_strlen);
+                                       (*index)--; /* this will get incremented at the bottom of the loop */
+                                       in_hint = 1;
+                                       break;
+                               }
+
+                               if(second_dash && in_hint) {
+                                       buffer_add_char(buf, c);
+                                       break;
+                               }
+
+                       case '*':
+                               on_star = 1;
+                               break;
+
+                       case '/':
+                               if(on_star) 
+                                       done = 1;
+                               else
+                               on_star = 0;
+                               break;
+
+                       default:
+                               on_star = 0;
+                               if(in_hint)
+                                       buffer_add_char(buf, c);
+               }
+
+               (*index)++;
+               if(done) break;
+       }
+
+       if( buf->n_used > 0 && buffer)
+               *buffer = buffer_data(buf);
+
+       buffer_free(buf);
+       return 0;
+}
+
+int json_handle_error(char* string, unsigned long* index, char* err_msg) {
+
+       char buf[60];
+       osrf_clearbuf(buf, sizeof(buf));
+
+       if(*index > 30)
+               strncpy( buf, string + (*index - 30), sizeof(buf) - 1 );
+       else
+               strncpy( buf, string, sizeof(buf) - 1 );
+
+       buf[ sizeof(buf) - 1 ] = '\0';
+
+       fprintf(stderr, 
+                       "\nError parsing json string at charracter %c "
+                       "(code %d) and index %ld\nString length: %d\nMsg:\t%s\nNear:\t%s\nFull String:\t%s\n", 
+                       string[*index], string[*index], *index, current_strlen, err_msg, buf, string );
+
+       return -1;
+}
+
+
+jsonObject* legacy_jsonParseFile( const char* filename ) {
+       return json_parse_file( filename );
+}
+       
+jsonObject* json_parse_file(const char* filename) {
+       if(!filename) return NULL;
+       char* data = file_to_string(filename);
+       jsonObject* o = json_parse_string(data);
+       free(data);
+       return o;
+}
+
+
+
+char* legacy_jsonObjectToJSON( const jsonObject* obj ) {
+
+       if(obj == NULL) return strdup("null");
+
+       growing_buffer* buf = buffer_init(64);
+
+       /* add class hints if we have a class name */
+       if(obj->classname) {
+               buffer_add(buf,"/*--S ");
+               buffer_add(buf,obj->classname);
+               buffer_add(buf, "--*/");
+       }
+
+       switch( obj->type ) {
+
+               case JSON_BOOL: 
+                       if(obj->value.b) buffer_add(buf, "true"); 
+                       else buffer_add(buf, "false"); 
+                       break;
+
+               case JSON_NUMBER: {
+                       buffer_add(buf, obj->value.s);
+                       break;
+               }
+
+               case JSON_NULL:
+                       buffer_add(buf, "null");
+                       break;
+
+               case JSON_STRING:
+                       buffer_add(buf, "\"");
+                       char* data = obj->value.s;
+                       int len = strlen(data);
+                       
+                       char* output = uescape(data, len, 1);
+                       buffer_add(buf, output);
+                       free(output);
+                       buffer_add(buf, "\"");
+                       break;
+
+               case JSON_ARRAY:
+                       buffer_add(buf, "[");
+                       int i;
+                       for( i = 0; i!= obj->size; i++ ) {
+                               const jsonObject* x = jsonObjectGetIndex(obj,i);
+                               char* data = legacy_jsonObjectToJSON(x);
+                               buffer_add(buf, data);
+                               free(data);
+                               if(i != obj->size - 1)
+                                       buffer_add(buf, ",");
+                       }
+                       buffer_add(buf, "]");
+                       break;  
+
+               case JSON_HASH:
+       
+                       buffer_add(buf, "{");
+                       jsonIterator* itr = jsonNewIterator(obj);
+                       jsonObject* tmp;
+       
+                       while( (tmp = jsonIteratorNext(itr)) ) {
+
+                               buffer_add(buf, "\"");
+
+                               char* key = itr->key;
+                               int len = strlen(key);
+                               char* output = uescape(key, len, 1);
+                               buffer_add(buf, output);
+                               free(output);
+
+                               buffer_add(buf, "\":");
+                               char* data =  legacy_jsonObjectToJSON(tmp);
+                               buffer_add(buf, data);
+                               if(jsonIteratorHasNext(itr))
+                                       buffer_add(buf, ",");
+                               free(data);
+                       }
+
+                       jsonIteratorFree(itr);
+                       buffer_add(buf, "}");
+                       break;
+               
+                       default:
+                               fprintf(stderr, "Unknown object type %d\n", obj->type);
+                               break;
+                               
+       }
+
+       /* close out the object hint */
+       if(obj->classname) {
+               buffer_add(buf, "/*--E ");
+               buffer_add(buf, obj->classname);
+               buffer_add(buf, "--*/");
+       }
+
+       char* data = buffer_data(buf);
+       buffer_free(buf);
+       return data;
+}
+
+
+
+static jsonObjectNode* makeNode(jsonObject* obj, unsigned long index, char* key) {
+    jsonObjectNode* node = safe_malloc(sizeof(jsonObjectNode));
+    node->item = obj;
+    node->index = index;
+    node->key = key;
+    return node;
+}
+
+jsonObjectIterator* jsonNewObjectIterator(const jsonObject* obj) {
+       if(!obj) return NULL;
+       jsonObjectIterator* itr = safe_malloc(sizeof(jsonObjectIterator));
+    itr->iterator = jsonNewIterator(obj);
+       itr->obj = obj;
+    itr->done = 0;
+    itr->current = NULL;
+       return itr;
+}
+
+jsonObjectNode* jsonObjectIteratorNext( jsonObjectIterator* itr ) {
+       if(itr == NULL || itr->done) return NULL;
+
+    if(itr->current) free(itr->current);
+    jsonObject* next = jsonIteratorNext(itr->iterator);
+    if(next == NULL) {
+        itr->current = NULL;
+        itr->done = 1;
+        return NULL;
+    }
+    itr->current = makeNode(next, itr->iterator->index, itr->iterator->key);
+    return itr->current;
+}
+
+void jsonObjectIteratorFree(jsonObjectIterator* iter) { 
+    if(iter->current) free(iter->current);
+    jsonIteratorFree(iter->iterator);
+       free(iter);
+}
+
+int jsonObjectIteratorHasNext(const jsonObjectIterator* itr) {
+       return (itr && itr->current);
+}
+
+
diff --git a/trunk/src/libopensrf/osrf_list.c b/trunk/src/libopensrf/osrf_list.c
new file mode 100644 (file)
index 0000000..a95691b
--- /dev/null
@@ -0,0 +1,168 @@
+#include <opensrf/osrf_list.h>
+
+#define OSRF_LIST_DEFAULT_SIZE 48 /* most opensrf lists are small... */
+#define OSRF_LIST_INC_SIZE 256
+//#define OSRF_LIST_MAX_SIZE 10240
+
+osrfList* osrfNewList() {
+       return osrfNewListSize( OSRF_LIST_DEFAULT_SIZE );
+}
+
+osrfList* osrfNewListSize( unsigned int size ) {
+       osrfList* list;
+       OSRF_MALLOC(list, sizeof(osrfList));
+       list->size = 0;
+       list->freeItem = NULL;
+    if( size <= 0 ) size = 16;
+       list->arrsize   = size;
+       OSRF_MALLOC( list->arrlist, list->arrsize * sizeof(void*) );
+
+       // Nullify all pointers in the array
+
+       int i;
+       for( i = 0; i < list->arrsize; ++i )
+               list->arrlist[ i ] = NULL;
+       
+       return list;
+}
+
+
+int osrfListPush( osrfList* list, void* item ) {
+       if(!(list)) return -1;
+       osrfListSet( list, item, list->size );
+       return 0;
+}
+
+int osrfListPushFirst( osrfList* list, void* item ) {
+       if(!(list && item)) return -1;
+       int i;
+       for( i = 0; i < list->size; i++ ) 
+               if(!list->arrlist[i]) break;
+       osrfListSet( list, item, i );
+       return list->size;
+}
+
+void* osrfListSet( osrfList* list, void* item, unsigned int position ) {
+       if(!list || position < 0) return NULL;
+
+       int newsize = list->arrsize;
+
+       while( position >= newsize ) 
+               newsize += OSRF_LIST_INC_SIZE;
+
+       if( newsize > list->arrsize ) { /* expand the list if necessary */
+               void** newarr;
+               OSRF_MALLOC(newarr, newsize * sizeof(void*));
+
+               // Copy the old pointers, and nullify the new ones
+               
+               int i;
+               for( i = 0; i < list->arrsize; i++ )
+                       newarr[i] = list->arrlist[i];
+               for( ; i < newsize; i++ )
+                       newarr[i] = NULL;
+               free(list->arrlist);
+               list->arrlist = newarr;
+               list->arrsize = newsize;
+       }
+
+       void* olditem = osrfListRemove( list, position );
+       list->arrlist[position] = item;
+       if( list->size <= position ) list->size = position + 1;
+       return olditem;
+}
+
+
+void* osrfListGetIndex( const osrfList* list, unsigned int position ) {
+       if(!list || position >= list->size || position < 0) return NULL;
+       return list->arrlist[position];
+}
+
+void osrfListFree( osrfList* list ) {
+       if(!list) return;
+
+       if( list->freeItem ) {
+               int i; void* val;
+               for( i = 0; i < list->size; i++ ) {
+                       if( (val = list->arrlist[i]) ) 
+                               list->freeItem(val);
+               }
+       }
+
+       free(list->arrlist);
+       free(list);
+}
+
+void* osrfListRemove( osrfList* list, unsigned int position ) {
+       if(!list || position >= list->size || position < 0) return NULL;
+
+       void* olditem = list->arrlist[position];
+       list->arrlist[position] = NULL;
+       if( list->freeItem ) {
+               list->freeItem(olditem);
+               olditem = NULL;
+       }
+
+       if( position == list->size - 1 ) list->size--;
+       return olditem;
+}
+
+
+int osrfListFind( const osrfList* list, void* addr ) {
+       if(!(list && addr)) return -1;
+       int index;
+       for( index = 0; index < list->size; index++ ) {
+               if( list->arrlist[index] == addr ) 
+                       return index;
+       }
+       return -1;
+}
+
+
+unsigned int osrfListGetCount( const osrfList* list ) {
+       if(!list) return -1;
+       return list->size;
+}
+
+
+void* osrfListPop( osrfList* list ) {
+       if(!list) return NULL;
+       return osrfListRemove( list, list->size - 1 );
+}
+
+
+osrfListIterator* osrfNewListIterator( const osrfList* list ) {
+       if(!list) return NULL;
+       osrfListIterator* itr;
+       OSRF_MALLOC(itr, sizeof(osrfListIterator));
+       itr->list = list;
+       itr->current = 0;
+       return itr;
+}
+
+void* osrfListIteratorNext( osrfListIterator* itr ) {
+       if(!(itr && itr->list)) return NULL;
+       if(itr->current >= itr->list->size) return NULL;
+       return itr->list->arrlist[itr->current++];
+}
+
+void osrfListIteratorFree( osrfListIterator* itr ) {
+       if(!itr) return;
+       free(itr);
+}
+
+
+void osrfListIteratorReset( osrfListIterator* itr ) {
+       if(!itr) return;
+       itr->current = 0;
+}
+
+
+void osrfListVanillaFree( void* item ) {
+       free(item);
+}
+
+void osrfListSetDefaultFree( osrfList* list ) {
+       if(!list) return;
+       list->freeItem = osrfListVanillaFree;
+}
diff --git a/trunk/src/libopensrf/osrf_message.c b/trunk/src/libopensrf/osrf_message.c
new file mode 100644 (file)
index 0000000..a774c84
--- /dev/null
@@ -0,0 +1,385 @@
+#include <opensrf/osrf_message.h>
+
+static char default_locale[17] = "en-US\0\0\0\0\0\0\0\0\0\0\0\0";
+static char* current_locale = NULL;
+
+osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ) {
+
+       osrfMessage* msg                        = (osrfMessage*) safe_malloc(sizeof(osrfMessage));
+       msg->m_type                                     = type;
+       msg->thread_trace                       = thread_trace;
+       msg->protocol                           = protocol;
+       msg->status_name                        = NULL;
+       msg->status_text                        = NULL;
+       msg->status_code                        = 0;
+       msg->next                                       = NULL;
+       msg->is_exception                       = 0;
+       msg->_params                            = NULL;
+       msg->_result_content            = NULL;
+       msg->result_string                      = NULL;
+       msg->method_name                        = NULL;
+       msg->full_param_string          = NULL;
+       msg->sender_locale              = NULL;
+       msg->sender_tz_offset           = 0;
+
+       return msg;
+}
+
+
+const char* osrf_message_get_last_locale() {
+       return current_locale;
+}
+
+char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
+       if( msg == NULL || locale == NULL ) return NULL;
+       return msg->sender_locale = strdup( locale );
+}
+
+const char* osrf_message_set_default_locale( const char* locale ) {
+       if( locale == NULL ) return NULL;
+       if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
+
+       strcpy( default_locale, locale );
+       return (const char*) default_locale;
+}
+
+void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
+       if( msg == NULL || method_name == NULL ) return;
+       msg->method_name = strdup( method_name );
+}
+
+
+void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
+       if(!msg|| !o) return;
+       if(!msg->_params)
+               msg->_params = jsonParseString("[]");
+       char* j = jsonObjectToJSON(o);
+       jsonObjectPush(msg->_params, jsonParseString(j));
+       free(j);
+}
+
+void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
+       if(!msg || !o) return;
+
+       if(o->type != JSON_ARRAY) {
+               osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
+               if(msg->_params) jsonObjectFree(msg->_params);
+               jsonObject* clone = jsonObjectClone(o);
+               msg->_params = jsonNewObject(NULL);
+               jsonObjectPush(msg->_params, clone);
+               return;
+       }
+
+       if(msg->_params) jsonObjectFree(msg->_params);
+       msg->_params = jsonObjectClone(o);
+}
+
+
+/* only works if parse_json_params is false */
+void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
+       if(msg == NULL || param_string == NULL) return;
+       if(!msg->_params) msg->_params = jsonParseString("[]");
+       jsonObjectPush(msg->_params, jsonParseString(param_string));
+}
+
+
+void osrf_message_set_status_info( osrfMessage* msg,
+               const char* status_name, const char* status_text, int status_code ) {
+       if(!msg) return;
+
+       if( status_name != NULL ) 
+               msg->status_name = strdup( status_name );
+
+       if( status_text != NULL )
+               msg->status_text = strdup( status_text );
+
+       msg->status_code = status_code;
+}
+
+
+void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
+       if( msg == NULL || json_string == NULL) return;
+       msg->result_string =    strdup(json_string);
+       if(json_string) msg->_result_content = jsonParseString(json_string);
+}
+
+
+
+void osrfMessageFree( osrfMessage* msg ) {
+       osrf_message_free( msg );
+}
+
+void osrf_message_free( osrfMessage* msg ) {
+       if( msg == NULL )
+               return;
+
+       if( msg->status_name != NULL )
+               free(msg->status_name);
+
+       if( msg->status_text != NULL )
+               free(msg->status_text);
+
+       if( msg->_result_content != NULL )
+               jsonObjectFree( msg->_result_content );
+
+       if( msg->result_string != NULL )
+               free( msg->result_string);
+
+       if( msg->method_name != NULL )
+               free(msg->method_name);
+
+       if( msg->sender_locale != NULL )
+               free(msg->sender_locale);
+
+       if( msg->_params != NULL )
+               jsonObjectFree(msg->_params);
+
+       free(msg);
+}
+
+
+char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
+       if( !msgs ) return NULL;
+
+       char* j;
+       int i = 0;
+       const osrfMessage* msg = NULL;
+       jsonObject* wrapper = jsonNewObject(NULL);
+
+       while( ((msg = msgs[i]) && (i++ < count)) ) 
+               jsonObjectPush(wrapper, osrfMessageToJSON( msg ));
+
+       j = jsonObjectToJSON(wrapper);
+       jsonObjectFree(wrapper);
+
+       return j;       
+}
+
+
+char* osrf_message_serialize(const osrfMessage* msg) {
+
+       if( msg == NULL ) return NULL;
+       char* j = NULL;
+
+       jsonObject* json = osrfMessageToJSON( msg );
+
+       if(json) {
+               jsonObject* wrapper = jsonNewObject(NULL);
+               jsonObjectPush(wrapper, json);
+               j = jsonObjectToJSON(wrapper);
+               jsonObjectFree(wrapper);
+       }
+
+       return j;
+}
+
+
+jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
+
+       jsonObject* json = jsonNewObject(NULL);
+       jsonObjectSetClass(json, "osrfMessage");
+       jsonObject* payload;
+       char sc[64];
+       osrf_clearbuf(sc, sizeof(sc));
+
+       char* str;
+
+       INT_TO_STRING(msg->thread_trace);
+       jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
+
+       if (msg->sender_locale != NULL) {
+               jsonObjectSetKey(json, "locale", jsonNewObject(msg->sender_locale));
+       } else if (current_locale != NULL) {
+               jsonObjectSetKey(json, "locale", jsonNewObject(current_locale));
+       } else {
+               jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
+       }
+
+       switch(msg->m_type) {
+               
+               case CONNECT: 
+                       jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
+                       break;
+
+               case DISCONNECT: 
+                       jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
+                       break;
+
+               case STATUS:
+                       jsonObjectSetKey(json, "type", jsonNewObject("STATUS"));
+                       payload = jsonNewObject(NULL);
+                       jsonObjectSetClass(payload, msg->status_name);
+                       jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
+                       snprintf(sc, sizeof(sc), "%d", msg->status_code);
+                       jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
+                       jsonObjectSetKey(json, "payload", payload);
+                       break;
+
+               case REQUEST:
+                       jsonObjectSetKey(json, "type", jsonNewObject("REQUEST"));
+                       payload = jsonNewObject(NULL);
+                       jsonObjectSetClass(payload, "osrfMethod");
+                       jsonObjectSetKey(payload, "method", jsonNewObject(msg->method_name));
+                       str = jsonObjectToJSON(msg->_params);
+                       jsonObjectSetKey(payload, "params", jsonParseString(str));
+                       free(str);
+                       jsonObjectSetKey(json, "payload", payload);
+
+                       break;
+
+               case RESULT:
+                       jsonObjectSetKey(json, "type", jsonNewObject("RESULT"));
+                       payload = jsonNewObject(NULL);
+                       jsonObjectSetClass(payload,"osrfResult");
+                       jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
+                       snprintf(sc, sizeof(sc), "%d", msg->status_code);
+                       jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
+                       str = jsonObjectToJSON(msg->_result_content);
+                       jsonObjectSetKey(payload, "content", jsonParseString(str));
+                       free(str);
+                       jsonObjectSetKey(json, "payload", payload);
+                       break;
+       }
+
+       return json;
+}
+
+
+int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
+
+       if(!string || !msgs || count <= 0) return 0;
+       int numparsed = 0;
+
+       jsonObject* json = jsonParseString(string);
+
+       if(!json) {
+               osrfLogWarning( OSRF_LOG_MARK, 
+                       "osrf_message_deserialize() unable to parse data: \n%s\n", string);
+               return 0;
+       }
+
+       int x;
+
+       for( x = 0; x < json->size && x < count; x++ ) {
+
+               const jsonObject* message = jsonObjectGetIndex(json, x);
+
+               if(message && message->type != JSON_NULL && 
+                       message->classname && !strcmp(message->classname, "osrfMessage")) {
+
+                       osrfMessage* new_msg = safe_malloc(sizeof(osrfMessage));
+                       new_msg->m_type = 0;
+                       new_msg->thread_trace = 0;
+                       new_msg->protocol = 0;
+                       new_msg->status_name = NULL;
+                       new_msg->status_text = NULL;
+                       new_msg->status_code = 0;
+                       new_msg->is_exception = 0;
+                       new_msg->_result_content = NULL;
+                       new_msg->result_string = NULL;
+                       new_msg->method_name = NULL;
+                       new_msg->_params = NULL;
+                       new_msg->next = NULL;
+                       new_msg->full_param_string = NULL;
+                       new_msg->sender_locale = NULL;
+                       new_msg->sender_tz_offset = 0;
+
+                       const jsonObject* tmp = jsonObjectGetKeyConst(message, "type");
+
+                       const char* t;
+                       if( ( t = jsonObjectGetString(tmp)) ) {
+
+                               if(!strcmp(t, "CONNECT"))               new_msg->m_type = CONNECT;
+                               else if(!strcmp(t, "DISCONNECT"))       new_msg->m_type = DISCONNECT;
+                               else if(!strcmp(t, "STATUS"))           new_msg->m_type = STATUS;
+                               else if(!strcmp(t, "REQUEST"))          new_msg->m_type = REQUEST;
+                               else if(!strcmp(t, "RESULT"))           new_msg->m_type = RESULT;
+                       }
+
+                       tmp = jsonObjectGetKeyConst(message, "threadTrace");
+                       if(tmp) {
+                               char* tt = jsonObjectToSimpleString(tmp);
+                               if(tt) {
+                                       new_msg->thread_trace = atoi(tt);
+                                       free(tt);
+                               }
+                       }
+
+                       /* use the sender's locale, or the global default */
+                       if (current_locale)
+                               free( current_locale );
+
+                       tmp = jsonObjectGetKeyConst(message, "locale");
+                       if(tmp) {
+                               new_msg->sender_locale = jsonObjectToSimpleString(tmp);
+                               current_locale = strdup( new_msg->sender_locale );
+                       } else {
+                               current_locale = NULL;
+                       }
+
+                       tmp = jsonObjectGetKeyConst(message, "protocol");
+
+                       if(tmp) {
+                               char* proto = jsonObjectToSimpleString(tmp);
+                               if(proto) {
+                                       new_msg->protocol = atoi(proto);
+                                       free(proto);
+                               }
+                       }
+
+                       tmp = jsonObjectGetKeyConst(message, "payload");
+                       if(tmp) {
+                               if(tmp->classname)
+                                       new_msg->status_name = strdup(tmp->classname);
+
+                               const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
+                               const char* tmp_str = jsonObjectGetString(tmp0);
+                               if(tmp_str)
+                                       new_msg->method_name = strdup(tmp_str);
+
+                               tmp0 = jsonObjectGetKeyConst(tmp,"params");
+                               if(tmp0) {
+                                       char* s = jsonObjectToJSON(tmp0);
+                                       new_msg->_params = jsonParseString(s);
+                                       if(new_msg->_params && new_msg->_params->type == JSON_NULL) 
+                                               new_msg->_params->type = JSON_ARRAY;
+                                       free(s);
+                               }
+
+                               tmp0 = jsonObjectGetKeyConst(tmp,"status");
+                               tmp_str = jsonObjectGetString(tmp0);
+                               if(tmp_str)
+                                       new_msg->status_text = strdup(tmp_str);
+
+                               tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
+                               if(tmp0) {
+                                       tmp_str = jsonObjectGetString(tmp0);
+                                       if(tmp_str)
+                                               new_msg->status_code = atoi(tmp_str);
+                                       if(tmp0->type == JSON_NUMBER)
+                                               new_msg->status_code = (int) jsonObjectGetNumber(tmp0);
+                               }
+
+                               tmp0 = jsonObjectGetKeyConst(tmp,"content");
+                               if(tmp0) {
+                                       char* s = jsonObjectToJSON(tmp0);
+                                       new_msg->_result_content = jsonParseString(s);
+                                       free(s);
+                               }
+
+                       }
+                       msgs[numparsed++] = new_msg;
+               }
+       }
+
+       jsonObjectFree(json);
+       return numparsed;
+}
+
+
+
+jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
+       if(msg) return msg->_result_content;
+       return NULL;
+}
+
diff --git a/trunk/src/libopensrf/osrf_prefork.c b/trunk/src/libopensrf/osrf_prefork.c
new file mode 100644 (file)
index 0000000..95ca398
--- /dev/null
@@ -0,0 +1,859 @@
+#include <opensrf/osrf_prefork.h>
+#include <opensrf/osrf_app_session.h>
+#include <opensrf/osrf_application.h>
+#include <signal.h>
+
+//#define READ_BUFSIZE 4096
+#define READ_BUFSIZE 1024
+//#define MAX_BUFSIZE 10485760 /* 10M enough? ;) */
+#define ABS_MAX_CHILDREN 256
+
+struct prefork_simple_struct {
+       int max_requests;
+       int min_children;
+       int max_children;
+       int fd;
+       int data_to_child;
+       int data_to_parent;
+       int current_num_children;
+       int keepalive; /* keepalive time for stateful sessions */
+       char* appname;
+       struct prefork_child_struct* first_child;
+       transport_client* connection;
+};
+typedef struct prefork_simple_struct prefork_simple;
+
+struct prefork_child_struct {
+       pid_t pid;
+       int read_data_fd;
+       int write_data_fd;
+       int read_status_fd;
+       int write_status_fd;
+       int min_children;
+       int available;
+       int max_requests;
+       char* appname;
+       int keepalive;
+       struct prefork_child_struct* next;
+};
+
+typedef struct prefork_child_struct prefork_child;
+
+static prefork_simple* prefork_simple_init( transport_client* client,
+       int max_requests, int min_children, int max_children );
+static prefork_child* launch_child( prefork_simple* forker );
+static void prefork_launch_children( prefork_simple* forker );
+static void prefork_run(prefork_simple* forker);
+static void add_prefork_child( prefork_simple* forker, prefork_child* child );
+
+//static prefork_child* find_prefork_child( prefork_simple* forker, pid_t pid );
+
+static void del_prefork_child( prefork_simple* forker, pid_t pid );
+static void check_children( prefork_simple* forker, int forever );
+static void prefork_child_process_request(prefork_child*, char* data);
+static int prefork_child_init_hook(prefork_child*);
+static prefork_child* prefork_child_init(
+               int max_requests, int read_data_fd, int write_data_fd,
+               int read_status_fd, int write_status_fd );
+
+/* listens on the 'data_to_child' fd and wait for incoming data */
+static void prefork_child_wait( prefork_child* child );
+static int prefork_free( prefork_simple* );
+static int prefork_child_free( prefork_child* );
+static void osrf_prefork_register_routers( const char* appname );
+static void osrf_prefork_child_exit( prefork_child* );
+
+
+/* true if we just deleted a child.  This will allow us to make sure we're
+       not trying to use freed memory */
+static sig_atomic_t child_dead;
+
+static void sigchld_handler( int sig );
+
+int osrf_prefork_run(const char* appname) {
+
+       if(!appname) {
+               osrfLogError( OSRF_LOG_MARK, "osrf_prefork_run requires an appname to run!");
+               return -1;
+       }
+
+       set_proc_title( "OpenSRF Listener [%s]", appname );
+
+       int maxr = 1000; 
+       int maxc = 10;
+       int minc = 3;
+    int kalive = 5;
+
+       osrfLogInfo( OSRF_LOG_MARK, "Loading config in osrf_forker for app %s", appname);
+
+       char* max_req = osrf_settings_host_value("/apps/%s/unix_config/max_requests", appname);
+       char* min_children = osrf_settings_host_value("/apps/%s/unix_config/min_children", appname);
+       char* max_children = osrf_settings_host_value("/apps/%s/unix_config/max_children", appname);
+       char* keepalive = osrf_settings_host_value("/apps/%s/keepalive", appname);
+
+       if(!keepalive) osrfLogWarning( OSRF_LOG_MARK, "Keepalive is not defined, assuming %d", kalive);
+       else kalive = atoi(keepalive);
+
+       if(!max_req) osrfLogWarning( OSRF_LOG_MARK, "Max requests not defined, assuming %d", maxr);
+       else maxr = atoi(max_req);
+
+       if(!min_children) osrfLogWarning( OSRF_LOG_MARK, "Min children not defined, assuming %d", minc);
+       else minc = atoi(min_children);
+
+       if(!max_children) osrfLogWarning( OSRF_LOG_MARK, "Max children not defined, assuming %d", maxc);
+       else maxc = atoi(max_children);
+
+    free(keepalive);
+       free(max_req);
+       free(min_children);
+       free(max_children);
+       /* --------------------------------------------------- */
+
+       char* resc = va_list_to_string("%s_listener", appname);
+
+       if(!osrfSystemBootstrapClientResc( NULL, NULL, resc )) {
+               osrfLogError( OSRF_LOG_MARK, "Unable to bootstrap client for osrf_prefork_run()");
+               free(resc);
+               return -1;
+       }
+
+       free(resc);
+
+       prefork_simple* forker = prefork_simple_init(
+               osrfSystemGetTransportClient(), maxr, minc, maxc);
+
+       if(forker == NULL) {
+               osrfLogError( OSRF_LOG_MARK, "osrf_prefork_run() failed to create prefork_simple object");
+               return -1;
+       }
+
+       forker->appname = strdup(appname);
+       forker->keepalive       = kalive;
+
+       prefork_launch_children(forker);
+
+       osrf_prefork_register_routers(appname);
+       
+       osrfLogInfo( OSRF_LOG_MARK, "Launching osrf_forker for app %s", appname);
+       prefork_run(forker);
+       
+       osrfLogWarning( OSRF_LOG_MARK, "prefork_run() retuned - how??");
+       prefork_free(forker);
+       return 0;
+
+}
+
+/* sends the "register" packet to the specified router */
+static void osrf_prefork_send_router_registration(const char* appname, const char* routerName, const char* routerDomain) {
+       transport_client* client = osrfSystemGetTransportClient();
+    char* jid = va_list_to_string( "%s@%s/router", routerName, routerDomain );
+    osrfLogInfo( OSRF_LOG_MARK, "%s registering with router %s", appname, jid );
+    transport_message* msg = message_init("registering", NULL, NULL, jid, NULL );
+    message_set_router_info( msg, NULL, NULL, appname, "register", 0 );
+    client_send_message( client, msg );
+    message_free( msg );
+    free(jid);
+}
+
+/** parses a single "complex" router configuration chunk */
+static void osrf_prefork_parse_router_chunk(const char* appname, jsonObject* routerChunk) {
+
+    char* routerName = jsonObjectGetString(jsonObjectGetKey(routerChunk, "name"));
+    char* domain = jsonObjectGetString(jsonObjectGetKey(routerChunk, "domain"));
+    jsonObject* services = jsonObjectGetKey(routerChunk, "services");
+    osrfLogDebug(OSRF_LOG_MARK, "found router config with domain %s and name %s", routerName, domain);
+
+    if(services && services->type == JSON_HASH) {
+        osrfLogDebug(OSRF_LOG_MARK, "investigating router information...");
+        services = jsonObjectGetKey(services, "service");
+        int j;
+        for(j = 0; j < services->size; j++ ) {
+            char* service = jsonObjectGetString(jsonObjectGetIndex(services, j));
+            if(!strcmp(appname, service))
+                osrf_prefork_send_router_registration(appname, routerName, domain);
+        }
+    } else {
+        osrf_prefork_send_router_registration(appname, routerName, domain);
+    }
+}
+
+static void osrf_prefork_register_routers( const char* appname ) {
+
+    jsonObject* routerInfo = osrfConfigGetValueObject(NULL, "/routers/router");
+
+    int i;
+    for(i = 0; i < routerInfo->size; i++) {
+        jsonObject* routerChunk = jsonObjectGetIndex(routerInfo, i);
+
+        if(routerChunk->type == JSON_STRING) {
+            /* this accomodates simple router configs */
+            char* routerName = osrfConfigGetValue( NULL, "/router_name" );
+            char* domain = osrfConfigGetValue(NULL, "/routers/router");
+            osrfLogDebug(OSRF_LOG_MARK, "found simple router settings with router name %s", routerName);
+            osrf_prefork_send_router_registration(appname, routerName, domain);
+
+        } else {
+            osrf_prefork_parse_router_chunk(appname, routerChunk);
+        }
+    }
+}
+
+static int prefork_child_init_hook(prefork_child* child) {
+
+       if(!child) return -1;
+       osrfLogDebug( OSRF_LOG_MARK, "Child init hook for child %d", child->pid);
+
+    osrfSystemInitCache();
+       char* resc = va_list_to_string("%s_drone",child->appname);
+
+   /* if we're a source-client, tell the logger now that we're a new process*/
+   char* isclient = osrfConfigGetValue(NULL, "/client");
+   if( isclient && !strcasecmp(isclient,"true") )
+      osrfLogSetIsClient(1);
+   free(isclient);
+
+
+       /* we want to remove traces of our parents socket connection 
+        * so we can have our own */
+       osrfSystemIgnoreTransportClient();
+
+       if(!osrfSystemBootstrapClientResc( NULL, NULL, resc)) {
+               osrfLogError( OSRF_LOG_MARK, "Unable to bootstrap client for osrf_prefork_run()");
+               free(resc);
+               return -1;
+       }
+
+       free(resc);
+
+       if( ! osrfAppRunChildInit(child->appname) ) {
+               osrfLogDebug(OSRF_LOG_MARK, "Prefork child_init succeeded\n");
+       } else {
+               osrfLogError(OSRF_LOG_MARK, "Prefork child_init failed\n");
+               return -1;
+       }
+
+       set_proc_title( "OpenSRF Drone [%s]", child->appname );
+       return 0;
+}
+
+static void prefork_child_process_request(prefork_child* child, char* data) {
+       if( !child ) return;
+
+       transport_client* client = osrfSystemGetTransportClient();
+
+       if(!client_connected(client)) {
+               osrfSystemIgnoreTransportClient();
+               osrfLogWarning(OSRF_LOG_MARK, "Reconnecting child to opensrf after disconnect...");
+               if(!osrf_system_bootstrap_client(NULL, NULL)) {
+                       osrfLogError( OSRF_LOG_MARK, 
+                               "Unable to bootstrap client in prefork_child_process_request()");
+                       sleep(1);
+         osrf_prefork_child_exit(child);
+               }
+       }
+
+       /* construct the message from the xml */
+       transport_message* msg = new_message_from_xml( data );
+
+       osrfAppSession* session = osrf_stack_transport_handler(msg, child->appname);
+       if(!session) return;
+
+       if( session->stateless && session->state != OSRF_SESSION_CONNECTED ) {
+               osrfAppSessionFree( session );
+               return;
+       }
+
+       osrfLogDebug( OSRF_LOG_MARK, "Entering keepalive loop for session %s", session->session_id );
+       int keepalive = child->keepalive;
+       int retval;
+       int recvd;
+       time_t start;
+       time_t end;
+
+       while(1) {
+
+               osrfLogDebug(OSRF_LOG_MARK, 
+                               "osrf_prefork calling queue_wait [%d] in keepalive loop", keepalive);
+               start           = time(NULL);
+               retval  = osrf_app_session_queue_wait(session, keepalive, &recvd);
+               end             = time(NULL);
+
+               osrfLogDebug(OSRF_LOG_MARK, "Data received == %d", recvd);
+
+               if(retval) {
+                       osrfLogError(OSRF_LOG_MARK, "queue-wait returned non-success %d", retval);
+                       break;
+               }
+
+               /* see if the client disconnected from us */
+               if(session->state != OSRF_SESSION_CONNECTED) break;
+
+               /* if no data was reveived within the timeout interval */
+               if( !recvd && (end - start) >= keepalive ) {
+                       osrfLogInfo(OSRF_LOG_MARK, "No request was received in %d seconds, exiting stateful session", keepalive);
+                       osrfAppSessionStatus( 
+                                       session, 
+                                       OSRF_STATUS_TIMEOUT, 
+                                       "osrfConnectStatus", 
+                                       0, "Disconnected on timeout" );
+
+                       break;
+               }
+       }
+
+       osrfLogDebug( OSRF_LOG_MARK, "Exiting keepalive loop for session %s", session->session_id );
+       osrfAppSessionFree( session );
+       return;
+}
+
+
+static prefork_simple*  prefork_simple_init( transport_client* client, 
+               int max_requests, int min_children, int max_children ) {
+
+       if( min_children > max_children ) {
+               osrfLogError( OSRF_LOG_MARK,  "min_children (%d) is greater "
+                               "than max_children (%d)", min_children, max_children );
+               return NULL;
+       }
+
+       if( max_children > ABS_MAX_CHILDREN ) {
+               osrfLogError( OSRF_LOG_MARK,  "max_children (%d) is greater than ABS_MAX_CHILDREN (%d)",
+                               max_children, ABS_MAX_CHILDREN );
+               return NULL;
+       }
+
+       osrfLogInfo(OSRF_LOG_MARK, "Prefork launching child with max_request=%d,"
+               "min_children=%d, max_children=%d", max_requests, min_children, max_children );
+
+       /* flesh out the struct */
+       prefork_simple* prefork = (prefork_simple*) safe_malloc(sizeof(prefork_simple));        
+       prefork->max_requests = max_requests;
+       prefork->min_children = min_children;
+       prefork->max_children = max_children;
+       prefork->fd           = 0;
+       prefork->data_to_child = 0;
+       prefork->data_to_parent = 0;
+       prefork->current_num_children = 0;
+       prefork->keepalive    = 0;
+       prefork->appname      = NULL;
+       prefork->first_child = NULL;
+       prefork->connection = client;
+
+       return prefork;
+}
+
+static prefork_child* launch_child( prefork_simple* forker ) {
+
+       pid_t pid;
+       int data_fd[2];
+       int status_fd[2];
+
+       /* Set up the data pipes and add the child struct to the parent */
+       if( pipe(data_fd) < 0 ) { /* build the data pipe*/
+               osrfLogError( OSRF_LOG_MARK,  "Pipe making error" );
+               return NULL;
+       }
+
+       if( pipe(status_fd) < 0 ) {/* build the status pipe */
+               osrfLogError( OSRF_LOG_MARK,  "Pipe making error" );
+               return NULL;
+       }
+
+       osrfLogInternal( OSRF_LOG_MARK,  "Pipes: %d %d %d %d", data_fd[0], data_fd[1], status_fd[0], status_fd[1] );
+       prefork_child* child = prefork_child_init( forker->max_requests, data_fd[0], 
+                       data_fd[1], status_fd[0], status_fd[1] );
+
+       child->appname = strdup(forker->appname);
+       child->keepalive = forker->keepalive;
+
+
+       add_prefork_child( forker, child );
+
+       if( (pid=fork()) < 0 ) {
+               osrfLogError( OSRF_LOG_MARK,  "Forking Error" );
+               return NULL;
+       }
+
+       if( pid > 0 ) {  /* parent */
+
+               signal(SIGCHLD, sigchld_handler);
+               (forker->current_num_children)++;
+               child->pid = pid;
+
+               osrfLogDebug( OSRF_LOG_MARK,  "Parent launched %d", pid );
+               /* *no* child pipe FD's can be closed or the parent will re-use fd's that
+                       the children are currently using */
+               return child;
+       }
+
+       else { /* child */
+
+               osrfLogInternal( OSRF_LOG_MARK, "I am  new child with read_data_fd = %d and write_status_fd = %d",
+                       child->read_data_fd, child->write_status_fd );
+
+               child->pid = getpid();
+               close( child->write_data_fd );
+               close( child->read_status_fd );
+
+               /* do the initing */
+               if( prefork_child_init_hook(child) == -1 ) {
+                       osrfLogError(OSRF_LOG_MARK, 
+                               "Forker child going away because we could not connect to OpenSRF...");
+         osrf_prefork_child_exit(child);
+               }
+
+               prefork_child_wait( child );
+      osrf_prefork_child_exit(child); /* just to be sure */
+        }
+       return NULL;
+}
+
+static void osrf_prefork_child_exit(prefork_child* child) {
+   osrfAppRunExitCode();
+   exit(0);
+}
+
+static void prefork_launch_children( prefork_simple* forker ) {
+       if(!forker) return;
+       int c = 0;
+       while( c++ < forker->min_children )
+               launch_child( forker );
+}
+
+
+static void sigchld_handler( int sig ) {
+       signal(SIGCHLD, sigchld_handler);
+       child_dead = 1;
+}
+
+
+void reap_children( prefork_simple* forker ) {
+
+       pid_t child_pid;
+       int status;
+
+       while( (child_pid=waitpid(-1,&status,WNOHANG)) > 0) 
+               del_prefork_child( forker, child_pid ); 
+
+       /* replenish */
+       while( forker->current_num_children < forker->min_children ) 
+               launch_child( forker );
+
+       child_dead = 0;
+}
+
+static void prefork_run(prefork_simple* forker) {
+
+       if( forker->first_child == NULL )
+               return;
+
+       transport_message* cur_msg = NULL;
+
+
+       while(1) {
+
+               if( forker->first_child == NULL ) {/* no more children */
+                       osrfLogWarning( OSRF_LOG_MARK, "No more children..." );
+                       return;
+               }
+
+               osrfLogDebug( OSRF_LOG_MARK, "Forker going into wait for data...");
+               cur_msg = client_recv( forker->connection, -1 );
+
+               //fprintf(stderr, "Got Data %f\n", get_timestamp_millis() );
+
+               if( cur_msg == NULL ) continue;
+
+               int honored = 0;        /* true if we've serviced the request */
+               int no_recheck = 0;
+
+               while( ! honored ) {
+
+                       if(!no_recheck) check_children( forker, 0 ); 
+                       no_recheck = 0;
+
+                       osrfLogDebug( OSRF_LOG_MARK,  "Server received inbound data" );
+                       int k;
+                       prefork_child* cur_child = forker->first_child;
+
+                       /* Look for an available child */
+                       for( k = 0; k < forker->current_num_children; k++ ) {
+
+                               osrfLogInternal( OSRF_LOG_MARK, "Searching for available child. cur_child->pid = %d", cur_child->pid );
+                               osrfLogInternal( OSRF_LOG_MARK, "Current num children %d and loop %d", forker->current_num_children, k);
+                       
+                               if( cur_child->available ) {
+                                       osrfLogDebug( OSRF_LOG_MARK,  "forker sending data to %d", cur_child->pid );
+
+                                       message_prepare_xml( cur_msg );
+                                       char* data = cur_msg->msg_xml;
+                                       if( ! data || strlen(data) < 1 ) break;
+
+                                       cur_child->available = 0;
+                                       osrfLogInternal( OSRF_LOG_MARK,  "Writing to child fd %d", cur_child->write_data_fd );
+
+                                       int written = 0;
+                                       //fprintf(stderr, "Writing Data %f\n", get_timestamp_millis() );
+                                       if( (written = write( cur_child->write_data_fd, data, strlen(data) + 1 )) < 0 ) {
+                                               osrfLogWarning( OSRF_LOG_MARK, "Write returned error %d", errno);
+                                               cur_child = cur_child->next;
+                                               continue;
+                                       }
+
+                                       //fprintf(stderr, "Wrote %d bytes to child\n", written);
+
+                                       forker->first_child = cur_child->next;
+                                       honored = 1;
+                                       break;
+                               } else 
+                                       cur_child = cur_child->next;
+                       } 
+
+                       /* if none available, add a new child if we can */
+                       if( ! honored ) {
+                               osrfLogDebug( OSRF_LOG_MARK, "Not enough children, attempting to add...");
+
+                               if( forker->current_num_children < forker->max_children ) {
+                                       osrfLogDebug( OSRF_LOG_MARK,  "Launching new child with current_num = %d",
+                                                       forker->current_num_children );
+
+                                       prefork_child* new_child = launch_child( forker );
+                    if( new_child ) {
+
+                                           message_prepare_xml( cur_msg );
+                                           char* data = cur_msg->msg_xml;
+
+                        if( data ) {
+                            int len = strlen(data);
+
+                            if( len > 0 ) {
+                                                   new_child->available = 0;
+                                                   osrfLogDebug( OSRF_LOG_MARK,  "Writing to new child fd %d : pid %d", 
+                                                               new_child->write_data_fd, new_child->pid );
+        
+                                                   if( write( new_child->write_data_fd, data, len + 1 ) >= 0 ) {
+                                                       forker->first_child = new_child->next;
+                                                       honored = 1;
+                                }
+                            }
+                        }
+                    }
+
+                               }
+                       }
+
+                       if( !honored ) {
+                               osrfLogWarning( OSRF_LOG_MARK,  "No children available, waiting...");
+
+                               check_children( forker, 1 );  /* non-poll version */
+                               /* tell the loop no to call check_children again, since we're calling it now */
+                               no_recheck = 1;
+                       }
+
+                       if( child_dead )
+                               reap_children(forker);
+
+
+                       //fprintf(stderr, "Parent done with request %f\n", get_timestamp_millis() );
+
+               } // honored?
+
+               message_free( cur_msg );
+
+       } /* top level listen loop */
+
+}
+
+
+/** XXX Add a flag which tells select() to wait forever on children
+ * in the best case, this will be faster than calling usleep(x), and
+ * in the worst case it won't be slower and will do less logging...
+ */
+
+static void check_children( prefork_simple* forker, int forever ) {
+
+       //check_begin:
+
+       int select_ret;
+       fd_set read_set;
+       FD_ZERO(&read_set);
+       int max_fd = 0;
+       int n;
+
+
+       if( child_dead )
+               reap_children(forker);
+
+       prefork_child* cur_child = forker->first_child;
+
+       int i;
+       for( i = 0; i!= forker->current_num_children; i++ ) {
+
+               if( cur_child->read_status_fd > max_fd )
+                       max_fd = cur_child->read_status_fd;
+               FD_SET( cur_child->read_status_fd, &read_set );
+               cur_child = cur_child->next;
+       }
+
+       FD_CLR(0,&read_set);/* just to be sure */
+
+       if( forever ) {
+               osrfLogWarning(OSRF_LOG_MARK, "We have no children available - waiting for one to show up...");
+
+               if( (select_ret=select( max_fd + 1 , &read_set, NULL, NULL, NULL)) == -1 ) {
+                       osrfLogWarning( OSRF_LOG_MARK,  "Select returned error %d on check_children", errno );
+               }
+               osrfLogInfo(OSRF_LOG_MARK, "select() completed after waiting on children to become available");
+
+       } else {
+
+               struct timeval tv;
+               tv.tv_sec       = 0;
+               tv.tv_usec      = 0;
+       
+               if( (select_ret=select( max_fd + 1 , &read_set, NULL, NULL, &tv)) == -1 ) {
+                       osrfLogWarning( OSRF_LOG_MARK,  "Select returned error %d on check_children", errno );
+               }
+       }
+
+       if( select_ret == 0 )
+               return;
+
+       /* see if one of a child has told us it's done */
+       cur_child = forker->first_child;
+       int j;
+       int num_handled = 0;
+       for( j = 0; j!= forker->current_num_children && num_handled < select_ret ; j++ ) {
+
+               if( FD_ISSET( cur_child->read_status_fd, &read_set ) ) {
+                       //printf( "Server received status from a child %d\n", cur_child->pid );
+                       osrfLogDebug( OSRF_LOG_MARK,  "Server received status from a child %d", cur_child->pid );
+
+                       num_handled++;
+
+                       /* now suck off the data */
+                       char buf[64];
+                       osrf_clearbuf( buf, sizeof(buf) );
+                       if( (n=read(cur_child->read_status_fd, buf, sizeof(buf) - 1)) < 0 ) {
+                               osrfLogWarning( OSRF_LOG_MARK, "Read error after select in child status read with errno %d", errno);
+                       }
+                       else {
+                               buf[n] = '\0';
+                               osrfLogDebug( OSRF_LOG_MARK,  "Read %d bytes from status buffer: %s", n, buf );
+                       }
+                       cur_child->available = 1;
+               }
+               cur_child = cur_child->next;
+       } 
+
+}
+
+
+static void prefork_child_wait( prefork_child* child ) {
+
+       int i,n;
+       growing_buffer* gbuf = buffer_init( READ_BUFSIZE );
+       char buf[READ_BUFSIZE];
+       osrf_clearbuf( buf, sizeof(buf) );
+
+       for( i = 0; i < child->max_requests; i++ ) {
+
+               n = -1;
+               int gotdata = 0;
+               clr_fl(child->read_data_fd, O_NONBLOCK );
+
+               while( (n=read(child->read_data_fd, buf, READ_BUFSIZE-1)) > 0 ) {
+                       buf[n] = '\0';
+                       osrfLogDebug(OSRF_LOG_MARK, "Prefork child read %d bytes of data", n);
+                       if(!gotdata)
+                               set_fl(child->read_data_fd, O_NONBLOCK );
+                       buffer_add( gbuf, buf );
+                       osrf_clearbuf( buf, sizeof(buf) );
+                       gotdata = 1;
+               }
+
+               if( errno == EAGAIN ) n = 0;
+
+      if( errno == EPIPE ) {
+         osrfLogDebug(OSRF_LOG_MARK, "C child attempted read on broken pipe, exiting...");
+         break;
+      }
+
+               if( n < 0 ) {
+                       osrfLogWarning( OSRF_LOG_MARK,  "Prefork child read returned error with errno %d", errno );
+                       break;
+
+               } else if( gotdata ) {
+                       osrfLogDebug(OSRF_LOG_MARK, "Prefork child got a request.. processing..");
+                       prefork_child_process_request(child, gbuf->buf);
+                       buffer_reset( gbuf );
+               }
+
+               if( i < child->max_requests - 1 ) 
+                       write( child->write_status_fd, "available" /*less than 64 bytes*/, 9 );
+       }
+
+       buffer_free(gbuf);
+
+       osrfLogDebug( OSRF_LOG_MARK, "Child with max-requests=%d, num-served=%d exiting...[%ld]", 
+                       child->max_requests, i, (long) getpid() );
+
+   osrf_prefork_child_exit(child); /* just to be sure */
+}
+
+
+static void add_prefork_child( prefork_simple* forker, prefork_child* child ) {
+       
+       if( forker->first_child == NULL ) {
+               forker->first_child = child;
+               child->next = child;
+               return;
+       }
+
+       /* we put the child in as the last because, regardless, 
+               we have to do the DLL splice dance, and this is the
+          simplest way */
+
+       prefork_child* start_child = forker->first_child;
+       while(1) {
+               if( forker->first_child->next == start_child ) 
+                       break;
+               forker->first_child = forker->first_child->next;
+       }
+
+       /* here we know that forker->first_child is the last element 
+               in the list and start_child is the first.  Insert the
+               new child between them*/
+
+       forker->first_child->next = child;
+       child->next = start_child;
+       return;
+}
+
+//static prefork_child* find_prefork_child( prefork_simple* forker, pid_t pid ) {
+//
+//     if( forker->first_child == NULL ) { return NULL; }
+//     prefork_child* start_child = forker->first_child;
+//     do {
+//             if( forker->first_child->pid == pid ) 
+//                     return forker->first_child;
+//     } while( (forker->first_child = forker->first_child->next) != start_child );
+//
+//     return NULL;
+//}
+
+
+static void del_prefork_child( prefork_simple* forker, pid_t pid ) { 
+
+       if( forker->first_child == NULL ) { return; }
+
+       (forker->current_num_children)--;
+       osrfLogDebug( OSRF_LOG_MARK, "Deleting Child: %d", pid );
+
+       prefork_child* start_child = forker->first_child; /* starting point */
+       prefork_child* cur_child        = start_child; /* current pointer */
+       prefork_child* prev_child       = start_child; /* the trailing pointer */
+
+       /* special case where there is only one in the list */
+       if( start_child == start_child->next ) {
+               if( start_child->pid == pid ) {
+                       forker->first_child = NULL;
+
+                       close( start_child->read_data_fd );
+                       close( start_child->write_data_fd );
+                       close( start_child->read_status_fd );
+                       close( start_child->write_status_fd );
+
+                       prefork_child_free( start_child );
+               }
+               return;
+       }
+
+
+       /* special case where the first item in the list needs to be removed */
+       if( start_child->pid == pid ) { 
+
+               /* find the last one so we can remove the start_child */
+               do { 
+                       prev_child = cur_child;
+                       cur_child = cur_child->next;
+               }while( cur_child != start_child );
+
+               /* now cur_child == start_child */
+               prev_child->next = cur_child->next;
+               forker->first_child = prev_child;
+
+               close( cur_child->read_data_fd );
+               close( cur_child->write_data_fd );
+               close( cur_child->read_status_fd );
+               close( cur_child->write_status_fd );
+
+               prefork_child_free( cur_child );
+               return;
+       } 
+
+       do {
+               prev_child = cur_child;
+               cur_child = cur_child->next;
+
+               if( cur_child->pid == pid ) {
+                       prev_child->next = cur_child->next;
+
+                       close( cur_child->read_data_fd );
+                       close( cur_child->write_data_fd );
+                       close( cur_child->read_status_fd );
+                       close( cur_child->write_status_fd );
+
+                       prefork_child_free( cur_child );
+                       return;
+               }
+
+       } while(cur_child != start_child);
+}
+
+
+
+
+static prefork_child* prefork_child_init( 
+       int max_requests, int read_data_fd, int write_data_fd, 
+       int read_status_fd, int write_status_fd ) {
+
+       prefork_child* child = (prefork_child*) safe_malloc(sizeof(prefork_child));
+       child->pid              = 0;
+       child->max_requests             = max_requests;
+       child->read_data_fd             = read_data_fd;
+       child->write_data_fd            = write_data_fd;
+       child->read_status_fd   = read_status_fd;
+       child->write_status_fd  = write_status_fd;
+       child->min_children     = 0;
+       child->available                        = 1;
+       child->appname          = NULL;
+       child->keepalive        = 0;
+       child->next             = NULL;
+
+       return child;
+}
+
+
+static int prefork_free( prefork_simple* prefork ) {
+       
+       while( prefork->first_child != NULL ) {
+               osrfLogInfo( OSRF_LOG_MARK,  "Killing children and sleeping 1 to reap..." );
+               kill( 0,        SIGKILL );
+               sleep(1);
+       }
+
+       client_free(prefork->connection);
+       free(prefork->appname);
+       free( prefork );
+       return 1;
+}
+
+static int prefork_child_free( prefork_child* child ) { 
+       free(child->appname);
+       close(child->read_data_fd);
+       close(child->write_status_fd);
+       free( child ); 
+       return 1;
+}
+
diff --git a/trunk/src/libopensrf/osrf_settings.c b/trunk/src/libopensrf/osrf_settings.c
new file mode 100644 (file)
index 0000000..e226a4d
--- /dev/null
@@ -0,0 +1,89 @@
+#include <opensrf/osrf_settings.h> 
+
+osrf_host_config* config = NULL;
+
+char* osrf_settings_host_value(const char* format, ...) {
+       VA_LIST_TO_STRING(format);
+
+       if( ! config ) {
+               const char * msg = "NULL config pointer";
+               fprintf( stderr, "osrf_settings_host_value: %s\n", msg );
+               osrfLogError( OSRF_LOG_MARK, msg );
+               exit( 99 );
+       }
+
+       jsonObject* o = jsonObjectFindPath(config->config, VA_BUF);
+       char* val = jsonObjectToSimpleString(o);
+       jsonObjectFree(o);
+       return val;
+}
+
+jsonObject* osrf_settings_host_value_object(const char* format, ...) {
+       VA_LIST_TO_STRING(format);
+
+       if( ! config ) {
+               const char * msg = "config pointer is NULL";
+               fprintf( stderr, "osrf_settings_host_value_object: %s\n", msg );
+               osrfLogError( OSRF_LOG_MARK, msg );
+               exit( 99 );
+       }
+
+       return jsonObjectFindPath(config->config, VA_BUF);
+}
+
+
+int osrf_settings_retrieve(const char* hostname) {
+
+       if(!config) {
+
+               osrfAppSession* session = osrfAppSessionClientInit("opensrf.settings");
+               jsonObject* params = jsonNewObject(NULL);
+               jsonObjectPush(params, jsonNewObject(hostname));
+               int req_id = osrfAppSessionMakeRequest( 
+                       session, params, "opensrf.settings.host_config.get", 1, NULL );
+               osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, 60 );
+               jsonObjectFree(params);
+
+               if(!omsg) {
+                       osrfLogError( OSRF_LOG_MARK, "No osrfMessage received from host %s (timeout?)", hostname);
+               } else if(!omsg->_result_content) {
+                       osrfMessageFree(omsg);
+                       osrfLogError(
+                               OSRF_LOG_MARK,
+                       "NULL or non-existant osrfMessage result content received from host %s, "
+                               "broken message or no settings for host",
+                               hostname
+                       );
+               } else {
+                       config = osrf_settings_new_host_config(hostname);
+                       config->config = jsonObjectClone(omsg->_result_content);
+                       osrfMessageFree(omsg);
+               }
+
+               osrf_app_session_request_finish( session, req_id );
+               osrfAppSessionFree( session );
+
+               if(!config) {
+                       osrfLogError( OSRF_LOG_MARK, "Unable to load config for host %s", hostname);
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+osrf_host_config* osrf_settings_new_host_config(const char* hostname) {
+       if(!hostname) return NULL;
+       osrf_host_config* c = safe_malloc(sizeof(osrf_host_config));
+       c->hostname = strdup(hostname);
+       c->config = NULL;
+       return c;
+}
+
+void osrf_settings_free_host_config(osrf_host_config* c) {
+       if(!c) c = config;
+       if(!c) return;
+       free(c->hostname);
+       jsonObjectFree(c->config);      
+       free(c);
+}
diff --git a/trunk/src/libopensrf/osrf_stack.c b/trunk/src/libopensrf/osrf_stack.c
new file mode 100644 (file)
index 0000000..aeab9c5
--- /dev/null
@@ -0,0 +1,276 @@
+#include <opensrf/osrf_stack.h>
+#include <opensrf/osrf_application.h>
+
+/* the max number of oilsMessage blobs present in any one root packet */
+#define OSRF_MAX_MSGS_PER_PACKET 256
+// -----------------------------------------------------------------------------
+
+static int osrf_stack_process( transport_client* client, int timeout, int* msg_received );
+static int osrf_stack_message_handler( osrfAppSession* session, osrfMessage* msg );
+static int osrf_stack_application_handler( osrfAppSession* session, osrfMessage* msg );
+static osrfMessage* _do_client( osrfAppSession*, osrfMessage* );
+static osrfMessage* _do_server( osrfAppSession*, osrfMessage* );
+
+/* tell osrfAppSession where the stack entry is */
+int (*osrf_stack_entry_point) (transport_client*, int, int*)  = &osrf_stack_process;
+
+static int osrf_stack_process( transport_client* client, int timeout, int* msg_received ) {
+       if( !client ) return -1;
+       transport_message* msg = NULL;
+       if(msg_received) *msg_received = 0;
+
+       while( (msg = client_recv( client, timeout )) ) {
+               if(msg_received) *msg_received = 1;
+               osrfLogDebug( OSRF_LOG_MARK,  "Received message from transport code from %s", msg->sender );
+               osrf_stack_transport_handler( msg, NULL );
+               timeout = 0;
+       }
+
+       if( client->error ) {
+               osrfLogWarning(OSRF_LOG_MARK, "transport_client had trouble reading from the socket..");
+               return -1;
+       }
+
+       if( ! client_connected( client ) ) return -1;
+
+       return 0;
+}
+
+
+
+// -----------------------------------------------------------------------------
+// Entry point into the stack
+// -----------------------------------------------------------------------------
+osrfAppSession* osrf_stack_transport_handler( transport_message* msg,
+               const char* my_service ) {
+
+       if(!msg) return NULL;
+
+   osrfLogSetXid(msg->osrf_xid);
+
+       osrfLogDebug( OSRF_LOG_MARK,  "Transport handler received new message \nfrom %s "
+                       "to %s with body \n\n%s\n", msg->sender, msg->recipient, msg->body );
+
+       if( msg->is_error && ! msg->thread ) {
+               osrfLogWarning( OSRF_LOG_MARK, "!! Received jabber layer error for %s ... exiting\n", msg->sender );
+               message_free( msg );
+               return NULL;
+       }
+
+       if(! msg->thread  && ! msg->is_error ) {
+               osrfLogWarning( OSRF_LOG_MARK, "Received a non-error message with no thread trace... dropping");
+               message_free( msg );
+               return NULL;
+       }
+
+       osrfAppSession* session = osrf_app_session_find_session( msg->thread );
+
+       if( !session && my_service ) 
+               session = osrf_app_server_session_init( msg->thread, my_service, msg->sender);
+
+       if( !session ) {
+               message_free( msg );
+               return NULL;
+       }
+       
+       if(!msg->is_error)
+               osrfLogDebug( OSRF_LOG_MARK, "Session [%s] found or built", session->session_id );
+
+       osrf_app_session_set_remote( session, msg->sender );
+       osrfMessage* arr[OSRF_MAX_MSGS_PER_PACKET];
+       memset(arr, 0, sizeof(arr));
+       int num_msgs = osrf_message_deserialize(msg->body, arr, OSRF_MAX_MSGS_PER_PACKET);
+
+       osrfLogDebug( OSRF_LOG_MARK,  "We received %d messages from %s", num_msgs, msg->sender );
+
+       double starttime = get_timestamp_millis();
+
+       int i;
+       for( i = 0; i != num_msgs; i++ ) {
+
+               /* if we've received a jabber layer error message (probably talking to 
+                       someone who no longer exists) and we're not talking to the original
+                       remote id for this server, consider it a redirect and pass it up */
+               if(msg->is_error) {
+                       osrfLogWarning( OSRF_LOG_MARK,  " !!! Received Jabber layer error message" ); 
+
+                       if(strcmp(session->remote_id,session->orig_remote_id)) {
+                               osrfLogWarning( OSRF_LOG_MARK,  "Treating jabber error as redirect for tt [%d] "
+                                       "and session [%s]", arr[i]->thread_trace, session->session_id );
+
+                               arr[i]->m_type = STATUS;
+                               arr[i]->status_code = OSRF_STATUS_REDIRECTED;
+
+                       } else {
+                               osrfLogWarning( OSRF_LOG_MARK, " * Jabber Error is for top level remote "
+                    " id [%s], no one to send my message to!  Cutting request short...", session->remote_id );
+                session->transport_error = 1;
+                break;
+                       }
+               }
+
+               osrf_stack_message_handler( session, arr[i] );
+       }
+
+       double duration = get_timestamp_millis() - starttime;
+       osrfLogInfo(OSRF_LOG_MARK, "Message processing duration %f", duration);
+
+       message_free( msg );
+       osrfLogDebug( OSRF_LOG_MARK, "after msg delete");
+
+       return session;
+}
+
+static int osrf_stack_message_handler( osrfAppSession* session, osrfMessage* msg ) {
+       if(session == NULL || msg == NULL)
+               return 0;
+
+       osrfMessage* ret_msg = NULL;
+
+       if( session->type ==  OSRF_SESSION_CLIENT )
+                ret_msg = _do_client( session, msg );
+       else
+               ret_msg= _do_server( session, msg );
+
+       if(ret_msg) {
+               osrfLogDebug( OSRF_LOG_MARK, "passing message %d / session %s to app handler", 
+                               msg->thread_trace, session->session_id );
+               osrf_stack_application_handler( session, ret_msg );
+       } else
+               osrfMessageFree(msg);
+
+       return 1;
+
+} 
+
+/** If we return a message, that message should be passed up the stack, 
+  * if we return NULL, we're finished for now...
+  */
+static osrfMessage* _do_client( osrfAppSession* session, osrfMessage* msg ) {
+       if(session == NULL || msg == NULL)
+               return NULL;
+
+       osrfMessage* new_msg;
+
+       if( msg->m_type == STATUS ) {
+               
+               switch( msg->status_code ) {
+
+                       case OSRF_STATUS_OK:
+                               osrfLogDebug( OSRF_LOG_MARK, "We connected successfully");
+                               session->state = OSRF_SESSION_CONNECTED;
+                               osrfLogDebug( OSRF_LOG_MARK,  "State: %x => %s => %d", session, session->session_id, session->state );
+                               return NULL;
+
+                       case OSRF_STATUS_COMPLETE:
+                               osrf_app_session_set_complete( session, msg->thread_trace );
+                               return NULL;
+
+                       case OSRF_STATUS_CONTINUE:
+                               osrf_app_session_request_reset_timeout( session, msg->thread_trace );
+                               return NULL;
+
+                       case OSRF_STATUS_REDIRECTED:
+                               osrf_app_session_reset_remote( session );
+                               session->state = OSRF_SESSION_DISCONNECTED;
+                               osrf_app_session_request_resend( session, msg->thread_trace );
+                               return NULL;
+
+                       case OSRF_STATUS_EXPFAILED: 
+                               osrf_app_session_reset_remote( session );
+                               session->state = OSRF_SESSION_DISCONNECTED;
+                               return NULL;
+
+                       case OSRF_STATUS_TIMEOUT:
+                               osrf_app_session_reset_remote( session );
+                               session->state = OSRF_SESSION_DISCONNECTED;
+                               osrf_app_session_request_resend( session, msg->thread_trace );
+                               return NULL;
+
+
+                       default:
+                               new_msg = osrf_message_init( RESULT, msg->thread_trace, msg->protocol );
+                               osrf_message_set_status_info( new_msg, 
+                                               msg->status_name, msg->status_text, msg->status_code );
+                               osrfLogWarning( OSRF_LOG_MARK, "The stack doesn't know what to do with " 
+                                               "the provided message code: %d, name %s. Passing UP.", 
+                                               msg->status_code, msg->status_name );
+                               new_msg->is_exception = 1;
+                               osrf_app_session_set_complete( session, msg->thread_trace );
+                               osrfMessageFree(msg);
+                               return new_msg;
+               }
+
+               return NULL;
+
+       } else if( msg->m_type == RESULT ) 
+               return msg;
+
+       return NULL;
+
+}
+
+
+/** If we return a message, that message should be passed up the stack, 
+  * if we return NULL, we're finished for now...
+  */
+static osrfMessage* _do_server( osrfAppSession* session, osrfMessage* msg ) {
+
+       if(session == NULL || msg == NULL) return NULL;
+
+       osrfLogDebug( OSRF_LOG_MARK, "Server received message of type %d", msg->m_type );
+
+       switch( msg->m_type ) {
+
+               case STATUS:
+                               return NULL;
+
+               case DISCONNECT:
+                               /* session will be freed by the forker */
+                               osrfLogDebug(OSRF_LOG_MARK, "Client sent explicit disconnect");
+                               session->state = OSRF_SESSION_DISCONNECTED;
+                               return NULL;
+
+               case CONNECT:
+                               osrfAppSessionStatus( session, OSRF_STATUS_OK, 
+                                               "osrfConnectStatus", msg->thread_trace, "Connection Successful" );
+                               session->state = OSRF_SESSION_CONNECTED;
+                               return NULL;
+
+               case REQUEST:
+
+                               osrfLogDebug( OSRF_LOG_MARK, "server passing message %d to application handler "
+                                               "for session %s", msg->thread_trace, session->session_id );
+                               return msg;
+
+               default:
+                       osrfLogWarning( OSRF_LOG_MARK, "Server cannot handle message of type %d", msg->m_type );
+                       session->state = OSRF_SESSION_DISCONNECTED;
+                       return NULL;
+
+       }
+}
+
+
+
+static int osrf_stack_application_handler( osrfAppSession* session, osrfMessage* msg ) {
+       if(session == NULL || msg == NULL) return 0;
+
+       if(msg->m_type == RESULT && session->type == OSRF_SESSION_CLIENT) {
+               osrf_app_session_push_queue( session, msg ); 
+               return 1;
+       }
+
+       if(msg->m_type != REQUEST) return 1;
+
+       char* method = msg->method_name;
+       char* app       = session->remote_service;
+       jsonObject* params = msg->_params;
+
+       osrfAppRunMethod( app, method,  session, msg->thread_trace, params );
+       osrfMessageFree(msg);
+               
+       return 1;
+}
+
+
diff --git a/trunk/src/libopensrf/osrf_system.c b/trunk/src/libopensrf/osrf_system.c
new file mode 100644 (file)
index 0000000..96f8c82
--- /dev/null
@@ -0,0 +1,478 @@
+#include <opensrf/osrf_system.h>
+#include <opensrf/osrf_application.h>
+#include <opensrf/osrf_prefork.h>
+#include <signal.h>
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 256
+#endif
+
+static void report_child_status( pid_t pid, int status );
+struct child_node;
+typedef struct child_node ChildNode;
+
+static void handleKillSignal(int signo) {
+    /* we are the top-level process and we've been 
+     * killed. Kill all of our children */
+    kill(0, SIGTERM);
+    sleep(1); /* give the children a chance to die before we reap them */
+    pid_t child_pid;
+    int status;
+    while( (child_pid=waitpid(-1,&status,WNOHANG)) > 0) 
+        osrfLogInfo(OSRF_LOG_MARK, "Killed child %d", child_pid);
+    _exit(0);
+}
+
+
+struct child_node
+{
+       ChildNode* pNext;
+       ChildNode* pPrev;
+       pid_t pid;
+       char* app;
+       char* libfile;
+};
+
+static ChildNode* child_list;
+
+static transport_client* osrfGlobalTransportClient = NULL;
+
+static void add_child( pid_t pid, const char* app, const char* libfile );
+static void delete_child( ChildNode* node );
+static void delete_all_children( void );
+static ChildNode* seek_child( pid_t pid );
+
+transport_client* osrfSystemGetTransportClient( void ) {
+       return osrfGlobalTransportClient;
+}
+
+void osrfSystemIgnoreTransportClient() {
+       osrfGlobalTransportClient = NULL;
+}
+
+int osrf_system_bootstrap_client( char* config_file, char* contextnode ) {
+       return osrfSystemBootstrapClientResc(config_file, contextnode, NULL);
+}
+
+int osrfSystemInitCache( void ) {
+
+       jsonObject* cacheServers = osrf_settings_host_value_object("/cache/global/servers/server");
+       char* maxCache = osrf_settings_host_value("/cache/global/max_cache_time");
+
+       if( cacheServers && maxCache) {
+
+               if( cacheServers->type == JSON_ARRAY ) {
+                       int i;
+                       const char* servers[cacheServers->size];
+                       for( i = 0; i != cacheServers->size; i++ ) {
+                               servers[i] = jsonObjectGetString( jsonObjectGetIndex(cacheServers, i) );
+                               osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[i]);
+                       }
+                       osrfCacheInit( servers, cacheServers->size, atoi(maxCache) );
+
+               } else {
+                       const char* servers[] = { jsonObjectGetString(cacheServers) };          
+                       osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[0]);
+                       osrfCacheInit( servers, 1, atoi(maxCache) );
+               }
+
+       } else {
+               osrfLogError( OSRF_LOG_MARK,  "Missing config value for /cache/global/servers/server _or_ "
+                       "/cache/global/max_cache_time");
+       }
+
+       jsonObjectFree( cacheServers );
+       return 0;
+}
+
+
+int osrfSystemBootstrap( const char* hostname, const char* configfile,
+               const char* contextNode ) {
+       if( !(hostname && configfile && contextNode) ) return -1;
+
+       /* first we grab the settings */
+       if(!osrfSystemBootstrapClientResc(configfile, contextNode, "settings_grabber" )) {
+               osrfLogError( OSRF_LOG_MARK,
+                       "Unable to bootstrap for host %s from configuration file %s",
+                       hostname, configfile );
+               return -1;
+       }
+
+       int retcode = osrf_settings_retrieve(hostname);
+       osrf_system_disconnect_client();
+
+       if( retcode ) {
+               osrfLogError( OSRF_LOG_MARK,
+                       "Unable to retrieve settings for host %s from configuration file %s",
+                       hostname, configfile );
+               return -1;
+       }
+
+       /** daemonize me **/
+       /* background and let our children do their thing */
+       /* NOTE: This has been moved from below the 'if (apps)' block below ... move it back if things go crazy */
+       daemonize();
+
+       jsonObject* apps = osrf_settings_host_value_object("/activeapps/appname");
+       osrfStringArray* arr = osrfNewStringArray(8);
+       
+       if(apps) {
+               int i = 0;
+
+               if(apps->type == JSON_STRING) {
+                       osrfStringArrayAdd(arr, jsonObjectGetString(apps));
+
+               } else {
+                       const jsonObject* app;
+                       while( (app = jsonObjectGetIndex(apps, i++)) ) 
+                               osrfStringArrayAdd(arr, jsonObjectGetString(app));
+               }
+               jsonObjectFree(apps);
+
+               char* appname = NULL;
+               i = 0;
+               while( (appname = osrfStringArrayGetString(arr, i++)) ) {
+
+                       char* lang = osrf_settings_host_value("/apps/%s/language", appname);
+
+                       if(lang && !strcasecmp(lang,"c"))  {
+
+                               char* libfile = osrf_settings_host_value("/apps/%s/implementation", appname);
+               
+                               if(! (appname && libfile) ) {
+                                       osrfLogWarning( OSRF_LOG_MARK, "Missing appname / libfile in settings config");
+                                       continue;
+                               }
+
+                               osrfLogInfo( OSRF_LOG_MARK, "Launching application %s with implementation %s", appname, libfile);
+               
+                               pid_t pid;
+               
+                               if( (pid = fork()) ) { 
+                                       // store pid in local list for re-launching dead children...
+                                       add_child( pid, appname, libfile );
+                                       osrfLogInfo( OSRF_LOG_MARK, "Running application child %s: process id %ld",
+                                                                appname, (long) pid );
+       
+                               } else {
+               
+                                       osrfLogInfo( OSRF_LOG_MARK, " * Running application %s\n", appname);
+                                       if( osrfAppRegisterApplication( appname, libfile ) == 0 ) 
+                                               osrf_prefork_run(appname);
+       
+                                       osrfLogDebug( OSRF_LOG_MARK, "Server exiting for app %s and library %s\n", appname, libfile );
+                                       exit(0);
+                               }
+                       } // language == c
+               } 
+       } // should we do something if there are no apps? does the wait(NULL) below do that for us?
+
+       osrfStringArrayFree(arr);
+
+    signal(SIGTERM, handleKillSignal);
+    signal(SIGINT, handleKillSignal);
+       
+       while(1) {
+               errno = 0;
+               int status;
+               pid_t pid = wait( &status );
+               if(-1 == pid) {
+                       if(errno == ECHILD)
+                               osrfLogError(OSRF_LOG_MARK, "We have no more live services... exiting");
+                       else
+                               osrfLogError(OSRF_LOG_MARK, "Exiting top-level system loop with error: %s", strerror(errno));
+                       break;
+               } else {
+                       report_child_status( pid, status );
+               }
+       }
+
+       delete_all_children();
+       return 0;
+}
+
+
+static void report_child_status( pid_t pid, int status )
+{
+       const char* app;
+       const char* libfile;
+       ChildNode* node = seek_child( pid );
+
+       if( node ) {
+               app     = node->app     ? node->app     : "[unknown]";
+               libfile = node->libfile ? node->libfile : "[none]";
+       } else
+               app = libfile = NULL;
+       
+       if( WIFEXITED( status ) )
+       {
+               int rc = WEXITSTATUS( status );  // return code of child process
+               if( rc )
+                       osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) exited with return code %d",
+                                                 (long) pid, app, rc );
+               else
+                       osrfLogInfo( OSRF_LOG_MARK, "Child process %ld (app %s) exited normally",
+                                                 (long) pid, app );
+       }
+       else if( WIFSIGNALED( status ) )
+       {
+               osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) killed by signal %d",
+                                         (long) pid, app, WTERMSIG( status) );
+       }
+       else if( WIFSTOPPED( status ) )
+       {
+               osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) stopped by signal %d",
+                                         (long) pid, app, (int) WSTOPSIG( status ) );
+       }
+
+       delete_child( node );
+}
+
+/*----------- Routines to manage list of children --*/
+
+static void add_child( pid_t pid, const char* app, const char* libfile )
+{
+       /* Construct new child node */
+       
+       ChildNode* node = safe_malloc( sizeof( ChildNode ) );
+
+       node->pid = pid;
+
+       if( app )
+               node->app = strdup( app );
+       else
+               node->app = NULL;
+
+       if( libfile )
+               node->libfile = strdup( libfile );
+       else
+               node->libfile = NULL;
+       
+       /* Add new child node to the head of the list */
+
+       node->pNext = child_list;
+       node->pPrev = NULL;
+
+       if( child_list )
+               child_list->pPrev = node;
+
+       child_list = node;
+}
+
+static void delete_child( ChildNode* node ) {
+
+       /* Sanity check */
+
+       if( ! node )
+               return;
+       
+       /* Detach the node from the list */
+
+       if( node->pPrev )
+               node->pPrev->pNext = node->pNext;
+       else
+               child_list = node->pNext;
+
+       if( node->pNext )
+               node->pNext->pPrev = node->pPrev;
+
+       /* Deallocate the node and its payload */
+
+       free( node->app );
+       free( node->libfile );
+       free( node );
+}
+
+static void delete_all_children( void ) {
+
+       while( child_list )
+               delete_child( child_list );
+}
+
+static ChildNode* seek_child( pid_t pid ) {
+
+       /* Return a pointer to the child node for the */
+       /* specified process ID, or NULL if not found */
+       
+       ChildNode* node = child_list;
+       while( node ) {
+               if( node->pid == pid )
+                       break;
+               else
+                       node = node->pNext;
+       }
+
+       return node;
+}
+
+/*----------- End of routines to manage list of children --*/
+
+
+int osrfSystemBootstrapClientResc( const char* config_file,
+               const char* contextnode, const char* resource ) {
+
+       int failure = 0;
+
+       if(osrfSystemGetTransportClient()) {
+               osrfLogInfo(OSRF_LOG_MARK, "Client is already bootstrapped");
+               return 1; /* we already have a client connection */
+       }
+
+       if( !( config_file && contextnode ) && ! osrfConfigHasDefaultConfig() ) {
+               osrfLogError( OSRF_LOG_MARK, "No Config File Specified\n" );
+               return -1;
+       }
+
+       if( config_file ) {
+               osrfConfig* cfg = osrfConfigInit( config_file, contextnode );
+               if(cfg)
+                       osrfConfigSetDefaultConfig(cfg);
+               else
+                       return 0;   /* Can't load configuration?  Bail out */
+       }
+
+
+       char* log_file          = osrfConfigGetValue( NULL, "/logfile");
+       if(!log_file) {
+               fprintf(stderr, "No log file specified in configuration file %s\n",
+                               config_file);
+               return -1;
+       }
+
+       char* log_level         = osrfConfigGetValue( NULL, "/loglevel" );
+       osrfStringArray* arr    = osrfNewStringArray(8);
+       osrfConfigGetValueList(NULL, arr, "/domain");
+
+       char* username          = osrfConfigGetValue( NULL, "/username" );
+       char* password          = osrfConfigGetValue( NULL, "/passwd" );
+       char* port              = osrfConfigGetValue( NULL, "/port" );
+       char* unixpath          = osrfConfigGetValue( NULL, "/unixpath" );
+       char* facility          = osrfConfigGetValue( NULL, "/syslog" );
+       char* actlog            = osrfConfigGetValue( NULL, "/actlog" );
+
+       /* if we're a source-client, tell the logger */
+       char* isclient = osrfConfigGetValue(NULL, "/client");
+       if( isclient && !strcasecmp(isclient,"true") )
+               osrfLogSetIsClient(1);
+       free(isclient);
+
+       int llevel = 0;
+       int iport = 0;
+       if(port) iport = atoi(port);
+       if(log_level) llevel = atoi(log_level);
+
+       if(!strcmp(log_file, "syslog")) {
+               osrfLogInit( OSRF_LOG_TYPE_SYSLOG, contextnode, llevel );
+               osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
+               if(actlog) osrfLogSetSyslogActFacility(osrfLogFacilityToInt(actlog));
+
+       } else {
+               osrfLogInit( OSRF_LOG_TYPE_FILE, contextnode, llevel );
+               osrfLogSetFile( log_file );
+       }
+
+
+       /* Get a domain, if one is specified */
+       const char* domain = osrfStringArrayGetString( arr, 0 ); /* just the first for now */
+       if(!domain) {
+               fprintf(stderr, "No domain specified in configuration file %s\n", config_file);
+               osrfLogError( OSRF_LOG_MARK, "No domain specified in configuration file %s\n", config_file);
+               failure = 1;
+       }
+
+       if(!username) {
+               fprintf(stderr, "No username specified in configuration file %s\n", config_file);
+               osrfLogError( OSRF_LOG_MARK, "No username specified in configuration file %s\n", config_file);
+               failure = 1;
+       }
+
+       if(!password) {
+               fprintf(stderr, "No password specified in configuration file %s\n", config_file);
+               osrfLogError( OSRF_LOG_MARK, "No password specified in configuration file %s\n", config_file);
+               failure = 1;
+       }
+
+       if((iport <= 0) && !unixpath) {
+               fprintf(stderr, "No unixpath or valid port in configuration file %s\n", config_file);
+               osrfLogError( OSRF_LOG_MARK, "No unixpath or valid port in configuration file %s\n",
+                       config_file);
+               failure = 1;
+       }
+
+       if (failure) {
+               osrfStringArrayFree(arr);
+               free(log_file);
+               free(log_level);
+               free(username);
+               free(password);
+               free(port);
+               free(unixpath);
+               free(facility);
+               free(actlog);
+               return 0;
+       }
+
+       osrfLogInfo( OSRF_LOG_MARK, "Bootstrapping system with domain %s, port %d, and unixpath %s",
+               domain, iport, unixpath ? unixpath : "(none)" );
+       transport_client* client = client_init( domain, iport, unixpath, 0 );
+
+       char host[HOST_NAME_MAX + 1] = "";
+       gethostname(host, sizeof(host) );
+       host[HOST_NAME_MAX] = '\0';
+
+       char tbuf[32];
+       tbuf[0] = '\0';
+       snprintf(tbuf, 32, "%f", get_timestamp_millis());
+
+       if(!resource) resource = "";
+
+       int len = strlen(resource) + 256;
+       char buf[len];
+       buf[0] = '\0';
+       snprintf(buf, len - 1, "%s_%s_%s_%ld", resource, host, tbuf, (long) getpid() );
+
+       if(client_connect( client, username, password, buf, 10, AUTH_DIGEST )) {
+               /* child nodes will leak the parents client... but we can't free
+                       it without disconnecting the parents client :( */
+               osrfGlobalTransportClient = client;
+       }
+
+       osrfStringArrayFree(arr);
+       free(actlog);
+       free(facility);
+       free(log_level);
+       free(log_file);
+       free(username);
+       free(password);
+       free(port);     
+       free(unixpath);
+
+       if(osrfGlobalTransportClient)
+               return 1;
+
+       return 0;
+}
+
+int osrf_system_disconnect_client( void ) {
+       client_disconnect( osrfGlobalTransportClient );
+       client_free( osrfGlobalTransportClient );
+       osrfGlobalTransportClient = NULL;
+       return 0;
+}
+
+static int shutdownComplete = 0;
+int osrf_system_shutdown( void ) {
+    if(shutdownComplete) return;
+       osrfConfigCleanup();
+    osrfCacheCleanup();
+       osrf_system_disconnect_client();
+       osrf_settings_free_host_config(NULL);
+       osrfAppSessionCleanup();
+       osrfLogCleanup();
+    shutdownComplete = 1;
+       return 1;
+}
+
+
+
+
diff --git a/trunk/src/libopensrf/osrf_transgroup.c b/trunk/src/libopensrf/osrf_transgroup.c
new file mode 100644 (file)
index 0000000..e6a4c52
--- /dev/null
@@ -0,0 +1,243 @@
+#include <opensrf/osrf_transgroup.h>
+#include <sys/select.h>
+
+
+osrfTransportGroupNode* osrfNewTransportGroupNode( 
+               char* domain, int port, char* username, char* password, char* resource ) {
+
+       if(!(domain && port && username && password && resource)) return NULL;
+
+       osrfTransportGroupNode* node = safe_malloc(sizeof(osrfTransportGroupNode));
+       node->domain    = strdup(domain);
+       node->port              = port;
+       node->username = strdup(username);
+       node->password = strdup(password);
+       node->domain    = strdup(domain);
+       node->resource  = strdup(resource);
+       node->active    = 0;
+       node->lastsent  = 0;
+       node->connection = client_init( domain, port, NULL, 0 );
+
+       return node;
+}
+
+
+osrfTransportGroup* osrfNewTransportGroup( osrfTransportGroupNode* nodes[], int count ) {
+       if(!nodes || count < 1) return NULL;
+
+       osrfTransportGroup* grp = safe_malloc(sizeof(osrfTransportGroup));
+       grp->nodes                                      = osrfNewHash();
+       grp->itr                                                = osrfNewHashIterator(grp->nodes);
+
+       int i;
+       for( i = 0; i != count; i++ ) {
+               if(!(nodes[i] && nodes[i]->domain) ) return NULL;
+               osrfHashSet( grp->nodes, nodes[i], nodes[i]->domain );
+               osrfLogDebug( OSRF_LOG_MARK, "Adding domain %s to TransportGroup", nodes[i]->domain);
+       }
+
+       return grp;
+}
+
+
+/* connect all of the nodes to their servers */
+int osrfTransportGroupConnectAll( osrfTransportGroup* grp ) {
+       if(!grp) return -1;
+       int active = 0;
+
+       osrfTransportGroupNode* node;
+       osrfHashIteratorReset(grp->itr);
+
+       while( (node = osrfHashIteratorNext(grp->itr)) ) {
+               osrfLogInfo( OSRF_LOG_MARK, "TransportGroup attempting to connect to domain %s", 
+                                                        node->connection->session->server);
+
+               if(client_connect( node->connection, node->username, 
+                                       node->password, node->resource, 10, AUTH_DIGEST )) {
+                       node->active = 1;
+                       active++;
+                       osrfLogInfo( OSRF_LOG_MARK, "TransportGroup successfully connected to domain %s", 
+                                                        node->connection->session->server);
+               } else {
+                       osrfLogWarning( OSRF_LOG_MARK, "TransportGroup unable to connect to domain %s", 
+                                                        node->connection->session->server);
+               }
+       }
+
+       osrfHashIteratorReset(grp->itr);
+       return active;
+}
+
+void osrfTransportGroupDisconnectAll( osrfTransportGroup* grp ) {
+       if(!grp) return;
+
+       osrfTransportGroupNode* node;
+       osrfHashIteratorReset(grp->itr);
+
+       while( (node = osrfHashIteratorNext(grp->itr)) ) {
+               osrfLogInfo( OSRF_LOG_MARK, "TransportGroup disconnecting from domain %s", 
+                                                        node->connection->session->server);
+               client_disconnect(node->connection);
+               node->active = 0;
+       }
+
+       osrfHashIteratorReset(grp->itr);
+}
+
+
+int osrfTransportGroupSendMatch( osrfTransportGroup* grp, transport_message* msg ) {
+       if(!(grp && msg)) return -1;
+
+       char domain[256];
+       domain[0] = '\0';
+       jid_get_domain( msg->recipient, domain, 255 );
+
+       osrfTransportGroupNode* node = osrfHashGet(grp->nodes, domain);
+       if(node) {
+               if( (client_send_message( node->connection, msg )) == 0 )
+                       return 0;
+       }
+
+       osrfLogWarning( OSRF_LOG_MARK, "Error sending message to domain %s", domain );
+       return -1;
+}
+
+int osrfTransportGroupSend( osrfTransportGroup* grp, transport_message* msg ) {
+
+       if(!(grp && msg)) return -1;
+       int bufsize = 256;
+
+       char domain[bufsize];
+       domain[0] = '\0';
+       jid_get_domain( msg->recipient, domain, bufsize - 1 );
+
+       char msgrecip[bufsize];
+       msgrecip[0] = '\0';
+       jid_get_username(msg->recipient, msgrecip, bufsize - 1);
+
+       char msgres[bufsize];
+       msgres[0] = '\0';
+       jid_get_resource(msg->recipient, msgres, bufsize - 1);
+
+       char* firstdomain = NULL;
+       char newrcp[1024];
+
+       int updateRecip = 1;
+       /* if we don't host this domain, don't update the recipient but send it as is */
+       if(!osrfHashGet(grp->nodes, domain)) updateRecip = 0;
+
+       osrfTransportGroupNode* node;
+
+       do {
+
+               node = osrfHashIteratorNext(grp->itr);
+               if(!node) osrfHashIteratorReset(grp->itr);
+
+               node = osrfHashIteratorNext(grp->itr);
+               if(!node) return -1;
+
+               if(firstdomain == NULL) {
+                       firstdomain = node->domain;
+
+               } else {
+                       if(!strcmp(firstdomain, node->domain)) { /* we've made a full loop */
+                               osrfLogWarning( OSRF_LOG_MARK, "We've tried to send to all domains.. giving up");
+                               return -1;
+                       }
+               }
+
+               /* update the recipient domain if necessary */
+
+               if(updateRecip) {
+                       snprintf(newrcp, sizeof(newrcp), "%s@%s/%s", msgrecip, node->domain, msgres);
+                       free(msg->recipient);
+                       msg->recipient = strdup(newrcp);
+               }
+
+               if( (client_send_message( node->connection, msg )) == 0 ) 
+                       return 0;
+
+       } while(1);
+
+       return -1;
+}
+
+static int __osrfTGWait( fd_set* fdset, int maxfd, int timeout ) {
+       if(!(fdset && maxfd)) return 0;
+
+       struct timeval tv;
+       tv.tv_sec = timeout;
+       tv.tv_usec = 0;
+       int retval = 0;
+
+       if( timeout < 0 ) {
+               if( (retval = select( maxfd + 1, fdset, NULL, NULL, NULL)) == -1 ) 
+                       return 0;
+
+       } else {
+               if( (retval = select( maxfd + 1, fdset, NULL, NULL, &tv)) == -1 ) 
+                       return 0;
+       }
+
+       return retval;
+}
+
+
+transport_message* osrfTransportGroupRecvAll( osrfTransportGroup* grp, int timeout ) {
+       if(!grp) return NULL;
+
+       int maxfd = 0;
+       fd_set fdset;
+       FD_ZERO( &fdset );
+
+       osrfTransportGroupNode* node;
+       osrfHashIterator* itr = osrfNewHashIterator(grp->nodes);
+
+       while( (node = osrfHashIteratorNext(itr)) ) {
+               if(node->active) {
+                       int fd = node->connection->session->sock_id;
+                       if( fd < maxfd ) maxfd = fd;
+                       FD_SET( fd, &fdset );
+               }
+       }
+       osrfHashIteratorReset(itr);
+
+       if( __osrfTGWait( &fdset, maxfd, timeout ) ) {
+               while( (node = osrfHashIteratorNext(itr)) ) {
+                       if(node->active) {
+                               int fd = node->connection->session->sock_id;
+                               if( FD_ISSET( fd, &fdset ) ) {
+                                       return client_recv( node->connection, 0 );
+                               }
+                       }
+               }
+       }
+
+       osrfHashIteratorFree(itr);
+       return NULL;
+}
+
+transport_message* osrfTransportGroupRecv( osrfTransportGroup* grp, char* domain, int timeout ) {
+       if(!(grp && domain)) return NULL;
+
+       osrfTransportGroupNode* node = osrfHashGet(grp->nodes, domain);
+       if(!node && node->connection && node->connection->session) return NULL;
+       int fd = node->connection->session->sock_id;
+
+       fd_set fdset;
+       FD_ZERO( &fdset );
+       FD_SET( fd, &fdset );
+
+       int active = __osrfTGWait( &fdset, fd, timeout );
+       if(active) return client_recv( node->connection, 0 );
+       
+       return NULL;
+}
+
+void osrfTransportGroupSetInactive( osrfTransportGroup* grp, char* domain ) {
+       if(!(grp && domain)) return;
+       osrfTransportGroupNode* node = osrfHashGet(grp->nodes, domain );
+       if(node) node->active = 0;
+}
+
+
diff --git a/trunk/src/libopensrf/sha.c b/trunk/src/libopensrf/sha.c
new file mode 100644 (file)
index 0000000..c48a4c9
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ *  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, MA 02111-1307, USA.
+ * 
+ *  Gabber
+ *  Copyright (C) 1999-2000 Dave Smith & Julian Missig
+ */
+
+
+/* 
+   Implements the Secure Hash Algorithm (SHA1)
+
+   Copyright (C) 1999 Scott G. Miller
+
+   Released under the terms of the GNU General Public License v2
+   see file COPYING for details
+
+   Credits: 
+      Robert Klep <robert@ilse.nl>  -- Expansion function fix 
+         Thomas "temas" Muldowney <temas@box5.net>:
+                       -- shahash() for string fun
+                       -- Will add the int32 stuff in a few
+                       
+   ---
+   FIXME: This source takes int to be a 32 bit integer.  This
+   may vary from system to system.  I'd use autoconf if I was familiar
+   with it.  Anyone want to help me out?
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#ifdef MACOS
+#  include <string.h>
+#else
+#  include <sys/stat.h>
+#  include <sys/types.h>
+#endif
+
+#include <string.h>
+
+#ifndef WIN32
+#  include <unistd.h>
+#  define INT64 long long
+#else
+#  include <string.h>
+#  define snprintf _snprintf
+#  define INT64 __int64
+#endif
+
+#define switch_endianness(x) (x<<24 & 0xff000000) | \
+                             (x<<8  & 0x00ff0000) | \
+                             (x>>8  & 0x0000ff00) | \
+                             (x>>24 & 0x000000ff)
+
+/* Initial hash values */
+#define Ai 0x67452301 
+#define Bi 0xefcdab89
+#define Ci 0x98badcfe
+#define Di 0x10325476
+#define Ei 0xc3d2e1f0
+
+/* SHA1 round constants */
+#define K1 0x5a827999
+#define K2 0x6ed9eba1
+#define K3 0x8f1bbcdc 
+#define K4 0xca62c1d6
+
+/* Round functions.  Note that f2() is used in both rounds 2 and 4 */
+#define f1(B,C,D) ((B & C) | ((~B) & D))
+#define f2(B,C,D) (B ^ C ^ D)
+#define f3(B,C,D) ((B & C) | (B & D) | (C & D))
+
+/* left circular shift functions (rotate left) */
+#define rol1(x) ((x<<1) | ((x>>31) & 1))
+#define rol5(A) ((A<<5) | ((A>>27) & 0x1f))
+#define rol30(B) ((B<<30) | ((B>>2) & 0x3fffffff))
+
+/*
+  Hashes 'data', which should be a pointer to 512 bits of data (sixteen
+  32 bit ints), into the ongoing 160 bit hash value (five 32 bit ints)
+  'hash'
+*/
+int 
+sha_hash(int *data, int *hash)  
+{
+  int W[80];
+  unsigned int A=hash[0], B=hash[1], C=hash[2], D=hash[3], E=hash[4];
+  unsigned int t, x, TEMP;
+
+  for (t=0; t<16; t++) 
+    {
+#ifndef WORDS_BIGENDIAN
+      W[t]=switch_endianness(data[t]);
+#else 
+      W[t]=data[t];
+#endif
+    }
+
+
+  /* SHA1 Data expansion */
+  for (t=16; t<80; t++) 
+    {
+      x=W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16];
+      W[t]=rol1(x);
+    }
+
+  /* SHA1 main loop (t=0 to 79) 
+   This is broken down into four subloops in order to use
+   the correct round function and constant */
+  for (t=0; t<20; t++) 
+    {
+      TEMP=rol5(A) + f1(B,C,D) + E + W[t] + K1;
+      E=D;
+      D=C;
+      C=rol30(B);
+      B=A;
+      A=TEMP;
+    }
+  for (; t<40; t++) 
+    {
+      TEMP=rol5(A) + f2(B,C,D) + E + W[t] + K2;
+      E=D;
+      D=C;
+      C=rol30(B);
+      B=A;
+      A=TEMP;
+    }
+  for (; t<60; t++) 
+    {
+      TEMP=rol5(A) + f3(B,C,D) + E + W[t] + K3;
+      E=D;
+      D=C;
+      C=rol30(B);
+      B=A;
+      A=TEMP;
+    }
+  for (; t<80; t++) 
+    {
+      TEMP=rol5(A) + f2(B,C,D) + E + W[t] + K4;
+      E=D;
+      D=C;
+      C=rol30(B);
+      B=A;
+      A=TEMP;
+    }
+  hash[0]+=A; 
+  hash[1]+=B;
+  hash[2]+=C;
+  hash[3]+=D;
+  hash[4]+=E;
+  return 0;
+}
+
+/*
+  Takes a pointer to a 160 bit block of data (five 32 bit ints) and
+  intializes it to the start constants of the SHA1 algorithm.  This
+  must be called before using hash in the call to sha_hash
+*/
+int 
+sha_init(int *hash) 
+{
+  hash[0]=Ai;
+  hash[1]=Bi;
+  hash[2]=Ci;
+  hash[3]=Di;
+  hash[4]=Ei;
+  return 0;
+}
+
+int strprintsha(char *dest, int *hashval) 
+{
+       int x;
+       char *hashstr = dest;
+       for (x=0; x<5; x++) 
+       {
+               snprintf(hashstr, 9, "%08x", hashval[x]);
+               hashstr+=8;
+       }
+       //snprintf(hashstr++, 1, "\0");
+       hashstr[0] = '\0';
+       hashstr++;
+
+       return 0;
+}
+
+char *shahash(const char *str) 
+{
+       char read_buffer[65];
+       //int read_buffer[64];
+       int c=1, i;
+       
+       INT64 length=0;
+
+       int strsz;
+       static char final[41];
+       int *hashval;
+
+       hashval = (int *)malloc(20);
+
+       sha_init(hashval);
+
+       strsz = strlen(str);
+
+       if(strsz == 0) 
+       {
+            memset(read_buffer, 0, 65);
+            read_buffer[0] = 0x80;
+            sha_hash((int *)read_buffer, hashval);
+       }
+
+       while (strsz>0) 
+       {
+               memset(read_buffer, 0, 65);
+               strncpy((char*)read_buffer, str, 64);
+               c = strlen((char *)read_buffer);
+               length+=c;
+               strsz-=c;
+               if (strsz<=0) 
+               {
+                       length<<=3;     
+                       read_buffer[c]=(char)0x80;
+                       for (i=c+1; i<64; i++) 
+                               read_buffer[i]=0;
+                       if (c>55) 
+                       {
+                               /* we need to do an entire new block */
+                               sha_hash((int *)read_buffer, hashval);
+                               for (i=0; i<14; i++) 
+                                       ((int*)read_buffer)[i]=0;
+                       }      
+#ifndef WORDS_BIGENDIAN
+                       for (i=0; i<8; i++) 
+                       {
+                               read_buffer[56+i]=(char)(length>>(56-(i*8))) & 0xff;
+                       }
+#else  
+                       memcpy(read_buffer+56, &length, 8);
+#endif
+               }
+               
+               sha_hash((int *)read_buffer, hashval);
+               str+=64;
+       }
+
+       strprintsha((char *)final, hashval);
+       free(hashval);
+       return (char *)final;
+}
diff --git a/trunk/src/libopensrf/socket_bundle.c b/trunk/src/libopensrf/socket_bundle.c
new file mode 100644 (file)
index 0000000..de9ca93
--- /dev/null
@@ -0,0 +1,771 @@
+#include <opensrf/socket_bundle.h>
+
+/* buffer used to read from the sockets */
+#define RBUFSIZE 1024
+
+static socket_node* _socket_add_node(socket_manager* mgr,
+               int endpoint, int addr_type, int sock_fd, int parent_id );
+static socket_node* socket_find_node(socket_manager* mgr, int sock_fd);
+static void socket_remove_node(socket_manager*, int sock_fd);
+static int _socket_send(int sock_fd, const char* data, int flags);
+static int _socket_route_data(socket_manager* mgr, int num_active, fd_set* read_set);
+static int _socket_route_data_id( socket_manager* mgr, int sock_id);
+static int _socket_handle_new_client(socket_manager* mgr, socket_node* node);
+static int _socket_handle_client_data(socket_manager* mgr, socket_node* node);
+
+
+/* -------------------------------------------------------------------- 
+       Test Code 
+       -------------------------------------------------------------------- */
+/*
+int count = 0;
+void printme(void* blob, socket_manager* mgr, 
+               int sock_fd, char* data, int parent_id) {
+
+       fprintf(stderr, "Got data from socket %d with parent %d => %s", 
+                       sock_fd, parent_id, data );
+
+       socket_send(sock_fd, data);
+
+       if(count++ > 2) {
+               socket_disconnect(mgr, sock_fd);
+               _socket_print_list(mgr);
+       }
+}
+
+int main(int argc, char* argv[]) {
+       socket_manager manager;
+       memset(&manager, 0, sizeof(socket_manager));
+       int port = 11000;
+       if(argv[1])
+               port = atoi(argv[1]);
+
+       manager.data_received = &printme;
+       socket_open_tcp_server(&manager, port);
+
+       while(1)
+               socket_wait_all(&manager, -1);
+
+       return 0;
+}
+*/
+/* -------------------------------------------------------------------- */
+
+
+/* allocates and inserts a new socket node into the nodeset.
+       if parent_id is positive and non-zero, it will be set */
+static socket_node* _socket_add_node(socket_manager* mgr, 
+               int endpoint, int addr_type, int sock_fd, int parent_id ) {
+
+       if(mgr == NULL) return NULL;
+       osrfLogInternal( OSRF_LOG_MARK, "Adding socket node with fd %d", sock_fd);
+       socket_node* new_node = safe_malloc(sizeof(socket_node));
+
+       new_node->endpoint      = endpoint;
+       new_node->addr_type     = addr_type;
+       new_node->sock_fd               = sock_fd;
+       new_node->next                  = NULL;
+       new_node->parent_id = 0;
+       if(parent_id > 0)
+               new_node->parent_id = parent_id;
+
+       new_node->next                  = mgr->socket;
+       mgr->socket                             = new_node;
+       return new_node;
+}
+
+/* creates a new server socket node and adds it to the socket set.
+       returns new socket fd on success.  -1 on failure.
+       socket_type is one of INET or UNIX  */
+int socket_open_tcp_server(socket_manager* mgr, int port, const char* listen_ip) {
+
+       if( mgr == NULL ) {
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_tcp_server(): NULL mgr"); 
+               return -1;
+       }
+
+       int sock_fd;
+       struct sockaddr_in server_addr;
+
+       errno = 0;
+       sock_fd = socket(AF_INET, SOCK_STREAM, 0);
+       if(sock_fd < 0) {
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_tcp_server(): Unable to create TCP socket: %s",
+                       strerror( errno ) );
+               return -1;
+       }
+
+       server_addr.sin_family = AF_INET;
+
+       if(listen_ip != NULL) {
+               struct in_addr addr;
+               if( inet_aton( listen_ip, &addr ) )
+                       server_addr.sin_addr.s_addr = addr.s_addr;
+               else {
+                       osrfLogError( OSRF_LOG_MARK, "Listener address is invalid: %s", listen_ip );
+                       return -1;
+               }
+       } else {
+               server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+       }
+
+       server_addr.sin_port = htons(port);
+
+       errno = 0;
+       if(bind( sock_fd, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_tcp_server(): cannot bind to port %d: %s",
+                       port, strerror( errno ) );
+               return -1;
+       }
+
+       errno = 0;
+       if(listen(sock_fd, 20) == -1) {
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_tcp_server(): listen() returned error: %s",
+                       strerror( errno ) );
+               return -1;
+       }
+
+       _socket_add_node(mgr, SERVER_SOCKET, INET, sock_fd, 0);
+       return sock_fd;
+}
+
+int socket_open_unix_server(socket_manager* mgr, const char* path) {
+       if(mgr == NULL || path == NULL) return -1;
+
+       osrfLogDebug( OSRF_LOG_MARK, "opening unix socket at %s", path);
+       int sock_fd;
+       struct sockaddr_un server_addr;
+
+       if(strlen(path) > sizeof(server_addr.sun_path) - 1)
+       {
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_unix_server(): path too long: %s",
+                       path );
+               return -1;
+       }
+
+       errno = 0;
+       sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if(sock_fd < 0){
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_unix_server(): socket() failed: %s",
+                       strerror( errno ) );
+               return -1;
+       }
+
+       server_addr.sun_family = AF_UNIX;
+       strcpy(server_addr.sun_path, path);
+
+       errno = 0;
+       if( bind(sock_fd, (struct sockaddr*) &server_addr, 
+                               sizeof(struct sockaddr_un)) < 0) {
+               osrfLogWarning( OSRF_LOG_MARK, 
+                       "socket_open_unix_server(): cannot bind to unix port %s: %s",
+                       path, strerror( errno ) );
+               return -1;
+       }
+
+       errno = 0;
+       if(listen(sock_fd, 20) == -1) {
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_unix_server(): listen() returned error: %s",
+                       strerror( errno ) );
+               return -1;
+       }
+
+       osrfLogDebug( OSRF_LOG_MARK, "unix socket successfully opened");
+       
+       int i = 1;
+
+       /* causing problems with router for some reason ... */
+       //osrfLogDebug( OSRF_LOG_MARK, "Setting SO_REUSEADDR");
+       //setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+       
+       //osrfLogDebug( OSRF_LOG_MARK, "Setting TCP_NODELAY");
+       setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i));
+
+       _socket_add_node(mgr, SERVER_SOCKET, UNIX, sock_fd, 0);
+       return sock_fd;
+}
+
+
+
+int socket_open_udp_server( 
+               socket_manager* mgr, int port, const char* listen_ip ) {
+
+       int sockfd;
+       struct sockaddr_in server_addr;
+
+       errno = 0;
+       if( (sockfd = socket( AF_INET, SOCK_DGRAM, 0 )) < 0 ) {
+               osrfLogWarning( OSRF_LOG_MARK, "Unable to create UDP socket: %s", strerror( errno ) );
+               return -1;
+       }
+
+       server_addr.sin_family = AF_INET;
+       server_addr.sin_port = htons(port);
+       if(listen_ip) {
+               struct in_addr addr;
+               if( inet_aton( listen_ip, &addr ) )
+                       server_addr.sin_addr.s_addr = addr.s_addr;
+               else {
+                       osrfLogError( OSRF_LOG_MARK, "UDP listener address is invalid: %s", listen_ip );
+                       return -1;
+               }
+       } else server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+       errno = 0;
+       if( (bind (sockfd, (struct sockaddr *) &server_addr,sizeof(server_addr))) ) {
+               osrfLogWarning( OSRF_LOG_MARK, "Unable to bind to UDP port %d: %s",
+                       port, strerror( errno ) );
+               return -1;
+       }
+
+       _socket_add_node(mgr, SERVER_SOCKET, INET, sockfd, 0);
+       return sockfd;
+}
+
+
+int socket_open_tcp_client(socket_manager* mgr, int port, const char* dest_addr) {
+
+       struct sockaddr_in remoteAddr, localAddr;
+   struct hostent *hptr;
+   int sock_fd;
+
+   // ------------------------------------------------------------------
+   // Create the socket
+   // ------------------------------------------------------------------
+   errno = 0;
+   if( (sock_fd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) {
+          osrfLogWarning( OSRF_LOG_MARK,  "socket_open_tcp_client(): Cannot create TCP socket: %s",
+                       strerror( errno ) );
+      return -1;
+   }
+
+       int i = 1;
+       //osrfLogDebug( OSRF_LOG_MARK, "Setting TCP_NODELAY");
+       setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i));
+
+
+   // ------------------------------------------------------------------
+   // Get the hostname
+   // ------------------------------------------------------------------
+   errno = 0;
+   if( (hptr = gethostbyname( dest_addr ) ) == NULL ) {
+          osrfLogWarning(  OSRF_LOG_MARK, "socket_open_tcp_client(): Unknown Host => %s: %s",
+                                               dest_addr, strerror( errno ) );
+      return -1;
+   }
+
+   // ------------------------------------------------------------------
+   // Construct server info struct
+   // ------------------------------------------------------------------
+   memset( &remoteAddr, 0, sizeof(remoteAddr));
+   remoteAddr.sin_family = AF_INET;
+   remoteAddr.sin_port = htons( 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
+   // ------------------------------------------------------------------
+   errno = 0;
+   if( bind( sock_fd, (struct sockaddr *) &localAddr, sizeof( localAddr ) ) < 0 ) {
+          osrfLogWarning( OSRF_LOG_MARK, "socket_open_tcp_client(): Cannot bind to local port: %s",
+               strerror( errno ) );
+      return -1;
+   }
+
+   // ------------------------------------------------------------------
+   // Connect to server
+   // ------------------------------------------------------------------
+   errno = 0;
+   if( connect( sock_fd, (struct sockaddr*) &remoteAddr, sizeof( struct sockaddr_in ) ) < 0 ) {
+          osrfLogWarning( OSRF_LOG_MARK, "socket_open_tcp_client(): Cannot connect to server %s: %s",
+                  dest_addr, strerror(errno) );
+          return -1;
+   }
+
+       _socket_add_node(mgr, CLIENT_SOCKET, INET, sock_fd, -1 );
+
+   return sock_fd;
+}
+
+
+int socket_open_udp_client( 
+               socket_manager* mgr, int port, const char* dest_addr) {
+
+       int sockfd;
+       struct sockaddr_in client_addr, server_addr;
+       struct hostent* host;
+
+       errno = 0;
+       if( (host = gethostbyname(dest_addr)) == NULL) {
+               osrfLogWarning( OSRF_LOG_MARK, "Unable to resolve host: %s: %s",
+                       dest_addr, strerror( errno ) );
+               return -1;
+       }
+
+       server_addr.sin_family = host->h_addrtype;
+       memcpy((char *) &server_addr.sin_addr.s_addr,
+                            host->h_addr_list[0], host->h_length);
+       server_addr.sin_port = htons(port);
+
+       errno = 0;
+       if( (sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0 ) {
+               osrfLogWarning( OSRF_LOG_MARK, "socket_open_udp_client(): Unable to create UDP socket: %s", strerror( errno ) );
+               return -1;
+       }
+
+       client_addr.sin_family = AF_INET;
+       client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+       client_addr.sin_port = htons(0);
+
+       errno = 0;
+       if( (bind(sockfd, (struct sockaddr *) &client_addr, sizeof(client_addr))) < 0 ) {
+               osrfLogWarning( OSRF_LOG_MARK, "Unable to bind UDP socket: %s", strerror( errno ) );
+               return -1;
+       }
+
+       _socket_add_node(mgr, CLIENT_SOCKET, INET, sockfd, -1 );
+
+       return sockfd;
+}
+
+
+int socket_open_unix_client(socket_manager* mgr, const char* sock_path) {
+
+       int sock_fd, len;
+   struct sockaddr_un usock;
+
+   if(strlen(sock_path) > sizeof(usock.sun_path) - 1)
+   {
+          osrfLogWarning( OSRF_LOG_MARK, "socket_open_unix_client(): path too long: %s",
+                  sock_path );
+          return -1;
+   }
+
+   errno = 0;
+   if( (sock_fd = socket( AF_UNIX, SOCK_STREAM, 0 )) < 0 ) {
+          osrfLogWarning(  OSRF_LOG_MARK, "socket_open_unix_client(): Cannot create UNIX socket: %s", strerror( errno ) );
+               return -1;
+       }
+
+   usock.sun_family = AF_UNIX;
+   strcpy( usock.sun_path, sock_path );
+
+   len = sizeof( usock.sun_family ) + strlen( usock.sun_path );
+
+   errno = 0;
+   if( connect( sock_fd, (struct sockaddr *) &usock, len ) < 0 ) {
+          osrfLogWarning(  OSRF_LOG_MARK, "Error connecting to unix socket: %s",
+                       strerror( errno ) );
+               return -1;
+       }
+
+       _socket_add_node(mgr, CLIENT_SOCKET, UNIX, sock_fd, -1 );
+
+   return sock_fd;
+}
+
+
+/* returns the socket_node with the given sock_fd */
+static socket_node* socket_find_node(socket_manager* mgr, int sock_fd) {
+       if(mgr == NULL) return NULL;
+       socket_node* node = mgr->socket;
+       while(node) {
+               if(node->sock_fd == sock_fd)
+                       return node;
+               node = node->next;
+       }
+       return NULL;
+}
+
+/* removes the node with the given sock_fd from the list and frees it */
+static void socket_remove_node(socket_manager* mgr, int sock_fd) {
+
+       if(mgr == NULL) return;
+
+       osrfLogDebug( OSRF_LOG_MARK, "removing socket %d", sock_fd);
+
+       socket_node* head = mgr->socket;
+       socket_node* tail = head;
+       if(head == NULL) return;
+
+       /* if removing the first node in the list */
+       if(head->sock_fd == sock_fd) {
+               mgr->socket = head->next;
+               free(head);
+               return;
+       }
+
+       head = head->next;
+
+       /* if removing any other node */
+       while(head) {
+               if(head->sock_fd == sock_fd) {
+                       tail->next = head->next;
+                       free(head);
+                       return;
+               }
+               tail = head;
+               head = head->next;
+       }
+}
+
+
+void _socket_print_list(socket_manager* mgr) {
+       if(mgr == NULL) return;
+       socket_node* node = mgr->socket;
+       osrfLogDebug( OSRF_LOG_MARK, "socket_node list: [");
+       while(node) {
+               osrfLogDebug( OSRF_LOG_MARK, "sock_fd: %d | parent_id: %d", 
+                               node->sock_fd, node->parent_id);
+               node = node->next;
+       }
+       osrfLogDebug( OSRF_LOG_MARK, "]");
+}
+
+/* sends the given data to the given socket */
+int socket_send(int sock_fd, const char* data) {
+       return _socket_send( sock_fd, data, 0);
+}
+
+/* utility method */
+static int _socket_send(int sock_fd, const char* data, int flags) {
+
+       signal(SIGPIPE, SIG_IGN); /* in case a unix socket was closed */
+
+       errno = 0;
+       size_t r = send( sock_fd, data, strlen(data), flags );
+       int local_errno = errno;
+       
+       if( r == -1 ) {
+               osrfLogWarning( OSRF_LOG_MARK, "_socket_send(): Error sending data with return %d", r );
+               osrfLogWarning( OSRF_LOG_MARK, "Last Sys Error: %s", strerror(local_errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+
+/* sends the given data to the given socket. 
+ * sets the send flag MSG_DONTWAIT which will allow the 
+ * process to continue even if the socket buffer is full
+ * returns 0 on success, -1 otherwise */
+//int socket_send_nowait( int sock_fd, const char* data) {
+//     return _socket_send( sock_fd, data, MSG_DONTWAIT);
+//}
+
+
+/*
+ * Waits at most usecs microseconds for the send buffer of the given
+ * socket to accept new data.  This does not guarantee that the 
+ * socket will accept all the data we want to give it.
+ */
+int socket_send_timeout( int sock_fd, const char* data, int usecs ) {
+
+       fd_set write_set;
+       FD_ZERO( &write_set );
+       FD_SET( sock_fd, &write_set );
+
+       int mil = 1000000;
+       int secs = (int) usecs / mil;
+       usecs = usecs - (secs * mil);
+
+       struct timeval tv;
+       tv.tv_sec = secs;
+       tv.tv_usec = usecs;
+
+       errno = 0;
+       int ret = select( sock_fd + 1, NULL, &write_set, NULL, &tv);
+       if( ret > 0 ) return _socket_send( sock_fd, data, 0);
+
+       osrfLogError(OSRF_LOG_MARK, "socket_send_timeout(): "
+               "timed out on send for socket %d after %d secs, %d usecs: %s",
+               sock_fd, secs, usecs, strerror( errno ) );
+
+       return -1;
+}
+
+
+/* disconnects the node with the given sock_fd and removes
+       it from the socket set */
+void socket_disconnect(socket_manager* mgr, int sock_fd) {
+       osrfLogInternal( OSRF_LOG_MARK, "Closing socket %d", sock_fd);
+       close( sock_fd );
+       socket_remove_node(mgr, sock_fd);
+}
+
+
+/* we assume that if select() fails, the socket is no longer valid */
+int socket_connected(int sock_fd) {
+       fd_set read_set;
+       FD_ZERO( &read_set );
+       FD_SET( sock_fd, &read_set );
+       if( select( sock_fd + 1, &read_set, NULL, NULL, NULL) == -1 ) 
+               return 0;
+       return 1;
+
+}
+
+/* this only waits on the server socket and does not handle the actual
+       data coming in from the client..... XXX */
+int socket_wait(socket_manager* mgr, int timeout, int sock_fd) {
+
+       int retval = 0;
+       fd_set read_set;
+       FD_ZERO( &read_set );
+       FD_SET( sock_fd, &read_set );
+
+       struct timeval tv;
+       tv.tv_sec = timeout;
+       tv.tv_usec = 0;
+       errno = 0;
+
+       if( timeout < 0 ) {  
+
+               // If timeout is -1, we block indefinitely
+               if( (retval = select( sock_fd + 1, &read_set, NULL, NULL, NULL)) == -1 ) {
+                       osrfLogDebug( OSRF_LOG_MARK, "Call to select() interrupted: Sys Error: %s", strerror(errno));
+                       return -1;
+               }
+
+       } else if( timeout > 0 ) { /* timeout of 0 means don't block */
+
+               if( (retval = select( sock_fd + 1, &read_set, NULL, NULL, &tv)) == -1 ) {
+                       osrfLogDebug( OSRF_LOG_MARK, "Call to select() interrupted: Sys Error: %s", strerror(errno));
+                       return -1;
+               }
+       }
+
+       osrfLogInternal( OSRF_LOG_MARK, "%d active sockets after select()", retval);
+       return _socket_route_data_id(mgr, sock_fd);
+}
+
+
+int socket_wait_all(socket_manager* mgr, int timeout) {
+
+       if(mgr == NULL) {
+               osrfLogWarning( OSRF_LOG_MARK,  "socket_wait_all(): null mgr" );
+               return -1;
+       }
+
+       int retval = 0;
+       fd_set read_set;
+       FD_ZERO( &read_set );
+
+       socket_node* node = mgr->socket;
+       int max_fd = 0;
+       while(node) {
+               osrfLogInternal( OSRF_LOG_MARK, "Adding socket fd %d to select set",node->sock_fd);
+               FD_SET( node->sock_fd, &read_set );
+               if(node->sock_fd > max_fd) max_fd = node->sock_fd;
+               node = node->next;
+       }
+       max_fd += 1;
+
+       struct timeval tv;
+       tv.tv_sec = timeout;
+       tv.tv_usec = 0;
+       errno = 0;
+
+       if( timeout < 0 ) {  
+
+               // If timeout is -1, there is no timeout passed to the call to select
+               if( (retval = select( max_fd, &read_set, NULL, NULL, NULL)) == -1 ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "select() call aborted: %s", strerror(errno));
+                       return -1;
+               }
+
+       } else if( timeout != 0 ) { /* timeout of 0 means don't block */
+
+               if( (retval = select( max_fd, &read_set, NULL, NULL, &tv)) == -1 ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "select() call aborted: %s", strerror(errno));
+                       return -1;
+               }
+       }
+
+       osrfLogDebug( OSRF_LOG_MARK, "%d active sockets after select()", retval);
+       return _socket_route_data(mgr, retval, &read_set);
+}
+
+/* iterates over the sockets in the set and handles active sockets.
+       new sockets connecting to server sockets cause the creation
+       of a new socket node.
+       Any new data read is is passed off to the data_received callback
+       as it arrives */
+/* determines if we're receiving a new client or data
+       on an existing client */
+static int _socket_route_data(
+       socket_manager* mgr, int num_active, fd_set* read_set) {
+
+       if(!(mgr && read_set)) return -1;
+
+       int last_failed_id = -1;
+
+
+       /* come back here if someone yanks a socket_node from beneath us */
+       while(1) {
+
+               socket_node* node = mgr->socket;
+               int handled = 0;
+               int status = 0;
+               
+               while(node && (handled < num_active)) {
+       
+                       int sock_fd = node->sock_fd;
+                       
+                       if(last_failed_id != -1) {
+                               /* in case it was not removed by our overlords */
+                               osrfLogInternal( OSRF_LOG_MARK, "Attempting to remove last_failed_id of %d", last_failed_id);
+                               socket_remove_node( mgr, last_failed_id );
+                               last_failed_id = -1;
+                               status = -1;
+                               break;
+                       }
+       
+                       /* does this socket have data? */
+                       if( FD_ISSET( sock_fd, read_set ) ) {
+       
+                               osrfLogInternal( OSRF_LOG_MARK, "Socket %d active", sock_fd);
+                               handled++;
+                               FD_CLR(sock_fd, read_set);
+       
+                               if(node->endpoint == SERVER_SOCKET) 
+                                       _socket_handle_new_client(mgr, node);
+       
+                               else
+                                       status = _socket_handle_client_data(mgr, node);
+       
+                               /* someone may have yanked a socket_node out from under 
+                                       us...start over with the first socket */
+                               if(status == -1)  {
+                                       last_failed_id = sock_fd;
+                                       osrfLogInternal( OSRF_LOG_MARK, "Backtracking back to start of loop because "
+                                                       "of -1 return code from _socket_handle_client_data()");
+                               }
+                       }
+
+                       if(status == -1) break;
+                       node = node->next;
+
+               } // is_set
+
+               if(status == 0) break;
+               if(status == -1) status = 0;
+       } 
+
+       return 0;
+}
+
+
+/* routes data from a single known socket */
+static int _socket_route_data_id( socket_manager* mgr, int sock_id) {
+       socket_node* node = socket_find_node(mgr, sock_id);     
+       int status = 0;
+
+       if(node) {
+               if(node->endpoint == SERVER_SOCKET) 
+                       _socket_handle_new_client(mgr, node);
+       
+               if(node->endpoint == CLIENT_SOCKET ) 
+                       status = _socket_handle_client_data(mgr, node);
+
+               if(status == -1) {
+                       socket_remove_node(mgr, sock_id);
+                       return -1;
+               }
+               return 0;
+       } 
+
+       return -1;
+}
+
+
+static int _socket_handle_new_client(socket_manager* mgr, socket_node* node) {
+       if(mgr == NULL || node == NULL) return -1;
+
+       errno = 0;
+       int new_sock_fd;
+       new_sock_fd = accept(node->sock_fd, NULL, NULL);
+       if(new_sock_fd < 0) {
+               osrfLogWarning( OSRF_LOG_MARK, "_socket_handle_new_client(): accept() failed: %s",
+                       strerror( errno ) );
+               return -1;
+       }
+
+       if(node->addr_type == INET) {
+               _socket_add_node(mgr, CLIENT_SOCKET, INET, new_sock_fd, node->sock_fd);
+               osrfLogDebug( OSRF_LOG_MARK, "Adding new INET client for %d", node->sock_fd);
+
+       } else if(node->addr_type == UNIX) {
+               _socket_add_node(mgr, CLIENT_SOCKET, UNIX, new_sock_fd, node->sock_fd);
+               osrfLogDebug( OSRF_LOG_MARK, "Adding new UNIX client for %d", node->sock_fd);
+       }
+
+       return 0;
+}
+
+
+static int _socket_handle_client_data(socket_manager* mgr, socket_node* node) {
+       if(mgr == NULL || node == NULL) return -1;
+
+       char buf[RBUFSIZE];
+       int read_bytes;
+       int sock_fd = node->sock_fd;
+
+       osrf_clearbuf(buf, sizeof(buf));
+       set_fl(sock_fd, O_NONBLOCK);
+
+       osrfLogInternal( OSRF_LOG_MARK, "%ld : Received data at %f\n", (long) getpid(), get_timestamp_millis());
+
+       while( (read_bytes = recv(sock_fd, buf, RBUFSIZE-1, 0) ) > 0 ) {
+               buf[read_bytes] = '\0';
+               osrfLogInternal( OSRF_LOG_MARK, "Socket %d Read %d bytes and data: %s", sock_fd, read_bytes, buf);
+               if(mgr->data_received)
+                       mgr->data_received(mgr->blob, mgr, sock_fd, buf, node->parent_id);
+
+               osrf_clearbuf(buf, sizeof(buf));
+       }
+    int local_errno = errno; /* capture errno as set by recv() */
+
+       if(socket_find_node(mgr, sock_fd)) {  /* someone may have closed this socket */
+               clr_fl(sock_fd, O_NONBLOCK); 
+               if(read_bytes < 0) { 
+                       if(local_errno != EAGAIN) 
+                               osrfLogWarning(OSRF_LOG_MARK,  " * Error reading socket with error %s", strerror(local_errno));
+               }
+
+       } else { return -1; } /* inform the caller that this node has been tampered with */
+
+       if(read_bytes == 0) {  /* socket closed by client */
+               if(mgr->on_socket_closed) {
+                       mgr->on_socket_closed(mgr->blob, sock_fd);
+               }
+               return -1;
+       }
+
+       return 0;
+
+}
+
+
+void socket_manager_free(socket_manager* mgr) {
+       if(mgr == NULL) return;
+       socket_node* tmp;
+       while(mgr->socket) {
+               tmp = mgr->socket->next;
+               socket_disconnect(mgr, mgr->socket->sock_fd);
+               mgr->socket = tmp;
+       }
+       free(mgr);
+
+}
+
diff --git a/trunk/src/libopensrf/socket_test.c b/trunk/src/libopensrf/socket_test.c
new file mode 100644 (file)
index 0000000..c921ac6
--- /dev/null
@@ -0,0 +1,33 @@
+#include <opensrf/socket_bundle.h>
+
+int count = 0;
+void printme(void* blob, socket_manager* mgr, 
+               int sock_fd, char* data, int parent_id) {
+
+       fprintf(stderr, "Got data from socket %d with parent %d => %s", 
+                       sock_fd, parent_id, data );
+
+       socket_send(sock_fd, data);
+
+       if(count++ > 2) {
+//             socket_disconnect(mgr, sock_fd);
+               _socket_print_list(mgr);
+               socket_manager_free(mgr);
+               exit(0);
+       }
+}
+
+int main(int argc, char* argv[]) {
+       socket_manager* manager = safe_malloc(sizeof(socket_manager));
+       int port = 11000;
+       if(argv[1])
+               port = atoi(argv[1]);
+
+       manager->data_received = &printme;
+       socket_open_tcp_server(manager, port);
+
+       while(1)
+               socket_wait_all(manager, -1);
+
+       return 0;
+}
diff --git a/trunk/src/libopensrf/string_array.c b/trunk/src/libopensrf/string_array.c
new file mode 100644 (file)
index 0000000..ca8393f
--- /dev/null
@@ -0,0 +1,93 @@
+#include <opensrf/string_array.h>
+
+osrfStringArray* osrfNewStringArray(int size) {
+       return init_string_array(size);
+}
+
+string_array* init_string_array(int size) {
+       if(size > STRING_ARRAY_MAX_SIZE)
+               osrfLogError( OSRF_LOG_MARK, "init_string_array size is too large");
+
+       string_array* arr;
+       OSRF_MALLOC( arr, sizeof(string_array));
+    arr->list = osrfNewListSize(size);
+    osrfListSetDefaultFree(arr->list);
+       arr->size = 0;
+       return arr;
+}
+
+
+void osrfStringArrayAdd(osrfStringArray* arr, char* string) {
+       string_array_add(arr, string);
+}
+
+void string_array_add(string_array* arr, char* str) {
+       if(arr == NULL || str == NULL ) return;
+       if( arr->size > STRING_ARRAY_MAX_SIZE ) 
+               osrfLogError( OSRF_LOG_MARK, "string_array_add size is too large");
+    osrfListPush(arr->list, strdup(str));
+    arr->size = arr->list->size;
+}
+
+char* osrfStringArrayGetString(osrfStringArray* arr, int index) {
+    if(!arr) return NULL;
+    return OSRF_LIST_GET_INDEX(arr->list, index);
+}
+
+char* string_array_get_string(string_array* arr, int index) {
+    if(!arr) return NULL;
+    return OSRF_LIST_GET_INDEX(arr->list, index);
+}
+
+
+void osrfStringArrayFree(osrfStringArray* arr) {
+    OSRF_STRING_ARRAY_FREE(arr);
+}
+
+void string_array_destroy(string_array* arr) {
+    OSRF_STRING_ARRAY_FREE(arr);
+}
+
+
+int osrfStringArrayContains( osrfStringArray* arr, char* string ) {
+       if(!(arr && string)) return 0;
+       int i;
+       for( i = 0; i < arr->size; i++ ) {
+        char* str = OSRF_LIST_GET_INDEX(arr->list, i);
+               if(str && !strcmp(str, string)) 
+            return 1;
+       }
+
+       return 0;
+}
+
+void osrfStringArrayRemove( osrfStringArray* arr, char* tstr) {
+       if(!(arr && tstr)) return;
+       int i;
+    char* str;
+
+       for( i = 0; i < arr->size; i++ ) {
+        /* find and remove the string */
+        str = OSRF_LIST_GET_INDEX(arr->list, i);
+               if(str && !strcmp(str, tstr)) {
+            osrfListRemove(arr->list, i);
+                       break;
+               }
+       }
+
+    /* disable automatic item freeing on delete and shift
+     * items up in the array to fill in the gap
+     */
+    arr->list->freeItem = NULL;
+       for( ; i < arr->size - 1; i++ ) 
+        osrfListSet(arr->list, OSRF_LIST_GET_INDEX(arr->list, i+1) , i);
+
+    /* remove the last item since it was shifted up */
+    osrfListRemove(arr->list, i);
+
+    /* re-enable automatic item freeing in delete */
+    osrfListSetDefaultFree(arr->list);
+       arr->size--;
+}
+
+
diff --git a/trunk/src/libopensrf/transport_client.c b/trunk/src/libopensrf/transport_client.c
new file mode 100644 (file)
index 0000000..d73d5fe
--- /dev/null
@@ -0,0 +1,222 @@
+#include <opensrf/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];
+                       osrf_clearbuf( buf, 0, sizeof(buf)); 
+                       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( const char* server, int port, const char* unix_path, int component ) {
+
+       if(server == NULL) return NULL;
+
+       /* build and clear the client object */
+       transport_client* client = safe_malloc( sizeof( transport_client) );
+
+       /* build and clear the message list */
+       client->m_list = safe_malloc( sizeof( transport_message_list ) );
+
+       client->m_list->next = NULL;
+       client->m_list->message = NULL;
+       client->m_list->type = MESSAGE_LIST_HEAD;
+
+       /* build the session */
+       
+       client->session = init_transport( server, port, unix_path, client, component );
+
+       client->session->message_callback = client_message_handler;
+       client->error = 0;
+
+       return client;
+}
+
+
+int client_connect( transport_client* client, 
+               const char* username, const char* password, const char* resource, 
+               int connect_timeout, enum TRANSPORT_AUTH_TYPE  auth_type ) {
+       if(client == NULL) return 0; 
+       return session_connect( client->session, username, 
+                       password, resource, connect_timeout, auth_type );
+}
+
+
+int client_disconnect( transport_client* client ) {
+       if( client == NULL ) { return 0; }
+       return session_disconnect( client->session );
+}
+
+int client_connected( const 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;
+       if( client->error ) return -1;
+       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 ) ) {
+                       int x;
+                       if( (x = session_wait( client->session, -1 )) ) {
+                               osrfLogDebug(OSRF_LOG_MARK, "session_wait returned failure code %d\n", x);
+                               client->error = 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)) ) {
+                               client->error = 1;
+                               osrfLogDebug(OSRF_LOG_MARK, "session_wait returned failure code %d: setting error=1\n", wait_ret);
+                               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);
+               }
+
+       }
+
+       /* 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;
+
+       transport_message_node* node = safe_malloc( sizeof( transport_message_node) );
+       node->next = NULL;
+       node->type = MESSAGE_LIST_ITEM;
+       node->message = msg;
+
+
+       /* find the last node and put this onto the end */
+       transport_message_node* tail = cli->m_list;
+       transport_message_node* current = tail->next;
+
+       while( current != NULL ) {
+               tail = current;
+               current = current->next;
+       }
+       tail->next = node;
+}
+
+
+int client_free( transport_client* client ){
+       if(client == NULL) return 0; 
+
+       session_free( client->session );
+       transport_message_node* current = client->m_list->next;
+       transport_message_node* next;
+
+       /* deallocate the list of messages */
+       while( current != NULL ) {
+               next = current->next;
+               message_free( current->message );
+               free(current);
+               current = next;
+       }
+
+       free( client->m_list );
+       free( client );
+       return 1;
+}
+
diff --git a/trunk/src/libopensrf/transport_message.c b/trunk/src/libopensrf/transport_message.c
new file mode 100644 (file)
index 0000000..b23e317
--- /dev/null
@@ -0,0 +1,405 @@
+#include <opensrf/transport_message.h>
+
+
+// ---------------------------------------------------------------------------------
+// Allocates and initializes a new transport_message
+// ---------------------------------------------------------------------------------
+transport_message* message_init( const char* body, const char* subject,
+               const char* thread, const char* recipient, const char* sender ) {
+
+       transport_message* msg = 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 ) {
+
+               osrfLogError(OSRF_LOG_MARK,  "message_init(): Out of Memory" );
+               free( msg->body );
+               free( msg->thread );
+               free( msg->subject );
+               free( msg->recipient );
+               free( msg->sender );
+               free( msg );
+               return NULL;
+       }
+
+       msg->router_from    = NULL;
+       msg->router_to      = NULL;
+       msg->router_class   = NULL;
+       msg->router_command = NULL;
+       msg->osrf_xid       = NULL;
+       msg->is_error       = 0;
+       msg->error_type     = NULL;
+       msg->error_code     = 0;
+       msg->broadcast      = 0;
+       msg->msg_xml        = NULL;
+
+       return msg;
+}
+
+
+transport_message* new_message_from_xml( const char* msg_xml ) {
+
+       if( msg_xml == NULL || *msg_xml == '\0' )
+               return NULL;
+
+       transport_message* new_msg = safe_malloc( sizeof(transport_message) );
+
+       new_msg->body           = NULL;
+       new_msg->subject        = NULL;
+       new_msg->thread         = NULL;
+       new_msg->recipient      = NULL;
+       new_msg->sender         = NULL;
+       new_msg->router_from    = NULL;
+       new_msg->router_to      = NULL;
+       new_msg->router_class   = NULL;
+       new_msg->router_command = NULL;
+       new_msg->osrf_xid       = NULL;
+       new_msg->is_error       = 0;
+       new_msg->error_type     = NULL;
+       new_msg->error_code     = 0;
+       new_msg->broadcast      = 0;
+       new_msg->msg_xml        = NULL;
+
+       xmlKeepBlanksDefault(0);
+       xmlDocPtr msg_doc = xmlReadDoc( BAD_CAST msg_xml, NULL, NULL, 0 );
+       xmlNodePtr root = xmlDocGetRootElement(msg_doc);
+
+       xmlChar* sender = xmlGetProp(root, BAD_CAST "from");
+       xmlChar* recipient      = xmlGetProp(root, BAD_CAST "to");
+       xmlChar* subject                = xmlGetProp(root, BAD_CAST "subject");
+       xmlChar* thread         = xmlGetProp( root, BAD_CAST "thread" );
+       xmlChar* router_from    = xmlGetProp( root, BAD_CAST "router_from" );
+       xmlChar* router_to      = xmlGetProp( root, BAD_CAST "router_to" );
+       xmlChar* router_class= xmlGetProp( root, BAD_CAST "router_class" );
+       xmlChar* broadcast      = xmlGetProp( root, BAD_CAST "broadcast" );
+       xmlChar* osrf_xid    = xmlGetProp( root, BAD_CAST "osrf_xid" );
+
+   if( osrf_xid ) {
+      message_set_osrf_xid( new_msg, (char*) osrf_xid);
+      xmlFree(osrf_xid);
+   }
+
+       if( router_from ) {
+               new_msg->sender         = strdup((const char*)router_from);
+       } else {
+               if( sender ) {
+                       new_msg->sender         = strdup((const char*)sender);
+                       xmlFree(sender);
+               }
+       }
+
+       if( recipient ) {
+               new_msg->recipient      = strdup((const char*)recipient);
+               xmlFree(recipient);
+       }
+       if(subject){
+               new_msg->subject                = strdup((const char*)subject);
+               xmlFree(subject);
+       }
+       if(thread) {
+               new_msg->thread         = strdup((const char*)thread);
+               xmlFree(thread);
+       }
+       if(router_from) {
+               new_msg->router_from    = strdup((const char*)router_from);
+               xmlFree(router_from);
+       }
+       if(router_to) {
+               new_msg->router_to      = strdup((const char*)router_to);
+               xmlFree(router_to);
+       }
+       if(router_class) {
+               new_msg->router_class = strdup((const char*)router_class);
+               xmlFree(router_class);
+       }
+       if(broadcast) {
+               if(strcmp((const char*) broadcast,"0") )
+                       new_msg->broadcast      = 1;
+               xmlFree(broadcast);
+       }
+
+       xmlNodePtr search_node = root->children;
+       while( search_node != NULL ) {
+
+               if( ! strcmp( (const char*) search_node->name, "thread" ) ) {
+                       if( search_node->children && search_node->children->content ) 
+                               new_msg->thread = strdup( (const char*) search_node->children->content );
+               }
+
+               if( ! strcmp( (const char*) search_node->name, "subject" ) ) {
+                       if( search_node->children && search_node->children->content )
+                               new_msg->subject = strdup( (const char*) search_node->children->content );
+               }
+
+               if( ! strcmp( (const char*) search_node->name, "body" ) ) {
+                       if( search_node->children && search_node->children->content )
+                               new_msg->body = strdup((const char*) search_node->children->content );
+               }
+
+               search_node = search_node->next;
+       }
+
+       if( new_msg->thread == NULL ) 
+               new_msg->thread = strdup("");
+       if( new_msg->subject == NULL )
+               new_msg->subject = strdup("");
+       if( new_msg->body == NULL )
+               new_msg->body = strdup("");
+
+       new_msg->msg_xml = xmlDocToString(msg_doc, 0);
+   xmlFreeDoc(msg_doc);
+   xmlCleanupParser();
+
+       return new_msg;
+}
+
+void message_set_osrf_xid( transport_message* msg, const char* osrf_xid ) {
+   if(!msg) return;
+   if( osrf_xid )
+      msg->osrf_xid = strdup(osrf_xid);
+   else msg->osrf_xid = strdup("");
+}
+
+void message_set_router_info( transport_message* msg, const char* router_from,
+               const char* router_to, const char* router_class, const char* router_command,
+               int broadcast_enabled ) {
+
+       if( !msg ) return;
+       
+       if(router_from)
+               msg->router_from                = strdup(router_from);
+       else
+               msg->router_from                = strdup("");
+
+       if(router_to)
+               msg->router_to                  = strdup(router_to);
+       else
+               msg->router_to                  = strdup("");
+
+       if(router_class)
+               msg->router_class               = strdup(router_class);
+       else 
+               msg->router_class               = strdup("");
+       
+       if(router_command)
+               msg->router_command     = strdup(router_command);
+       else
+               msg->router_command     = strdup("");
+
+       msg->broadcast = broadcast_enabled;
+
+       if( msg->router_from == NULL || msg->router_to == NULL ||
+                       msg->router_class == NULL || msg->router_command == NULL ) 
+               osrfLogError(OSRF_LOG_MARK,  "message_set_router_info(): Out of Memory" );
+
+       return;
+}
+
+
+
+/* encodes the message for traversal */
+int message_prepare_xml( transport_message* msg ) {
+       if( !msg ) return 0;
+       if( msg->msg_xml == NULL )
+               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);
+       free(msg->osrf_xid);
+       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 ) { 
+               osrfLogWarning(OSRF_LOG_MARK,  "Passing NULL message to message_to_xml()"); 
+               return NULL; 
+       }
+
+       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];
+               osrf_clearbuf( code_buf, sizeof(code_buf));
+               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 );
+       xmlNewProp( message_node, BAD_CAST "osrf_xid", BAD_CAST msg->osrf_xid );
+
+       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 && *thread ) {
+               thread_node = xmlNewChild(message_node, NULL, (xmlChar*) "thread", NULL );
+               xmlNodePtr txt = xmlNewText((xmlChar*) thread);
+               xmlAddChild(thread_node, txt);
+               xmlAddChild(message_node, thread_node); 
+       }
+
+       if( subject && *subject ) {
+               subject_node = xmlNewChild(message_node, NULL, (xmlChar*) "subject", NULL );
+               xmlNodePtr txt = xmlNewText((xmlChar*) subject);
+               xmlAddChild(subject_node, txt);
+               xmlAddChild( message_node, subject_node ); 
+       }
+
+       if( body && *body ) {
+               body_node = xmlNewChild(message_node, NULL, (xmlChar*) "body", NULL);
+               xmlNodePtr txt = xmlNewText((xmlChar*) body);
+               xmlAddChild(body_node, txt);
+               xmlAddChild( message_node, body_node ); 
+       }
+
+       xmlBufferPtr xmlbuf = xmlBufferCreate();
+       xmlNodeDump( xmlbuf, doc, xmlDocGetRootElement(doc), 0, 0);
+       char* xml = strdup((const char*) (xmlBufferContent(xmlbuf)));
+       xmlBufferFree(xmlbuf);
+       xmlFreeDoc( doc );               
+       xmlCleanupParser();
+       return xml;
+}
+
+
+
+void jid_get_username( const char* jid, char buf[], int size ) {
+
+       if( jid == NULL || buf == NULL || size <= 0 ) { return; }
+
+       buf[ 0 ] = '\0';
+       
+       /* 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] == '@' ) {
+                       if(i > size)  i = size;
+                       memcpy( buf, jid, i );
+                       buf[i] = '\0';
+                       return;
+               }
+       }
+}
+
+
+void jid_get_resource( const char* jid, char buf[], int size)  {
+       if( jid == NULL || buf == NULL || size <= 0 ) { return; }
+
+       // Find the last slash, if any
+       
+       const char* start = strrchr( jid, '/' );
+       if( start ) {
+
+               // Copy the text beyond the slash, up to a maximum size
+
+               size_t len = strlen( ++start );
+               if( len > size ) len = size;
+               memcpy( buf, start, len );
+               buf[ len ] = '\0';
+       }
+       else
+               buf[ 0 ] = '\0';
+}
+
+void jid_get_domain( const char* jid, char buf[], int size ) {
+
+       if(jid == NULL) return;
+
+       int len = strlen(jid);
+       int i;
+       int index1 = 0; 
+       int index2 = 0;
+
+       for( i = 0; i!= len; i++ ) {
+               if(jid[i] == '@')
+                       index1 = i + 1;
+               else if(jid[i] == '/' && index1 != 0)
+                       index2 = i;
+       }
+
+       if( index1 > 0 && index2 > 0 && index2 > index1 ) {
+               int dlen = index2 - index1;
+               if(dlen > size) dlen = size;
+               memcpy( buf, jid + index1, dlen );
+               buf[dlen] = '\0'; // memcpy doesn't provide the nul
+       }
+       else
+               buf[ 0 ] = '\0';
+}
+
+void set_msg_error( transport_message* msg, const char* type, int err_code ) {
+
+       if( !msg ) return;
+       
+       if( type != NULL && *type ) {
+               msg->error_type = safe_malloc( strlen(type)+1); 
+               strcpy( msg->error_type, type );
+               msg->error_code = err_code;
+       }
+       msg->is_error = 1;
+}
diff --git a/trunk/src/libopensrf/transport_session.c b/trunk/src/libopensrf/transport_session.c
new file mode 100644 (file)
index 0000000..f0f70f1
--- /dev/null
@@ -0,0 +1,666 @@
+#include <opensrf/transport_session.h>
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 256
+#endif
+
+static char* get_xml_attr( const xmlChar** atts, const char* attr_name );
+
+// ---------------------------------------------------------------------------------
+// returns a built and allocated transport_session object.
+// This codes does no network activity, only memory initilization
+// ---------------------------------------------------------------------------------
+transport_session* init_transport(  const char* server, 
+       int port, const char* unix_path, void* user_data, int component ) {
+
+       /* create the session struct */
+       transport_session* session = 
+               (transport_session*) safe_malloc( sizeof(transport_session) );
+
+       session->user_data = user_data;
+
+       session->component = component;
+
+       /* 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 );
+       session->session_id                     = buffer_init( 64 );
+
+       session->message_error_code = 0;
+
+       /* for OpenSRF extensions */
+       session->router_to_buffer               = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_from_buffer     = buffer_init( JABBER_JID_BUFSIZE );
+       session->osrf_xid_buffer        = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_class_buffer    = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_command_buffer  = buffer_init( JABBER_JID_BUFSIZE );
+
+       session->router_broadcast   = 0;
+
+       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 ||
+                       session->session_id == NULL ) { 
+
+               osrfLogError(OSRF_LOG_MARK,  "init_transport(): buffer_init returned NULL" );
+               buffer_free( session->body_buffer );
+               buffer_free( session->subject_buffer );
+               buffer_free( session->thread_buffer );
+               buffer_free( session->from_buffer );
+               buffer_free( session->status_buffer );
+               buffer_free( session->recipient_buffer );
+               buffer_free( session->router_to_buffer );
+               buffer_free( session->router_from_buffer );
+               buffer_free( session->router_class_buffer );
+               buffer_free( session->router_command_buffer );
+               buffer_free( session->session_id );
+               free( session );
+               return 0;
+       }
+
+
+       /* initialize the jabber state machine */
+       session->state_machine = (jabber_machine*) safe_malloc( sizeof(jabber_machine) );
+       session->state_machine->connected        = 0;
+       session->state_machine->connecting       = 0;
+       session->state_machine->in_message       = 0;
+       session->state_machine->in_message_body  = 0;
+       session->state_machine->in_thread        = 0;
+       session->state_machine->in_subject       = 0;
+       session->state_machine->in_error         = 0;
+       session->state_machine->in_message_error = 0;
+       session->state_machine->in_iq            = 0;
+       session->state_machine->in_presence      = 0;
+       session->state_machine->in_status        = 0;
+
+       /* initialize the sax push parser */
+       session->parser_ctxt = xmlCreatePushParserCtxt(SAXHandler, session, "", 0, NULL);
+
+       /* initialize the transport_socket structure */
+       session->sock_mgr = (socket_manager*) safe_malloc( sizeof(socket_manager) );
+
+       session->sock_mgr->data_received = &grab_incoming;
+       session->sock_mgr->on_socket_closed = NULL;
+       session->sock_mgr->socket = NULL;
+       session->sock_mgr->blob = session;
+       
+       session->port = port;
+       session->server = strdup(server);
+       if(unix_path)   
+               session->unix_path = strdup(unix_path);
+       else session->unix_path = NULL;
+
+       session->sock_id = 0;
+       session->message_callback = NULL;
+
+       return session;
+}
+
+
+
+/* XXX FREE THE BUFFERS */
+int session_free( transport_session* session ) {
+       if( ! session ) { return 0; }
+
+       if(session->sock_mgr)
+               socket_manager_free(session->sock_mgr);
+
+       if( session->state_machine ) free( session->state_machine );
+       if( session->parser_ctxt) {
+               xmlFreeDoc( session->parser_ctxt->myDoc );
+               xmlFreeParserCtxt(session->parser_ctxt);
+       }
+
+       xmlCleanupCharEncodingHandlers();
+       xmlDictCleanup();
+       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->osrf_xid_buffer);
+       buffer_free(session->router_class_buffer);
+       buffer_free(session->router_command_buffer);
+       buffer_free(session->session_id);
+
+       free(session->server);
+       free(session->unix_path);
+
+       free( session );
+       return 1;
+}
+
+
+int session_wait( transport_session* session, int timeout ) {
+       if( ! session || ! session->sock_mgr ) {
+               return 0;
+       }
+
+       int ret =  socket_wait( session->sock_mgr, timeout, session->sock_id );
+
+       if( ret ) {
+               osrfLogDebug(OSRF_LOG_MARK, "socket_wait returned error code %d", ret);
+               session->state_machine->connected = 0;
+       }
+       return ret;
+}
+
+int session_send_msg( 
+               transport_session* session, transport_message* msg ) {
+
+       if( ! session ) { return -1; }
+
+       if( ! session->state_machine->connected ) {
+               osrfLogWarning(OSRF_LOG_MARK, "State machine is not connected in send_msg()");
+               return -1;
+       }
+
+       message_prepare_xml( msg );
+       //tcp_send( session->sock_obj, msg->msg_xml );
+       return socket_send( session->sock_id, msg->msg_xml );
+
+}
+
+
+/* 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, enum TRANSPORT_AUTH_TYPE auth_type ) {
+
+       int size1 = 0;
+       int size2 = 0;
+
+       if( ! session ) { 
+               osrfLogWarning(OSRF_LOG_MARK,  "session is null in connect" );
+               return 0; 
+       }
+
+
+       //char* server = session->sock_obj->server;
+       char* server = session->server;
+
+       if( ! session->sock_id ) {
+
+               if(session->port > 0) {
+                       if( (session->sock_id = socket_open_tcp_client(
+                               session->sock_mgr, session->port, session->server)) <= 0 ) 
+                       return 0;
+
+               } else if(session->unix_path != NULL) {
+                       if( (session->sock_id = socket_open_unix_client(
+                               session->sock_mgr, session->unix_path)) <= 0 ) 
+                       return 0;
+               }
+               else {
+                       osrfLogWarning( OSRF_LOG_MARK, "Can't open session: no port or unix path" );
+                       return 0;
+               }
+       }
+
+       if( session->component ) {
+
+               /* the first Jabber connect stanza */
+               char our_hostname[HOST_NAME_MAX + 1] = "";
+               gethostname(our_hostname, sizeof(our_hostname) );
+               our_hostname[HOST_NAME_MAX] = '\0';
+               size1 = 150 + strlen( server );
+               char stanza1[ size1 ]; 
+               snprintf( stanza1, sizeof(stanza1),
+                               "<stream:stream version='1.0' xmlns:stream='http://etherx.jabber.org/streams' "
+                               "xmlns='jabber:component:accept' to='%s' from='%s' xml:lang='en'>",
+                               username, our_hostname );
+
+               /* send the first stanze */
+               session->state_machine->connecting = CONNECTING_1;
+
+//             if( ! tcp_send( session->sock_obj, stanza1 ) ) {
+               if( socket_send( session->sock_id, stanza1 ) ) {
+                       osrfLogWarning(OSRF_LOG_MARK, "error sending");
+                       return 0;
+               }
+       
+               /* wait for reply */
+               //tcp_wait( session->sock_obj, connect_timeout ); /* make the timeout smarter XXX */
+               socket_wait(session->sock_mgr, connect_timeout, session->sock_id);
+       
+               /* server acknowledges our existence, now see if we can login */
+               if( session->state_machine->connecting == CONNECTING_2 ) {
+       
+                       int ss = session->session_id->n_used + strlen(password) + 5;
+                       char hashstuff[ss];
+                       snprintf( hashstuff, sizeof(hashstuff), "%s%s", session->session_id->buf, password );
+
+                       char* hash = shahash( hashstuff );
+                       size2 = 100 + strlen( hash );
+                       char stanza2[ size2 ];
+                       snprintf( stanza2, sizeof(stanza2), "<handshake>%s</handshake>", hash );
+       
+                       //if( ! tcp_send( session->sock_obj, stanza2 )  ) {
+                       if( socket_send( session->sock_id, stanza2 )  ) {
+                               osrfLogWarning(OSRF_LOG_MARK, "error sending");
+                               return 0;
+                       }
+               }
+
+       } else { /* we're not a component */
+
+               /* the first Jabber connect stanza */
+               size1 = 100 + strlen( server );
+               char stanza1[ size1 ]; 
+               snprintf( stanza1, sizeof(stanza1), 
+                               "<stream:stream to='%s' xmlns='jabber:client' "
+                               "xmlns:stream='http://etherx.jabber.org/streams'>",
+                       server );
+       
+
+               /* send the first stanze */
+               session->state_machine->connecting = CONNECTING_1;
+               //if( ! tcp_send( session->sock_obj, stanza1 ) ) {
+               if( socket_send( session->sock_id, stanza1 ) ) {
+                       osrfLogWarning(OSRF_LOG_MARK, "error sending");
+                       return 0;
+               }
+
+
+               /* wait for reply */
+               //tcp_wait( session->sock_obj, connect_timeout ); /* make the timeout smarter XXX */
+               socket_wait( session->sock_mgr, connect_timeout, session->sock_id ); /* make the timeout smarter XXX */
+
+               if( auth_type == AUTH_PLAIN ) {
+
+                       /* the second jabber connect stanza including login info*/
+                       size2 = 150 + strlen( username ) + strlen(password) + strlen(resource);
+                       char stanza2[ size2 ];
+                       snprintf( stanza2, sizeof(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 );
+       
+                       /* server acknowledges our existence, now see if we can login */
+                       if( session->state_machine->connecting == CONNECTING_2 ) {
+                               //if( ! tcp_send( session->sock_obj, stanza2 )  ) {
+                               if( socket_send( session->sock_id, stanza2 )  ) {
+                                       osrfLogWarning(OSRF_LOG_MARK, "error sending");
+                                       return 0;
+                               }
+                       }
+
+               } else if( auth_type == AUTH_DIGEST ) {
+
+                       int ss = session->session_id->n_used + strlen(password) + 5;
+                       char hashstuff[ss];
+                       snprintf( hashstuff, sizeof(hashstuff), "%s%s", session->session_id->buf, password );
+
+                       char* hash = shahash( hashstuff );
+
+                       /* the second jabber connect stanza including login info*/
+                       size2 = 150 + strlen( hash ) + strlen(password) + strlen(resource);
+                       char stanza2[ size2 ];
+                       snprintf( stanza2, sizeof(stanza2), 
+                                       "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'>"
+                                       "<username>%s</username><digest>%s</digest><resource>%s</resource></query></iq>",
+                                       username, hash, resource );
+       
+                       /* server acknowledges our existence, now see if we can login */
+                       if( session->state_machine->connecting == CONNECTING_2 ) {
+                               //if( ! tcp_send( session->sock_obj, stanza2 )  ) {
+                               if( socket_send( session->sock_id, stanza2 )  ) {
+                                       osrfLogWarning(OSRF_LOG_MARK, "error sending");
+                                       return 0;
+                               }
+                       }
+
+               }
+
+       } // not component
+
+
+       /* wait for reply */
+       //tcp_wait( session->sock_obj, connect_timeout );
+       socket_wait( session->sock_mgr, connect_timeout, session->sock_id );
+
+       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 ) {
+void grab_incoming(void* blob, socket_manager* mgr, int sockid, char* data, int parent) {
+       transport_session* ses = (transport_session*) blob;
+       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( (char*) 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->osrf_xid_buffer, get_xml_attr( atts, "osrf_xid" ) );
+               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( (char*) name, "body" ) == 0 ) {
+                       ses->state_machine->in_message_body = 1;
+                       return;
+               }
+       
+               if( strcmp( (char*) name, "subject" ) == 0 ) {
+                       ses->state_machine->in_subject = 1;
+                       return;
+               }
+       
+               if( strcmp( (char*) name, "thread" ) == 0 ) {
+                       ses->state_machine->in_thread = 1;
+                       return;
+               }
+
+       }
+
+       if( strcmp( (char*) 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( (char*) name, "status" ) == 0 ) {
+               ses->state_machine->in_status = 1;
+               return;
+       }
+
+
+       if( strcmp( (char*) name, "stream:error" ) == 0 ) {
+               ses->state_machine->in_error = 1;
+               ses->state_machine->connected = 0;
+               osrfLogWarning(  OSRF_LOG_MARK, "Received <stream:error> message from Jabber server" );
+               return;
+       }
+
+
+       /* first server response from a connect attempt */
+       if( strcmp( (char*) name, "stream:stream" ) == 0 ) {
+               if( ses->state_machine->connecting == CONNECTING_1 ) {
+                       ses->state_machine->connecting = CONNECTING_2;
+                       buffer_add( ses->session_id, get_xml_attr(atts, "id") );
+               }
+       }
+
+       if( strcmp( (char*) name, "handshake" ) == 0 ) {
+               ses->state_machine->connected = 1;
+               ses->state_machine->connecting = 0;
+               return;
+       }
+
+
+       if( strcmp( (char*) 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" ) );
+               osrfLogInfo( OSRF_LOG_MARK,  "Received <error> message with type %s and code %s", 
+                       get_xml_attr( atts, "type"), get_xml_attr( atts, "code") );
+               return;
+       }
+
+       if( strcmp( (char*) 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 ) {
+                       osrfLogWarning( OSRF_LOG_MARK,  "Error connecting to jabber" );
+                       return;
+               }
+       }
+}
+
+// ------------------------------------------------------------------
+// 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.
+// ------------------------------------------------------------------
+static char* get_xml_attr( const xmlChar** atts, const char* attr_name ) {
+       int i;
+       if (atts != NULL) {
+               for(i = 0;(atts[i] != NULL);i++) {
+                       if( strcmp( (char*) 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( (char*) 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 );
+
+         message_set_osrf_xid( msg, ses->osrf_xid_buffer->buf );
+
+                       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( (const char*) name, "body" ) == 0 ) {
+               ses->state_machine->in_message_body = 0;
+               return;
+       }
+
+       if( strcmp( (const char*) name, "subject" ) == 0 ) {
+               ses->state_machine->in_subject = 0;
+               return;
+       }
+
+       if( strcmp( (const char*) name, "thread" ) == 0 ) {
+               ses->state_machine->in_thread = 0;
+               return;
+       }
+       
+       if( strcmp( (const char*) name, "iq" ) == 0 ) {
+               ses->state_machine->in_iq = 0;
+               if( ses->message_error_code > 0 ) {
+                       osrfLogWarning( OSRF_LOG_MARK,  "Error in IQ packet: code %d",  ses->message_error_code );
+                       osrfLogWarning( OSRF_LOG_MARK,  "Error 401 means not authorized" );
+               }
+               reset_session_buffers( session );
+               return;
+       }
+
+       if( strcmp( (const char*) 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( (const char*) name, "status" ) == 0 ) {
+               ses->state_machine->in_status = 0;
+               return;
+       }
+
+       if( strcmp( (const char*) name, "error" ) == 0 ) {
+               ses->state_machine->in_message_error = 0;
+               return;
+       }
+
+       if( strcmp( (const char*) 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->osrf_xid_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 );
+       buffer_reset( ses->session_id );
+
+       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];
+       strncpy( data, (const 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... */
+               osrfLogWarning( OSRF_LOG_MARK,  "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, "transport_session XML 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, "transport_session XML 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>");
+       socket_send(session->sock_id, "</stream:stream>");
+       socket_disconnect(session->sock_mgr, session->sock_id);
+       return 0;
+       //return tcp_disconnect( session->sock_obj );
+}
+
diff --git a/trunk/src/libopensrf/utils.c b/trunk/src/libopensrf/utils.c
new file mode 100644 (file)
index 0000000..4a22b02
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+Copyright (C) 2005  Georgia Public Library Service 
+
+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.
+*/
+
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+#include <errno.h>
+
+inline void* safe_malloc( int size ) {
+       void* ptr = (void*) malloc( size );
+       if( ptr == NULL ) {
+               osrfLogError( OSRF_LOG_MARK, "Out of Memory" );
+               exit(99);
+       }
+       memset( ptr, 0, size ); // remove this after safe_calloc transition
+       return ptr;
+}
+
+inline void* safe_calloc( int size ) {
+       void* ptr = (void*) calloc( 1, size );
+       if( ptr == NULL ) {
+               osrfLogError( OSRF_LOG_MARK, "Out of Memory" );
+               exit(99);
+       }
+       return ptr;
+}
+
+/****************
+ The following static variables, and the following two functions,
+ overwrite the argv array passed to main().  The purpose is to
+ change the program name as reported by ps and similar utilities.
+
+ Warning: this code makes the non-portable assumption that the
+ strings to which argv[] points are contiguous in memory.  The
+ C Standard makes no such guarantee.
+ ****************/
+static char** global_argv = NULL;
+static int global_argv_size = 0;
+
+int init_proc_title( int argc, char* argv[] ) {
+
+       global_argv = argv;
+
+       int i = 0;
+       while( i < argc ) {
+               int len = strlen( global_argv[i]);
+               osrf_clearbuf( global_argv[i], len );
+               global_argv_size += len;
+               i++;
+       }
+
+       global_argv_size -= 2;
+       return 0;
+}
+
+int set_proc_title( const char* format, ... ) {
+       VA_LIST_TO_STRING(format);
+       osrf_clearbuf( *(global_argv), global_argv_size);
+       return snprintf( *(global_argv), global_argv_size, VA_BUF );
+}
+
+
+/* utility method for profiling */
+double get_timestamp_millis( void ) {
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       double time     = (int)tv.tv_sec        + ( ((double)tv.tv_usec / 1000000) );
+       return time;
+}
+
+
+/* setting/clearing file flags */
+int set_fl( int fd, int flags ) {
+       
+       int val;
+
+       if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) 
+               return -1;
+
+       val |= flags;
+
+       if( fcntl( fd, F_SETFL, val ) < 0 ) 
+               return -1;
+
+       return 0;
+}
+       
+int clr_fl( int fd, int flags ) {
+       
+       int val;
+
+       if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) 
+               return -1;
+
+       val &= ~flags;
+
+       if( fcntl( fd, F_SETFL, val ) < 0 ) 
+               return -1;
+
+       return 0;
+}
+
+long va_list_size(const char* format, va_list args) {
+       int len = 0;
+       len = vsnprintf(NULL, 0, format, args);
+       va_end(args);
+       len += 2;
+       return len;
+}
+
+
+char* va_list_to_string(const char* format, ...) {
+
+       long len = 0;
+       va_list args;
+       va_list a_copy;
+
+       va_copy(a_copy, args);
+
+       va_start(args, format);
+       len = va_list_size(format, args);
+
+       char buf[len];
+       osrf_clearbuf(buf, sizeof(buf));
+
+       va_start(a_copy, format);
+       vsnprintf(buf, len - 1, format, a_copy);
+       va_end(a_copy);
+       return strdup(buf);
+}
+
+// ---------------------------------------------------------------------------------
+// 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;
+       OSRF_MALLOC(gb, len);
+
+       gb->n_used = 0;/* nothing stored so far */
+       gb->size = num_initial_bytes;
+       OSRF_MALLOC(gb->buf, gb->size + 1);
+
+       return gb;
+}
+
+
+/* Expand the internal buffer of a growing_buffer so that it */
+/* will accommodate a specified string length.  Return 0 if  */
+/* successful, or 1 otherwise. */
+
+/* Note that we do not check to see if the buffer is already */
+/* big enough.  It is the responsibility of the calling      */
+/* function to call this only when necessary. */
+
+static int buffer_expand( growing_buffer* gb, size_t total_len ) {
+
+       // Make sure the request is not excessive
+       
+       if( total_len >= BUFFER_MAX_SIZE ) {
+               fprintf(stderr, "Buffer reached MAX_SIZE of %lu",
+                               (unsigned long) BUFFER_MAX_SIZE );
+               buffer_free( gb );
+               return 1;
+       }
+
+       // Pick a big enough buffer size, but don't exceed a maximum
+       
+       while( total_len >= gb->size ) {
+               gb->size *= 2;
+       }
+
+       if( gb->size > BUFFER_MAX_SIZE )
+               gb->size = BUFFER_MAX_SIZE;
+
+       // Allocate and populate the new buffer
+       
+       char* new_data;
+       OSRF_MALLOC( new_data, gb->size );
+       memcpy( new_data, gb->buf, gb->n_used );
+       new_data[ gb->n_used ] = '\0';
+
+       // Replace the old buffer
+       
+       free( gb->buf );
+       gb->buf = new_data;
+       return 0;
+}
+
+
+int buffer_fadd(growing_buffer* gb, const char* format, ... ) {
+
+       if(!gb || !format) return 0; 
+
+       long len = 0;
+       va_list args;
+       va_list a_copy;
+
+       va_copy(a_copy, args);
+
+       va_start(args, format);
+       len = va_list_size(format, args);
+
+       char buf[len];
+       osrf_clearbuf(buf, sizeof(buf));
+
+       va_start(a_copy, format);
+       vsnprintf(buf, len - 1, format, a_copy);
+       va_end(a_copy);
+
+       return buffer_add(gb, buf);
+
+}
+
+
+int buffer_add(growing_buffer* gb, const 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;
+
+       if( total_len >= gb->size ) {
+               if( buffer_expand( gb, total_len ) )
+                       return -1;
+       }
+
+       strcat( gb->buf, data );
+       gb->n_used = total_len;
+       return total_len;
+}
+
+
+int buffer_reset( growing_buffer *gb){
+       if( gb == NULL ) { return -1; }
+       if( gb->buf == NULL ) { return -1; }
+       osrf_clearbuf( gb->buf, sizeof(gb->buf) );
+       gb->n_used = 0;
+       return gb->n_used;
+}
+
+/* Return a pointer to the text within a growing_buffer, */
+/* while destroying the growing_buffer itself.           */
+
+char* buffer_release( growing_buffer* gb) {
+       char* s = gb->buf;
+       s[gb->n_used] = '\0';
+       free( gb );
+       return s;
+}
+
+/* Destroy a growing_buffer and the text it contains */
+
+int buffer_free( growing_buffer* gb ) {
+       if( gb == NULL ) 
+               return 0;
+       free( gb->buf );
+       free( gb );
+       return 1;
+}
+
+char* buffer_data( const growing_buffer *gb) {
+       return strdup( gb->buf );
+}
+
+int buffer_chomp(growing_buffer* gb) {
+       if( gb == NULL ) { return -1; }
+    if(gb->n_used > 0) {
+           gb->n_used--;
+           gb->buf[gb->n_used] = '\0';
+    }
+    return gb->n_used;
+}
+
+
+/*
+#define OSRF_BUFFER_ADD_CHAR(gb, c)\
+       do {\
+               if(gb) {\
+                       if(gb->n_used < gb->size - 1)\
+                               gb->buf[gb->n_used++] = c;\
+                       else\
+                               buffer_add_char(gb, c);\
+               }\
+       }while(0)
+       */
+
+int buffer_add_char(growing_buffer* gb, char c ) {
+       if(gb && gb->buf) {
+
+               int total_len = gb->n_used + 1;
+
+               if( total_len >= gb->size ) {
+                       if( buffer_expand( gb, total_len ) )
+                               return -1;
+               }
+       
+               gb->buf[ gb->n_used ]   = c;
+               gb->buf[ ++gb->n_used ] = '\0';
+       }
+       
+       return gb->n_used;
+}
+
+
+char* uescape( const char* string, int size, int full_escape ) {
+
+       growing_buffer* buf = buffer_init(size + 64);
+       int clen = 0;
+       int idx = 0;
+       unsigned long int c = 0x0;
+
+       while (string[idx]) {
+
+               c = 0x0;
+
+               if ((unsigned char)string[idx] >= 0x80) { // not ASCII
+
+                       if ((unsigned char)string[idx] >= 0xC0 && (unsigned char)string[idx] <= 0xF4) { // starts a UTF8 string
+
+                               clen = 1;
+                               if (((unsigned char)string[idx] & 0xF0) == 0xF0) {
+                                       clen = 3;
+                                       c = (unsigned char)string[idx] ^ 0xF0;
+
+                               } else if (((unsigned char)string[idx] & 0xE0) == 0xE0) {
+                                       clen = 2;
+                                       c = (unsigned char)string[idx] ^ 0xE0;
+
+                               } else if (((unsigned char)string[idx] & 0xC0) == 0xC0) {
+                                       clen = 1;
+                                       c = (unsigned char)string[idx] ^ 0xC0;
+                               }
+
+                               for (;clen;clen--) {
+
+                                       idx++; // look at the next byte
+                                       c = (c << 6) | ((unsigned char)string[idx] & 0x3F); // add this byte worth
+
+                               }
+
+                               buffer_fadd(buf, "\\u%04x", c);
+
+                       } else {
+                               buffer_free(buf);
+                               return NULL;
+                       }
+
+               } else {
+                       c = string[idx];
+
+                       /* escape the usual suspects */
+                       if(full_escape) {
+                               switch(c) {
+                                       case '"':
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               OSRF_BUFFER_ADD_CHAR(buf, '"');
+                                               break;
+       
+                                       case '\b':
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               OSRF_BUFFER_ADD_CHAR(buf, 'b');
+                                               break;
+       
+                                       case '\f':
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               OSRF_BUFFER_ADD_CHAR(buf, 'f');
+                                               break;
+       
+                                       case '\t':
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               OSRF_BUFFER_ADD_CHAR(buf, 't');
+                                               break;
+       
+                                       case '\n':
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               OSRF_BUFFER_ADD_CHAR(buf, 'n');
+                                               break;
+       
+                                       case '\r':
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               OSRF_BUFFER_ADD_CHAR(buf, 'r');
+                                               break;
+
+                                       case '\\':
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               OSRF_BUFFER_ADD_CHAR(buf, '\\');
+                                               break;
+
+                                       default:
+                                               if( c < 32 ) buffer_fadd(buf, "\\u%04x", c);
+                                               else OSRF_BUFFER_ADD_CHAR(buf, c);
+                               }
+
+                       } else {
+                               OSRF_BUFFER_ADD_CHAR(buf, c);
+                       }
+               }
+
+               idx++;
+       }
+
+       return buffer_release(buf);
+}
+
+
+// A function to turn a process into a daemon 
+int daemonize( void ) {
+       pid_t f = fork();
+
+       if (f == -1) {
+               osrfLogError( OSRF_LOG_MARK, "Failed to fork!" );
+               return -1;
+
+       } else if (f == 0) { // We're in the child now...
+               
+               // Change directories.  Otherwise whatever directory
+               // we're in couldn't be deleted until the program
+               // terminated -- possibly causing some inconvenience.
+               chdir( "/" );
+
+               /* create new session */
+               setsid();
+
+               // Now that we're no longer attached to a terminal,
+               // we don't want any traffic on the standard streams
+               freopen( "/dev/null", "r", stdin );
+               freopen( "/dev/null", "w", stdout );
+               freopen( "/dev/null", "w", stderr );
+               
+               return 0;
+
+       } else { // We're in the parent...
+               _exit(0);
+       }
+}
+
+
+/* Return 1 if the string represents an integer,  */
+/* as recognized by strtol(); Otherwise return 0. */
+
+int stringisnum(const char* s) {
+       char* w;
+       strtol(s, &w, 10);
+       return *w ? 0 : 1;
+}
+       
+
+
+char* file_to_string(const char* filename) {
+
+       if(!filename) return NULL;
+
+       FILE * file = fopen( filename, "r" );
+       if( !file ) {
+               osrfLogError( OSRF_LOG_MARK, "Unable to open file [%s]", filename );
+               return NULL;
+       }
+
+       size_t num_read;
+       char buf[ BUFSIZ + 1 ];
+       growing_buffer* gb = buffer_init(sizeof(buf));
+
+       while( ( num_read = fread( buf, 1, sizeof(buf) - 1, file) ) ) {
+               buf[ num_read ] = '\0';
+               buffer_add(gb, buf);
+       }
+
+       fclose(file);
+
+       return buffer_release(gb);
+}
+
+
+char* md5sum( const char* text, ... ) {
+
+       struct md5_ctx ctx;
+       unsigned char digest[16];
+
+       MD5_start (&ctx);
+
+       VA_LIST_TO_STRING(text);
+
+       int i;
+       for ( i=0 ; i != strlen(VA_BUF) ; i++ )
+               MD5_feed (&ctx, VA_BUF[i]);
+
+       MD5_stop (&ctx, digest);
+
+       char buf[16];
+       char final[256];
+       osrf_clearbuf(final, sizeof(final));
+
+       for ( i=0 ; i<16 ; i++ ) {
+               snprintf(buf, sizeof(buf), "%02x", digest[i]);
+               strcat( final, buf );
+       }
+
+       return strdup(final);
+
+}
+
+int osrfUtilsCheckFileDescriptor( int fd ) {
+
+       fd_set tmpset;
+       FD_ZERO(&tmpset);
+       FD_SET(fd, &tmpset);
+
+       struct timeval tv;
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+
+       if( select(fd + 1, &tmpset, NULL, NULL, &tv) == -1 ) {
+               if( errno == EBADF ) return -1;
+       }
+
+       return 0;
+}
+
diff --git a/trunk/src/libopensrf/xml_utils.c b/trunk/src/libopensrf/xml_utils.c
new file mode 100644 (file)
index 0000000..2bdb4ce
--- /dev/null
@@ -0,0 +1,123 @@
+#include <opensrf/xml_utils.h>
+
+/* helper function */
+static jsonObject* _xmlToJSON(xmlNodePtr node, jsonObject*);
+
+void recurse_doc( xmlNodePtr node ) {
+       if( node == NULL ) return;
+       printf("Recurse: %s =>  %s", node->name, node->content );
+       xmlNodePtr t = node->children;
+       while(t) {
+               recurse_doc(t);
+               t = t->next;
+       }
+}
+
+
+
+jsonObject* xmlDocToJSON(xmlDocPtr doc) {
+       if(!doc) return NULL;
+       return _xmlToJSON(xmlDocGetRootElement(doc), NULL);
+}
+
+static jsonObject* _xmlToJSON(xmlNodePtr node, jsonObject* obj) {
+
+       if(!node) return NULL;
+       if(xmlIsBlankNode(node)) return NULL;
+       if(obj == NULL) obj = jsonNewObject(NULL);
+
+       if(node->type == XML_TEXT_NODE) {
+               jsonObjectSetString(obj, (char*) node->content);        
+
+       } else if(node->type == XML_ELEMENT_NODE || node->type == XML_ATTRIBUTE_NODE ) {
+
+               jsonObject* new_obj = jsonNewObject(NULL);
+
+               jsonObject* old;
+
+               /* do the duplicate node / array shuffle */
+               if( (old = jsonObjectGetKey(obj, (char*) node->name)) ) {
+                       if(old->type == JSON_ARRAY ) {
+                               jsonObjectPush(old, new_obj);
+                       } else {
+                               jsonObject* arr = jsonNewObject(NULL);
+                               jsonObjectPush(arr, jsonObjectClone(old));
+                               jsonObjectPush(arr, new_obj);
+                               jsonObjectSetKey(obj, (char*) node->name, arr);
+                       }
+               } else {
+                       jsonObjectSetKey(obj, (char*) node->name, new_obj);
+               }
+
+               xmlNodePtr child = node->children;
+                if (child) { // at least one...
+                       if (child != node->last) { // more than one -- ignore TEXT nodes
+                               while(child) {
+                                       if (child->type != XML_TEXT_NODE) _xmlToJSON(child, new_obj);
+                                       child = child->next;
+                               }
+                       } else {
+                               _xmlToJSON(child, new_obj);
+                       }
+                }
+       }       
+
+       return obj;
+}
+
+
+char* xmlDocToString(xmlDocPtr doc, int full) {
+
+       if(!doc) return NULL;
+
+       char* xml;
+
+       if(full) {
+
+               xmlChar* xmlbuf;
+               int size;
+               xmlDocDumpMemory(doc, &xmlbuf, &size);
+               xml = strdup((char*) (xmlbuf));
+               xmlFree(xmlbuf);
+               return xml;
+
+       } else {
+
+               xmlBufferPtr xmlbuf = xmlBufferCreate();
+               xmlNodeDump( xmlbuf, doc, xmlDocGetRootElement(doc), 0, 0);
+               xml = strdup((char*) (xmlBufferContent(xmlbuf)));
+               xmlBufferFree(xmlbuf);
+               return xml;
+
+       }
+}
+
+
+
+
+char* xmlSaxAttr( const xmlChar** atts, const char* name ) {
+       if( atts && name ) {
+               int i;
+               for(i = 0; (atts[i] != NULL); i++) {
+                       if(!strcmp((char*) atts[i], name)) {
+                               if(atts[++i]) return (char*) atts[i];
+                       }
+               }
+       }
+       return NULL;
+}
+
+
+int xmlAddAttrs( xmlNodePtr node, const xmlChar** atts ) {
+       if( node && atts ) {
+               int i;
+               for(i = 0; (atts[i] != NULL); i++) {
+                       if(atts[i+1]) {
+                               xmlSetProp(node, atts[i], atts[i+1]);
+                               i++;
+                       }
+               }
+       }
+       return 0;
+}
+
diff --git a/trunk/src/perl/Changes b/trunk/src/perl/Changes
new file mode 100644 (file)
index 0000000..c12049f
--- /dev/null
@@ -0,0 +1,5 @@
+Revision history for OpenSRF
+
+0.9     2006/07
+        First version, released on an unsuspecting world.
+
diff --git a/trunk/src/perl/MANIFEST b/trunk/src/perl/MANIFEST
new file mode 100644 (file)
index 0000000..931f8b0
--- /dev/null
@@ -0,0 +1,40 @@
+Changes
+MANIFEST
+Makefile.PL
+README
+lib/OpenSRF.pm
+lib/OpenSRF/Application.pm
+lib/OpenSRF/Application/Client.pm
+lib/OpenSRF/Application/Persist.pm
+lib/OpenSRF/Application/Settings.pm
+lib/OpenSRF/Application/Demo/Math.pm
+lib/OpenSRF/Application/Demo/MathDB.pm
+lib/OpenSRF/AppSession.pm
+lib/OpenSRF/DomainObject/oilsMessage.pm
+lib/OpenSRF/DomainObject/oilsMethod.pm
+lib/OpenSRF/DomainObject/oilsResponse.pm
+lib/OpenSRF/EX.pm
+lib/OpenSRF/MultiSession.pm
+lib/OpenSRF/System.pm
+lib/OpenSRF/Transport.pm
+lib/OpenSRF/Transport/Listener.pm
+lib/OpenSRF/Transport/PeerHandle.pm
+lib/OpenSRF/Transport/SlimJabber.pm
+lib/OpenSRF/Transport/SlimJabber/Client.pm
+lib/OpenSRF/Transport/SlimJabber/Inbound.pm
+lib/OpenSRF/Transport/SlimJabber/MessageWrapper.pm
+lib/OpenSRF/Transport/SlimJabber/PeerConnection.pm
+lib/OpenSRF/Transport/SlimJabber/XMPPMessage.pm
+lib/OpenSRF/Transport/SlimJabber/XMPPReader.pm
+lib/OpenSRF/UnixServer.pm
+lib/OpenSRF/Utils.pm
+lib/OpenSRF/Utils/Cache.pm
+lib/OpenSRF/Utils/Config.pm
+lib/OpenSRF/Utils/JSON.pm
+lib/OpenSRF/Utils/Logger.pm
+lib/OpenSRF/Utils/LogServer.pm
+lib/OpenSRF/Utils/SettingsClient.pm
+lib/OpenSRF/Utils/SettingsParser.pm
+t/00-load.t
+t/pod-coverage.t
+t/pod.t
diff --git a/trunk/src/perl/Makefile.PL b/trunk/src/perl/Makefile.PL
new file mode 100644 (file)
index 0000000..69bd7d5
--- /dev/null
@@ -0,0 +1,28 @@
+use inc::Module::Install;
+
+# Define metadata
+name           'OpenSRF';
+all_from       'lib/OpenSRF.pm';
+license        'perl';
+
+# Specific dependencies
+requires 'Cache::Memcached' => 0;
+requires 'Data::Dumper'     => 0;
+requires 'DateTime'         => 0;
+requires 'DBI'              => 0;
+requires 'Digest::MD5'      => 0;
+requires 'Errno'            => 0;
+requires 'Error'            => 0;
+requires 'FreezeThaw'       => 0;
+requires 'IO'               => 0;
+requires 'JSON::XS'         => 0;
+requires 'Net::Domain'      => 0;
+requires 'Net::Server'      => 0;
+requires 'Time::HiRes'      => 0;
+requires 'Time::Local'      => 0;
+requires 'UNIVERSAL::require' => 0;
+requires 'Unix::Syslog'       => 0;
+requires 'XML::LibXML'        => 0;
+requires 'DateTime::Format::ISO8601' => 0;
+
+WriteAll;
diff --git a/trunk/src/perl/README b/trunk/src/perl/README
new file mode 100644 (file)
index 0000000..b7015e5
--- /dev/null
@@ -0,0 +1,33 @@
+OpenSRF
+
+OpenSRF (Open OpenSRF (Open Scalable Request Framework) is a core
+subsystem of the Evergreen ILS.
+
+INSTALLATION
+
+To install this module, run the following commands:
+
+       perl Makefile.PL
+       make
+       make test
+       make install
+
+SUPPORT AND DOCUMENTATION
+
+After installing, you can find documentation for this module with the
+perldoc command.
+
+    perldoc OpenSRF
+
+You can also look for information at:
+
+    http://svn.open-ils.org/trac/OpenSRF
+
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2008 Equinox Software, Inc.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
diff --git a/trunk/src/perl/inc/Module/Install.pm b/trunk/src/perl/inc/Module/Install.pm
new file mode 100644 (file)
index 0000000..87bed66
--- /dev/null
@@ -0,0 +1,364 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+BEGIN {
+       require 5.004;
+}
+use strict 'vars';
+
+use vars qw{$VERSION};
+BEGIN {
+       # All Module::Install core packages now require synchronised versions.
+       # This will be used to ensure we don't accidentally load old or
+       # different versions of modules.
+       # This is not enforced yet, but will be some time in the next few
+       # releases once we can make sure it won't clash with custom
+       # Module::Install extensions.
+       $VERSION = '0.76';
+
+       *inc::Module::Install::VERSION = *VERSION;
+       @inc::Module::Install::ISA     = __PACKAGE__;
+
+}
+
+
+
+
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) { die <<"END_DIE" }
+
+Please invoke ${\__PACKAGE__} with:
+
+       use inc::${\__PACKAGE__};
+
+not:
+
+       use ${\__PACKAGE__};
+
+END_DIE
+
+
+
+
+
+# If the script that is loading Module::Install is from the future,
+# then make will detect this and cause it to re-run over and over
+# again. This is bad. Rather than taking action to touch it (which
+# is unreliable on some platforms and requires write permissions)
+# for now we should catch this and refuse to run.
+if ( -f $0 and (stat($0))[9] > time ) { die <<"END_DIE" }
+
+Your installer $0 has a modification time in the future.
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+
+
+
+
+
+# Build.PL was formerly supported, but no longer is due to excessive
+# difficulty in implementing every single feature twice.
+if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
+
+Module::Install no longer supports Build.PL.
+
+It was impossible to maintain duel backends, and has been deprecated.
+
+Please remove all Build.PL files and only use the Makefile.PL installer.
+
+END_DIE
+
+
+
+
+
+# To save some more typing in Module::Install installers, every...
+# use inc::Module::Install
+# ...also acts as an implicit use strict.
+$^H |= strict::bits(qw(refs subs vars));
+
+
+
+
+
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+sub autoload {
+       my $self = shift;
+       my $who  = $self->_caller;
+       my $cwd  = Cwd::cwd();
+       my $sym  = "${who}::AUTOLOAD";
+       $sym->{$cwd} = sub {
+               my $pwd = Cwd::cwd();
+               if ( my $code = $sym->{$pwd} ) {
+                       # delegate back to parent dirs
+                       goto &$code unless $cwd eq $pwd;
+               }
+               $$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+               unshift @_, ( $self, $1 );
+               goto &{$self->can('call')} unless uc($1) eq $1;
+       };
+}
+
+sub import {
+       my $class = shift;
+       my $self  = $class->new(@_);
+       my $who   = $self->_caller;
+
+       unless ( -f $self->{file} ) {
+               require "$self->{path}/$self->{dispatch}.pm";
+               File::Path::mkpath("$self->{prefix}/$self->{author}");
+               $self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+               $self->{admin}->init;
+               @_ = ($class, _self => $self);
+               goto &{"$self->{name}::import"};
+       }
+
+       *{"${who}::AUTOLOAD"} = $self->autoload;
+       $self->preload;
+
+       # Unregister loader and worker packages so subdirs can use them again
+       delete $INC{"$self->{file}"};
+       delete $INC{"$self->{path}.pm"};
+
+       return 1;
+}
+
+sub preload {
+       my $self = shift;
+       unless ( $self->{extensions} ) {
+               $self->load_extensions(
+                       "$self->{prefix}/$self->{path}", $self
+               );
+       }
+
+       my @exts = @{$self->{extensions}};
+       unless ( @exts ) {
+               my $admin = $self->{admin};
+               @exts = $admin->load_all_extensions;
+       }
+
+       my %seen;
+       foreach my $obj ( @exts ) {
+               while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+                       next unless $obj->can($method);
+                       next if $method =~ /^_/;
+                       next if $method eq uc($method);
+                       $seen{$method}++;
+               }
+       }
+
+       my $who = $self->_caller;
+       foreach my $name ( sort keys %seen ) {
+               *{"${who}::$name"} = sub {
+                       ${"${who}::AUTOLOAD"} = "${who}::$name";
+                       goto &{"${who}::AUTOLOAD"};
+               };
+       }
+}
+
+sub new {
+       my ($class, %args) = @_;
+
+       # ignore the prefix on extension modules built from top level.
+       my $base_path = Cwd::abs_path($FindBin::Bin);
+       unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+               delete $args{prefix};
+       }
+
+       return $args{_self} if $args{_self};
+
+       $args{dispatch} ||= 'Admin';
+       $args{prefix}   ||= 'inc';
+       $args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+       $args{bundle}   ||= 'inc/BUNDLES';
+       $args{base}     ||= $base_path;
+       $class =~ s/^\Q$args{prefix}\E:://;
+       $args{name}     ||= $class;
+       $args{version}  ||= $class->VERSION;
+       unless ( $args{path} ) {
+               $args{path}  = $args{name};
+               $args{path}  =~ s!::!/!g;
+       }
+       $args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+       $args{wrote}      = 0;
+
+       bless( \%args, $class );
+}
+
+sub call {
+       my ($self, $method) = @_;
+       my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+       goto &{$obj->can($method)};
+}
+
+sub load {
+       my ($self, $method) = @_;
+
+       $self->load_extensions(
+               "$self->{prefix}/$self->{path}", $self
+       ) unless $self->{extensions};
+
+       foreach my $obj (@{$self->{extensions}}) {
+               return $obj if $obj->can($method);
+       }
+
+       my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+       my $obj = $admin->load($method, 1);
+       push @{$self->{extensions}}, $obj;
+
+       $obj;
+}
+
+sub load_extensions {
+       my ($self, $path, $top) = @_;
+
+       unless ( grep { lc $_ eq lc $self->{prefix} } @INC ) {
+               unshift @INC, $self->{prefix};
+       }
+
+       foreach my $rv ( $self->find_extensions($path) ) {
+               my ($file, $pkg) = @{$rv};
+               next if $self->{pathnames}{$pkg};
+
+               local $@;
+               my $new = eval { require $file; $pkg->can('new') };
+               unless ( $new ) {
+                       warn $@ if $@;
+                       next;
+               }
+               $self->{pathnames}{$pkg} = delete $INC{$file};
+               push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+       }
+
+       $self->{extensions} ||= [];
+}
+
+sub find_extensions {
+       my ($self, $path) = @_;
+
+       my @found;
+       File::Find::find( sub {
+               my $file = $File::Find::name;
+               return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+               my $subpath = $1;
+               return if lc($subpath) eq lc($self->{dispatch});
+
+               $file = "$self->{path}/$subpath.pm";
+               my $pkg = "$self->{name}::$subpath";
+               $pkg =~ s!/!::!g;
+
+               # If we have a mixed-case package name, assume case has been preserved
+               # correctly.  Otherwise, root through the file to locate the case-preserved
+               # version of the package name.
+               if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+                       my $content = Module::Install::_read($subpath . '.pm');
+                       my $in_pod  = 0;
+                       foreach ( split //, $content ) {
+                               $in_pod = 1 if /^=\w/;
+                               $in_pod = 0 if /^=cut/;
+                               next if ($in_pod || /^=cut/);  # skip pod text
+                               next if /^\s*#/;               # and comments
+                               if ( m/^\s*package\s+($pkg)\s*;/i ) {
+                                       $pkg = $1;
+                                       last;
+                               }
+                       }
+               }
+
+               push @found, [ $file, $pkg ];
+       }, $path ) if -d $path;
+
+       @found;
+}
+
+
+
+
+
+#####################################################################
+# Utility Functions
+
+sub _caller {
+       my $depth = 0;
+       my $call  = caller($depth);
+       while ( $call eq __PACKAGE__ ) {
+               $depth++;
+               $call = caller($depth);
+       }
+       return $call;
+}
+
+sub _read {
+       local *FH;
+       open FH, "< $_[0]" or die "open($_[0]): $!";
+       my $str = do { local $/; <FH> };
+       close FH or die "close($_[0]): $!";
+       return $str;
+}
+
+sub _write {
+       local *FH;
+       open FH, "> $_[0]" or die "open($_[0]): $!";
+       foreach ( 1 .. $#_ ) { print FH $_[$_] or die "print($_[0]): $!" }
+       close FH or die "close($_[0]): $!";
+}
+
+sub _version ($) {
+       my $s = shift || 0;
+          $s =~ s/^(\d+)\.?//;
+       my $l = $1 || 0;
+       my @v = map { $_ . '0' x (3 - length $_) } $s =~ /(\d{1,3})\D?/g;
+          $l = $l . '.' . join '', @v if @v;
+       return $l + 0;
+}
+
+# Cloned from Params::Util::_CLASS
+sub _CLASS ($) {
+       (
+               defined $_[0]
+               and
+               ! ref $_[0]
+               and
+               $_[0] =~ m/^[^\W\d]\w*(?:::\w+)*$/s
+       ) ? $_[0] : undef;
+}
+
+1;
+
+# Copyright 2008 Adam Kennedy.
diff --git a/trunk/src/perl/inc/Module/Install/Base.pm b/trunk/src/perl/inc/Module/Install/Base.pm
new file mode 100644 (file)
index 0000000..76b32f8
--- /dev/null
@@ -0,0 +1,72 @@
+#line 1
+package Module::Install::Base;
+
+$VERSION = '0.76';
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+       my $w = $SIG{__WARN__};
+       $SIG{__WARN__} = sub { $w };
+}
+
+### This is the ONLY module that shouldn't have strict on
+# use strict;
+
+#line 41
+
+sub new {
+    my ($class, %args) = @_;
+
+    foreach my $method ( qw(call load) ) {
+        *{"$class\::$method"} = sub {
+            shift()->_top->$method(@_);
+        } unless defined &{"$class\::$method"};
+    }
+
+    bless( \%args, $class );
+}
+
+#line 61
+
+sub AUTOLOAD {
+    my $self = shift;
+    local $@;
+    my $autoload = eval { $self->_top->autoload } or return;
+    goto &$autoload;
+}
+
+#line 76
+
+sub _top { $_[0]->{_top} }
+
+#line 89
+
+sub admin {
+    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+}
+
+#line 101
+
+sub is_admin {
+    $_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $Fake;
+sub new { $Fake ||= bless(\@_, $_[0]) }
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+       $SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 146
diff --git a/trunk/src/perl/inc/Module/Install/Can.pm b/trunk/src/perl/inc/Module/Install/Can.pm
new file mode 100644 (file)
index 0000000..dd9a81c
--- /dev/null
@@ -0,0 +1,82 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Module::Install::Base;
+use Config ();
+### This adds a 5.005 Perl version dependency.
+### This is a bug and will be fixed.
+use File::Spec ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+       $VERSION = '0.76';
+       $ISCORE  = 1;
+       @ISA     = qw{Module::Install::Base};
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+       my ($self, $mod, $ver) = @_;
+       $mod =~ s{::|\\}{/}g;
+       $mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+       my $pkg = $mod;
+       $pkg =~ s{/}{::}g;
+       $pkg =~ s{\.pm$}{}i;
+
+       local $@;
+       eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+       my ($self, $cmd) = @_;
+
+       my $_cmd = $cmd;
+       return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+       for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+               my $abs = File::Spec->catfile($dir, $_[1]);
+               return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+       }
+
+       return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+       my $self   = shift;
+       my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+       # $Config{cc} may contain args; try to find out the program part
+       while (@chunks) {
+               return $self->can_run("@chunks") || (pop(@chunks), next);
+       }
+
+       return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+       require ExtUtils::MM_Cygwin;
+       require ExtUtils::MM_Win32;
+       if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+               *ExtUtils::MM_Cygwin::maybe_command = sub {
+                       my ($self, $file) = @_;
+                       if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+                               ExtUtils::MM_Win32->maybe_command($file);
+                       } else {
+                               ExtUtils::MM_Unix->maybe_command($file);
+                       }
+               }
+       }
+}
+
+1;
+
+__END__
+
+#line 157
diff --git a/trunk/src/perl/inc/Module/Install/Fetch.pm b/trunk/src/perl/inc/Module/Install/Fetch.pm
new file mode 100644 (file)
index 0000000..58df9ff
--- /dev/null
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+       $VERSION = '0.76';
+       $ISCORE  = 1;
+       @ISA     = qw{Module::Install::Base};
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) = 
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) = 
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous@example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;
diff --git a/trunk/src/perl/inc/Module/Install/Makefile.pm b/trunk/src/perl/inc/Module/Install/Makefile.pm
new file mode 100644 (file)
index 0000000..05af6ef
--- /dev/null
@@ -0,0 +1,251 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use Module::Install::Base;
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+       $VERSION = '0.76';
+       $ISCORE  = 1;
+       @ISA     = qw{Module::Install::Base};
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+       shift;
+
+       # Infinite loop protection
+       my @c = caller();
+       if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+               die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+       }
+
+       # In automated testing, always use defaults
+       if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+               local $ENV{PERL_MM_USE_DEFAULT} = 1;
+               goto &ExtUtils::MakeMaker::prompt;
+       } else {
+               goto &ExtUtils::MakeMaker::prompt;
+       }
+}
+
+sub makemaker_args {
+       my $self = shift;
+       my $args = ( $self->{makemaker_args} ||= {} );
+       %$args = ( %$args, @_ );
+       return $args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+       my $self = sShift;
+       my $name = shift;
+       my $args = $self->makemaker_args;
+       $args->{name} = defined $args->{$name}
+               ? join( ' ', $args->{name}, @_ )
+               : join( ' ', @_ );
+}
+
+sub build_subdirs {
+       my $self    = shift;
+       my $subdirs = $self->makemaker_args->{DIR} ||= [];
+       for my $subdir (@_) {
+               push @$subdirs, $subdir;
+       }
+}
+
+sub clean_files {
+       my $self  = shift;
+       my $clean = $self->makemaker_args->{clean} ||= {};
+         %$clean = (
+               %$clean, 
+               FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
+       );
+}
+
+sub realclean_files {
+       my $self      = shift;
+       my $realclean = $self->makemaker_args->{realclean} ||= {};
+         %$realclean = (
+               %$realclean, 
+               FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
+       );
+}
+
+sub libs {
+       my $self = shift;
+       my $libs = ref $_[0] ? shift : [ shift ];
+       $self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+       my $self = shift;
+       $self->makemaker_args( INC => shift );
+}
+
+my %test_dir = ();
+
+sub _wanted_t {
+       /\.t$/ and -f $_ and $test_dir{$File::Find::dir} = 1;
+}
+
+sub tests_recursive {
+       my $self = shift;
+       if ( $self->tests ) {
+               die "tests_recursive will not work if tests are already defined";
+       }
+       my $dir = shift || 't';
+       unless ( -d $dir ) {
+               die "tests_recursive dir '$dir' does not exist";
+       }
+       %test_dir = ();
+       require File::Find;
+       File::Find::find( \&_wanted_t, $dir );
+       $self->tests( join ' ', map { "$_/*.t" } sort keys %test_dir );
+}
+
+sub write {
+       my $self = shift;
+       die "&Makefile->write() takes no arguments\n" if @_;
+
+       # Make sure we have a new enough
+       require ExtUtils::MakeMaker;
+
+       # MakeMaker can complain about module versions that include
+       # an underscore, even though its own version may contain one!
+       # Hence the funny regexp to get rid of it.  See RT #35800
+       # for details.
+
+       $self->configure_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/ );
+
+       # Generate the 
+       my $args = $self->makemaker_args;
+       $args->{DISTNAME} = $self->name;
+       $args->{NAME}     = $self->module_name || $self->name;
+       $args->{VERSION}  = $self->version;
+       $args->{NAME}     =~ s/-/::/g;
+       if ( $self->tests ) {
+               $args->{test} = { TESTS => $self->tests };
+       }
+       if ($] >= 5.005) {
+               $args->{ABSTRACT} = $self->abstract;
+               $args->{AUTHOR}   = $self->author;
+       }
+       if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+               $args->{NO_META} = 1;
+       }
+       if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+               $args->{SIGN} = 1;
+       }
+       unless ( $self->is_admin ) {
+               delete $args->{SIGN};
+       }
+
+       # merge both kinds of requires into prereq_pm
+       my $prereq = ($args->{PREREQ_PM} ||= {});
+       %$prereq = ( %$prereq,
+               map { @$_ }
+               map { @$_ }
+               grep $_,
+               ($self->configure_requires, $self->build_requires, $self->requires)
+       );
+
+       # Remove any reference to perl, PREREQ_PM doesn't support it
+       delete $args->{PREREQ_PM}->{perl};
+
+       # merge both kinds of requires into prereq_pm
+       my $subdirs = ($args->{DIR} ||= []);
+       if ($self->bundles) {
+               foreach my $bundle (@{ $self->bundles }) {
+                       my ($file, $dir) = @$bundle;
+                       push @$subdirs, $dir if -d $dir;
+                       delete $prereq->{$file};
+               }
+       }
+
+       if ( my $perl_version = $self->perl_version ) {
+               eval "use $perl_version; 1"
+                       or die "ERROR: perl: Version $] is installed, "
+                       . "but we need version >= $perl_version";
+       }
+
+       $args->{INSTALLDIRS} = $self->installdirs;
+
+       my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+
+       my $user_preop = delete $args{dist}->{PREOP};
+       if (my $preop = $self->admin->preop($user_preop)) {
+               $args{dist} = $preop;
+       }
+
+       my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+       $self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+       my $self          = shift;
+       my $makefile_name = shift;
+       my $top_class     = ref($self->_top) || '';
+       my $top_version   = $self->_top->VERSION || '';
+
+       my $preamble = $self->preamble 
+               ? "# Preamble by $top_class $top_version\n"
+                       . $self->preamble
+               : '';
+       my $postamble = "# Postamble by $top_class $top_version\n"
+               . ($self->postamble || '');
+
+       local *MAKEFILE;
+       open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+       my $makefile = do { local $/; <MAKEFILE> };
+       close MAKEFILE or die $!;
+
+       $makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+       $makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+       $makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+       $makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+       $makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+       # Module::Install will never be used to build the Core Perl
+       # Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+       # PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+       $makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+       #$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+       # Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+       $makefile =~ s/(\"?)-I\$\(PERL_LIB\)\1//g;
+
+       # XXX - This is currently unused; not sure if it breaks other MM-users
+       # $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+       open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+       print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+       close MAKEFILE  or die $!;
+
+       1;
+}
+
+sub preamble {
+       my ($self, $text) = @_;
+       $self->{preamble} = $text . $self->{preamble} if defined $text;
+       $self->{preamble};
+}
+
+sub postamble {
+       my ($self, $text) = @_;
+       $self->{postamble} ||= $self->admin->postamble;
+       $self->{postamble} .= $text if defined $text;
+       $self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 377
diff --git a/trunk/src/perl/inc/Module/Install/Metadata.pm b/trunk/src/perl/inc/Module/Install/Metadata.pm
new file mode 100644 (file)
index 0000000..90175f0
--- /dev/null
@@ -0,0 +1,487 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+       $VERSION = '0.76';
+       $ISCORE  = 1;
+       @ISA     = qw{Module::Install::Base};
+}
+
+my @scalar_keys = qw{
+       name
+       module_name
+       abstract
+       author
+       version
+       distribution_type
+       tests
+       installdirs
+};
+
+my @tuple_keys = qw{
+       configure_requires
+       build_requires
+       requires
+       recommends
+       bundles
+       resources
+};
+
+my @resource_keys = qw{
+       homepage
+       bugtracker
+       repository
+};
+
+sub Meta              { shift          }
+sub Meta_ScalarKeys   { @scalar_keys   }
+sub Meta_TupleKeys    { @tuple_keys    }
+sub Meta_ResourceKeys { @resource_keys }
+
+foreach my $key ( @scalar_keys ) {
+       *$key = sub {
+               my $self = shift;
+               return $self->{values}{$key} if defined wantarray and !@_;
+               $self->{values}{$key} = shift;
+               return $self;
+       };
+}
+
+foreach my $key ( @resource_keys ) {
+       *$key = sub {
+               my $self = shift;
+               unless ( @_ ) {
+                       return () unless $self->{values}{resources};
+                       return map  { $_->[1] }
+                              grep { $_->[0] eq $key }
+                              @{ $self->{values}{resources} };
+               }
+               return $self->{values}{resources}{$key} unless @_;
+               my $uri = shift or die(
+                       "Did not provide a value to $key()"
+               );
+               $self->resources( $key => $uri );
+               return 1;
+       };
+}
+
+sub requires {
+       my $self = shift;
+       while ( @_ ) {
+               my $module  = shift or last;
+               my $version = shift || 0;
+               push @{ $self->{values}{requires} }, [ $module, $version ];
+       }
+       $self->{values}{requires};
+}
+
+sub build_requires {
+       my $self = shift;
+       while ( @_ ) {
+               my $module  = shift or last;
+               my $version = shift || 0;
+               push @{ $self->{values}{build_requires} }, [ $module, $version ];
+       }
+       $self->{values}{build_requires};
+}
+
+sub configure_requires {
+       my $self = shift;
+       while ( @_ ) {
+               my $module  = shift or last;
+               my $version = shift || 0;
+               push @{ $self->{values}{configure_requires} }, [ $module, $version ];
+       }
+       $self->{values}{configure_requires};
+}
+
+sub recommends {
+       my $self = shift;
+       while ( @_ ) {
+               my $module  = shift or last;
+               my $version = shift || 0;
+               push @{ $self->{values}{recommends} }, [ $module, $version ];
+       }
+       $self->{values}{recommends};
+}
+
+sub bundles {
+       my $self = shift;
+       while ( @_ ) {
+               my $module  = shift or last;
+               my $version = shift || 0;
+               push @{ $self->{values}{bundles} }, [ $module, $version ];
+       }
+       $self->{values}{bundles};
+}
+
+# Resource handling
+my %lc_resource = map { $_ => 1 } qw{
+       homepage
+       license
+       bugtracker
+       repository
+};
+
+sub resources {
+       my $self = shift;
+       while ( @_ ) {
+               my $name  = shift or last;
+               my $value = shift or next;
+               if ( $name eq lc $name and ! $lc_resource{$name} ) {
+                       die("Unsupported reserved lowercase resource '$name'");
+               }
+               $self->{values}{resources} ||= [];
+               push @{ $self->{values}{resources} }, [ $name, $value ];
+       }
+       $self->{values}{resources};
+}
+
+# Aliases for build_requires that will have alternative
+# meanings in some future version of META.yml.
+sub test_requires      { shift->build_requires(@_) }
+sub install_requires   { shift->build_requires(@_) }
+
+# Aliases for installdirs options
+sub install_as_core    { $_[0]->installdirs('perl')   }
+sub install_as_cpan    { $_[0]->installdirs('site')   }
+sub install_as_site    { $_[0]->installdirs('site')   }
+sub install_as_vendor  { $_[0]->installdirs('vendor') }
+
+sub sign {
+       my $self = shift;
+       return $self->{values}{sign} if defined wantarray and ! @_;
+       $self->{values}{sign} = ( @_ ? $_[0] : 1 );
+       return $self;
+}
+
+sub dynamic_config {
+       my $self = shift;
+       unless ( @_ ) {
+               warn "You MUST provide an explicit true/false value to dynamic_config\n";
+               return $self;
+       }
+       $self->{values}{dynamic_config} = $_[0] ? 1 : 0;
+       return 1;
+}
+
+sub perl_version {
+       my $self = shift;
+       return $self->{values}{perl_version} unless @_;
+       my $version = shift or die(
+               "Did not provide a value to perl_version()"
+       );
+       $version =~ s/_.+$//;
+       $version = $version + 0; # Numify
+       unless ( $version >= 5.005 ) {
+               die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n";
+       }
+       $self->{values}{perl_version} = $version;
+       return 1;
+}
+
+sub license {
+       my $self = shift;
+       return $self->{values}{license} unless @_;
+       my $license = shift or die(
+               'Did not provide a value to license()'
+       );
+       $self->{values}{license} = $license;
+
+       # Automatically fill in license URLs
+       if ( $license eq 'perl' ) {
+               $self->resources( license => 'http://dev.perl.org/licenses/' );
+       }
+
+       return 1;
+}
+
+sub all_from {
+       my ( $self, $file ) = @_;
+
+       unless ( defined($file) ) {
+               my $name = $self->name or die(
+                       "all_from called with no args without setting name() first"
+               );
+               $file = join('/', 'lib', split(/-/, $name)) . '.pm';
+               $file =~ s{.*/}{} unless -e $file;
+               unless ( -e $file ) {
+                       die("all_from cannot find $file from $name");
+               }
+       }
+
+       # Some methods pull from POD instead of code.
+       # If there is a matching .pod, use that instead
+       my $pod = $file;
+       $pod =~ s/\.pm$/.pod/i;
+       $pod = $file unless -e $pod;
+
+       # Pull the different values
+       $self->name_from($file)         unless $self->name;
+       $self->version_from($file)      unless $self->version;
+       $self->perl_version_from($file) unless $self->perl_version;
+       $self->author_from($pod)        unless $self->author;
+       $self->license_from($pod)       unless $self->license;
+       $self->abstract_from($pod)      unless $self->abstract;
+
+       return 1;
+}
+
+sub provides {
+       my $self     = shift;
+       my $provides = ( $self->{values}{provides} ||= {} );
+       %$provides = (%$provides, @_) if @_;
+       return $provides;
+}
+
+sub auto_provides {
+       my $self = shift;
+       return $self unless $self->is_admin;
+       unless (-e 'MANIFEST') {
+               warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+               return $self;
+       }
+       # Avoid spurious warnings as we are not checking manifest here.
+       local $SIG{__WARN__} = sub {1};
+       require ExtUtils::Manifest;
+       local *ExtUtils::Manifest::manicheck = sub { return };
+
+       require Module::Build;
+       my $build = Module::Build->new(
+               dist_name    => $self->name,
+               dist_version => $self->version,
+               license      => $self->license,
+       );
+       $self->provides( %{ $build->find_dist_packages || {} } );
+}
+
+sub feature {
+       my $self     = shift;
+       my $name     = shift;
+       my $features = ( $self->{values}{features} ||= [] );
+       my $mods;
+
+       if ( @_ == 1 and ref( $_[0] ) ) {
+               # The user used ->feature like ->features by passing in the second
+               # argument as a reference.  Accomodate for that.
+               $mods = $_[0];
+       } else {
+               $mods = \@_;
+       }
+
+       my $count = 0;
+       push @$features, (
+               $name => [
+                       map {
+                               ref($_) ? ( ref($_) eq 'HASH' ) ? %$_ : @$_ : $_
+                       } @$mods
+               ]
+       );
+
+       return @$features;
+}
+
+sub features {
+       my $self = shift;
+       while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+               $self->feature( $name, @$mods );
+       }
+       return $self->{values}{features}
+               ? @{ $self->{values}{features} }
+               : ();
+}
+
+sub no_index {
+       my $self = shift;
+       my $type = shift;
+       push @{ $self->{values}{no_index}{$type} }, @_ if $type;
+       return $self->{values}{no_index};
+}
+
+sub read {
+       my $self = shift;
+       $self->include_deps( 'YAML::Tiny', 0 );
+
+       require YAML::Tiny;
+       my $data = YAML::Tiny::LoadFile('META.yml');
+
+       # Call methods explicitly in case user has already set some values.
+       while ( my ( $key, $value ) = each %$data ) {
+               next unless $self->can($key);
+               if ( ref $value eq 'HASH' ) {
+                       while ( my ( $module, $version ) = each %$value ) {
+                               $self->can($key)->($self, $module => $version );
+                       }
+               } else {
+                       $self->can($key)->($self, $value);
+               }
+       }
+       return $self;
+}
+
+sub write {
+       my $self = shift;
+       return $self unless $self->is_admin;
+       $self->admin->write_meta;
+       return $self;
+}
+
+sub version_from {
+       require ExtUtils::MM_Unix;
+       my ( $self, $file ) = @_;
+       $self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+       require ExtUtils::MM_Unix;
+       my ( $self, $file ) = @_;
+       $self->abstract(
+               bless(
+                       { DISTNAME => $self->name },
+                       'ExtUtils::MM_Unix'
+               )->parse_abstract($file)
+        );
+}
+
+# Add both distribution and module name
+sub name_from {
+       my ($self, $file) = @_;
+       if (
+               Module::Install::_read($file) =~ m/
+               ^ \s*
+               package \s*
+               ([\w:]+)
+               \s* ;
+               /ixms
+       ) {
+               my ($name, $module_name) = ($1, $1);
+               $name =~ s{::}{-}g;
+               $self->name($name);
+               unless ( $self->module_name ) {
+                       $self->module_name($module_name);
+               }
+       } else {
+               die("Cannot determine name from $file\n");
+       }
+}
+
+sub perl_version_from {
+       my $self = shift;
+       if (
+               Module::Install::_read($_[0]) =~ m/
+               ^
+               (?:use|require) \s*
+               v?
+               ([\d_\.]+)
+               \s* ;
+               /ixms
+       ) {
+               my $perl_version = $1;
+               $perl_version =~ s{_}{}g;
+               $self->perl_version($perl_version);
+       } else {
+               warn "Cannot determine perl version info from $_[0]\n";
+               return;
+       }
+}
+
+sub author_from {
+       my $self    = shift;
+       my $content = Module::Install::_read($_[0]);
+       if ($content =~ m/
+               =head \d \s+ (?:authors?)\b \s*
+               ([^\n]*)
+               |
+               =head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+               .*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+               ([^\n]*)
+       /ixms) {
+               my $author = $1 || $2;
+               $author =~ s{E<lt>}{<}g;
+               $author =~ s{E<gt>}{>}g;
+               $self->author($author);
+       } else {
+               warn "Cannot determine author info from $_[0]\n";
+       }
+}
+
+sub license_from {
+       my $self = shift;
+       if (
+               Module::Install::_read($_[0]) =~ m/
+               (
+                       =head \d \s+
+                       (?:licen[cs]e|licensing|copyright|legal)\b
+                       .*?
+               )
+               (=head\\d.*|=cut.*|)
+               \z
+       /ixms ) {
+               my $license_text = $1;
+               my @phrases      = (
+                       'under the same (?:terms|license) as perl itself' => 'perl',        1,
+                       'GNU public license'                              => 'gpl',         1,
+                       'GNU lesser public license'                       => 'lgpl',        1,
+                       'BSD license'                                     => 'bsd',         1,
+                       'Artistic license'                                => 'artistic',    1,
+                       'GPL'                                             => 'gpl',         1,
+                       'LGPL'                                            => 'lgpl',        1,
+                       'BSD'                                             => 'bsd',         1,
+                       'Artistic'                                        => 'artistic',    1,
+                       'MIT'                                             => 'mit',         1,
+                       'proprietary'                                     => 'proprietary', 0,
+               );
+               while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) {
+                       $pattern =~ s{\s+}{\\s+}g;
+                       if ( $license_text =~ /\b$pattern\b/i ) {
+                               if ( $osi and $license_text =~ /All rights reserved/i ) {
+                                       print "WARNING: 'All rights reserved' in copyright may invalidate Open Source license.\n";
+                               }
+                               $self->license($license);
+                               return 1;
+                       }
+               }
+       }
+
+       warn "Cannot determine license info from $_[0]\n";
+       return 'unknown';
+}
+
+sub bugtracker_from {
+       my $self    = shift;
+       my $content = Module::Install::_read($_[0]);
+       my @links   = $content =~ m/L\<(http\:\/\/rt\.cpan\.org\/[^>]+)\>/g;
+       unless ( @links ) {
+               warn "Cannot determine bugtracker info from $_[0]\n";
+               return 0;
+       }
+       if ( @links > 1 ) {
+               warn "Found more than on rt.cpan.org link in $_[0]\n";
+               return 0;
+       }
+
+       # Set the bugtracker
+       bugtracker( $links[0] );
+       return 1;
+}
+
+sub install_script {
+       my $self = shift;
+       my $args = $self->makemaker_args;
+       my $exe  = $args->{EXE_FILES} ||= [];
+        foreach ( @_ ) {
+               if ( -f $_ ) {
+                       push @$exe, $_;
+               } elsif ( -d 'script' and -f "script/$_" ) {
+                       push @$exe, "script/$_";
+               } else {
+                       die("Cannot find script '$_'");
+               }
+       }
+}
+
+1;
diff --git a/trunk/src/perl/inc/Module/Install/Win32.pm b/trunk/src/perl/inc/Module/Install/Win32.pm
new file mode 100644 (file)
index 0000000..f890074
--- /dev/null
@@ -0,0 +1,64 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+       $VERSION = '0.76';
+       @ISA     = qw{Module::Install::Base};
+       $ISCORE  = 1;
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+       my $self = shift;
+       $self->load('can_run');
+       $self->load('get_file');
+
+       require Config;
+       return unless (
+               $^O eq 'MSWin32'                     and
+               $Config::Config{make}                and
+               $Config::Config{make} =~ /^nmake\b/i and
+               ! $self->can_run('nmake')
+       );
+
+       print "The required 'nmake' executable not found, fetching it...\n";
+
+       require File::Basename;
+       my $rv = $self->get_file(
+               url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+               ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+               local_dir => File::Basename::dirname($^X),
+               size      => 51928,
+               run       => 'Nmake15.exe /o > nul',
+               check_for => 'Nmake.exe',
+               remove    => 1,
+       );
+
+       die <<'END_MESSAGE' unless $rv;
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+
+}
+
+1;
diff --git a/trunk/src/perl/inc/Module/Install/WriteAll.pm b/trunk/src/perl/inc/Module/Install/WriteAll.pm
new file mode 100644 (file)
index 0000000..a50d31e
--- /dev/null
@@ -0,0 +1,40 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+       $VERSION = '0.76';
+       @ISA     = qw{Module::Install::Base};
+       $ISCORE  = 1;
+}
+
+sub WriteAll {
+       my $self = shift;
+       my %args = (
+               meta        => 1,
+               sign        => 0,
+               inline      => 0,
+               check_nmake => 1,
+               @_,
+       );
+
+       $self->sign(1)                if $args{sign};
+       $self->Meta->write            if $args{meta};
+       $self->admin->WriteAll(%args) if $self->is_admin;
+
+       $self->check_nmake if $args{check_nmake};
+       unless ( $self->makemaker_args->{PL_FILES} ) {
+               $self->makemaker_args( PL_FILES => {} );
+       }
+
+       if ( $args{inline} ) {
+               $self->Inline->write;
+       } else {
+               $self->Makefile->write;
+       }
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF.pm b/trunk/src/perl/lib/OpenSRF.pm
new file mode 100644 (file)
index 0000000..4bb598b
--- /dev/null
@@ -0,0 +1,84 @@
+package OpenSRF;
+
+use strict;
+use vars qw/$AUTOLOAD/;
+
+use Error;
+require UNIVERSAL::require;
+
+# $Revision$
+
+=head1 NAME
+
+OpenSRF - Top level class for OpenSRF perl modules.
+
+=head1 VERSION
+
+Version 0.9.1
+
+=cut
+
+our $VERSION = 0.9.1;
+
+=head1 METHODS
+
+=head2 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
+}
+
+
+
+=head2 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";
+}
+
+=head2 class
+
+Returns the scalar value of its caller.
+
+=cut
+
+sub class { return scalar(caller); }
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/AppSession.pm b/trunk/src/perl/lib/OpenSRF/AppSession.pm
new file mode 100644 (file)
index 0000000..b322665
--- /dev/null
@@ -0,0 +1,1048 @@
+package OpenSRF::AppSession;
+use OpenSRF::DomainObject::oilsMessage;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Transport::PeerHandle;
+use OpenSRF::Utils::JSON;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Config;
+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";
+my $_last_locale = 'en-US';
+
+our %_CACHE;
+our @_RESEND_QUEUE;
+
+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 transport_connected {
+       my $self = shift;
+       if( ! exists $self->{peer_handle} || ! $self->{peer_handle} ) {
+               return 0;
+       }
+       return $self->{peer_handle}->tcp_connected();
+}
+
+sub connected {
+       my $self = shift;
+       return $self->state == 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();
+}
+
+
+# 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;
+
+       warn "Missing args to server_build():\n" .
+               "sess_id: $sess_id, remote_id: $remote_id, service: $service\n" 
+               unless ($sess_id and $remote_id and $service);
+
+       return undef unless ($sess_id and $remote_id and $service);
+
+       if ( my $thingy = $class->find($sess_id) ) {
+               $thingy->remote_id( $remote_id );
+               return $thingy;
+       }
+
+       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]");
+
+               warn "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 );
+
+               return undef;
+       }
+
+       my $config_client = OpenSRF::Utils::SettingsClient->new();
+       my $stateless = $config_client->config_value("apps", $service, "stateless");
+
+       #my $max_requests = $conf->$service->max_requests;
+       my $max_requests        = $config_client->config_value("apps",$service,"max_requests");
+       $logger->debug( "Max Requests for $service is $max_requests", INTERNAL ) if (defined $max_requests);
+
+       $logger->transport( "AppSession creating new session: $sess_id", INTERNAL );
+
+       my $self = bless { recv_queue  => [],
+                          request_queue  => [],
+                          requests  => 0,
+                          session_data  => {},
+                          callbacks  => {},
+                          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,
+                               stateless => $stateless,
+                        } => $class;
+
+       return $_CACHE{$sess_id} = $self;
+}
+
+sub session_data {
+       my $self = shift;
+       my ($name, $datum) = @_;
+
+       $self->{session_data}->{$name} = $datum if (defined $datum);
+       return $self->{session_data}->{$name};
+}
+
+sub service { return shift()->{service}; }
+
+sub continue_request {
+       my $self = shift;
+       $self->{'requests'}++;
+       return 1 if (!$self->{'max_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 session_locale {
+       my( $self, $type ) = @_;
+       if( $type ) {
+        $_last_locale = $type if ($self->endpoint == SERVER);
+               return $self->{'session_locale'} = $type;
+       }
+       return $self->{'session_locale'};
+}
+
+sub last_sent_type {
+       my( $self, $type ) = @_;
+       if( $type ) {
+               return $self->{'last_sent_type'} = $type;
+       }
+       return $self->{'last_sent_type'};
+}
+
+sub get_app_targets {
+       my $app = shift;
+
+       my $conf = OpenSRF::Utils::Config->current;
+       my $router_name = $conf->bootstrap->router_name || 'router';
+       my $domain = $conf->bootstrap->domain;
+       $logger->error("use of <domains/> is deprecated") if $conf->bootstrap->domains;
+
+       unless($router_name and $domain) {
+               throw OpenSRF::EX::Config 
+                       ("Missing router config information 'router_name' and 'domain'");
+       }
+
+    return ("$router_name\@$domain/$app");
+}
+
+sub stateless {
+       my $self = shift;
+       my $state = shift;
+       $self->{stateless} = $state if (defined $state);
+       return $self->{stateless};
+}
+
+# When we're a client and we want to connect to a remote service
+sub create {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $app = shift;
+        my $api_level = shift;
+       my $quiet = shift;
+       my $locale = shift || $_last_locale;
+
+       $api_level = 1 if (!defined($api_level));
+                               
+       $logger->debug( "AppSession creating new client session for $app", DEBUG );
+
+       my $stateless = 0;
+       my $c = OpenSRF::Utils::SettingsClient->new();
+       # we can get an infinite loop if we're grabbing the settings and we
+       # need the settings to grab the settings...
+       if($app ne "opensrf.settings" || $c->has_config()) { 
+               $stateless = $c->config_value("apps", $app, "stateless");
+       }
+
+       my $sess_id = time . rand( $$ );
+       while ( $class->find($sess_id) ) {
+               $sess_id = time . rand( $$ );
+       }
+
+       
+       my ($r_id) = get_app_targets($app);
+
+       my $peer_handle = OpenSRF::Transport::PeerHandle->retrieve("client"); 
+       if( ! $peer_handle ) {
+               $peer_handle = OpenSRF::Transport::PeerHandle->retrieve("system_client");
+       }
+
+       my $self = bless { app_name    => $app,
+                          request_queue  => [],
+                          endpoint    => CLIENT,
+                          state       => DISCONNECTED,#since we're init'ing
+                          session_id  => $sess_id,
+                          remote_id   => $r_id,
+                          raise_error   => $quiet ? 0 : 1,
+                          session_locale   => $locale,
+                          api_level   => $api_level,
+                          orig_remote_id   => $r_id,
+                               peer_handle => $peer_handle,
+                               session_threadTrace => 0,
+                               stateless               => $stateless,
+                        } => $class;
+
+       $logger->debug( "Created new client session $app : $sess_id" );
+
+       return $_CACHE{$sess_id} = $self;
+}
+
+sub raise_remote_errors {
+       my $self = shift;
+       my $err = shift;
+       $self->{raise_error} = $err if (defined $err);
+       return $self->{raise_error};
+}
+
+sub api_level {
+       return shift()->{api_level};
+}
+
+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;
+
+
+       if ( ref( $self ) and  $self->state && $self->state == CONNECTED  ) {
+               $logger->transport("AppSession already connected", DEBUG );
+       } else {
+               $logger->transport("AppSession not connected, connecting..", DEBUG );
+       }
+       return $self if ( ref( $self ) and  $self->state && $self->state == CONNECTED  );
+
+
+       my $app = shift;
+       my $api_level = shift;
+       $api_level = 1 unless (defined $api_level);
+
+       $self = $class->create($app, @_) if (!ref($self));
+
+       return undef unless ($self);
+
+       $self->{api_level} = $api_level;
+
+       $self->reset;
+       $self->state(CONNECTING);
+       $self->send('CONNECT', "");
+
+
+       # if we want to connect to settings, we may not have 
+       # any data for the settings client to work with...
+       # just using a default for now XXX
+
+       my $time_remaining = 5;
+
+
+#      my $client = OpenSRF::Utils::SettingsClient->new();
+#      my $trans = $client->config_value("client_connection","transport_host");
+#
+#      if(!ref($trans)) {
+#              $time_remaining = $trans->{connect_timeout};
+#      } else {
+#              # XXX for now, just use the first
+#              $time_remaining = $trans->[0]->{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);
+
+       $self->stateless(0);
+
+       return $self;
+}
+
+sub finish {
+       my $self = shift;
+       if( ! $self->session_id ) {
+               return 0;
+       }
+}
+
+sub unregister_callback {
+       my $self = shift;
+       my $type = shift;
+       my $cb = shift;
+       if (exists $self->{callbacks}{$type}) {
+               delete $self->{callbacks}{$type}{$cb};
+               return $cb;
+       }
+       return undef;
+}
+
+sub register_callback {
+       my $self = shift;
+       my $type = shift;
+       my $cb = shift;
+       my $cb_key = "$cb";
+       $self->{callbacks}{$type}{$cb_key} = $cb;
+       return $cb_key;
+}
+
+sub kill_me {
+       my $self = shift;
+       if( ! $self->session_id ) { return 0; }
+
+       # run each 'death' callback;
+       if (exists $self->{callbacks}{death}) {
+               for my $sub (values %{$self->{callbacks}{death}}) {
+                       $sub->($self);
+               }
+       }
+
+       $self->disconnect;
+       $logger->transport( "AppSession killing self: " . $self->session_id(), DEBUG );
+       delete $_CACHE{$self->session_id};
+       delete($$self{$_}) for (keys %$self);
+}
+
+sub disconnect {
+       my $self = shift;
+
+       # run each 'disconnect' callback;
+       if (exists $self->{callbacks}{disconnect}) {
+               for my $sub (values %{$self->{callbacks}{disconnect}}) {
+                       $sub->($self);
+               }
+       }
+
+       if ( !$self->stateless and $self->state != DISCONNECTED ) {
+               $self->send('DISCONNECT', "") if ($self->endpoint == CLIENT);
+               $self->state( DISCONNECTED ); 
+       }
+
+       $self->reset;
+}
+
+sub request {
+       my $self = shift;
+       my $meth = shift;
+       return unless $self;
+
+   # tell the logger to create a new xid - the logger will decide if it's really necessary
+   $logger->mk_osrf_xid;
+
+       my $method;
+       if (!ref $meth) {
+               $method = new OpenSRF::DomainObject::oilsMethod ( method => $meth );
+       } else {
+               $method = $meth;
+       }
+       
+       $method->params( @_ );
+
+       $self->send('REQUEST',$method);
+}
+
+sub full_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(CONNECT => '', REQUEST => $method, DISCONNECT => '');
+}
+
+sub send {
+       my $self = shift;
+       my @payload_list = @_; # this is a Domain Object
+
+       return unless ($self and $self->{peer_handle});
+
+       $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 = ();
+
+       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 && !$connecting) {
+                               next;
+                       }
+               }
+
+               if( $msg_type eq "CONNECT" ) { 
+                       $connecting++; 
+               }
+
+               my $msg = OpenSRF::DomainObject::oilsMessage->new();
+               $msg->type($msg_type);
+       
+               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->api_level($self->api_level);
+               $msg->payload($payload) if $payload;
+
+        my $locale = $self->session_locale;
+               $msg->sender_locale($locale) if ($locale);
+       
+               push @doc, $msg;
+
+       
+               $logger->info( "AppSession sending ".$msg->type." to ".$self->remote_id.
+                       " with threadTrace [".$msg->threadTrace."]");
+
+       }
+       
+       if ($self->endpoint == CLIENT and ! $disconnect) {
+               $self->queue_wait(0);
+
+
+               if($self->stateless && $self->state != CONNECTED) {
+                       $self->reset;
+                       $logger->debug("AppSession is stateless in send", INTERNAL );
+               }
+
+               if( !$self->stateless and $self->state != CONNECTED ) {
+
+                       $logger->debug( "Sending connect before request 1", INTERNAL );
+
+                       unless (($self->state == CONNECTING && $connecting )) {
+                               $logger->debug( "Sending connect before request 2", INTERNAL );
+                               my $v = $self->connect();
+                               if( ! $v ) {
+                                       $logger->debug( "Unable to connect to remote service in AppSession::send()", ERROR );
+                                       return undef;
+                               }
+                               if( ref($v) and $v->can("class") and $v->class->isa( "OpenSRF::EX" ) ) {
+                                       return $v;
+                               }
+                       }
+               }
+
+       } 
+       my $json = OpenSRF::Utils::JSON->perl2JSON(\@doc);
+       $logger->internal("AppSession sending doc: $json");
+
+       $self->{peer_handle}->send( 
+                                       to     => $self->remote_id,
+                                  thread => $self->session_id,
+                                  body   => $json );
+
+       if( $disconnect) {
+               $self->state( DISCONNECTED );
+       }
+
+       my $req = $self->app_request( $tT );
+       $req->{_start} = time;
+       return $req
+}
+
+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_api_level {
+       my $self = shift;
+       my $new_last_message_api_level = shift;
+
+       my $old_last_message_api_level = $self->{last_message_api_level};
+       if (defined $new_last_message_api_level) {
+               $self->{last_message_api_level} = $new_last_message_api_level;
+               return $new_last_message_api_level unless ($old_last_message_api_level);
+       }
+
+       return $old_last_message_api_level;
+}
+
+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 {
+       return undef;
+       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});
+               $self->{recv_timeout} = $args{timeout};
+       }
+
+       #$args{timeout} = 0 if ($self->complete);
+
+       if(defined($args{timeout})) {
+               $logger->debug( ref($self) ."->recv with timeout " . $args{timeout}, INTERNAL );
+       }
+
+       my $avail = @{ $self->{recv_queue} };
+       $self->{remaining_recv_timeout} = $self->{recv_timeout};
+
+       if (!$args{count}) {
+               if (wantarray) {
+                       $args{count} = $avail;
+               } else {
+                       $args{count} = 1;
+               }
+       }
+
+       while ( $self->{remaining_recv_timeout} > 0 and $avail < $args{count} ) {
+                       last if $self->complete;
+                       my $starttime = time;
+                       $self->queue_wait($self->{remaining_recv_timeout});
+                       my $endtime = time;
+                       if ($self->{timeout_reset}) {
+                               $self->{timeout_reset} = 0;
+                       } else {
+                               $self->{remaining_recv_timeout} -= ($endtime - $starttime)
+                       }
+                       $avail = @{ $self->{recv_queue} };
+       }
+
+    $self->timed_out(1) if ( $self->{remaining_recv_timeout} <= 0 );
+
+       my @list;
+       while ( my $msg = shift @{ $self->{recv_queue} } ) {
+               push @list, $msg;
+               last if (scalar(@list) >= $args{count});
+       }
+
+       $logger->debug( "Number of matched responses: " . @list, DEBUG );
+       $self->queue_wait(0); # check for statuses
+       
+       return $list[0] if (!wantarray);
+       return @list;
+}
+
+sub timed_out {
+    my $self = shift;
+    my $out = shift;
+    $self->{timed_out} = $out if (defined $out);
+    return $self->{timed_out};
+}
+
+sub push_resend {
+       my $self = shift;
+       push @OpenSRF::AppSession::_RESEND_QUEUE, @_;
+}
+
+sub flush_resend {
+       my $self = shift;
+       $logger->debug( "Resending..." . @_RESEND_QUEUE, INTERNAL );
+       while ( my $req = shift @OpenSRF::AppSession::_RESEND_QUEUE ) {
+               $req->resend unless $req->complete;
+       }
+}
+
+
+sub queue_wait {
+       my $self = shift;
+       if( ! $self->{peer_handle} ) { return 0; }
+       my $timeout = shift || 0;
+       $logger->debug( "Calling queue_wait($timeout)" , INTERNAL );
+       my $o = $self->{peer_handle}->process($timeout);
+       $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;
+       return unless $self;
+       $self->send( 'STATUS', @_ );
+}
+
+sub reset_request_timeout {
+       my $self = shift;
+       my $tt = shift;
+       my $req = $self->app_request($tt);
+       $req->{remaining_recv_timeout} = $req->{recv_timeout};
+       $req->{timout_reset} = 1;
+}
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::AppRequest;
+use base qw/OpenSRF::AppSession/;
+use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use Time::HiRes qw/time usleep/;
+
+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,
+                       timeout_reset           => 0,
+                       recv_timeout            => 30,
+                       remaining_recv_timeout  => 30,
+                       recv_queue              => [],
+       };
+
+       bless $self => $class;
+
+       push @{ $self->session->{request_queue} }, $self;
+
+       return $self;
+}
+
+sub recv_timeout {
+       my $self = shift;
+       my $timeout = shift;
+       if (defined $timeout) {
+               $self->{recv_timeout} = $timeout;
+               $self->{remaining_recv_timeout} = $timeout;
+       }
+       return $self->{recv_timeout};
+}
+
+sub queue_size {
+       my $size = @{$_[0]->{recv_queue}};
+       return $size;
+}
+       
+sub send {
+       my $self = shift;
+       return unless ($self and $self->session and !$self->complete);
+       $self->session->send(@_);
+}
+
+sub finish {
+       my $self = shift;
+       return unless $self->session;
+       $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;
+               $self->{_duration} = time - $self->{_start} if ($self->{complete});
+       } else {
+               $self->session->queue_wait(0);
+       }
+       return $self->{complete};
+}
+
+sub duration {
+       my $self = shift;
+       $self->wait_complete;
+       return $self->{_duration};
+}
+
+sub wait_complete {
+       my $self = shift;
+       my $timeout = shift || 10;
+       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;
+       if( !$resp ) { return 0; }
+       if( UNIVERSAL::isa($resp, "Error")) {
+               $self->{failed} = $resp;
+               $self->complete(1);
+               #return; eventually...
+       }
+       push @{ $self->{recv_queue} }, $resp;
+}
+
+sub failed {
+       my $self = shift;
+       return $self->{failed};
+}
+
+sub queue_wait {
+       my $self = shift;
+       return $self->session->queue_wait(@_)
+}
+
+sub payload { return shift()->{payload}; }
+
+sub resend {
+       my $self = shift;
+       return unless ($self and $self->session and !$self->complete);
+       OpenSRF::Utils::Logger->debug( "I'm resending the request for threadTrace ". $self->threadTrace, DEBUG);
+       return $self->session->send('REQUEST', $self->payload, $self->threadTrace );
+}
+
+sub status {
+       my $self = shift;
+       my $msg = shift;
+       return unless ($self and $self->session and !$self->complete);
+       $self->session->send( 'STATUS',$msg, $self->threadTrace );
+}
+
+sub stream_push {
+       my $self = shift;
+       my $msg = shift;
+       $self->respond( $msg );
+}
+
+sub respond {
+       my $self = shift;
+       my $msg = shift;
+       return unless ($self and $self->session and !$self->complete);
+
+       my $response;
+       if (ref($msg) && UNIVERSAL::isa($msg, 'OpenSRF::DomainObject::oilsResult')) {
+               $response = $msg;
+       } else {
+               $response = new OpenSRF::DomainObject::oilsResult;
+               $response->content($msg);
+       }
+
+       $self->session->send('RESULT', $response, $self->threadTrace);
+}
+
+sub respond_complete {
+       my $self = shift;
+       my $msg = shift;
+       return unless ($self and $self->session and !$self->complete);
+
+       my $response;
+       if (ref($msg) && UNIVERSAL::isa($msg, 'OpenSRF::DomainObject::oilsResult')) {
+               $response = $msg;
+       } else {
+               $response = new OpenSRF::DomainObject::oilsResult;
+               $response->content($msg);
+       }
+
+       my $stat = OpenSRF::DomainObject::oilsConnectStatus->new(
+               statusCode => STATUS_COMPLETE(),
+               status => 'Request Complete' );
+
+
+       $self->session->send( 'RESULT' => $response, 'STATUS' => $stat, $self->threadTrace);
+       $self->complete(1);
+}
+
+sub register_death_callback {
+       my $self = shift;
+       my $cb = shift;
+       $self->session->register_callback( death => $cb );
+}
+
+
+# utility method.  checks to see of the request failed.
+# if so, throws an OpenSRF::EX::ERROR. if everything is
+# ok, it returns the content of the request
+sub gather {
+       my $self = shift;
+       my $finish = shift;
+       $self->wait_complete;
+       my $resp = $self->recv( timeout => 60 );
+       if( $self->failed() ) { 
+               throw OpenSRF::EX::ERROR
+                       ($self->failed()->stringify());
+       }
+       if(!$resp) { return undef; }
+       my $content = $resp->content;
+       if($finish) { $self->finish();}
+       return $content;
+}
+
+
+package OpenSRF::AppSubrequest;
+
+sub respond {
+       my $self = shift;
+       my $resp = shift;
+       push @{$$self{resp}}, $resp if (defined $resp);
+}
+sub respond_complete { respond(@_); }
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+       return bless({resp => [], @_}, $class);
+}
+
+sub responses { @{$_[0]->{resp}} }
+
+sub session {
+       my $x = shift;
+       my $s = shift;
+       $x->{session} = $s if ($s);
+       return $x->{session};
+}
+
+sub status {}
+
+
+1;
+
diff --git a/trunk/src/perl/lib/OpenSRF/Application.pm b/trunk/src/perl/lib/OpenSRF/Application.pm
new file mode 100644 (file)
index 0000000..0329a02
--- /dev/null
@@ -0,0 +1,745 @@
+package OpenSRF::Application;
+# vim:noet:ts=4
+use vars qw/$_app $log @_METHODS $thunk $server_class/;
+
+use base qw/OpenSRF/;
+use OpenSRF::AppSession;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Utils::Logger qw/:level $logger/;
+use Data::Dumper;
+use Time::HiRes qw/time/;
+use OpenSRF::EX qw/:try/;
+use Carp;
+use OpenSRF::Utils::JSON;
+#use OpenSRF::UnixServer;  # to get the server class from UnixServer::App
+
+sub DESTROY{};
+
+use strict;
+use warnings;
+
+$log = 'OpenSRF::Utils::Logger';
+
+our $in_request = 0;
+our @pending_requests;
+
+sub package {
+       my $self = shift;
+       return 1 unless ref($self);
+       return $self->{package};
+}
+
+sub signature {
+       my $self = shift;
+       return 0 unless ref($self);
+       return $self->{signature};
+}
+
+sub strict {
+    my $self = shift; 
+    return 0 unless ref($self);
+    return $self->{strict};
+}
+
+sub argc {
+       my $self = shift;
+       return 0 unless ref($self);
+       return $self->{argc};
+}
+
+sub api_name {
+       my $self = shift;
+       return 1 unless ref($self);
+       return $self->{api_name};
+}
+
+sub api_level {
+       my $self = shift;
+       return 1 unless ref($self);
+       return $self->{api_level};
+}
+
+sub session {
+       my $self = shift;
+       my $session = shift;
+
+       if($session) {
+               $self->{session} = $session;
+       }
+       return $self->{session};
+}
+
+sub server_class {
+       my $class = shift;
+       if($class) {
+               $server_class = $class;
+       }
+       return $server_class;
+}
+
+sub thunk {
+       my $self = shift;
+       my $flag = shift;
+       $thunk = $flag if (defined $flag);
+       return $thunk;
+}
+
+sub application_implementation {
+       my $self = shift;
+       my $app = shift;
+
+       if (defined $app) {
+               $_app = $app;
+               $_app->use;
+               if( $@ ) {
+                       $log->error( "Error loading application_implementation: $app -> $@", ERROR);
+               }
+
+       }
+
+       return $_app;
+}
+
+sub handler {
+       my ($self, $session, $app_msg) = @_;
+
+       if( ! $app_msg ) {
+               return 1;  # error?
+       }
+
+       my $app = $self->application_implementation;
+
+       if ($session->last_message_type eq 'REQUEST') {
+
+        my @p = $app_msg->params;
+               my $method_name = $app_msg->method;
+               my $method_proto = $session->last_message_api_level;
+               $log->info("CALL: $method_name [". (@p ? join(', ',@p) : '') ."]");
+
+               my $coderef = $app->method_lookup( $method_name, $method_proto, 1, 1 );
+
+               unless ($coderef) {
+                       $session->status( OpenSRF::DomainObject::oilsMethodException->new( 
+                                               statusCode => STATUS_NOTFOUND(),
+                                               status => "Method [$method_name] not found for $app"));
+                       return 1;
+               }
+
+               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."]", INTERNAL );
+                       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}", INTERNAL );
+
+                       my $resp;
+                       try {
+                               # un-if(0) this block to enable param checking based on signature and argc
+                               if ($coderef->strict) {
+                                       if (@args < $coderef->argc) {
+                                               die     "Not enough params passed to ".
+                                                       $coderef->api_name." : requires ". $coderef->argc
+                                       }
+                                       if (@args) {
+                                               my $sig = $coderef->signature;
+                                               if ($sig && exists $sig->{params}) {
+                                                       for my $p (0 .. scalar(@{ $sig->{params} }) - 1 ) {
+                                                               my $s = $sig->{params}->[$p];
+                                                               my $a = $args[$p];
+                                                               if ($s->{class} && OpenSRF::Utils::JSON->lookup_hint(ref $a) ne $s->{class}) {
+                                                                       die "Incorrect param class at position $p : should be a '$$s{class}'";
+                                                               } elsif ($s->{type}) {
+                                                                       if (lc($s->{type}) eq 'object' && $a !~ /HASH/o) {
+                                                                               die "Incorrect param type at position $p : should be an 'object'";
+                                                                       } elsif (lc($s->{type}) eq 'array' && $a !~ /ARRAY/o) {
+                                                                               die "Incorrect param type at position $p : should be an 'array'";
+                                                                       } elsif (lc($s->{type}) eq 'number' && (ref($a) || $a !~ /^-?\d+(?:\.\d+)?$/o)) {
+                                                                               die "Incorrect param type at position $p : should be a 'number'";
+                                                                       } elsif (lc($s->{type}) eq 'string' && ref($a)) {
+                                                                               die "Incorrect param type at position $p : should be a 'string'";
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               my $start = time();
+                               $resp = $coderef->run( $appreq, @args); 
+                               my $time = sprintf '%.3f', time() - $start;
+
+                               $log->debug( "Method duration for [$method_name]:  ". $time );
+                               if( defined( $resp ) ) {
+                                       $appreq->respond_complete( $resp );
+                               } else {
+                                       $appreq->status( OpenSRF::DomainObject::oilsConnectStatus->new(
+                                                               statusCode => STATUS_COMPLETE(),
+                                                               status => 'Request Complete' ) );
+                               }
+                       } catch Error with {
+                               my $e = shift;
+                               warn "Caught error from 'run' method: $e\n";
+
+                               if(UNIVERSAL::isa($e,"Error")) {
+                                       $e = $e->stringify();
+                               } 
+                               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\n"
+                                       )
+                               );
+                       };
+
+
+
+                       # ----------------------------------------------
+
+
+                       # 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 {
+                                       # un-if(0) this block to enable param checking based on signature and argc
+                                       if (0) {
+                                               if (@args < $aref->[2]->argc) {
+                                                       die     "Not enough params passed to ".
+                                                               $aref->[2]->api_name." : requires ". $aref->[2]->argc
+                                               }
+                                               if (@args) {
+                                                       my $sig = $aref->[2]->signature;
+                                                       if ($sig && exists $sig->{params}) {
+                                                               for my $p (0 .. scalar(@{ $sig->{params} }) - 1 ) {
+                                                                       my $s = $sig->{params}->[$p];
+                                                                       my $a = $args[$p];
+                                                                       if ($s->{class} && OpenSRF::Utils::JSON->lookup_hint(ref $a) ne $s->{class}) {
+                                                                               die "Incorrect param class at position $p : should be a '$$s{class}'";
+                                                                       } elsif ($s->{type}) {
+                                                                               if (lc($s->{type}) eq 'object' && $a !~ /HASH/o) {
+                                                                                       die "Incorrect param type at position $p : should be an 'object'";
+                                                                               } elsif (lc($s->{type}) eq 'array' && $a !~ /ARRAY/o) {
+                                                                                       die "Incorrect param type at position $p : should be an 'array'";
+                                                                               } elsif (lc($s->{type}) eq 'number' && (ref($a) || $a !~ /^-?\d+(?:\.\d+)?$/o)) {
+                                                                                       die "Incorrect param type at position $p : should be a 'number'";
+                                                                               } elsif (lc($s->{type}) eq 'string' && ref($a)) {
+                                                                                       die "Incorrect param type at position $p : should be a 'string'";
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+
+                                       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]->api_name." -> ".join(', ',@{$aref->[1]}).']:  '.$time, DEBUG );
+
+                                       $appreq = $aref->[0];   
+                                       if( ref( $response ) ) {
+                                               $appreq->respond_complete( $response );
+                                       } else {
+                                               $appreq->status( OpenSRF::DomainObject::oilsConnectStatus->new(
+                                                                       statusCode => STATUS_COMPLETE(),
+                                                                       status => 'Request Complete' ) );
+                                       }
+                                       $log->debug( "Executed: " . $appreq->threadTrace, INTERNAL );
+                               } catch Error with {
+                                       my $e = shift;
+                                       if(UNIVERSAL::isa($e,"Error")) {
+                                               $e = $e->stringify();
+                                       }
+                                       $session->status(
+                                               OpenSRF::DomainObject::oilsMethodException->new(
+                                                               statusCode => STATUS_INTERNALSERVERERROR(),
+                                                               status => "Call to [".$aref->[2]->api_name."] faild:  $e"
+                                               )
+                                       );
+                               };
+                               $in_request--;
+                       }
+
+                       return 1;
+               } 
+
+               $log->info("Received non-REQUEST message in Application handler");
+
+               my $res = OpenSRF::DomainObject::oilsMethodException->new( 
+                               status => "Received non-REQUEST message in Application handler");
+               $session->send('ERROR', $res);
+               $session->kill_me;
+               return 1;
+
+       } else {
+               $session->push_queue([ $app_msg, $session->last_threadTrace ]);
+       }
+
+       $session->last_message_type('');
+       $session->last_message_api_level('');
+
+       return 1;
+}
+
+sub is_registered {
+       my $self = shift;
+       my $api_name = shift;
+       my $api_level = shift || 1;
+       return exists($_METHODS[$api_level]{$api_name});
+}
+
+
+sub normalize_whitespace {
+       my $txt = shift;
+
+       $txt =~ s/^\s+//gso;
+       $txt =~ s/\s+$//gso;
+       $txt =~ s/\s+/ /gso;
+       $txt =~ s/\n//gso;
+       $txt =~ s/\. /\.  /gso;
+
+       return $txt;
+}
+
+sub parse_string_signature {
+       my $string = shift;
+       return [] unless $string;
+       my @chunks = split(/\@/smo, $string);
+
+       my @params;
+       my $ret;
+       my $desc = '';
+       for (@chunks) {
+               if (/^return (.+)$/so) {
+                       $ret = [normalize_whitespace($1)];
+               } elsif (/^param (\w+) \b(.+)$/so) {
+                       push @params, [ $1, normalize_whitespace($2) ];
+               } else {
+                       $desc .= '@' if $desc;
+                       $desc .= $_;
+               }
+       }
+
+       return [normalize_whitespace($desc),\@params, $ret];
+}
+
+sub parse_array_signature {
+       my $array = shift;
+       my ($d,$p,$r) = @$array;
+       return {} unless ($d or $p or $r);
+
+       return {
+               desc    => $d,
+               params  => [
+                       map { 
+                               { name  => $$_[0],
+                                 desc  => $$_[1],
+                                 type  => $$_[2],
+                                 class => $$_[3],
+                               }
+                       } @$p
+               ],
+               'return'=>
+                       { desc  => $$r[0],
+                         type  => $$r[1],
+                         class => $$r[2],
+                       }
+       };
+}
+
+sub register_method {
+       my $self = shift;
+       my $app = ref($self) || $self;
+       my %args = @_;
+
+
+       throw OpenSRF::DomainObject::oilsMethodException unless ($args{method});
+
+       $args{api_level} = 1 unless(defined($args{api_level}));
+       $args{stream} ||= 0;
+       $args{remote} ||= 0;
+       $args{argc} ||= 0;
+       $args{package} ||= $app;                
+       $args{server_class} = server_class();
+       $args{api_name} ||= $args{server_class} . '.' . $args{method};
+
+       # un-if(0) this block to enable signature parsing
+       if (!$args{signature}) {
+               if ($args{notes} && !ref($args{notes})) {
+                       $args{signature} =
+                               parse_array_signature( parse_string_signature( $args{notes} ) );
+               }
+       } elsif( !ref($args{signature}) ) {
+               $args{signature} =
+                       parse_array_signature( parse_string_signature( $args{signature} ) );
+       } elsif( ref($args{signature}) eq 'ARRAY') {
+               $args{signature} =
+                       parse_array_signature( $args{signature} );
+       }
+       
+       unless ($args{object_hint}) {
+               ($args{object_hint} = $args{package}) =~ s/::/_/go;
+       }
+
+       OpenSRF::Utils::JSON->register_class_hint( name => $args{package}, hint => $args{object_hint}, type => "hash" );
+
+       $_METHODS[$args{api_level}]{$args{api_name}} = bless \%args => $app;
+
+       __PACKAGE__->register_method(
+               stream => 0,
+               argc => $args{argc},
+               api_name => $args{api_name}.'.atomic',
+               method => 'make_stream_atomic',
+               notes => "This is a system generated method.  Please see the definition for $args{api_name}",
+       ) if ($args{stream});
+}
+
+sub retrieve_remote_apis {
+       my $method = shift;
+       my $session = OpenSRF::AppSession->create('router');
+       try {
+               $session->connect or OpenSRF::EX::WARN->throw("Connection to router timed out");
+       } catch Error with {
+               my $e = shift;
+               $log->debug( "Remote subrequest returned an error:\n". $e );
+               return undef;
+       } finally {
+               return undef unless ($session->state == $session->CONNECTED);
+       };
+
+       my $req = $session->request( 'opensrf.router.info.class.list' );
+       my $list = $req->recv;
+
+       if( UNIVERSAL::isa($list,"Error") ) {
+               throw $list;
+       }
+
+       my $content = $list->content;
+
+       $req->finish;
+       $session->finish;
+       $session->disconnect;
+
+       my %u_list = map { ($_ => 1) } @$content;
+
+       for my $class ( keys %u_list ) {
+               next if($class eq $server_class);
+               populate_remote_method_cache($class, $method);
+       }
+}
+
+sub populate_remote_method_cache {
+       my $class = shift;
+       my $meth = shift;
+
+       my $session = OpenSRF::AppSession->create($class);
+       try {
+               $session->connect or OpenSRF::EX::WARN->throw("Connection to $class timed out");
+
+               my $call = 'opensrf.system.method.all' unless (defined $meth);
+               $call = 'opensrf.system.method' if (defined $meth);
+
+               my $req = $session->request( $call, $meth );
+
+               while (my $method = $req->recv) {
+                       next if (UNIVERSAL::isa($method, 'Error'));
+
+                       $method = $method->content;
+                       next if ( exists($_METHODS[$$method{api_level}]) &&
+                               exists($_METHODS[$$method{api_level}]{$$method{api_name}}) );
+                       $method->{remote} = 1;
+                       bless($method, __PACKAGE__ );
+                       $_METHODS[$$method{api_level}]{$$method{api_name}} = $method;
+               }
+
+               $req->finish;
+               $session->finish;
+               $session->disconnect;
+
+       } catch Error with {
+               my $e = shift;
+               $log->debug( "Remote subrequest returned an error:\n". $e );
+               return undef;
+       };
+}
+
+sub method_lookup {             
+       my $self = shift;
+       my $method = shift;
+       my $proto = shift;
+       my $no_recurse = shift || 0;
+       my $no_remote = shift || 0;
+
+       # this instead of " || 1;" above to allow api_level 0
+       $proto = $self->api_level unless (defined $proto);
+
+       my $class = ref($self) || $self;
+
+       $log->debug("Lookup of [$method] by [$class] in api_level [$proto]", DEBUG);
+       $log->debug("Available methods\n\t".join("\n\t", keys %{ $_METHODS[$proto] }), INTERNAL);
+
+       my $meth;
+       if (__PACKAGE__->thunk) {
+               for my $p ( reverse(1 .. $proto) ) {
+                       if (exists $_METHODS[$p]{$method}) {
+                               $meth = $_METHODS[$p]{$method};
+                       }
+               }
+       } else {
+               if (exists $_METHODS[$proto]{$method}) {
+                       $meth = $_METHODS[$proto]{$method};
+               }
+       }
+
+       if (defined $meth) {
+               if($no_remote and $meth->{remote}) {
+                       $log->debug("OH CRAP We're not supposed to return remote methods", WARN);
+                       return undef;
+               }
+
+       } elsif (!$no_recurse) {
+               $log->debug("We didn't find [$method], asking everyone else.", DEBUG);
+               retrieve_remote_apis($method);
+               $meth = $self->method_lookup($method,$proto,1);
+       }
+
+       return $meth;
+}
+
+sub run {
+       my $self = shift;
+       my $req = shift;
+
+       my $resp;
+       my @params = @_;
+
+       if ( !UNIVERSAL::isa($req, 'OpenSRF::AppRequest') ) {
+               $log->debug("Creating a SubRequest object", DEBUG);
+               unshift @params, $req;
+               $req = OpenSRF::AppSubrequest->new;
+               $req->session( $self->session ) if ($self->session);
+
+       } else {
+               $log->debug("This is a top level request", DEBUG);
+       }
+
+       if (!$self->{remote}) {
+               my $code = \&{$self->{package} . '::' . $self->{method}};
+               my $err = undef;
+
+               try {
+                       $resp = $code->($self, $req, @params);
+
+               } catch Error with {
+                       $err = shift;
+
+                       if( ref($self) eq 'HASH') {
+                               $log->error("Sub $$self{package}::$$self{method} DIED!!!\n\t$err\n", ERROR);
+                       }
+               };
+
+               if($err) {
+                       if(UNIVERSAL::isa($err,"Error")) { 
+                               throw $err;
+                       } else {
+                               die $err->stringify; 
+                       }
+               }
+
+
+               $log->debug("Coderef for [$$self{package}::$$self{method}] has been run", DEBUG);
+
+               if ( ref($req) and UNIVERSAL::isa($req, 'OpenSRF::AppSubrequest') ) {
+                       $req->respond($resp) if (defined $resp);
+                       $log->debug("SubRequest object is responding with : " . join(" ",$req->responses), DEBUG);
+                       return $req->responses;
+               } else {
+                       $log->debug("A top level Request object is responding $resp", DEBUG) if (defined $resp);
+                       return $resp;
+               }
+       } else {
+               my $session = OpenSRF::AppSession->create($self->{server_class});
+               try {
+                       #$session->connect or OpenSRF::EX::WARN->throw("Connection to [$$self{server_class}] timed out");
+                       my $remote_req = $session->request( $self->{api_name}, @params );
+                       while (my $remote_resp = $remote_req->recv) {
+                               OpenSRF::Utils::Logger->debug("Remote Subrequest Received " . $remote_resp, INTERNAL );
+                               if( UNIVERSAL::isa($remote_resp,"Error") ) {
+                                       throw $remote_resp;
+                               }
+                               $req->respond( $remote_resp->content );
+                       }
+                       $remote_req->finish();
+
+               } catch Error with {
+                       my $e = shift;
+                       $log->debug( "Remote subrequest returned an error:\n". $e );
+                       return undef;
+               };
+
+               if ($session) {
+                       $session->disconnect();
+                       $session->finish();
+               }
+
+               $log->debug( "Remote Subrequest Responses " . join(" ", $req->responses), INTERNAL );
+
+               return $req->responses;
+       }
+       # huh? how'd we get here...
+       return undef;
+}
+
+sub introspect {
+       my $self = shift;
+       my $client = shift;
+       my $method = shift;
+       my $limit = shift;
+       my $offset = shift;
+
+       if ($self->api_name =~ /all$/o) {
+               $offset = $limit;
+               $limit = $method;
+               $method = undef; 
+       }
+
+       my ($seen,$returned) = (0,0);
+       for my $api_level ( reverse(1 .. $#_METHODS) ) {
+               for my $api_name ( sort keys %{$_METHODS[$api_level]} ) {
+                       if (!$offset || $offset <= $seen) {
+                               if (!$_METHODS[$api_level]{$api_name}{remote}) {
+                                       if (defined($method)) {
+                                               if ($api_name =~ $method) {
+                                                       if (!$limit || $returned < $limit) {
+                                                               $client->respond( $_METHODS[$api_level]{$api_name} );
+                                                               $returned++;
+                                                       }
+                                               }
+                                       } else {
+                                               if (!$limit || $returned < $limit) {
+                                                       $client->respond( $_METHODS[$api_level]{$api_name} );
+                                                       $returned++;
+                                               }
+                                       }
+                               }
+                       }
+                       $seen++;
+               }
+       }
+
+       return undef;
+}
+__PACKAGE__->register_method(
+       stream => 1,
+       method => 'introspect',
+       api_name => 'opensrf.system.method.all',
+       argc => 0,
+       signature => {
+               desc => q/This method is used to introspect an entire OpenSRF Application/,
+               return => {
+                       desc => q/A stream of objects describing the methods available via this OpenSRF Application/,
+                       type => 'object'
+               }
+       },
+);
+__PACKAGE__->register_method(
+       stream => 1,
+       method => 'introspect',
+       argc => 1,
+       api_name => 'opensrf.system.method',
+       argc => 1,
+       signature => {
+               desc => q/Use this method to get the definition of a single OpenSRF Method/,
+               params => [
+                       { desc => q/The method to introspect/,
+                         type => 'string' },
+               ],
+               return => { desc => q/An object describing the method requested, or an error if it can't be found/,
+                           type => 'object' }
+       },
+);
+
+sub echo_method {
+       my $self = shift;
+       my $client = shift;
+       my @args = @_;
+
+       $client->respond( $_ ) for (@args);
+       return undef;
+}
+__PACKAGE__->register_method(
+       stream => 1,
+       method => 'echo_method',
+       argc => 1,
+       api_name => 'opensrf.system.echo',
+       signature => {
+               desc => q/A test method that will echo back it's arguments in a streaming response/,
+               params => [
+                       { desc => q/One or more arguments to echo back/ }
+               ],
+               return => { desc => q/A stream of the arguments passed/ }
+       },
+);
+
+sub time_method {
+       my( $self, $conn ) = @_;
+       return CORE::time;
+}
+__PACKAGE__->register_method(
+       method => 'time_method',
+       argc => 0,
+       api_name => 'opensrf.system.time',
+       signature => {
+               desc => q/Returns the current system time as epoch seconds/,
+               return => { desc => q/epoch seconds/ }
+       }
+);
+
+sub make_stream_atomic {
+       my $self = shift;
+       my $req = shift;
+       my @args = @_;
+
+       (my $m_name = $self->api_name) =~ s/\.atomic$//o;
+       my $m = $self->method_lookup($m_name);
+
+       $m->session( $req->session );
+       my @results = $m->run(@args);
+       $m->session('');
+
+       return \@results;
+}
+
+
+1;
+
+
diff --git a/trunk/src/perl/lib/OpenSRF/Application/Client.pm b/trunk/src/perl/lib/OpenSRF/Application/Client.pm
new file mode 100644 (file)
index 0000000..f5d11a2
--- /dev/null
@@ -0,0 +1,6 @@
+package OpenSRF::App::Client;
+use base 'OpenSRF::Application';
+use OpenSRF::Utils::Logger qw/:level/;
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Application/Demo/Math.pm b/trunk/src/perl/lib/OpenSRF/Application/Demo/Math.pm
new file mode 100644 (file)
index 0000000..7f41456
--- /dev/null
@@ -0,0 +1,83 @@
+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 send_request {
+       my $self = shift;
+       my $client = shift;
+
+       my $method_name = shift;
+       my @params = @_;
+
+       my $session = OpenSRF::AppSession->create( "opensrf.dbmath" );
+       my $request = $session->request( "dbmath.$method_name", @params );
+       my $response = $request->recv();
+       if(!$response) { return undef; }
+       if($response->isa("Error")) {throw $response ($response->stringify);}
+       $session->finish();
+
+       return $response->content;
+
+}
+__PACKAGE__->register_method( method => 'send_request', api_name => '_send_request' );
+
+__PACKAGE__->register_method( method => 'add_1', api_name => 'add' );
+sub add_1 {
+       my $self = shift;
+       my $client = shift;
+       my @args = @_;
+
+       my $meth = $self->method_lookup('_send_request');
+       my ($result) = $meth->run('add',@args);
+
+       return $result;
+}
+
+__PACKAGE__->register_method( method => 'sub_1', api_name => 'sub' );
+sub sub_1 {
+       my $self = shift;
+       my $client = shift;
+       my @args = @_;
+
+       my $meth = $self->method_lookup('_send_request');
+       my ($result) = $meth->run('sub',@args);
+
+       return $result;
+}
+
+__PACKAGE__->register_method( method => 'mult_1', api_name => 'mult' );
+sub mult_1 {
+       my $self = shift;
+       my $client = shift;
+       my @args = @_;
+
+       my $meth = $self->method_lookup('_send_request');
+       my ($result) = $meth->run('mult',@args);
+
+       return $result;
+}
+
+__PACKAGE__->register_method( method => 'div_1', api_name => 'div' );
+sub div_1 {
+       my $self = shift;
+       my $client = shift;
+       my @args = @_;
+
+       my $meth = $self->method_lookup('_send_request');
+       my ($result) = $meth->run('div',@args);
+
+       return $result;
+}
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Application/Demo/MathDB.pm b/trunk/src/perl/lib/OpenSRF/Application/Demo/MathDB.pm
new file mode 100644 (file)
index 0000000..6cdc78c
--- /dev/null
@@ -0,0 +1,58 @@
+package OpenSRF::Application::Demo::MathDB;
+use OpenSRF::Utils::JSON;
+use base qw/OpenSRF::Application/;
+use OpenSRF::Application;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Utils::Logger qw/:level/;
+use strict;
+use warnings;
+
+sub DESTROY{}
+our $log = 'OpenSRF::Utils::Logger';
+sub initialize {}
+
+__PACKAGE__->register_method( method => 'add_1', api_name => 'dbmath.add' );
+sub add_1 {
+       my $self = shift;
+       my $client = shift;
+
+       my $n1 = shift; 
+       my $n2 = shift;
+       my $a = $n1 + $n2;
+       return OpenSRF::Utils::JSON::number->new($a);
+}
+
+__PACKAGE__->register_method( method => 'sub_1', api_name => 'dbmath.sub' );
+sub sub_1 {
+       my $self = shift;
+       my $client = shift;
+
+       my $n1 = shift; 
+       my $n2 = shift;
+       my $a = $n1 - $n2;
+       return OpenSRF::Utils::JSON::number->new($a);
+}
+
+__PACKAGE__->register_method( method => 'mult_1', api_name => 'dbmath.mult' );
+sub mult_1 {
+       my $self = shift;
+       my $client = shift;
+
+       my $n1 = shift; 
+       my $n2 = shift;
+       my $a = $n1 * $n2;
+       return OpenSRF::Utils::JSON::number->new($a);
+}
+
+__PACKAGE__->register_method( method => 'div_1', api_name => 'dbmath.div' );
+sub div_1 {
+       my $self = shift;
+       my $client = shift;
+
+       my $n1 = shift; 
+       my $n2 = shift;
+       my $a = $n1 / $n2;
+       return OpenSRF::Utils::JSON::number->new($a);
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Application/Persist.pm b/trunk/src/perl/lib/OpenSRF/Application/Persist.pm
new file mode 100644 (file)
index 0000000..b8b291f
--- /dev/null
@@ -0,0 +1,517 @@
+package OpenSRF::Application::Persist;
+use base qw/OpenSRF::Application/;
+use OpenSRF::Application;
+
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::EX qw/:try/;
+use OpenSRF::Utils qw/:common/;
+use OpenSRF::Utils::Logger;
+use OpenSRF::Utils::JSON;
+use DBI;
+
+use vars qw/$dbh $log $default_expire_time/;
+
+sub initialize {
+       $log = 'OpenSRF::Utils::Logger';
+
+       $sc = OpenSRF::Utils::SettingsClient->new;
+
+       my $dbfile = $sc->config_value( apps => 'opensrf.persist' => app_settings => 'dbfile');
+       unless ($dbfile) {
+               throw OpenSRF::EX::PANIC ("Can't find my dbfile for SQLite!");
+       }
+
+       my $init_dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","");
+       $init_dbh->{AutoCommit} = 1;
+       $init_dbh->{RaiseError} = 0;
+
+       $init_dbh->do( <<"      SQL" );
+               CREATE TABLE storage (
+                       id      INTEGER PRIMARY KEY,
+                       name_id INTEGER,
+                       value   TEXT
+               );
+       SQL
+
+       $init_dbh->do( <<"      SQL" );
+               CREATE TABLE store_name (
+                       id      INTEGER PRIMARY KEY,
+                       name    TEXT UNIQUE
+               );
+       SQL
+
+       $init_dbh->do( <<"      SQL" );
+               CREATE TABLE store_expire (
+                       id              INTEGER PRIMARY KEY,
+                       atime           INTEGER,
+                       expire_interval INTEGER
+               );
+       SQL
+
+}
+
+sub child_init {
+       my $sc = OpenSRF::Utils::SettingsClient->new;
+
+       $default_expire_time = $sc->config_value( apps => 'opensrf.persist' => app_settings => 'default_expire_time' );
+       $default_expire_time ||= 300;
+
+       my $dbfile = $sc->config_value( apps => 'opensrf.persist' => app_settings => 'dbfile');
+       unless ($dbfile) {
+               throw OpenSRF::EX::PANIC ("Can't find my dbfile for SQLite!");
+       }
+
+       $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","");
+       $dbh->{AutoCommit} = 1;
+       $dbh->{RaiseError} = 0;
+
+}
+
+sub create_store {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift || '';
+
+       try {
+       
+               my $continue = 0;
+               try {
+                       _get_name_id($name);
+
+               } catch Error with { 
+                       $continue++;
+               };
+
+               throw OpenSRF::EX::WARN ("Duplicate key:  object name [$name] already exists!  " . $dbh->errstr)
+                       unless ($continue);
+
+               my $sth = $dbh->prepare("INSERT INTO store_name (name) VALUES (?);");
+               $sth->execute($name);
+               $sth->finish;
+
+               unless ($name) {
+                       my $last_id = $dbh->last_insert_id(undef, undef, 'store_name', 'id');
+                       $name = 'AUTOGENERATED!!'.$last_id;
+                       $dbh->do("UPDATE store_name SET name = '$name' WHERE id = '$last_id';");
+               }
+
+               _flush_by_name($name);
+               return $name;
+       } catch Error with {
+               return undef;
+       };
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.slot.create',
+       method => 'create_store',
+       argc => 1,
+);
+
+
+sub create_expirable_store {
+       my $self = shift;
+       my $client = shift;
+       my $name = shift || do { throw OpenSRF::EX::InvalidArg ("Expirable slots must be given a name!") };
+       my $time = shift || $default_expire_time;
+
+       try {
+               ($name) = $self->method_lookup( 'opensrf.persist.slot.create' )->run( $name );
+               return undef unless $name;
+
+               $self->method_lookup('opensrf.persist.slot.set_expire')->run($name, $time);
+               return $name;
+       } catch Error with {
+               return undef;
+       };
+
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.slot.create_expirable',
+       method => 'create_expirable_store',
+       argc => 2,
+);
+
+sub _update_expire_atime {
+       my $id = shift;
+       $dbh->do('UPDATE store_expire SET atime = ? WHERE id = ?', {}, time(), $id);
+}
+
+sub set_expire_interval {
+       my $self = shift;
+       my $client = shift;
+       my $slot = shift;
+       my $new_interval = shift;
+
+       try {
+               my $etime = interval_to_seconds($new_interval);
+               my $sid = _get_name_id($slot);
+
+               $dbh->do('DELETE FROM store_expire where id = ?', {}, $sid);
+               return 0 if ($etime == 0);
+
+               $dbh->do('INSERT INTO store_expire (id, atime, expire_interval) VALUES (?,?,?);',{},$sid,time(),$etime);
+               return $etime;
+       } 
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.slot.set_expire',
+       method => 'set_expire_interval',
+       argc => 2,
+);
+
+sub find_slot {
+       my $self = shift;
+       my $client = shift;
+       my $slot = shift;
+
+       my $sid = _get_name_id($slot);
+       return $slot if ($sid);
+       return undef;
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.slot.find',
+       method => 'find_slot',
+       argc => 2,
+);
+
+sub get_expire_interval {
+       my $self = shift;
+       my $client = shift;
+       my $slot = shift;
+
+       my $sid = _get_name_id($slot);
+       my ($int) = $dbh->selectrow_array('SELECT expire_interval FROM store_expire WHERE id = ?;',{},$sid);
+       return undef unless ($int);
+
+       my ($future) = $dbh->selectrow_array('SELECT atime + expire_interval FROM store_expire WHERE id = ?;',{},$sid);
+       return $future - time();
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.slot.get_expire',
+       method => 'get_expire_interval',
+       argc => 2,
+);
+
+
+sub _sweep_expired_slots {
+       return if (shift());
+
+       my $expired_slots = $dbh->selectcol_arrayref(<<"        SQL", {}, time() );
+               SELECT id FROM store_expire WHERE (atime + expire_interval) <= ?;
+       SQL
+
+       return unless ($expired_slots);
+
+       $dbh->do('DELETE FROM storage WHERE name_id IN ('.join(',', map { '?' } @$expired_slots).');', {}, @$expired_slots);
+       $dbh->do('DELETE FROM store_expire WHERE id IN ('.join(',', map { '?' } @$expired_slots).');', {}, @$expired_slots);
+       for my $id (@$expired_slots) {
+               _flush_by_name(_get_id_name($id), 1);
+       }
+}
+
+sub add_item {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No name specified!");
+       };
+
+       my $value = shift || '';
+
+       try {
+               my $name_id = _get_name_id($name);
+       
+               if ($self->api_name =~ /object/) {
+                       $dbh->do('DELETE FROM storage WHERE name_id = ?;', {}, $name_id);
+               }
+
+               $dbh->do('INSERT INTO storage (name_id,value) VALUES (?,?);', {}, $name_id, OpenSRF::Utils::JSON->perl2JSON($value));
+
+               _flush_by_name($name);
+
+               return $name;
+       } catch Error with {
+               return undef;
+       };
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.object.set',
+       method => 'add_item',
+       argc => 2,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.queue.push',
+       method => 'add_item',
+       argc => 2,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.stack.push',
+       method => 'add_item',
+       argc => 2,
+);
+
+sub _get_id_name {
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No slot id specified!");
+       };
+
+
+       my $name_id = $dbh->selectcol_arrayref("SELECT name FROM store_name WHERE id = ?;", {}, $name);
+
+       if (!ref($name_id) || !defined($name_id->[0])) {
+               throw OpenSRF::EX::WARN ("Slot id [$name] does not exist!");
+       }
+
+       return $name_id->[0];
+}
+
+sub _get_name_id {
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No slot name specified!");
+       };
+
+
+       my $name_id = $dbh->selectrow_arrayref("SELECT id FROM store_name WHERE name = ?;", {}, $name);
+
+       if (!ref($name_id) || !defined($name_id->[0])) {
+               throw OpenSRF::EX::WARN ("Slot name [$name] does not exist!");
+       }
+
+       return $name_id->[0];
+}
+
+sub destroy_store {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift;
+
+       my $problem = 0;
+       try {
+               my $name_id = _get_name_id($name);
+       
+               $dbh->do("DELETE FROM storage WHERE name_id = ?;", {}, $name_id);
+               $dbh->do("DELETE FROM store_name WHERE id = ?;", {}, $name_id);
+               $dbh->do("DELETE FROM store_expire WHERE id = ?;", {}, $name_id);
+
+               _sweep_expired_slots();
+               return $name;
+       } catch Error with {
+               return undef;
+       };
+
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.slot.destroy',
+       method => 'destroy_store',
+       argc => 1,
+);
+
+sub _flush_by_name {
+       my $name = shift;
+       my $no_sweep = shift;
+       my $name_id = _get_name_id($name);
+
+       unless ($no_sweep) {
+               _update_expire_atime($name);
+               _sweep_expired_slots();
+       }
+       
+       if ($name =~ /^AUTOGENERATED!!/) {
+               my $count = $dbh->selectcol_arrayref("SELECT COUNT(*) FROM storage WHERE name_id = ?;", {}, $name_id);
+               if (!ref($count) || $$count[0] == 0) {
+                       $dbh->do("DELETE FROM store_name WHERE name = ?;", {}, $name);
+               }
+       }
+}
+       
+sub pop_queue {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No queue name specified!");
+       };
+
+       try {
+               my $name_id = _get_name_id($name);
+
+               my $value = $dbh->selectrow_arrayref('SELECT id, value FROM storage WHERE name_id = ? ORDER BY id ASC LIMIT 1;', {}, $name_id);
+               $dbh->do('DELETE FROM storage WHERE id = ?;',{}, $value->[0]) unless ($self->api_name =~ /peek$/);
+
+               _flush_by_name($name);
+
+               return OpenSRF::Utils::JSON->JSON2perl( $value->[1] );
+       } catch Error with {
+               #my $e = shift;
+               #return $e;
+               return undef;
+       };
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.queue.peek',
+       method => 'pop_queue',
+       argc => 1,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.queue.pop',
+       method => 'pop_queue',
+       argc => 1,
+);
+
+
+sub peek_slot {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No slot name specified!");
+       };
+       my $name_id = _get_name_id($name);
+
+       my $order = 'ASC';
+       $order = 'DESC' if ($self->api_name =~ /stack/o);
+       
+       my $values = $dbh->selectall_arrayref("SELECT value FROM storage WHERE name_id = ? ORDER BY id $order;", {}, $name_id);
+
+       $client->respond( OpenSRF::Utils::JSON->JSON2perl( $_->[0] ) ) for (@$values);
+
+       _flush_by_name($name);
+       return undef;
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.queue.peek.all',
+       method => 'peek_slot',
+       argc => 1,
+       stream => 1,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.stack.peek.all',
+       method => 'peek_slot',
+       argc => 1,
+       stream => 1,
+);
+
+
+sub store_size {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No queue name specified!");
+       };
+       my $name_id = _get_name_id($name);
+
+       my $value = $dbh->selectcol_arrayref('SELECT SUM(LENGTH(value)) FROM storage WHERE name_id = ?;', {}, $name_id);
+
+       return OpenSRF::Utils::JSON->JSON2perl( $value->[0] );
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.queue.size',
+       method => 'shift_stack',
+       argc => 1,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.stack.size',
+       method => 'shift_stack',
+       argc => 1,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.object.size',
+       method => 'shift_stack',
+       argc => 1,
+);
+
+sub store_depth {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No queue name specified!");
+       };
+       my $name_id = _get_name_id($name);
+
+       my $value = $dbh->selectcol_arrayref('SELECT COUNT(*) FROM storage WHERE name_id = ?;', {}, $name_id);
+
+       return OpenSRF::Utils::JSON->JSON2perl( $value->[0] );
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.queue.length',
+       method => 'shift_stack',
+       argc => 1,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.stack.depth',
+       method => 'shift_stack',
+       argc => 1,
+);
+
+sub shift_stack {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No slot name specified!");
+       };
+
+       try {
+               my $name_id = _get_name_id($name);
+
+               my $value = $dbh->selectrow_arrayref('SELECT id, value FROM storage WHERE name_id = ? ORDER BY id DESC LIMIT 1;', {}, $name_id);
+               $dbh->do('DELETE FROM storage WHERE id = ?;',{}, $value->[0]) unless ($self->api_name =~ /peek$/);
+
+               _flush_by_name($name);
+
+               return OpenSRF::Utils::JSON->JSON2perl( $value->[1] );
+       } catch Error with {
+               my $e = shift;
+               return undef;
+       };
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.stack.peek',
+       method => 'shift_stack',
+       argc => 1,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.stack.pop',
+       method => 'shift_stack',
+       argc => 1,
+);
+
+sub get_object {
+       my $self = shift;
+       my $client = shift;
+
+       my $name = shift or do {
+               throw OpenSRF::EX::WARN ("No object name specified!");
+       };
+
+       try {
+               my $name_id = _get_name_id($name);
+
+               my $value = $dbh->selectrow_arrayref('SELECT name_id, value FROM storage WHERE name_id = ? ORDER BY id DESC LIMIT 1;', {}, $name_id);
+               $dbh->do('DELETE FROM storage WHERE name_id = ?',{}, $value->[0]) unless ($self->api_name =~ /peek$/);
+
+               _flush_by_name($name);
+
+               return OpenSRF::Utils::JSON->JSON2perl( $value->[1] );
+       } catch Error with {
+               return undef;
+       };
+}
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.object.peek',
+       method => 'shift_stack',
+       argc => 1,
+);
+__PACKAGE__->register_method(
+       api_name => 'opensrf.persist.object.get',
+       method => 'shift_stack',
+       argc => 1,
+);
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Application/Settings.pm b/trunk/src/perl/lib/OpenSRF/Application/Settings.pm
new file mode 100644 (file)
index 0000000..66d9f32
--- /dev/null
@@ -0,0 +1,42 @@
+package OpenSRF::Application::Settings;
+use OpenSRF::Application;
+use OpenSRF::Utils::SettingsParser;
+use OpenSRF::Utils::Logger qw/$logger/;
+use base 'OpenSRF::Application';
+
+sub child_exit {
+    $logger->debug("settings server child exiting...$$");
+}
+
+
+__PACKAGE__->register_method( method => 'get_host_config', api_name => 'opensrf.settings.host_config.get' );
+sub get_host_config {
+       my( $self, $client, $host ) = @_;
+       my $parser = OpenSRF::Utils::SettingsParser->new();
+       return $parser->get_server_config($host);
+}
+
+__PACKAGE__->register_method( method => 'get_default_config', api_name => 'opensrf.settings.default_config.get' );
+sub get_default_config {
+       my( $self, $client ) = @_;
+       my $parser = OpenSRF::Utils::SettingsParser->new();
+       return $parser->get_default_config();
+}
+
+
+
+
+__PACKAGE__->register_method( method => 'xpath_get', api_name => 'opensrf.settings.xpath.get' );
+
+__PACKAGE__->register_method( 
+               method  => 'xpath_get', 
+               api_name => 'opensrf.settings.xpath.get.raw' );
+
+sub xpath_get {
+       my($self, $client, $xpath) = @_;
+       warn "*************** Received XPATH $xpath\n";
+       return  OpenSRF::Utils::SettingsParser->new()->_get_all( $xpath );
+}
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/DomainObject/oilsMessage.pm b/trunk/src/perl/lib/OpenSRF/DomainObject/oilsMessage.pm
new file mode 100644 (file)
index 0000000..240089f
--- /dev/null
@@ -0,0 +1,339 @@
+package OpenSRF::DomainObject::oilsMessage;
+use OpenSRF::Utils::JSON;
+use OpenSRF::AppSession;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Utils::Logger qw/:level/;
+use warnings; use strict;
+use OpenSRF::EX qw/:try/;
+
+OpenSRF::Utils::JSON->register_class_hint(hint => 'osrfMessage', name => 'OpenSRF::DomainObject::oilsMessage', type => 'hash');
+
+sub toString {
+       my $self = shift;
+       my $pretty = shift;
+       return OpenSRF::Utils::JSON->perl2prettyJSON($self) if ($pretty);
+       return OpenSRF::Utils::JSON->perl2JSON($self);
+}
+
+sub new {
+       my $self = shift;
+       my $class = ref($self) || $self;
+       my %args = @_;
+       return bless \%args => $class;
+}
+
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsMessage
+
+=head1
+
+use OpenSRF::DomainObject::oilsMessage;
+
+my $msg = OpenSRF::DomainObject::oilsMessage->new( type => 'CONNECT' );
+
+$msg->payload( $domain_object );
+
+=head1 ABSTRACT
+
+OpenSRF::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 = 'OpenSRF::Utils::Logger';
+
+=head1 METHODS
+
+=head2 OpenSRF::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;
+       my $val = shift;
+       $self->{type} = $val if (defined $val);
+       return $self->{type};
+}
+
+=head2 OpenSRF::DomainObject::oilsMessage->api_level( [$new_api_level] )
+
+=over 4
+
+Used to specify the api_level of message.  Currently, only api_level 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 api_level {
+       my $self = shift;
+       my $val = shift;
+       $self->{api_level} = $val if (defined $val);
+       return $self->{api_level};
+}
+
+=head2 OpenSRF::DomainObject::oilsMessage->sender_locale( [$locale] );
+
+=over 4
+
+Sets or gets the current message locale hint.  Useful for telling the
+server how you see the world.
+
+=back
+
+=cut
+
+sub sender_locale {
+       my $self = shift;
+       my $val = shift;
+       $self->{locale} = $val if (defined $val);
+       return $self->{locale};
+}
+
+=head2 OpenSRF::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;
+       my $val = shift;
+       $self->{threadTrace} = $val if (defined $val);
+       return $self->{threadTrace};
+}
+
+=head2 OpenSRF::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 OpenSRF::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 $val = shift;
+       $self->{payload} = $val if (defined $val);
+       return $self->{payload};
+}
+
+=head2 OpenSRF::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 $locale = $self->sender_locale || '';
+       my $api_level = $self->api_level || 1;
+       my $tT = $self->threadTrace;
+
+    $log->debug("Message locale is $locale", DEBUG);
+
+       $session->last_message_type($mtype);
+       $session->last_message_api_level($api_level);
+       $session->last_threadTrace($tT);
+       $session->session_locale($locale);
+
+       $log->debug(" Received api_level => [$api_level], MType => [$mtype], ".
+                       "from [".$session->remote_id."], threadTrace[".$self->threadTrace."]");
+
+       my $val;
+       if ( $session->endpoint == $session->SERVER() ) {
+               $val = $self->do_server( $session, $mtype, $api_level, $tT );
+
+       } elsif ($session->endpoint == $session->CLIENT()) {
+               $val = $self->do_client( $session, $mtype, $api_level, $tT );
+       }
+
+       if( $val ) {
+               return OpenSRF::Application->handler($session, $self->payload);
+       } else {
+               $log->debug("Request was handled internally", DEBUG);
+       }
+
+       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, $api_level, $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->disconnect;
+               $session->kill_me;
+               return 0;
+       }
+
+       if ($session->state == $session->CONNECTING()) {
+
+               if($mtype ne "CONNECT" and $session->stateless) {
+                       return 1; #pass the message up the stack
+               }
+
+               # 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 = OpenSRF::DomainObject::oilsBrokenSession->new(
+                                       status => "Connection seems to be mangled: Got $mtype instead of CONNECT",
+                       );
+
+                       $session->status($res);
+                       $session->kill_me;
+                       return 0;
+
+               }
+               
+               my $res = OpenSRF::DomainObject::oilsConnectStatus->new;
+               $session->status($res);
+               $session->state( $session->CONNECTED );
+
+               return 0;
+       }
+
+
+       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, $api_level, $tT) = @_;
+
+
+       if ($mtype eq 'STATUS') {
+
+               if ($self->payload->statusCode == STATUS_OK) {
+                       $session->state($session->CONNECTED);
+                       $log->debug("We connected successfully to ".$session->app);
+                       return 0;
+               }
+
+               if ($self->payload->statusCode == STATUS_TIMEOUT) {
+                       $session->state( $session->DISCONNECTED );
+                       $session->reset;
+                       $session->connect;
+                       $session->push_resend( $session->app_request($self->threadTrace) );
+                       $log->debug("Disconnected because of timeout");
+                       return 0;
+
+               } elsif ($self->payload->statusCode == STATUS_REDIRECTED) {
+                       $session->state( $session->DISCONNECTED );
+                       $session->reset;
+                       $session->connect;
+                       $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) {
+                       $session->reset_request_timeout($self->threadTrace);
+                       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
+
+               #$session->state( $session->DISCONNECTED() );
+               #$session->reset;
+
+       } elsif ($session->state == $session->CONNECTING()) {
+               # This should be changed to check the type of response (is it a connectException?, etc.)
+       }
+
+       if( $self->payload and $self->payload->isa( "ERROR" ) ) { 
+               if ($session->raise_remote_errors) {
+                       $self->payload->throw();
+               }
+       }
+
+       $log->debug("oilsMessage passing to Application: " . $self->type." : ".$session->remote_id );
+
+       return 1;
+
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/DomainObject/oilsMethod.pm b/trunk/src/perl/lib/OpenSRF/DomainObject/oilsMethod.pm
new file mode 100644 (file)
index 0000000..f83727b
--- /dev/null
@@ -0,0 +1,99 @@
+package OpenSRF::DomainObject::oilsMethod;
+
+use OpenSRF::Utils::JSON;
+OpenSRF::Utils::JSON->register_class_hint(hint => 'osrfMethod', name => 'OpenSRF::DomainObject::oilsMethod', type => 'hash');
+
+sub toString {
+       my $self = shift;
+       my $pretty = shift;
+       return OpenSRF::Utils::JSON->perl2prettyJSON($self) if ($pretty);
+       return OpenSRF::Utils::JSON->perl2JSON($self);
+}
+
+sub new {
+       my $self = shift;
+       my $class = ref($self) || $self;
+       my %args = @_;
+       return bless \%args => $class;
+}
+
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsMethod
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsMethod;
+
+my $method = OpenSRF::DomainObject::oilsMethod->new( method => 'search' );
+
+$method->return_type( 'mods' );
+
+$method->params( 'title:harry potter' );
+
+$client->send( 'REQUEST', $method );
+
+=head1 METHODS
+
+=head2 OpenSRF::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;
+       my $val = shift;
+       $self->{method} = $val if (defined $val);
+       return $self->{method};
+}
+
+=head2 OpenSRF::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;
+       my $val = shift;
+       $self->{return_type} = $val if (defined $val);
+       return $self->{return_type};
+}
+
+=head2 OpenSRF::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 = @_;
+       $self->{params} = \@args if (@args);
+       return @{ $self->{params} };
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/DomainObject/oilsResponse.pm b/trunk/src/perl/lib/OpenSRF/DomainObject/oilsResponse.pm
new file mode 100644 (file)
index 0000000..aeaee77
--- /dev/null
@@ -0,0 +1,448 @@
+package OpenSRF::DomainObject::oilsResponse;
+use vars qw/@EXPORT_OK %EXPORT_TAGS/;
+use Exporter;
+use OpenSRF::Utils::JSON;
+use base qw/Exporter/;
+use OpenSRF::Utils::Logger qw/:level/;
+
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfResponse', name => 'OpenSRF::DomainObject::oilsResponse', type => 'hash' );
+
+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
+
+OpenSRF::DomainObject::oilsResponse
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+
+my $resp = OpenSRF::DomainObject::oilsResponse->new;
+
+$resp->status( 'a status message' );
+
+$resp->statusCode( STATUS_CONTINUE );
+
+$client->respond( $resp );
+
+=head1 ABSTRACT
+
+OpenSRF::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 = 'OpenSRF::Utils::Logger';
+
+sub toString {
+       my $self = shift;
+       my $pretty = shift;
+       return OpenSRF::Utils::JSON->perl2prettyJSON($self) if ($pretty);
+       return OpenSRF::Utils::JSON->perl2JSON($self);
+}
+
+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 bless( \%args => $class );
+}
+
+sub status {
+       my $self = shift;
+       my $val = shift;
+       $self->{status} = $val if (defined $val);
+       return $self->{status};
+}
+
+sub statusCode {
+       my $self = shift;
+       my $val = shift;
+       $self->{statusCode} = $val if (defined $val);
+       return $self->{statusCode};
+}
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsStatus;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use base 'OpenSRF::DomainObject::oilsResponse';
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfStatus', name => 'OpenSRF::DomainObject::oilsStatus', type => 'hash' );
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsException
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse;
+
+...
+
+# something happens.
+
+$client->status( OpenSRF::DomainObject::oilsStatus->new );
+
+=head1 ABSTRACT
+
+The base class for Status messages sent between client and server.  This
+is implemented on top of the C<OpenSRF::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 OpenSRF::DomainObject::oilsConnectStatus;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use base 'OpenSRF::DomainObject::oilsStatus';
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfConnectStatus', name => 'OpenSRF::DomainObject::oilsConnectStatus', type => 'hash' );
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsConnectStatus
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse;
+
+...
+
+# something happens.
+
+$client->status( new OpenSRF::DomainObject::oilsConnectStatus );
+
+=head1 ABSTRACT
+
+The class for Stati relating to the connection status of a session.  This
+is implemented on top of the C<OpenSRF::DomainObject::oilsStatus> class, and 
+sets the default B<status> to C<Connection Successful> and B<statusCode> to C<STATUS_OK>.
+
+=head1 SEE ALSO
+
+B<OpenSRF::DomainObject::oilsStatus>
+
+=cut
+
+$status = 'Connection Successful';
+$statusCode = STATUS_OK;
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsContinueStatus;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use base 'OpenSRF::DomainObject::oilsStatus';
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfContinueStatus', name => 'OpenSRF::DomainObject::oilsContinueStatus', type => 'hash' );
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsContinueStatus
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse;
+
+...
+
+# something happens.
+
+$client->status( new OpenSRF::DomainObject::oilsContinueStatus );
+
+=head1 ABSTRACT
+
+Implements the STATUS_CONTINUE message, informing the client that it should
+continue to wait for a response to its request.
+
+=head1 SEE ALSO
+
+B<OpenSRF::DomainObject::oilsStatus>
+
+=cut
+
+$status = 'Please hold.  Creating response...';
+$statusCode = STATUS_CONTINUE;
+
+1;
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsResult;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use base 'OpenSRF::DomainObject::oilsResponse';
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfResult', name => 'OpenSRF::DomainObject::oilsResult', type => 'hash' );
+
+
+$status = 'OK';
+$statusCode = STATUS_OK;
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsResult
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse;
+
+ .... do stuff, create $object ...
+
+my $res = OpenSRF::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<OpenSRF::DomainObject::oilsResponse>, and
+sets B<status> to C<OK> and B<statusCode> to C<STATUS_OK>.
+
+=head1 METHODS
+
+=head2 OpenSRF::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 $val = shift;
+
+       $self->{content} = $val if (defined $val);
+       return $self->{content};
+}
+
+=head1 SEE ALSO
+
+B<OpenSRF::DomainObject::oilsResponse>
+
+=cut
+
+1;
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsException;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::EX;
+use base qw/OpenSRF::EX OpenSRF::DomainObject::oilsResponse/;
+use vars qw/$status $statusCode/;
+use Error;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfException', name => 'OpenSRF::DomainObject::oilsException', type => 'hash' );
+
+sub message {
+       my $self = shift;
+       return '<' . $self->statusCode . '>  ' . $self->status;
+}
+
+sub new {
+       my $class = shift;
+       return $class->OpenSRF::DomainObject::oilsResponse::new( @_ );
+}
+
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsException
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse;
+
+...
+
+# something breaks.
+
+$client->send( 'ERROR', OpenSRF::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<OpenSRF::DomainObject::oilsResponse> class, and 
+sets the default B<status> to C<Exception occurred> and B<statusCode> to C<STATUS_BADREQUEST>.
+
+=cut
+
+$status = 'Exception occurred';
+$statusCode = STATUS_INTERNALSERVERERROR;
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsConnectException;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::EX;
+use base qw/OpenSRF::DomainObject::oilsException OpenSRF::EX::ERROR/;
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfConnectException', name => 'OpenSRF::DomainObject::oilsConnectException', type => 'hash' );
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsConnectException
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse;
+
+...
+
+# something breaks while connecting.
+
+$client->send( 'ERROR', new OpenSRF::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<OpenSRF::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<OpenSRF::DomainObject::oilsException>
+
+=cut
+
+
+$status = 'Connect Request Failed';
+$statusCode = STATUS_FORBIDDEN;
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsMethodException;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use base 'OpenSRF::DomainObject::oilsException';
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfMethodException', name => 'OpenSRF::DomainObject::oilsMethodException', type => 'hash' );
+
+=head1 NAME
+
+OpenSRF::DomainObject::oilsMethodException
+
+=head1 SYNOPSIS
+
+use OpenSRF::DomainObject::oilsResponse;
+
+...
+
+# something breaks while looking up or starting
+# a method call.
+
+$client->send( 'ERROR', new OpenSRF::DomainObject::oilsMethodException );
+
+=head1 ABSTRACT
+
+The class for Exceptions that occur during the B<CONNECT> phase of a session.  This
+is implemented on top of the C<OpenSRF::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<OpenSRF::DomainObject::oilsException>
+
+=cut
+
+
+$status = 'A server error occurred during method execution';
+$statusCode = STATUS_INTERNALSERVERERROR;
+
+# -------------------------------------------
+
+package OpenSRF::DomainObject::oilsServerError;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use base 'OpenSRF::DomainObject::oilsException';
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfServerError', name => 'OpenSRF::DomainObject::oilsServerError', type => 'hash' );
+
+$status = 'Internal Server Error';
+$statusCode = STATUS_INTERNALSERVERERROR;
+
+# -------------------------------------------
+
+package OpenSRF::DomainObject::oilsBrokenSession;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::EX;
+use base qw/OpenSRF::DomainObject::oilsException OpenSRF::EX::ERROR/;
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfBrokenSession', name => 'OpenSRF::DomainObject::oilsBrokenSession', type => 'hash' );
+$status = "Request on Disconnected Session";
+$statusCode = STATUS_EXPFAILED;
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsXMLParseError;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::EX;
+use base qw/OpenSRF::DomainObject::oilsException OpenSRF::EX::ERROR/;
+use vars qw/$status $statusCode/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfXMLParseError', name => 'OpenSRF::DomainObject::oilsXMLParseError', type => 'hash' );
+$status = "XML Parse Error";
+$statusCode = STATUS_EXPFAILED;
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::DomainObject::oilsAuthException;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::EX;
+use base qw/OpenSRF::DomainObject::oilsException OpenSRF::EX::ERROR/;
+OpenSRF::Utils::JSON->register_class_hint( hint => 'osrfAuthException', name => 'OpenSRF::DomainObject::oilsAuthException', type => 'hash' );
+use vars qw/$status $statusCode/;
+$status = "Authentication Failure";
+$statusCode = STATUS_FORBIDDEN;
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/EX.pm b/trunk/src/perl/lib/OpenSRF/EX.pm
new file mode 100644 (file)
index 0000000..bf86bda
--- /dev/null
@@ -0,0 +1,224 @@
+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'} .": $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($package, $file, $line) = get_caller();
+       my $name = ref($self);
+       my $msg = $self->message();
+
+    my ($sec,$min,$hour,$mday,$mon,$year) = localtime();
+    $year += 1900; $mon += 1;
+    my $date = sprintf(
+        '%s-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d',
+        $year, $mon, $mday, $hour, $min, $sec);
+
+    return "Exception: $name $date $package $file:$line $msg\n";
+}
+
+
+# --- determine the originating caller of this exception
+sub get_caller() {
+
+       my $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::Socket;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Socket Exception";
+
+=head2 OpenSRF::EX::Socket
+
+Thrown when there is a network layer exception
+
+=cut
+
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::Config;
+use base 'OpenSRF::EX::PANIC';
+our $ex_msg_header = "Config Exception";
+
+=head2 OpenSRF::EX::Config
+
+Thrown when a package requires a config option that it cannot retrieve
+or the config file itself cannot be loaded
+
+=cut
+
+
+# -------------------------------------------------------------------
+package OpenSRF::EX::User;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "User Exception";
+
+=head2 OpenSRF::EX::User
+
+Thrown when an error occurs due to user identification information
+
+=cut
+
+package OpenSRF::EX::Session;
+use base 'OpenSRF::EX::ERROR';
+our $ex_msg_header = "Session Error";
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/MultiSession.pm b/trunk/src/perl/lib/OpenSRF/MultiSession.pm
new file mode 100644 (file)
index 0000000..dd0579c
--- /dev/null
@@ -0,0 +1,283 @@
+package OpenSRF::MultiSession;
+use OpenSRF::AppSession;
+use OpenSRF::Utils::Logger;
+use Time::HiRes qw/time usleep/;
+
+my $log = 'OpenSRF::Utils::Logger';
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $self = bless {@_} => $class;
+
+       $self->{api_level} = 1 if (!defined($self->{api_level}));
+       $self->{session_hash_function} = \&_dummy_session_hash_function
+               if (!defined($self->{session_hash_function}));
+
+       if ($self->{cap}) {
+               $self->session_cap($self->{cap}) if (!$self->session_cap);
+               $self->request_cap($self->{cap}) if (!$self->request_cap);
+       }
+
+       if (!$self->session_cap) {
+               # XXX make adaptive the default once the logic is in place
+               #$self->adaptive(1);
+
+               $self->session_cap(10);
+       }
+       if (!$self->request_cap) {
+               # XXX make adaptive the default once the logic is in place
+               #$self->adaptive(1);
+
+               $self->request_cap(10);
+       }
+
+       $self->{sessions} = [];
+       $self->{running} = [];
+       $self->{completed} = [];
+       $self->{failed} = [];
+
+       for ( 1 .. $self->session_cap) {
+               push @{ $self->{sessions} },
+                       OpenSRF::AppSession->create(
+                               $self->{app},
+                               $self->{api_level},
+                               1
+                       );
+               #print "Creating connection ".$self->{sessions}->[-1]->session_id." ...\n";
+               $log->debug("Creating connection ".$self->{sessions}->[-1]->session_id." ...");
+       }
+
+       return $self;
+}
+
+sub _dummy_session_hash_function {
+       my $self = shift;
+       $self->{_dummy_hash_counter} = 1 if (!exists($self->{_dummy_hash_counter}));
+       return $self->{_dummy_hash_counter}++;
+}
+
+sub connect {
+       my $self = shift;
+       for my $ses (@{$self->{sessions}}) {
+               $ses->connect unless ($ses->connected);
+       }
+}
+
+sub finish {
+       my $self = shift;
+       $_->finish for (@{$self->{sessions}});
+}
+
+sub disconnect {
+       my $self = shift;
+       $_->disconnect for (@{$self->{sessions}});
+}
+
+sub session_hash_function {
+       my $self = shift;
+       my $session_hash_function = shift;
+       return unless (ref $self);
+
+       $self->{session_hash_function} = $session_hash_function if (defined $session_hash_function);
+       return $self->{session_hash_function};
+}
+
+sub failure_handler {
+       my $self = shift;
+       my $failure_handler = shift;
+       return unless (ref $self);
+
+       $self->{failure_handler} = $failure_handler if (defined $failure_handler);
+       return $self->{failure_handler};
+}
+
+sub success_handler {
+       my $self = shift;
+       my $success_handler = shift;
+       return unless (ref $self);
+
+       $self->{success_handler} = $success_handler if (defined $success_handler);
+       return $self->{success_handler};
+}
+
+sub session_cap {
+       my $self = shift;
+       my $cap = shift;
+       return unless (ref $self);
+
+       $self->{session_cap} = $cap if (defined $cap);
+       return $self->{session_cap};
+}
+
+sub request_cap {
+       my $self = shift;
+       my $cap = shift;
+       return unless (ref $self);
+
+       $self->{request_cap} = $cap if (defined $cap);
+       return $self->{request_cap};
+}
+
+sub adaptive {
+       my $self = shift;
+       my $adapt = shift;
+       return unless (ref $self);
+
+       $self->{adaptive} = $adapt if (defined $adapt);
+       return $self->{adaptive};
+}
+
+sub completed {
+       my $self = shift;
+       my $count = shift;
+       return unless (ref $self);
+
+
+       if (wantarray) {
+               $count ||= scalar @{$self->{completed}}; 
+       }
+
+       if (defined $count) {
+               return () unless (@{$self->{completed}});
+               return splice @{$self->{completed}}, 0, $count;
+       }
+
+       return scalar @{$self->{completed}};
+}
+
+sub failed {
+       my $self = shift;
+       my $count = shift;
+       return unless (ref $self);
+
+
+       if (wantarray) {
+               $count ||= scalar @{$self->{failed}}; 
+       }
+
+       if (defined $count) {
+               return () unless (@{$self->{failed}});
+               return splice @{$self->{failed}}, 0, $count;
+       }
+
+       return scalar @{$self->{failed}};
+}
+
+sub running {
+       my $self = shift;
+       return unless (ref $self);
+       return scalar(@{ $self->{running} });
+}
+
+
+sub request {
+       my $self = shift;
+       my $hash_param;
+
+       my $method = shift;
+       if (ref $method) {
+               $hash_param = $method;
+               $method = shift;
+       }
+
+       my @params = @_;
+
+       $self->session_reap;
+       if ($self->running < $self->request_cap ) {
+               my $index = $self->session_hash_function->($self, (defined $hash_param ? $hash_param : ()), $method, @params);
+               my $ses = $self->{sessions}->[$index % $self->session_cap]; 
+
+               #print "Running $method using session ".$ses->session_id."\n";
+
+               my $req = $ses->request( $method, @params );
+
+               push @{ $self->{running} },
+                       { req => $req,
+                         meth => $method,
+                         hash => $hash_param,
+                         params => [@params]
+                       };
+
+               $log->debug("Making request [$method] ".$self->running."...");
+
+               return $req;
+       } elsif (!$self->adaptive) {
+               #print "Oops.  Too many running: ".$self->running."\n";
+               $self->session_wait;
+               return $self->request((defined $hash_param ? $hash_param : ()), $method => @params);
+       } else {
+               # XXX do addaptive stuff ...
+       }
+}
+
+sub session_wait {
+       my $self = shift;
+       my $all = shift;
+
+       my $count;
+       if ($all) {
+               $count = $self->running;
+               while ($self->running) {
+                       $self->session_reap;
+               }
+               return $count;
+       } else {
+               while(($count = $self->session_reap) == 0 && $self->running) {
+                       usleep 100;
+               }
+               return $count;
+       }
+}
+
+sub session_reap {
+       my $self = shift;
+
+       my @done;
+       my @running;
+       while ( my $req = shift @{ $self->{running} } ) {
+               if ($req->{req}->complete) {
+                       #print "Currently running: ".$self->running."\n";
+
+                       $req->{response} = [ $req->{req}->recv ];
+                       $req->{duration} = $req->{req}->duration;
+
+                       #print "Completed ".$req->{meth}." in session ".$req->{req}->session->session_id." [$req->{duration}]\n";
+
+                       if ($req->{req}->failed) {
+                               #print "ARG!!!! Failed command $req->{meth} in session ".$req->{req}->session->session_id."\n";
+                               $req->{error} = $req->{req}->failed;
+                               push @{ $self->{failed} }, $req;
+                       } else {
+                               push @{ $self->{completed} }, $req;
+                       }
+
+                       push @done, $req;
+
+               } else {
+                       #$log->debug("Still running ".$req->{meth}." in session ".$req->{req}->session->session_id);
+                       push @running, $req;
+               }
+       }
+       push @{ $self->{running} }, @running;
+
+       for my $req ( @done ) {
+               my $handler = $req->{error} ? $self->failure_handler : $self->success_handler;
+               $handler->($self, $req) if ($handler);
+
+               $req->{req}->finish;
+               delete $$req{$_} for (keys %$req);
+
+       }
+
+       my $complete = scalar @done;
+       my $incomplete = scalar @running;
+
+       #$log->debug("Still running $incomplete, completed $complete");
+
+       return $complete;
+}
+
+1;
+
diff --git a/trunk/src/perl/lib/OpenSRF/System.pm b/trunk/src/perl/lib/OpenSRF/System.pm
new file mode 100644 (file)
index 0000000..1f11f6f
--- /dev/null
@@ -0,0 +1,71 @@
+package OpenSRF::System;
+use strict; use warnings;
+use OpenSRF;
+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::EX qw/:try/;
+use POSIX qw/setsid :sys_wait_h/;
+use OpenSRF::Utils::Config; 
+use OpenSRF::Utils::SettingsParser;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Application;
+use Net::Server::PreFork;
+
+my $bootstrap_config_file;
+sub import {
+       my( $self, $config ) = @_;
+       $bootstrap_config_file = $config;
+}
+
+$| = 1;
+
+sub DESTROY {}
+
+sub load_bootstrap_config {
+       return if OpenSRF::Utils::Config->current;
+
+    die "Please provide a bootstrap config file to OpenSRF::System\n"
+        unless $bootstrap_config_file;
+
+       OpenSRF::Utils::Config->load(config_file => $bootstrap_config_file);
+       OpenSRF::Utils::JSON->register_class_hint(name => "OpenSRF::Application", hint => "method", type => "hash");
+       OpenSRF::Transport->message_envelope("OpenSRF::Transport::SlimJabber::MessageWrapper");
+       OpenSRF::Transport::PeerHandle->set_peer_client("OpenSRF::Transport::SlimJabber::PeerConnection");
+       OpenSRF::Transport::Listener->set_listener("OpenSRF::Transport::SlimJabber::Inbound");
+       OpenSRF::Application->server_class('client');
+}
+
+# ----------------------------------------------
+# Bootstraps a single client connection.  
+# named params are 'config_file' and 'client_name'
+sub bootstrap_client {
+       my $self = shift;
+
+       my $con = OpenSRF::Transport::PeerHandle->retrieve;
+    return if $con and $con->tcp_connected;
+
+       my %params = @_;
+
+       $bootstrap_config_file = 
+               $params{config_file} || $bootstrap_config_file;
+
+       my $app = $params{client_name} || "client";
+
+       load_bootstrap_config();
+       OpenSRF::Utils::Logger::set_config();
+       OpenSRF::Transport::PeerHandle->construct($app);
+}
+
+sub connected {
+       if (my $con = OpenSRF::Transport::PeerHandle->retrieve) {
+               return 1 if $con->tcp_connected;
+       }
+       return 0;
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Transport.pm b/trunk/src/perl/lib/OpenSRF/Transport.pm
new file mode 100644 (file)
index 0000000..69e803e
--- /dev/null
@@ -0,0 +1,198 @@
+package OpenSRF::Transport;
+use strict; use warnings;
+use base 'OpenSRF';
+use Time::HiRes qw/time/;
+use OpenSRF::AppSession;
+use OpenSRF::Utils::JSON;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::EX qw/:try/;
+use OpenSRF::Transport::SlimJabber::MessageWrapper;
+
+#------------------ 
+# --- 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;
+               $envelope->use;
+               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 $start_time = time();
+       my( $class, $service, $data ) = @_;
+
+       $logger->transport( "Transport handler() received $data", INTERNAL );
+
+       my $remote_id   = $data->from;
+       my $sess_id     = $data->thread;
+       my $body        = $data->body;
+       my $type        = $data->type;
+
+       $logger->set_osrf_xid($data->osrf_xid);
+
+
+       if (defined($type) and $type eq 'error') {
+               throw OpenSRF::EX::Session ("$remote_id IS NOT CONNECTED TO THE NETWORK!!!");
+
+       }
+
+       # 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 ) {
+
+           my $c = OpenSRF::Utils::SettingsClient->new();
+        if($c->config_value("apps", $app_session->service, "migratable")) {
+            $logger->debug("service is migratable, new client is $remote_id");
+        } else {
+
+                   $logger->warn("Backend Gone or invalid sender");
+                   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 JSON contained within the message 
+       my $doc; 
+       eval { $doc = OpenSRF::Utils::JSON->JSON2perl($body); };
+       if( $@ ) {
+
+               $logger->warn("Received bogus JSON: $@");
+               $logger->warn("Bogus JSON data: $body");
+               my $res = OpenSRF::DomainObject::oilsXMLParseError->new( status => "JSON Parse Error --- $body\n\n$@" );
+
+               $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 );
+                       # below will lead to infinite looping, should return an exception
+                       #$app_session->push_resend( $app_session->app_request( 
+                       #               $doc->documentElement->firstChild->threadTrace ) );
+                       $logger->debug(
+                               "Got Jabber error on client connection $remote_id, nothing we can do..", ERROR );
+                       return 1;
+               }
+       }
+
+       # cycle through and pass each oilsMessage contained in the message
+       # up to the message layer for processing.
+       for my $msg (@$doc) {
+
+               next unless (   $msg && UNIVERSAL::isa($msg => 'OpenSRF::DomainObject::oilsMessage'));
+
+               if( $app_session->endpoint == $app_session->SERVER() ) {
+
+                       try {  
+
+                               if( ! $msg->handler( $app_session ) ) { return 0; }
+
+                               $logger->debug("Successfully handled message", DEBUG);
+
+                       } catch Error with {
+
+                               my $e = shift;
+                               my $res = OpenSRF::DomainObject::oilsServerError->new();
+                               $res->status( $res->status . "\n$e");
+                               $app_session->status($res) if $res;
+                               $app_session->kill_me;
+                               return 0;
+
+                       };
+
+               } else { 
+
+                       if( ! $msg->handler( $app_session ) ) { return 0; } 
+                       $logger->info("Successfully handled message", DEBUG);
+
+               }
+
+       }
+
+       $logger->debug(sprintf("Message processing duration: %.3fs",(time() - $start_time)), DEBUG);
+
+       return $app_session;
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/Listener.pm b/trunk/src/perl/lib/OpenSRF/Transport/Listener.pm
new file mode 100644 (file)
index 0000000..afa9fb7
--- /dev/null
@@ -0,0 +1,45 @@
+package OpenSRF::Transport::Listener;
+use base 'OpenSRF';
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::Transport::SlimJabber::Inbound;
+use base 'OpenSRF::Transport::SlimJabber::Inbound';
+
+=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 ) = @_;
+       OpenSRF::Utils::Logger->debug("Loading Listener $listener");
+       if( $listener ) {
+               $listener->use;
+               if( $@ ) {
+                       OpenSRF::Utils::Logger->error(
+                                       "Unable to set transport listener: $@", ERROR );
+               }
+               unshift @ISA, $listener;
+       }
+}
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/PeerHandle.pm b/trunk/src/perl/lib/OpenSRF/Transport/PeerHandle.pm
new file mode 100644 (file)
index 0000000..e263971
--- /dev/null
@@ -0,0 +1,40 @@
+package OpenSRF::Transport::PeerHandle;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX;
+use base qw/OpenSRF::Transport::SlimJabber::PeerConnection/;
+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 ) {
+               $peer->use;
+               if( $@ ) {
+                       throw OpenSRF::EX::PANIC ( "Unable to set peer client: $@" );
+               }
+               unshift @ISA, $peer;
+       }
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber.pm b/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber.pm
new file mode 100644 (file)
index 0000000..7963b93
--- /dev/null
@@ -0,0 +1,18 @@
+package OpenSRF::Transport::SlimJabber;
+use base qw/OpenSRF::Transport/;
+
+=head2 OpenSRF::Transport::SlimJabber
+
+Implements the Transport interface for providing the system with appropriate
+classes for handling transport layer messaging
+
+=cut
+
+
+sub get_listener { return "OpenSRF::Transport::SlimJabber::Inbound"; }
+
+sub get_peer_client { return "OpenSRF::Transport::SlimJabber::PeerConnection"; }
+
+sub get_msg_envelope { return "OpenSRF::Transport::SlimJabber::MessageWrapper"; }
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/Client.pm b/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/Client.pm
new file mode 100644 (file)
index 0000000..e6f6705
--- /dev/null
@@ -0,0 +1,204 @@
+package OpenSRF::Transport::SlimJabber::Client;
+
+use strict;
+use warnings;
+
+use OpenSRF::EX;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenSRF::Transport::SlimJabber::XMPPReader;
+use OpenSRF::Transport::SlimJabber::XMPPMessage;
+use IO::Socket::UNIX;
+use FreezeThaw qw/freeze/;
+
+sub DESTROY{
+    shift()->disconnect;
+}
+
+=head1 NAME
+
+OpenSRF::Transport::SlimJabber::Client
+
+=head1 SYNOPSIS
+
+
+
+=head1 DESCRIPTION
+
+
+
+=cut
+
+=head1 METHODS
+
+=head2 new
+
+=cut
+
+sub new {
+       my( $class, %params ) = @_;
+    my $self = bless({}, ref($class) || $class);
+    $self->params(\%params);
+       return $self;
+}
+
+=head2 reader
+
+=cut
+
+sub reader {
+    my($self, $reader) = @_;
+    $self->{reader} = $reader if $reader;
+    return $self->{reader};
+}
+
+=head2 params
+
+=cut
+
+sub params {
+    my($self, $params) = @_;
+    $self->{params} = $params if $params;
+    return $self->{params};
+}
+
+=head2 socket
+
+=cut
+
+sub socket {
+    my($self, $socket) = @_;
+    $self->{socket} = $socket if $socket;
+    return $self->{socket};
+}
+
+=head2 disconnect
+
+=cut
+
+sub disconnect {
+    my $self = shift;
+       $self->reader->disconnect if $self->reader;
+}
+
+
+=head2 gather
+
+=cut
+
+sub gather { 
+    my $self = shift; 
+    $self->process( 0 ); 
+}
+
+# -------------------------------------------------
+
+=head2 tcp_connected
+
+=cut
+
+sub tcp_connected {
+       my $self = shift;
+    return $self->reader->tcp_connected if $self->reader;
+    return 0;
+}
+
+
+
+=head2 send
+
+=cut
+
+sub send {
+       my $self = shift;
+    my $msg = OpenSRF::Transport::SlimJabber::XMPPMessage->new(@_);
+    $msg->osrf_xid($logger->get_osrf_xid);
+    $self->reader->send($msg->to_xml);
+}
+
+=head2 initialize
+
+=cut
+
+sub initialize {
+
+       my $self = shift;
+
+       my $host        = $self->params->{host}; 
+       my $port        = $self->params->{port}; 
+       my $username    = $self->params->{username};
+       my $resource    = $self->params->{resource};
+       my $password    = $self->params->{password};
+
+    my $jid = "$username\@$host/$resource";
+
+       my $conf = OpenSRF::Utils::Config->current;
+
+       my $tail = "_$$";
+       $tail = "" if !$conf->bootstrap->router_name and $username eq "router";
+    $resource = "$resource$tail";
+
+    my $socket = IO::Socket::INET->new(
+        PeerHost => $host,
+        PeerPort => int($port),
+        Proto  => 'tcp' );
+
+    throw OpenSRF::EX::Jabber("Could not open TCP socket to Jabber server: $@")
+           unless ( $socket and $socket->connected );
+
+    $self->socket($socket);
+    $self->reader(OpenSRF::Transport::SlimJabber::XMPPReader->new($socket));
+    $self->reader->connect($host, $username, $password, $resource);
+
+    throw OpenSRF::EX::Jabber("Could not authenticate with Jabber server: $@")
+           unless ( $self->reader->connected );
+
+       return $self;
+}
+
+
+=head2 construct
+
+=cut
+
+sub construct {
+       my( $class, $app ) = @_;
+       $class->peer_handle($class->new( $app )->initialize());
+}
+
+
+=head2 process
+
+=cut
+
+sub process {
+       my($self, $timeout) = @_;
+
+       $timeout ||= 0;
+    $timeout = int($timeout);
+
+       unless( $self->reader and $self->reader->connected ) {
+        throw OpenSRF::EX::JabberDisconnected 
+            ("This JabberClient instance is no longer connected to the server ");
+       }
+
+    return $self->reader->wait_msg($timeout);
+}
+
+
+=head2 flush_socket
+
+Sets the socket to O_NONBLOCK, reads all of the data off of the
+socket, the restores the sockets flags.  Returns 1 on success, 0 if
+the socket isn't connected.
+
+=cut
+
+sub flush_socket {
+       my $self = shift;
+    return $self->reader->flush_socket;
+}
+
+1;
+
+
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/Inbound.pm b/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/Inbound.pm
new file mode 100644 (file)
index 0000000..9194927
--- /dev/null
@@ -0,0 +1,165 @@
+package OpenSRF::Transport::SlimJabber::Inbound;
+use strict;use warnings;
+use base qw/OpenSRF::Transport::SlimJabber::Client/;
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Config;
+use Time::HiRes qw/usleep/;
+use FreezeThaw qw/freeze/;
+
+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
+
+{
+       my $unix_sock;
+       sub unix_sock { return $unix_sock; }
+       my $instance;
+
+       sub new {
+               my( $class, $app ) = @_;
+               $class = ref( $class ) || $class;
+               if( ! $instance ) {
+
+                       my $conf = OpenSRF::Utils::Config->current;
+                       my $domain = $conf->bootstrap->domain;
+            $logger->error("use of <domains/> is deprecated") if $conf->bootstrap->domains;
+
+                       my $username    = $conf->bootstrap->username;
+                       my $password    = $conf->bootstrap->passwd;
+                       my $port                        = $conf->bootstrap->port;
+                       my $host                        = $domain;
+                       my $resource    = $app . '_listener_at_' . $conf->env->hostname;
+
+                       my $router_name = $conf->bootstrap->router_name;
+                       # no router, only one listener running..
+                       if(!$router_name) { 
+                               $username = "router";
+                               $resource = $app; 
+                       }
+
+                       OpenSRF::Utils::Logger->transport("Inbound as $username, $password, $resource, $host, $port\n", INTERNAL );
+
+                       my $self = __PACKAGE__->SUPER::new( 
+                                       username                => $username,
+                                       resource                => $resource,
+                                       password                => $password,
+                                       host                    => $host,
+                                       port                    => $port,
+                                       );
+
+                       $self->{app} = $app;
+                                       
+                       my $client = OpenSRF::Utils::SettingsClient->new();
+                       my $f = $client->config_value("dirs", "sock");
+                       $unix_sock = join( "/", $f, 
+                                       $client->config_value("apps", $app, "unix_config", "unix_sock" ));
+                       bless( $self, $class );
+                       $instance = $self;
+               }
+               return $instance;
+       }
+
+}
+
+sub DESTROY {
+       my $self = shift;
+       for my $router (@{$self->{routers}}) {
+               if($self->tcp_connected()) {
+            $logger->info("disconnecting from router $router");
+                       $self->send( to => $router, body => "registering", 
+                               router_command => "unregister" , router_class => $self->{app} );
+               }
+       }
+}
+       
+sub listen {
+       my $self = shift;
+       
+    $self->{routers} = [];
+
+       try {
+
+               my $conf = OpenSRF::Utils::Config->current;
+        my $router_name = $conf->bootstrap->router_name;
+               my $routers = $conf->bootstrap->routers;
+        $logger->info("loading router info $routers");
+
+        for my $router (@$routers) {
+            if(ref $router) {
+                if( !$router->{services} || grep { $_ eq $self->{app} } @{$router->{services}->{service}} ) {
+                    my $name = $router->{name};
+                    my $domain = $router->{domain};
+                    my $target = "$name\@$domain/router";
+                    push(@{$self->{routers}}, $target);
+                    $logger->info( $self->{app} . " connecting to router $target");
+                    $self->send( to => $target, body => "registering", router_command => "register" , router_class => $self->{app} );
+                }
+            } else {
+                my $target = "$router_name\@$router/router";
+                push(@{$self->{routers}}, $target);
+                $logger->info( $self->{app} . " connecting to router $target");
+                $self->send( to => $target, body => "registering", router_command => "register" , router_class => $self->{app} );
+            }
+        }
+               
+       } catch Error with {
+               $logger->transport( $self->{app} . ": No routers defined" , WARN ); 
+               # no routers defined
+       };
+
+
+       
+                       
+       $logger->transport( $self->{app} . " going into listen loop", INFO );
+
+       while(1) {
+       
+               my $sock = $self->unix_sock();
+               my $o;
+
+               $logger->debug("Inbound listener calling process()");
+
+               try {
+                       $o = $self->process(-1);
+
+                       if(!$o){
+                               $logger->error(
+                                       "Inbound received no data from the Jabber socket in process()");
+                               usleep(100000); # otherwise we loop and pound syslog logger with errors
+                       }
+
+               } catch OpenSRF::EX::JabberDisconnected with {
+
+                       $logger->error("Inbound process lost its ".
+                               "jabber connection.  Attempting to reconnect...");
+                       $self->initialize;
+                       $o = undef;
+               };
+
+
+               if($o) {
+                       my $socket = IO::Socket::UNIX->new( Peer => $sock  );
+                       throw OpenSRF::EX::Socket( 
+                               "Unable to connect to UnixServer: socket-file: $sock \n :=> $! " )
+                               unless ($socket->connected);
+                       print $socket freeze($o);
+                       $socket->close;
+               } 
+       }
+
+       throw OpenSRF::EX::Socket( "How did we get here?!?!" );
+}
+
+1;
+
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/MessageWrapper.pm b/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/MessageWrapper.pm
new file mode 100644 (file)
index 0000000..0fa95c5
--- /dev/null
@@ -0,0 +1,72 @@
+package OpenSRF::Transport::SlimJabber::MessageWrapper;
+use strict; use warnings;
+use OpenSRF::Transport::SlimJabber::XMPPMessage;
+
+# ----------------------------------------------------------
+# Legacy wrapper for XMPPMessage
+# ----------------------------------------------------------
+
+sub new {
+       my $class = shift;
+    my $msg = shift;
+    return bless({msg => $msg}, ref($class) || $class);
+}
+
+sub msg {
+    my($self, $msg) = @_;
+    $self->{msg} = $msg if $msg;
+    return $self->{msg};
+}
+
+sub toString {
+    return $_[0]->msg->to_xml;
+}
+
+sub get_body {
+    return $_[0]->msg->body;
+}
+
+sub get_sess_id {
+    return $_[0]->msg->thread;
+}
+
+sub get_msg_type {
+    return $_[0]->msg->type;
+}
+
+sub get_remote_id {
+    return $_[0]->msg->from;
+}
+
+sub setType {
+    $_[0]->msg->type(shift());
+}
+
+sub setTo {
+    $_[0]->msg->to(shift());
+}
+
+sub setThread {
+    $_[0]->msg->thread(shift());
+}
+
+sub setBody {
+    $_[0]->msg->body(shift());
+}
+
+sub set_router_command {
+    $_[0]->msg->router_command(shift());
+}
+sub set_router_class {
+    $_[0]->msg->router_class(shift());
+}
+
+sub set_osrf_xid {
+    $_[0]->msg->osrf_xid(shift());
+}
+
+sub get_osrf_xid {
+   return $_[0]->msg->osrf_xid;
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/PeerConnection.pm b/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/PeerConnection.pm
new file mode 100644 (file)
index 0000000..040ffb8
--- /dev/null
@@ -0,0 +1,99 @@
+package OpenSRF::Transport::SlimJabber::PeerConnection;
+use strict;
+use base qw/OpenSRF::Transport::SlimJabber::Client/;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX qw/:try/;
+
+=head1 Description
+
+Represents a single connection to a remote peer.  The 
+Jabber values are loaded from the config file.  
+
+Subclasses OpenSRF::Transport::SlimJabber::Client.
+
+=cut
+
+=head2 new()
+
+       new( $appname );
+
+       The $appname parameter tells this class how to find the correct
+       Jabber username, password, etc to connect to the server.
+
+=cut
+
+our %apps_hash;
+our $_singleton_connection;
+
+sub retrieve { 
+       my( $class, $app ) = @_;
+       return $_singleton_connection;
+}
+
+
+sub new {
+       my( $class, $app ) = @_;
+
+       my $peer_con = $class->retrieve;
+       return $peer_con if ($peer_con and $peer_con->tcp_connected);
+
+       my $config = OpenSRF::Utils::Config->current;
+
+       if( ! $config ) {
+               throw OpenSRF::EX::Config( "No suitable config found for PeerConnection" );
+       }
+
+       my $conf                        = OpenSRF::Utils::Config->current;
+       my $domain = $conf->bootstrap->domain;
+       my $h = $conf->env->hostname;
+       OpenSRF::Utils::Logger->error("use of <domains/> is deprecated") if $conf->bootstrap->domains;
+
+       my $username    = $conf->bootstrap->username;
+       my $password    = $conf->bootstrap->passwd;
+       my $port        = $conf->bootstrap->port;
+       my $resource    = "${app}_drone_at_$h";
+       my $host        = $domain; # XXX for now...
+
+       if( $app eq "client" ) { $resource = "client_at_$h"; }
+
+       OpenSRF::EX::Config->throw( "JPeer could not load all necessary values from config" )
+               unless ( $username and $password and $resource and $host and $port );
+
+       OpenSRF::Utils::Logger->transport( "Built Peer with", INTERNAL );
+
+       my $self = __PACKAGE__->SUPER::new( 
+               username                => $username,
+               resource                => $resource,
+               password                => $password,
+               host                    => $host,
+               port                    => $port,
+               );      
+                                       
+       bless( $self, $class );
+
+       $self->app($app);
+
+       $_singleton_connection = $self;
+       $apps_hash{$app} = $self;
+
+       return $_singleton_connection;
+       return $apps_hash{$app};
+}
+
+sub process {
+       my $self = shift;
+       my $val = $self->SUPER::process(@_);
+       return 0 unless $val;
+       return OpenSRF::Transport->handler($self->app, $val);
+}
+
+sub app {
+       my $self = shift;
+       my $app = shift;
+       $self->{app} = $app if $app;
+       return $self->{app};
+}
+
+1;
+
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/XMPPMessage.pm b/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/XMPPMessage.pm
new file mode 100644 (file)
index 0000000..9bd5328
--- /dev/null
@@ -0,0 +1,134 @@
+package OpenSRF::Transport::SlimJabber::XMPPMessage;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenSRF::EX qw/:try/;
+use strict; use warnings;
+use XML::LibXML;
+
+use constant JABBER_MESSAGE =>
+    "<message to='%s' from='%s' router_command='%s' router_class='%s' osrf_xid='%s'>".
+    "<thread>%s</thread><body>%s</body></message>";
+
+sub new {
+    my $class = shift;
+    my %args = @_;
+    my $self = bless({}, $class);
+
+    if($args{xml}) {
+        $self->parse_xml($args{xml});
+
+    } else {
+        $self->{to} = $args{to} || '';
+        $self->{from} = $args{from} || '';
+        $self->{thread} = $args{thread} || '';
+        $self->{body} = $args{body} || '';
+        $self->{osrf_xid} = $args{osrf_xid} || '';
+        $self->{router_command} = $args{router_command} || '';
+        $self->{router_class} = $args{router_class} || '';
+    }
+
+    return $self;
+}
+
+sub to {
+    my($self, $to) = @_;
+    $self->{to} = $to if defined $to;
+    return $self->{to};
+}
+sub from {
+    my($self, $from) = @_;
+    $self->{from} = $from if defined $from;
+    return $self->{from};
+}
+sub thread {
+    my($self, $thread) = @_;
+    $self->{thread} = $thread if defined $thread;
+    return $self->{thread};
+}
+sub body {
+    my($self, $body) = @_;
+    $self->{body} = $body if defined $body;
+    return $self->{body};
+}
+sub status {
+    my($self, $status) = @_;
+    $self->{status} = $status if defined $status;
+    return $self->{status};
+}
+sub type {
+    my($self, $type) = @_;
+    $self->{type} = $type if defined $type;
+    return $self->{type};
+}
+sub err_type {
+    my($self, $err_type) = @_;
+    $self->{err_type} = $err_type if defined $err_type;
+    return $self->{err_type};
+}
+sub err_code {
+    my($self, $err_code) = @_;
+    $self->{err_code} = $err_code if defined $err_code;
+    return $self->{err_code};
+}
+sub osrf_xid {
+    my($self, $osrf_xid) = @_;
+    $self->{osrf_xid} = $osrf_xid if defined $osrf_xid;
+    return $self->{osrf_xid};
+}
+sub router_command {
+    my($self, $router_command) = @_;
+    $self->{router_command} = $router_command if defined $router_command;
+    return $self->{router_command};
+}
+sub router_class {
+    my($self, $router_class) = @_;
+    $self->{router_class} = $router_class if defined $router_class;
+    return $self->{router_class};
+}
+
+
+sub to_xml {
+    my $self = shift;
+
+    my $body = $self->{body};
+    $body =~ s/&/&amp;/sog;
+    $body =~ s/</&lt;/sog;
+    $body =~ s/>/&gt;/sog;
+
+    return sprintf(
+        JABBER_MESSAGE,
+        $self->{to},
+        $self->{from},
+        $self->{router_command},
+        $self->{router_class},
+        $self->{osrf_xid},
+        $self->{thread},
+        $body
+    );
+}
+
+sub parse_xml {
+    my($self, $xml) = @_;
+    my($doc, $err);
+
+    try {
+        $doc = XML::LibXML->new->parse_string($xml);
+    } catch Error with {
+        my $err = shift;
+        $logger->error("Error parsing message xml: $xml --- $err");
+    };
+    throw $err if $err;
+
+    my $root = $doc->documentElement;
+
+    $self->{body} = $root->findnodes('/message/body').'';
+    $self->{thread} = $root->findnodes('/message/thread').'';
+    $self->{from} = $root->getAttribute('router_from');
+    $self->{from} = $root->getAttribute('from') unless $self->{from};
+    $self->{to} = $root->getAttribute('to');
+    $self->{type} = $root->getAttribute('type');
+    $self->{osrf_xid} = $root->getAttribute('osrf_xid');
+}
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/XMPPReader.pm b/trunk/src/perl/lib/OpenSRF/Transport/SlimJabber/XMPPReader.pm
new file mode 100644 (file)
index 0000000..086a7a6
--- /dev/null
@@ -0,0 +1,352 @@
+package OpenSRF::Transport::SlimJabber::XMPPReader;
+use strict; use warnings;
+use XML::Parser;
+use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
+use Time::HiRes qw/time/;
+use OpenSRF::Transport::SlimJabber::XMPPMessage;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+# -----------------------------------------------------------
+# Connect, disconnect, and authentication messsage templates
+# -----------------------------------------------------------
+use constant JABBER_CONNECT =>
+    "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+
+use constant JABBER_BASIC_AUTH =>
+    "<iq id='123' type='set'><query xmlns='jabber:iq:auth'>" .
+    "<username>%s</username><password>%s</password><resource>%s</resource></query></iq>";
+
+use constant JABBER_DISCONNECT => "</stream:stream>";
+
+
+# -----------------------------------------------------------
+# XMPP Stream states
+# -----------------------------------------------------------
+use constant DISCONNECTED   => 1;
+use constant CONNECT_RECV   => 2;
+use constant CONNECTED      => 3;
+
+
+# -----------------------------------------------------------
+# XMPP Message states
+# -----------------------------------------------------------
+use constant IN_NOTHING => 1;
+use constant IN_BODY    => 2;
+use constant IN_THREAD  => 3;
+use constant IN_STATUS  => 4;
+
+
+# -----------------------------------------------------------
+# Constructor, getter/setters
+# -----------------------------------------------------------
+sub new {
+    my $class = shift;
+    my $socket = shift;
+
+    my $self = bless({}, $class);
+
+    $self->{queue} = [];
+    $self->{stream_state} = DISCONNECTED;
+    $self->{xml_state} = IN_NOTHING;
+    $self->socket($socket);
+
+    my $p = new XML::Parser(Handlers => {
+        Start => \&start_element,
+        End   => \&end_element,
+        Char  => \&characters,
+    });
+
+    $self->parser($p->parse_start); # create a push parser
+    $self->parser->{_parent_} = $self;
+    $self->{message} = OpenSRF::Transport::SlimJabber::XMPPMessage->new;
+    return $self;
+}
+
+sub push_msg {
+    my($self, $msg) = @_; 
+    push(@{$self->{queue}}, $msg) if $msg;
+}
+
+sub next_msg {
+    my $self = shift;
+    return shift @{$self->{queue}};
+}
+
+sub peek_msg {
+    my $self = shift;
+    return (@{$self->{queue}} > 0);
+}
+
+sub parser {
+    my($self, $parser) = @_;
+    $self->{parser} = $parser if $parser;
+    return $self->{parser};
+}
+
+sub socket {
+    my($self, $socket) = @_;
+    $self->{socket} = $socket if $socket;
+    return $self->{socket};
+}
+
+sub stream_state {
+    my($self, $stream_state) = @_;
+    $self->{stream_state} = $stream_state if $stream_state;
+    return $self->{stream_state};
+}
+
+sub xml_state {
+    my($self, $xml_state) = @_;
+    $self->{xml_state} = $xml_state if $xml_state;
+    return $self->{xml_state};
+}
+
+sub message {
+    my($self, $message) = @_;
+    $self->{message} = $message if $message;
+    return $self->{message};
+}
+
+
+# -----------------------------------------------------------
+# Stream and connection handling methods
+# -----------------------------------------------------------
+
+sub connect {
+    my($self, $domain, $username, $password, $resource) = @_;
+    
+    $self->send(sprintf(JABBER_CONNECT, $domain));
+    $self->wait(10);
+
+    unless($self->{stream_state} == CONNECT_RECV) {
+        $logger->error("No initial XMPP response from server");
+        return 0;
+    }
+
+    $self->send(sprintf(JABBER_BASIC_AUTH, $username, $password, $resource));
+    $self->wait(10);
+
+    unless($self->connected) {
+        $logger->error('XMPP connect failed');
+        return 0;
+    }
+
+    return 1;
+}
+
+sub disconnect {
+    my $self = shift;
+    if($self->tcp_connected) {
+        $self->send(JABBER_DISCONNECT); 
+        shutdown($self->socket, 2);
+    }
+    close($self->socket);
+}
+
+# -----------------------------------------------------------
+# returns true if this stream is connected to the server
+# -----------------------------------------------------------
+sub connected {
+    my $self = shift;
+    return ($self->tcp_connected and $self->{stream_state} == CONNECTED);
+}
+
+# -----------------------------------------------------------
+# returns true if the socket is connected
+# -----------------------------------------------------------
+sub tcp_connected {
+    my $self = shift;
+    return ($self->socket and $self->socket->connected);
+}
+
+# -----------------------------------------------------------
+# sends pre-formated XML
+# -----------------------------------------------------------
+sub send {
+    my($self, $xml) = @_;
+    $self->{socket}->print($xml);
+}
+
+# -----------------------------------------------------------
+# Puts a file handle into blocking mode
+# -----------------------------------------------------------
+sub set_block {
+    my $fh = shift;
+    my  $flags = fcntl($fh, F_GETFL, 0);
+    $flags &= ~O_NONBLOCK;
+    fcntl($fh, F_SETFL, $flags);
+}
+
+
+# -----------------------------------------------------------
+# Puts a file handle into non-blocking mode
+# -----------------------------------------------------------
+sub set_nonblock {
+    my $fh = shift;
+    my  $flags = fcntl($fh, F_GETFL, 0);
+    fcntl($fh, F_SETFL, $flags | O_NONBLOCK);
+}
+
+
+sub wait {
+    my($self, $timeout) = @_;
+     
+    return $self->next_msg if $self->peek_msg;
+
+    $timeout ||= 0;
+    $timeout = undef if $timeout < 0;
+    my $socket = $self->{socket};
+
+    set_block($socket);
+    
+    # build the select readset
+    my $infile = '';
+    vec($infile, $socket->fileno, 1) = 1;
+    return undef unless select($infile, undef, undef, $timeout);
+
+    # now slurp the data off the socket
+    my $buf;
+    my $read_size = 1024;
+    while(my $n = sysread($socket, $buf, $read_size)) {
+        $self->{parser}->parse_more($buf) if $buf;
+        if($n < $read_size or $self->peek_msg) {
+            set_block($socket);
+            last;
+        }
+        set_nonblock($socket);
+    }
+
+    return $self->next_msg;
+}
+
+# -----------------------------------------------------------
+# Waits up to timeout seconds for a fully-formed XMPP
+# message to arrive.  If timeout is < 0, waits indefinitely
+# -----------------------------------------------------------
+sub wait_msg {
+    my($self, $timeout) = @_;
+    my $xml;
+
+    $timeout = 0 unless defined $timeout;
+
+    if($timeout < 0) {
+        while(1) {
+            return $xml if $xml = $self->wait($timeout); 
+        }
+
+    } else {
+        while($timeout >= 0) {
+            my $start = time;
+            return $xml if $xml = $self->wait($timeout); 
+            $timeout -= time - $start;
+        }
+    }
+
+    return undef;
+}
+
+
+# -----------------------------------------------------------
+# SAX Handlers
+# -----------------------------------------------------------
+
+
+sub start_element {
+    my($parser, $name, %attrs) = @_;
+    my $self = $parser->{_parent_};
+
+    if($name eq 'message') {
+
+        my $msg = $self->{message};
+        $msg->{to} = $attrs{'to'};
+        $msg->{from} = $attrs{router_from} if $attrs{router_from};
+        $msg->{from} = $attrs{from} unless $msg->{from};
+        $msg->{osrf_xid} = $attrs{'osrf_xid'};
+        $msg->{type} = $attrs{type};
+
+    } elsif($name eq 'body') {
+        $self->{xml_state} = IN_BODY;
+
+    } elsif($name eq 'thread') {
+        $self->{xml_state} = IN_THREAD;
+
+    } elsif($name eq 'stream:stream') {
+        $self->{stream_state} = CONNECT_RECV;
+
+    } elsif($name eq 'iq') {
+        if($attrs{type} and $attrs{type} eq 'result') {
+            $self->{stream_state} = CONNECTED;
+        }
+
+    } elsif($name eq 'status') {
+        $self->{xml_state } = IN_STATUS;
+
+    } elsif($name eq 'stream:error') {
+        $self->{stream_state} = DISCONNECTED;
+
+    } elsif($name eq 'error') {
+        $self->{message}->{err_type} = $attrs{'type'};
+        $self->{message}->{err_code} = $attrs{'code'};
+        $self->{stream_state} = DISCONNECTED;
+    }
+}
+
+sub characters {
+    my($parser, $chars) = @_;
+    my $self = $parser->{_parent_};
+    my $state = $self->{xml_state};
+
+    if($state == IN_BODY) {
+        $self->{message}->{body} .= $chars;
+
+    } elsif($state == IN_THREAD) {
+        $self->{message}->{thread} .= $chars;
+
+    } elsif($state == IN_STATUS) {
+        $self->{message}->{status} .= $chars;
+    }
+}
+
+sub end_element {
+    my($parser, $name) = @_;
+    my $self = $parser->{_parent_};
+    $self->{xml_state} = IN_NOTHING;
+
+    if($name eq 'message') {
+        $self->push_msg($self->{message});
+        $self->{message} = OpenSRF::Transport::SlimJabber::XMPPMessage->new;
+
+    } elsif($name eq 'stream:stream') {
+        $self->{stream_state} = DISCONNECTED;
+    }
+}
+
+sub flush_socket {
+       my $self = shift;
+       my $socket = $self->socket;
+    return 0 unless $socket and $socket->connected;
+
+    my $flags = fcntl($socket, F_GETFL, 0);
+    fcntl($socket, F_SETFL, $flags | O_NONBLOCK);
+
+    while( my $n = sysread( $socket, my $buf, 8192 ) ) {
+        $logger->debug("flush_socket dropped $n bytes of data");
+        $logger->error("flush_socket dropped data on disconnected socket: $buf")
+            unless($socket->connected);
+    }
+
+    fcntl($socket, F_SETFL, $flags);
+    return 0 unless $socket->connected;
+    return 1;
+}
+
+
+
+
+
+1;
+
+
+
+
+
diff --git a/trunk/src/perl/lib/OpenSRF/UnixServer.pm b/trunk/src/perl/lib/OpenSRF/UnixServer.pm
new file mode 100644 (file)
index 0000000..c4b48c8
--- /dev/null
@@ -0,0 +1,266 @@
+package OpenSRF::UnixServer;
+use strict; use warnings;
+use base qw/OpenSRF/;
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::Logger qw(:level $logger);
+use OpenSRF::Transport::PeerHandle;
+use OpenSRF::Application;
+use OpenSRF::AppSession;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::System;
+use OpenSRF::Utils::SettingsClient;
+use Time::HiRes qw(time);
+use OpenSRF::Utils::JSON;
+use vars qw/@ISA $app/;
+use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
+use Carp;
+use FreezeThaw qw/thaw/;
+
+use IO::Socket::INET;
+use IO::Socket::UNIX;
+
+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
+
+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 );
+#              my $client = OpenSRF::Utils::SettingsClient->new();
+#              if( $client->config_value("server_type") !~ /fork/i || 
+#                              OpenSRF::Utils::Config->current->bootstrap->settings_config ) {
+#                      warn "Calling hooks for non-prefork\n";
+#                      $self->configure_hook();
+#                      $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; }
+
+       my $orig = $0;
+       $0 = "$0*";
+
+       if( ! $data or ! defined( $data ) or $data eq "" ) {
+               close($self->{server}->{client}); 
+               $logger->debug("Unix child received empty data from socket", ERROR);
+               $0 = $orig;
+               return;
+       }
+
+
+       if( ! close( $self->{server}->{client} ) ) {
+               $logger->debug( "Error closing Unix socket: $!", ERROR );
+       }
+
+       my $app = $self->app();
+       $logger->transport( "UnixServer for $app received $data", INTERNAL );
+
+       # --------------------------------------------------------------
+       # Drop all data from the socket before coninuting to process
+       # --------------------------------------------------------------
+       my $ph = OpenSRF::Transport::PeerHandle->retrieve;
+       if(!$ph->flush_socket()) {
+               $logger->error("We received a request ".
+                       "and we are no longer connected to the jabber network. ".
+                       "We will go away and drop this request: $data");
+               exit;
+       }
+
+    ($data) = thaw($data);
+       my $app_session = OpenSRF::Transport->handler( $self->app(), $data );
+
+       if(!ref($app_session)) {
+               $logger->transport( "Did not receive AppSession from transport handler, returning...", WARN );
+               $0 = $orig;
+               return;
+       }
+
+       if($app_session->stateless and $app_session->state != $app_session->CONNECTED()){
+               $logger->debug("Exiting keepalive for stateless session / orig = $orig");
+               $app_session->kill_me;
+               $0 = $orig;
+               return;
+       }
+
+
+       my $client = OpenSRF::Utils::SettingsClient->new();
+       my $keepalive = $client->config_value("apps", $self->app(), "keepalive");
+
+       my $req_counter = 0;
+       while( $app_session and 
+                       $app_session->state and 
+                       $app_session->state != $app_session->DISCONNECTED() and
+                       $app_session->find( $app_session->session_id ) ) {
+               
+
+               my $before = time;
+               $logger->debug( "UnixServer calling queue_wait $keepalive", INTERNAL );
+               $app_session->queue_wait( $keepalive );
+               $logger->debug( "after queue wait $keepalive", INTERNAL );
+               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( $app_session && $app_session->queue_wait(0) ) {
+               $logger->debug( "Looping on zombies " . $x++ , DEBUG);
+       }
+
+       $logger->debug( "Timed out, disconnected, or authentication failed" );
+       $app_session->kill_me if ($app_session);
+
+       $0 = $orig;
+}
+
+
+sub serve {
+       my( $self ) = @_;
+
+       my $app = $self->app();
+       $logger->set_service($app);
+
+       $0 = "OpenSRF master [$app]";
+
+       my $client = OpenSRF::Utils::SettingsClient->new();
+    my @base = ('apps', $app, 'unix_config' );
+
+       my $min_servers = $client->config_value(@base, 'min_children');
+       my $max_servers = $client->config_value(@base, "max_children" );
+       my $min_spare = $client->config_value(@base, "min_spare_children" );
+       my $max_spare = $client->config_value(@base, "max_spare_children" );
+       my $max_requests = $client->config_value(@base, "max_requests" );
+    # fwiw, these file paths are (obviously) not portable
+       my $log_file = join("/", $client->config_value("dirs", "log"), $client->config_value(@base, "unix_log" ));
+       my $port = join("/", $client->config_value("dirs", "sock"), $client->config_value(@base, "unix_sock" ));
+       my $pid_file = join("/", $client->config_value("dirs", "pid"), $client->config_value(@base, "unix_pid" ));
+
+    $min_spare ||= $min_servers;
+    $max_spare ||= $max_servers;
+    $max_requests ||= 1000;
+
+    $logger->info("UnixServer: min=$min_servers, max=$max_servers, min_spare=$min_spare ".
+        "max_spare=$max_spare, max_req=$max_requests, log_file=$log_file, port=$port, pid_file=$pid_file");
+
+    $self->run(
+        min_servers => $min_servers,
+        max_servers => $max_servers,
+        min_spare_servers => $min_spare,
+        max_spare_servers => $max_spare,
+        max_requests => $max_requests,
+        log_file => $log_file,
+        port => $port,
+        proto => 'unix',
+        pid_file => $pid_file,
+    );
+
+}
+
+
+sub configure_hook {
+       my $self = shift;
+       my $app = $self->app;
+
+       # boot a client
+       OpenSRF::System->bootstrap_client( client_name => "system_client" );
+
+       $logger->debug( "Setting application implementation for $app", DEBUG );
+       my $client = OpenSRF::Utils::SettingsClient->new();
+       my $imp = $client->config_value("apps", $app, "implementation");
+       OpenSRF::Application::server_class($app);
+       OpenSRF::Application->application_implementation( $imp );
+       OpenSRF::Utils::JSON->register_class_hint( name => $imp, hint => $app, type => "hash" );
+       OpenSRF::Application->application_implementation->initialize()
+               if (OpenSRF::Application->application_implementation->can('initialize'));
+
+       if( $client->config_value("server_type") !~ /fork/i  ) {
+               $self->child_init_hook();
+       }
+
+       my $con = OpenSRF::Transport::PeerHandle->retrieve;
+       if($con) {
+               $con->disconnect;
+       }
+
+       return OpenSRF::Application->application_implementation;
+}
+
+sub child_init_hook { 
+
+       $0 =~ s/master/drone/g;
+
+       if ($ENV{OPENSRF_PROFILE}) {
+               my $file = $0;
+               $file =~ s/\W/_/go;
+               eval "use Devel::Profiler output_file => '/tmp/profiler_$file.out', buffer_size => 0;";
+               if ($@) {
+                       $logger->debug("Could not load Devel::Profiler: $@",ERROR);
+               } else {
+                       $0 .= ' [PROFILING]';
+                       $logger->debug("Running under Devel::Profiler", INFO);
+               }
+       }
+
+       my $self = shift;
+
+#      $logger->transport( 
+#                      "Creating PeerHandle from UnixServer child_init_hook", INTERNAL );
+       OpenSRF::Transport::PeerHandle->construct( $self->app() );
+       $logger->transport( "PeerHandle Created from UnixServer child_init_hook", INTERNAL );
+
+       OpenSRF::Application->application_implementation->child_init
+               if (OpenSRF::Application->application_implementation->can('child_init'));
+
+       return OpenSRF::Transport::PeerHandle->retrieve;
+}
+
+sub child_finish_hook {
+    $logger->debug("attempting to call child exit handler...");
+       OpenSRF::Application->application_implementation->child_exit
+               if (OpenSRF::Application->application_implementation->can('child_exit'));
+}
+
+
+1;
+
diff --git a/trunk/src/perl/lib/OpenSRF/Utils.pm b/trunk/src/perl/lib/OpenSRF/Utils.pm
new file mode 100644 (file)
index 0000000..46816cb
--- /dev/null
@@ -0,0 +1,464 @@
+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;
+use DateTime;
+use DateTime::Format::ISO8601;
+use DateTime::TimeZone;
+
+our $date_parser = DateTime::Format::ISO8601->new;
+
+# 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 tree_filter)],
+       daemon          => [qw(safe_fork set_psname daemonize)],
+       datetime        => [qw(clense_ISO8601 gmtime_ISO8601 interval_to_seconds seconds_to_interval)],
+);
+
+Exporter::export_ok_tags('common','daemon','datetime');  # 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 tree_filter {
+       my $tree = shift;
+       my $field = shift;
+       my $filter = shift;
+
+       my @things = $filter->($tree);
+       for my $v ( @{$tree->$field} ){
+               push @things, $filter->($v);
+               push @things, tree_filter($v, $field, $filter);
+       }
+       return @things
+}
+
+#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+)\s*/g) {
+               my ($count, $type) = ($1, $2);
+                $amount += $count if ($type eq 's');
+                $amount += 60 * $count if ($type =~ /^m(?!o)/oi);
+                $amount += 60 * 60 * $count if ($type =~ /^h/);
+                $amount += 60 * 60 * 24 * $count if ($type =~ /^d/oi);
+                $amount += 60 * 60 * 24 * 7 * $count if ($2 =~ /^w/oi);
+                $amount += ((60 * 60 * 24 * 365)/12) * $count if ($type =~ /^mo/io);
+                $amount += 60 * 60 * 24 * 365 * $count if ($type =~ /^y/oi);
+        }
+        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 = "0s" 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);
+}
+
+sub gmtime_ISO8601 {
+       my $self = shift;
+       my @date = gmtime;
+
+       my $y = $date[5] + 1900;
+       my $M = $date[4] + 1;
+       my $d = $date[3];
+       my $h = $date[2];
+       my $m = $date[1];
+       my $s = $date[0];
+
+       return sprintf('%d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d+00:00', $y, $M, $d, $h, $m, $s);
+}
+
+sub clense_ISO8601 {
+       my $self = shift;
+       my $date = shift || $self;
+       if ($date =~ /^\s*(\d{4})-?(\d{2})-?(\d{2})/o) {
+               my $new_date = "$1-$2-$3";
+
+               if ($date =~/(\d{2}):(\d{2}):(\d{2})/o) {
+                       $new_date .= "T$1:$2:$3";
+
+                       my $z;
+                       if ($date =~ /([-+]{1})([0-9]{1,2})(?::?([0-9]{1,2}))*\s*$/o) {
+                               $z = sprintf('%s%0.2d%0.2d',$1,$2,$3)
+                       } else {
+                               $z =  DateTime::TimeZone::offset_as_string(
+                                       DateTime::TimeZone
+                                               ->new( name => 'local' )
+                                               ->offset_for_datetime(
+                                                       $date_parser->parse_datetime($new_date)
+                                               )
+                               );
+                       }
+
+                       if (length($z) > 3 && index($z, ':') == -1) {
+                               substr($z,3,0) = ':';
+                               substr($z,6,0) = ':' if (length($z) > 6);
+                       }
+               
+                       $new_date .= $z;
+               } else {
+                       $new_date .= "T00:00:00";
+               }
+
+               return $new_date;
+       }
+       return $date;
+}
+
+=head2 $utils_obj->daemonize('ps_name') OR daemonize('ps_name')
+
+Turns the current process into a daemon.  B<ps_name> is optional, and is used
+as the argument to I<< set_psname() >> if passed.
+
+
+=cut
+
+sub daemonize {
+       my $self = shift;
+       my $PS_NAME = shift || $self;
+       my $pid;
+       if ($pid = safe_fork($self)) {
+               exit 0;
+       } elsif (defined($pid)) {
+               set_psname($PS_NAME);
+               chdir '/';
+               setsid;
+               return $$;
+       }
+}
+
+=head2 $utils_obj->safe_fork('ps_name') OR safe_fork('ps_name');
+
+Forks the current process in a retry loop.  B<ps_name> is optional, and is used
+as the argument to I<< set_psname() >> if passed.
+
+
+=cut
+
+sub safe_fork {
+       my $self = shift;
+       my $pid;
+
+FORK:
+       {
+               if (defined($pid = fork())) {
+                       srand(time ^ ($$ + ($$ << 15))) unless ($pid);
+                       return $pid;
+               } elsif ($! == EAGAIN) {
+                       $self->error("Can't fork()!  $!, taking 5 and trying again.") if (ref $self);
+                       sleep 5;
+                       redo FORK;
+               } else {
+                       $self->error("Can't fork()! $!") if ($! && ref($self));
+                       exit $!;
+               }
+       }
+}
+
+#------------------------------------------------------------------------------------------------------------------------------------
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Utils/Cache.pm b/trunk/src/perl/lib/OpenSRF/Utils/Cache.pm
new file mode 100644 (file)
index 0000000..1fb56a4
--- /dev/null
@@ -0,0 +1,257 @@
+package OpenSRF::Utils::Cache;
+use strict; use warnings;
+use base qw/OpenSRF/;
+use Cache::Memcached;
+use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::JSON;
+
+my $log = 'OpenSRF::Utils::Logger';
+
+=head1 NAME
+
+OpenSRF::Utils::Cache
+
+=head1 SYNOPSIS
+
+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;
+
+# ------------------------------------------------------
+# Persist methods and method names
+# ------------------------------------------------------
+my $persist_add_slot; 
+my $persist_push_stack;
+my $persist_peek_stack;
+my $persist_destroy_slot;
+my $persist_slot_get_expire;
+my $persist_slot_find;
+
+my $max_persist_time;
+my $persist_add_slot_name       = "opensrf.persist.slot.create_expirable";
+my $persist_push_stack_name     = "opensrf.persist.stack.push";
+my $persist_peek_stack_name     = "opensrf.persist.stack.peek";
+my $persist_destroy_slot_name   = "opensrf.persist.slot.destroy";
+my $persist_slot_get_expire_name = "opensrf.persist.slot.get_expire";
+my $persist_slot_find_name      = "opensrf.persist.slot.find";;
+
+# ------------------------------------------------------
+
+=head1 METHODS
+
+=head2 current
+
+Return a named cache if it exists
+
+=cut
+
+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 );
+}
+
+
+=head2 new
+
+Create a new named memcache object.
+
+=cut
+
+sub new {
+
+       my( $class, $cache_type, $persist ) = @_;
+       $cache_type ||= 'global';
+       $class = ref( $class ) || $class;
+
+       return $caches{$cache_type} if (defined $caches{$cache_type});
+
+       my $conf = OpenSRF::Utils::SettingsClient->new;
+       my $servers = $conf->config_value( cache => $cache_type => servers => 'server' );
+       $max_persist_time = $conf->config_value( cache => $cache_type => 'max_cache_time' );
+
+       $servers = [ $servers ] if(!ref($servers));
+
+       my $self = {};
+       $self->{persist} = $persist || 0;
+       $self->{memcache} = Cache::Memcached->new( { servers => $servers } ); 
+       if(!$self->{memcache}) {
+               throw OpenSRF::EX::PANIC ("Unable to create a new memcache object for $cache_type");
+       }
+
+       bless($self, $class);
+       $caches{$cache_type} = $self;
+       return $self;
+}
+
+
+=head2 put_cache
+
+=cut
+
+sub put_cache {
+       my($self, $key, $value, $expiretime ) = @_;
+       return undef unless( defined $key and defined $value );
+
+       $value = OpenSRF::Utils::JSON->perl2JSON($value);
+
+       if($self->{persist}){ _load_methods(); }
+
+       $expiretime ||= $max_persist_time;
+
+       unless( $self->{memcache}->set( $key, $value, $expiretime ) ) {
+               $log->error("Unable to store $key => [".length($value)." bytes]  in memcached server" );
+               return undef;
+       }
+
+       $log->debug("Stored $key => $value in memcached server", INTERNAL);
+
+       if($self->{"persist"}) {
+
+               my ($slot) = $persist_add_slot->run("_CACHEVAL_$key", $expiretime . "s");
+
+               if(!$slot) {
+                       # slot may already exist
+                       ($slot) = $persist_slot_find->run("_CACHEVAL_$key");
+                       if(!defined($slot)) {
+                               throw OpenSRF::EX::ERROR ("Unable to create cache slot $key in persist server" );
+                       } else {
+                               #XXX destroy the slot and rebuild it to prevent DOS
+                       }
+               }
+
+               ($slot) = $persist_push_stack->run("_CACHEVAL_$key", $value);
+
+               if(!$slot) {
+                       throw OpenSRF::EX::ERROR ("Unable to push data onto stack in persist slot _CACHEVAL_$key" );
+               }
+       }
+
+       return $key;
+}
+
+
+=head2 delete_cache
+
+=cut
+
+sub delete_cache {
+       my( $self, $key ) = @_;
+       if(!$key) { return undef; }
+       if($self->{persist}){ _load_methods(); }
+       $self->{memcache}->delete($key);
+       if( $self->{persist} ) {
+               $persist_destroy_slot->run("_CACHEVAL_$key");
+       }
+       return $key; 
+}
+
+
+=head2 get_cache
+
+=cut
+
+sub get_cache {
+       my($self, $key ) = @_;
+
+       my $val = $self->{memcache}->get( $key );
+       return OpenSRF::Utils::JSON->JSON2perl($val) if defined($val);
+
+       if($self->{persist}){ _load_methods(); }
+
+       # if not in memcache but we are persisting, the put it into memcache
+       if( $self->{"persist"} ) {
+               $val = $persist_peek_stack->( "_CACHEVAL_$key" );
+               if(defined($val)) {
+                       my ($expire) = $persist_slot_get_expire->run("_CACHEVAL_$key");
+                       if($expire)     {
+                               $self->{memcache}->set( $key, $val, $expire);
+                       } else {
+                               $self->{memcache}->set( $key, $val, $max_persist_time);
+                       }
+                       return OpenSRF::Utils::JSON->JSON2perl($val);
+               }
+       }
+       return undef;
+}
+
+
+=head2 _load_methods
+
+=cut
+
+sub _load_methods {
+
+       if(!$persist_add_slot) {
+               $persist_add_slot = 
+                       OpenSRF::Application->method_lookup($persist_add_slot_name);
+               if(!ref($persist_add_slot)) {
+                       throw OpenSRF::EX::PANIC ("Unable to retrieve method $persist_add_slot_name");
+               }
+       }
+
+       if(!$persist_push_stack) {
+               $persist_push_stack = 
+                       OpenSRF::Application->method_lookup($persist_push_stack_name);
+               if(!ref($persist_push_stack)) {
+                       throw OpenSRF::EX::PANIC ("Unable to retrieve method $persist_push_stack_name");
+               }
+       }
+
+       if(!$persist_peek_stack) {
+               $persist_peek_stack = 
+                       OpenSRF::Application->method_lookup($persist_peek_stack_name);
+               if(!ref($persist_peek_stack)) {
+                       throw OpenSRF::EX::PANIC ("Unable to retrieve method $persist_peek_stack_name");
+               }
+       }
+
+       if(!$persist_destroy_slot) {
+               $persist_destroy_slot = 
+                       OpenSRF::Application->method_lookup($persist_destroy_slot_name);
+               if(!ref($persist_destroy_slot)) {
+                       throw OpenSRF::EX::PANIC ("Unable to retrieve method $persist_destroy_slot_name");
+               }
+       }
+       if(!$persist_slot_get_expire) {
+               $persist_slot_get_expire = 
+                       OpenSRF::Application->method_lookup($persist_slot_get_expire_name);
+               if(!ref($persist_slot_get_expire)) {
+                       throw OpenSRF::EX::PANIC ("Unable to retrieve method $persist_slot_get_expire_name");
+               }
+       }
+       if(!$persist_slot_find) {
+               $persist_slot_find = 
+                       OpenSRF::Application->method_lookup($persist_slot_find_name);
+               if(!ref($persist_slot_find)) {
+                       throw OpenSRF::EX::PANIC ("Unable to retrieve method $persist_slot_find_name");
+               }
+       }
+}
+
+
+
+
+
+
+
+1;
+
diff --git a/trunk/src/perl/lib/OpenSRF/Utils/Config.pm b/trunk/src/perl/lib/OpenSRF/Utils/Config.pm
new file mode 100755 (executable)
index 0000000..ca400f7
--- /dev/null
@@ -0,0 +1,411 @@
+package OpenSRF::Utils::Config::Section;
+
+no strict 'refs';
+
+use vars qw/@ISA $AUTOLOAD $VERSION/;
+push @ISA, qw/OpenSRF::Utils/;
+
+use OpenSRF::Utils (':common');
+use Net::Domain qw/hostfqdn/;
+
+$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;
+
+       $self->_sub_builder('__id');
+       # Hard-code this to match old bootstrap.conf section name
+       $self->__id('bootstrap');
+
+       my $bootstrap = shift;
+
+       foreach my $key (sort keys %$bootstrap) {
+               $self->_sub_builder($key);
+               $self->$key($bootstrap->{$key});
+       }
+
+       return $self;
+}
+
+package OpenSRF::Utils::Config;
+
+use vars qw/@ISA $AUTOLOAD $VERSION $OpenSRF::Utils::ConfigCache/;
+push @ISA, qw/OpenSRF::Utils/;
+
+use FileHandle;
+use XML::LibXML;
+use OpenSRF::Utils (':common');  
+use OpenSRF::Utils::Logger;
+use Net::Domain qw/hostfqdn/;
+
+#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->bootstrap();
+
+  $config_obj->bootstrap->loglevel(0);
+
+  open FH, '>'.$config_obj->FILE() . '.new';
+  print FH $config_obj;
+  close FH;
+
+=head1 DESCRIPTION
+
+This module is mainly used by other OpenSRF modules to load an OpenSRF
+configuration file.  OpenSRF configuration files are XML files that
+contain a C<< <config> >> root element and an C<< <opensrf> >> child
+element (in XPath notation, C</config/opensrf/>). Each child element
+is converted into a hash key=>value pair. Elements that contain other
+XML elements are pushed into arrays and added as an array reference to
+the hash. Scalar values have whitespace trimmed from the left and
+right sides.
+
+Child elements of C<< <config> >> other than C<< <opensrf> >> are
+currently ignored by this module.
+
+=head1 EXAMPLE
+
+Given an OpenSRF configuration file named F<opensrf_core.xml> with the
+following content:
+
+  <?xml version='1.0'?>
+  <config>
+    <opensrf>
+      <router_name>router</router_name>
+
+      <routers> 
+       <router>localhost</router>
+       <router>otherhost</router>
+      </routers>
+
+      <logfile>/var/log/osrfsys.log</logfile>
+    </opensrf>
+  </config>
+
+... calling C<< OpenSRF::Utils::Config->load(config_file =>
+'opensrf_core.xml') >> will create a hash with the following
+structure:
+
+  {
+    router_name => 'router',
+    routers => ['localhost', 'otherhost'],
+    logfile => '/var/log/osrfsys.log'
+  }
+
+You can retrieve any of these values by name from the bootstrap
+section of C<$config_obj>; for example:
+
+  $config_obj->bootstrap->router_name
+
+=head1 NOTES
+
+For compatibility with a previous version of OpenSRF configuration
+files, the F</config/opensrf/> section has a hardcoded name of
+B<bootstrap>. However, future iterations of this module may extend the
+ability of the module to parse the entire OpenSRF configuration file
+and provide sections named after the sibling elements of
+C</config/opensrf>.
+
+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.
+
+
+=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 = $ENV{'OSRF_HOSTNAME'} || hostfqdn();
+       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 $parser = XML::LibXML->new();
+
+       # Hash of config values
+       my %bootstrap;
+       
+       # Return an XML::LibXML::Document object
+       my $config = $parser->parse_file($self->FILE);
+
+       unless ($config) {
+               OpenSRF::Utils::Logger->error("Could not open ".$self->FILE.": $!\n");
+               die "Could not open ".$self->FILE.": $!\n";
+       }
+
+       # Return an XML::LibXML::NodeList object matching all child elements
+       # of <config><opensrf>...
+       my $osrf_cfg = $config->findnodes('/config/opensrf/child::*');
+
+       # Iterate through the nodes to pull out key=>value pairs of config settings
+       foreach my $node ($osrf_cfg->get_nodelist()) {
+               my $child_state = 0;
+
+               # This will be overwritten if it's a scalar setting
+               $bootstrap{$node->nodeName()} = [];
+
+               foreach my $child_node ($node->childNodes) {
+                       # from libxml/tree.h: nodeType 1 = ELEMENT_NODE
+                       next if $child_node->nodeType() != 1;
+
+                       # If the child node is an element, this element may
+                       # have multiple values; therefore, push it into an array
+            my $content = OpenSRF::Utils::Config::extract_child($child_node);
+                       push(@{$bootstrap{$node->nodeName()}}, $content) if $content;
+                       $child_state = 1;
+               }
+               if (!$child_state) {
+                       $bootstrap{$node->nodeName()} = OpenSRF::Utils::Config::extract_text($node->textContent);
+               }
+       }
+
+       my $section = $self->section_pkg->new(\%bootstrap);
+       my $sub_name = $section->SECTION;
+       $self->_sub_builder($sub_name);
+       $self->$sub_name($section);
+
+}
+sub extract_child {
+    my $node = shift;
+    use OpenSRF::Utils::SettingsParser;
+    return OpenSRF::Utils::SettingsParser::XML2perl($node);
+}
+
+sub extract_text {
+       my $self = shift;
+       $self =~ s/^\s*([.*?])\s*$//m;
+       return $self;
+}
+
+#------------------------------------------------------------------------------------------------------------------------------------
+
+=head1 SEE ALSO
+
+       OpenSRF::Utils
+
+=head1 LIMITATIONS
+
+Elements containing heterogeneous child elements are treated as though they have the same element name;
+for example:
+  <routers>
+    <router>localhost</router>
+    <furniture>chair</furniture>
+  </routers>
+
+... will simply generate a key=>value pair of C<< routers => ['localhost', 'chair'] >>.
+
+=head1 BUGS
+
+No known bugs, but report any to open-ils-dev@list.georgialibraries.org or mrylander@gmail.com.
+
+=head1 COPYRIGHT AND LICENSING
+
+Copyright (C) 2000-2007, Mike Rylander
+Copyright (C) 2007, Laurentian University, Dan Scott <dscott@laurentian.ca>
+
+The OpenSRF::Utils::Config module is free software. You may distribute under the terms
+of the GNU General Public License version 2 or greater.
+
+=cut
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Utils/JSON.pm b/trunk/src/perl/lib/OpenSRF/Utils/JSON.pm
new file mode 100644 (file)
index 0000000..bfefb86
--- /dev/null
@@ -0,0 +1,128 @@
+package OpenSRF::Utils::JSON;
+use JSON::XS;
+use vars qw/%_class_map/;
+
+my $parser = JSON::XS->new;
+$parser->ascii(1); # output \u escaped strings
+$parser->allow_nonref(1);
+
+sub true {
+    return $parser->true();
+}
+
+sub false {
+    return $parser->false();
+}
+
+sub register_class_hint {
+       my $class = shift;
+       my %args = @_;
+       $_class_map{hints}{$args{hint}} = \%args;
+       $_class_map{classes}{$args{name}} = \%args;
+}
+
+sub lookup_class {
+       my $self = shift;
+       my $hint = shift;
+       return $_class_map{hints}{$hint}{name}
+}
+
+sub lookup_hint {
+       my $self = shift;
+       my $class = shift;
+       return $_class_map{classes}{$class}{hint}
+}
+
+sub _json_hint_to_class {
+       my $type = shift;
+       my $hint = shift;
+
+       return $_class_map{hints}{$hint}{name} if (exists $_class_map{hints}{$hint});
+       
+       $type = 'hash' if ($type eq '}');
+       $type = 'array' if ($type eq ']');
+
+       OpenSRF::Utils::JSON->register_class_hint(name => $hint, hint => $hint, type => $type);
+
+       return $hint;
+}
+
+
+my $JSON_CLASS_KEY = '__c';
+my $JSON_PAYLOAD_KEY = '__p';
+
+sub JSON2perl {
+       my( $class, $string ) = @_;
+       my $perl = $class->rawJSON2perl($string);
+       return $class->JSONObject2Perl($perl);
+}
+
+sub perl2JSON {
+       my( $class, $obj ) = @_;
+       my $json = $class->perl2JSONObject($obj);
+       return $class->rawPerl2JSON($json);
+}
+
+sub JSONObject2Perl {
+       my $class = shift;
+       my $obj = shift;
+       my $ref = ref($obj);
+       if( $ref eq 'HASH' ) {
+               if( defined($obj->{$JSON_CLASS_KEY})) {
+                       my $cls = $obj->{$JSON_CLASS_KEY};
+            $cls =~ s/^\s+//o;
+            $cls =~ s/\s+$//o;
+                       if( $obj = $class->JSONObject2Perl($obj->{$JSON_PAYLOAD_KEY}) ) {
+                               $cls = $class->lookup_class($cls) || $cls;
+                               return bless(\$obj, $cls) unless ref($obj); 
+                               return bless($obj, $cls);
+                       }
+                       return undef;
+               }
+               $obj->{$_} = $class->JSONObject2Perl($obj->{$_}) for (keys %$obj);
+       } elsif( $ref eq 'ARRAY' ) {
+               $obj->[$_] = $class->JSONObject2Perl($obj->[$_]) for(0..scalar(@$obj) - 1);
+       }
+       return $obj;
+}
+
+sub perl2JSONObject {
+       my $class = shift;
+       my $obj = shift;
+       my $ref = ref($obj);
+
+       return $obj unless $ref;
+
+    return $obj if $ref eq 'JSON::XS::Boolean';
+       my $newobj;
+
+    if(UNIVERSAL::isa($obj, 'HASH')) {
+        $newobj = {};
+        $newobj->{$_} = $class->perl2JSONObject($obj->{$_}) for (keys %$obj);
+    } elsif(UNIVERSAL::isa($obj, 'ARRAY')) {
+        $newobj = [];
+        $newobj->[$_] = $class->perl2JSONObject($obj->[$_]) for(0..scalar(@$obj) - 1);
+    }
+
+    if($ref ne 'HASH' and $ref ne 'ARRAY') {
+               $ref = $class->lookup_hint($ref) || $ref;
+               $newobj = {$JSON_CLASS_KEY => $ref, $JSON_PAYLOAD_KEY => $newobj};
+    }
+
+       return $newobj; 
+}
+
+
+sub rawJSON2perl {
+       my $class = shift;
+    my $json = shift;
+    return undef unless defined $json and $json !~ /^\s*$/o;
+    return $parser->decode($json);
+}
+
+sub rawPerl2JSON {
+       my ($class, $perl) = @_;
+    return $parser->encode($perl);
+}
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Utils/LogServer.pm b/trunk/src/perl/lib/OpenSRF/Utils/LogServer.pm
new file mode 100644 (file)
index 0000000..c27f512
--- /dev/null
@@ -0,0 +1,149 @@
+package OpenSRF::Utils::LogServer;
+use strict; use warnings;
+use base qw(OpenSRF);
+use IO::Socket::INET;
+use FileHandle;
+use OpenSRF::Utils::Config;
+use Fcntl;
+use Time::HiRes qw(gettimeofday);
+use OpenSRF::Utils::Logger;
+
+=head2 Name
+
+OpenSRF::Utils::LogServer
+
+=cut
+
+=head2 Synopsis
+
+Networ Logger
+
+=cut
+
+=head2 Description
+
+
+=cut
+
+
+
+our $config;
+our $port;
+our $bufsize = 4096;
+our $proto;
+our @file_info;
+
+
+sub DESTROY {
+       for my $file (@file_info) {
+               if( $file->handle ) {
+                       close( $file->handle );
+               }
+       }
+}
+
+
+sub serve {
+
+       $config = OpenSRF::Utils::Config->current;
+
+       unless ($config) { throw OpenSRF::EX::Config ("No suitable config found"); }
+
+       $port = $config->system->log_port;
+       $proto = $config->system->log_proto;
+
+
+       my $server = IO::Socket::INET->new(
+               LocalPort       => $port,
+               Proto                   => $proto )
+       or die "Error creating server socket : $@\n"; 
+
+
+
+       while ( 1 ) {
+               my $client = <$server>;
+               process( $client );
+       }
+
+       close( $server );
+}
+
+sub process {
+       my $client = shift;
+       my @params = split(/\|/,$client);
+       my $log = shift @params;
+
+       if( (!$log) || (!@params) ) {
+               warn "Invalid logging params: $log\n";
+               return;
+       }
+
+       # Put |'s back in since they are stripped 
+       # from the message by 'split'
+       my $message;
+       if( @params > 1 ) {
+               foreach my $param (@params) {
+                       if( $param ne $params[0] ) {
+                               $message .= "|";
+                       }
+                       $message .= $param;
+               }
+       }
+       else{ $message = "@params"; }
+
+       my @lines = split( "\n", $message );
+       my $time = format_time();
+
+       my $fh;
+
+       my ($f_obj) = grep { $_->name eq $log } @file_info;
+
+       unless( $f_obj and ($fh=$f_obj->handle) ) {
+               my $file = $config->logs->$log;
+
+               sysopen( $fh, $file, O_WRONLY|O_APPEND|O_CREAT ) 
+                       or warn "Cannot sysopen $log: $!";
+               $fh->autoflush(1);
+
+               my $obj = new OpenSRF::Utils::NetLogFile( $log, $file, $fh );
+               push @file_info, $obj;
+       }
+
+       foreach my $line (@lines) {
+               print $fh "$time $line\n" || die "$!";
+       }
+
+}
+
+sub format_time {
+       my ($s, $ms) = gettimeofday();
+       my @time = localtime( $s );
+       $ms = substr( $ms, 0, 3 );
+       my $year = $time[5] + 1900;
+       my $mon = $time[4] + 1;
+       my $day = $time[3];
+       my $hour = $time[2];
+       my $min = $time[1];
+       my $sec = $time[0];
+       $mon = "0" . "$mon" if ( length($mon) == 1 );
+       $day = "0" . "$day" if ( length($day) == 1 );
+       $hour = "0" . "$hour" if ( length($hour) == 1 );
+       $min = "0" . "$min" if (length($min) == 1 );
+       $sec = "0" . "$sec" if (length($sec) == 1 );
+
+       my $proc = $$;
+       while( length( $proc ) < 5 ) { $proc = "0" . "$proc"; }
+       return "[$year-$mon-$day $hour:$min:$sec.$ms $proc]";
+}
+
+
+package OpenSRF::Utils::NetLogFile;
+
+sub new{ return bless( [ $_[1], $_[2], $_[3] ], $_[0] ); }
+
+sub name { return $_[0]->[0]; }
+sub file { return $_[0]->[1]; }
+sub handle { return $_[0]->[2]; }
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Utils/Logger.pm b/trunk/src/perl/lib/OpenSRF/Utils/Logger.pm
new file mode 100644 (file)
index 0000000..e911224
--- /dev/null
@@ -0,0 +1,270 @@
+package OpenSRF::Utils::Logger;
+# vim:ts=4:noet:
+use strict;
+use vars qw($AUTOLOAD @EXPORT_OK %EXPORT_TAGS);
+use Exporter;
+use Unix::Syslog qw(:macros :subs);
+use base qw/OpenSRF Exporter/;
+use FileHandle;
+use Time::HiRes qw(gettimeofday);
+use OpenSRF::Utils::Config;
+use Fcntl;
+
+=head1
+
+Logger code
+
+my $logger = OpenSRF::Utils::Logger;
+$logger->error( $msg );
+
+For backwards compability, a log level may also be provided to each log
+function thereby overriding the level defined by the function.
+
+i.e. $logger->error( $msg, WARN );  # logs at log level WARN
+
+=cut
+
+@EXPORT_OK = qw/ NONE ERROR WARN INFO DEBUG INTERNAL /;
+push @EXPORT_OK, '$logger';
+
+%EXPORT_TAGS = ( level => [ qw/ NONE ERROR WARN INFO DEBUG INTERNAL / ], logger => [ '$logger' ] );
+
+my $config;                                                    # config handle
+my $loglevel = INFO();                         # global log level
+my $logfile;                                           # log file
+my $facility;                                          # syslog facility
+my $actfac;                                                    # activity log syslog facility
+my $actfile;                                           # activity log file
+my $service = $0;                      # default service name
+my $syslog_enabled = 0;                        # is syslog enabled?
+my $act_syslog_enabled = 0;    # is syslog enabled?
+my $logfile_enabled = 1;               # are we logging to a file?
+my $act_logfile_enabled = 1;   # are we logging to a file?
+
+our $logger = "OpenSRF::Utils::Logger";
+
+# log levels
+sub ACTIVITY   { return -1; }
+sub NONE                       { return 0;     }
+sub ERROR              { return 1;     }
+sub WARN                       { return 2;     }
+sub INFO                       { return 3;     }
+sub DEBUG              { return 4;     }
+sub INTERNAL   { return 5;     }
+sub ALL                        { return 100; }
+
+my $isclient;  # true if we control the osrf_xid
+
+# load up our config options
+sub set_config {
+
+       return if defined $config;
+
+       $config = OpenSRF::Utils::Config->current;
+       if( !defined($config) ) {
+               $loglevel = INFO();
+               warn "*** Logger found no config.  Using STDERR ***\n";
+               return;
+       }
+
+       $loglevel =  $config->bootstrap->loglevel; 
+
+       $logfile = $config->bootstrap->logfile;
+       if($logfile =~ /^syslog/) {
+               $syslog_enabled = 1;
+               $logfile_enabled = 0;
+        $logfile = $config->bootstrap->syslog;
+               $facility = $logfile;
+               $logfile = undef;
+               $facility = _fac_to_const($facility);
+               openlog($service, 0, $facility);
+
+       } else { $logfile = "$logfile"; }
+
+
+    if($syslog_enabled) {
+        # --------------------------------------------------------------
+        # if we're syslogging, see if we have a special syslog facility 
+        # for activity logging.  If not, use the syslog facility for
+        # standard logging
+        # --------------------------------------------------------------
+        $act_syslog_enabled = 1;
+        $act_logfile_enabled = 0;
+        $actfac = $config->bootstrap->actlog || $config->bootstrap->syslog;
+        $actfac = _fac_to_const($actfac);
+        $actfile = undef;
+    } else {
+        # --------------------------------------------------------------
+        # we're not syslogging, use any specified activity log file.
+        # Fall back to the standard log file otherwise
+        # --------------------------------------------------------------
+               $act_syslog_enabled = 0;
+               $act_logfile_enabled = 1;
+        $actfile = $config->bootstrap->actlog || $config->bootstrap->logfile;
+    }
+
+       my $client = OpenSRF::Utils::Config->current->bootstrap->client();
+       if (!$client) {
+               $isclient = 0;
+               return;
+       }
+       $isclient = ($client =~ /^true$/iog) ?  1 : 0;
+}
+
+sub _fac_to_const {
+       my $name = shift;
+       return LOG_LOCAL0 unless $name;
+       return LOG_LOCAL0 if $name =~ /local0/i;
+       return LOG_LOCAL1 if $name =~ /local1/i;
+       return LOG_LOCAL2 if $name =~ /local2/i;
+       return LOG_LOCAL3 if $name =~ /local3/i;
+       return LOG_LOCAL4 if $name =~ /local4/i;
+       return LOG_LOCAL5 if $name =~ /local5/i;
+       return LOG_LOCAL6 if $name =~ /local6/i;
+       return LOG_LOCAL7 if $name =~ /local7/i;
+       return LOG_LOCAL0;
+}
+
+sub is_syslog {
+       set_config();
+       return $syslog_enabled;
+}
+
+sub is_act_syslog {
+       set_config();
+       return $act_syslog_enabled;
+}
+
+sub is_filelog {
+       set_config();
+       return $logfile_enabled;
+}
+
+sub is_act_filelog {
+       set_config();
+       return $act_logfile_enabled;
+}
+
+sub set_service {
+       my( $self, $svc ) = @_;
+       $service = $svc;        
+       if( is_syslog() ) {
+               closelog();
+               openlog($service, 0, $facility);
+       }
+}
+
+sub error {
+       my( $self, $msg, $level ) = @_;
+       $level = ERROR() unless defined ($level);
+       _log_message( $msg, $level );
+}
+
+sub warn {
+       my( $self, $msg, $level ) = @_;
+       $level = WARN() unless defined ($level);
+       _log_message( $msg, $level );
+}
+
+sub info {
+       my( $self, $msg, $level ) = @_;
+       $level = INFO() unless defined ($level);
+       _log_message( $msg, $level );
+}
+
+sub debug {
+       my( $self, $msg, $level ) = @_;
+       $level = DEBUG() unless defined ($level);
+       _log_message( $msg, $level );
+}
+
+sub internal {
+       my( $self, $msg, $level ) = @_;
+       $level = INTERNAL() unless defined ($level);
+       _log_message( $msg, $level );
+}
+
+sub activity {
+       my( $self, $msg ) = @_;
+       _log_message( $msg, ACTIVITY() );
+}
+
+# for backward compability
+sub transport {
+       my( $self, $msg, $level ) = @_;
+       $level = DEBUG() unless defined ($level);
+       _log_message( $msg, $level );
+}
+
+
+# ----------------------------------------------------------------------
+# creates a new xid if necessary
+# ----------------------------------------------------------------------
+my $osrf_xid = '';
+my $osrf_xid_inc = 0;
+sub mk_osrf_xid {
+   return unless $isclient;
+   $osrf_xid_inc++;
+   return $osrf_xid = "$^T${$}$osrf_xid_inc";
+}
+
+sub set_osrf_xid { 
+   return if $isclient; # if we're a client, we control our xid
+   $osrf_xid = $_[1]; 
+}
+
+sub get_osrf_xid { return $osrf_xid; }
+# ----------------------------------------------------------------------
+
+   
+sub _log_message {
+       my( $msg, $level ) = @_;
+       return if $level > $loglevel;
+
+       my $l; my $n; 
+       my $fac = $facility;
+
+       if ($level == ERROR())                  {$l = LOG_ERR; $n = "ERR "; }
+       elsif ($level == WARN())                {$l = LOG_WARNING; $n = "WARN"; }
+       elsif ($level == INFO())                {$l = LOG_INFO; $n = "INFO"; }  
+       elsif ($level == DEBUG())               {$l = LOG_DEBUG; $n = "DEBG"; }
+       elsif ($level == INTERNAL())    {$l = LOG_DEBUG; $n = "INTL"; }
+       elsif ($level == ACTIVITY())    {$l = LOG_INFO; $n = "ACT"; $fac = $actfac; }
+
+       my( undef, $file, $line_no ) = caller(1);
+   $file =~ s#/.*/##og;
+
+       # help syslog with the formatting
+       $msg =~ s/\%/\%\%/gso if( is_act_syslog() or is_syslog() );
+
+       $msg = "[$n:"."$$".":$file:$line_no:$osrf_xid] $msg";
+
+       $msg = substr($msg, 0, 1536); 
+
+       if( $level == ACTIVITY() ) {
+               if( is_act_syslog() ) { syslog( $fac | $l, $msg ); } 
+               elsif( is_act_filelog() ) { _write_file( $msg, 1 ); }
+
+       } else {
+               if( is_syslog() ) { syslog( $fac | $l, $msg ); }
+               elsif( is_filelog() ) { _write_file($msg); }
+       }
+}
+
+
+sub _write_file {
+       my( $msg, $isact) = @_;
+       my $file = $logfile;
+       $file = $actfile if $isact;
+       my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);  
+       $year += 1900; $mon += 1;
+       sysopen( SINK, $file, O_NONBLOCK|O_WRONLY|O_APPEND|O_CREAT ) 
+               or die "Cannot sysopen $logfile: $!";
+       binmode(SINK, ':utf8');
+       printf SINK "[%04d-%02d-%02d %02d:%02d:%02d] %s %s\n", $year, $mon, $mday, $hour, $min, $sec, $service, $msg;
+       close( SINK );
+}
+
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Utils/SettingsClient.pm b/trunk/src/perl/lib/OpenSRF/Utils/SettingsClient.pm
new file mode 100755 (executable)
index 0000000..ab936f3
--- /dev/null
@@ -0,0 +1,123 @@
+use strict; use warnings;
+package OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::SettingsParser;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use OpenSRF::Utils::Config;
+use OpenSRF::EX qw(:try);
+
+use vars qw/$host_config/;
+
+
+sub new {return bless({},shift());}
+my $session;
+$host_config = undef;
+
+my $we_cache = 1;
+sub set_cache {
+       my($self, $val) = @_;
+       if(defined($val)) { $we_cache = $val; }
+}
+
+sub has_config {
+       if($host_config) { return 1; }
+       return 0;
+}
+
+
+# ------------------------------------
+# utility method for grabbing config info
+sub config_value {
+       my($self,@keys) = @_;
+
+
+       my $bsconfig = OpenSRF::Utils::Config->current;
+       die "No bootstrap config exists.  Have you bootstrapped?\n" unless $bsconfig;
+       my $host = $bsconfig->env->hostname;
+
+       if($we_cache) {
+               if(!$host_config) { grab_host_config($host); }
+       } else {
+               grab_host_config($host);
+       }
+
+       if(!$host_config) {
+               throw OpenSRF::EX::Config ("Unable to retrieve host config for $host" );
+       }
+
+       my $hash = $host_config;
+
+       # XXX TO DO, check local config 'version', 
+       # call out to settings server when necessary....
+       try {
+               for my $key (@keys) {
+                       if(!ref($hash) eq 'HASH'){
+                               return undef;
+                       }
+                       $hash = $hash->{$key};
+               }
+
+       } catch Error with {
+               my $e = shift;
+               throw OpenSRF::EX::Config ("No Config information for @keys : $e : $@");
+       };
+
+       return $hash;
+
+}
+
+
+# XXX make smarter and more robust...
+sub grab_host_config {
+
+       my $host = shift;
+
+       $session = OpenSRF::AppSession->create( "opensrf.settings" ) unless $session;
+       my $bsconfig = OpenSRF::Utils::Config->current;
+
+       my $resp;
+       my $req;
+       try {
+
+               if( ! ($session->connect()) ) {die "Settings Connect timed out\n";}
+               $req = $session->request( "opensrf.settings.host_config.get", $host );
+               $resp = $req->recv( timeout => 10 );
+
+       } catch OpenSRF::EX with {
+
+               if( ! ($session->connect()) ) {die "Settings Connect timed out\n";}
+               $req = $session->request( "opensrf.settings.default_config.get" );
+               $resp = $req->recv( timeout => 10 );
+
+       } catch Error with {
+
+               my $e = shift;
+               warn "Connection to Settings Failed  $e : $@ ***\n";
+               die $e;
+
+       } otherwise {
+
+               my $e = shift;
+               warn "Settings Retrieval Failed  $e : $@ ***\n";
+               die $e;
+       };
+
+       if(!$resp) {
+               warn "No Response from settings server...going to sleep\n";
+               sleep;
+       }
+
+       if( $resp && UNIVERSAL::isa( $resp, "OpenSRF::EX" ) ) {
+               throw $resp;
+       }
+
+       $host_config = $resp->content();
+       $req->finish();
+       $session->disconnect();
+       $session->finish;
+       $session->kill_me();
+}
+
+
+
+1;
diff --git a/trunk/src/perl/lib/OpenSRF/Utils/SettingsParser.pm b/trunk/src/perl/lib/OpenSRF/Utils/SettingsParser.pm
new file mode 100755 (executable)
index 0000000..ac36dca
--- /dev/null
@@ -0,0 +1,162 @@
+use strict; use warnings;
+package OpenSRF::Utils::SettingsParser;
+use OpenSRF::Utils::Config;
+use OpenSRF::EX qw(:try);
+
+
+
+use XML::LibXML;
+
+sub DESTROY{}
+our $log = 'OpenSRF::Utils::Logger';
+my $parser;
+my $doc;
+
+sub new { return bless({},shift()); }
+
+
+# returns 0 if the config file could not be found or if there is a parse error
+# returns 1 if successful
+sub initialize {
+
+       my ($self,$bootstrap_config) = @_;
+       return 0 unless($self and $bootstrap_config);
+
+       $parser = XML::LibXML->new();
+       $parser->keep_blanks(0);
+       try {
+               $doc = $parser->parse_file( $bootstrap_config );
+       } catch Error with {
+               return 0;
+       };
+       return 1;
+}
+
+sub _get { _get_overlay(@_) }
+
+sub _get_overlay {
+       my( $self, $xpath ) = @_;
+       my @nodes = $doc->documentElement->findnodes( $xpath );
+       
+       my $base = XML2perl(shift(@nodes));
+       my @overlays;
+       for my $node (@nodes) {
+               push @overlays, XML2perl($node);
+       }
+
+       for my $ol ( @overlays ) {
+               $base = merge_perl($base, $ol);
+       }
+       
+       return $base;
+}
+
+sub _get_all {
+       my( $self, $xpath ) = @_;
+       my @nodes = $doc->documentElement->findnodes( $xpath );
+       
+       my @overlays;
+       for my $node (@nodes) {
+               push @overlays, XML2perl($node);
+       }
+
+       return \@overlays;
+}
+
+sub merge_perl {
+       my $base = shift;
+       my $ol = shift;
+
+       if (ref($ol)) {
+               if (ref($ol) eq 'HASH') {
+                       for my $key (keys %$ol) {
+                               if (ref($$ol{$key}) and ref($$ol{$key}) eq ref($$base{$key})) {
+                                       merge_perl($$base{$key}, $$ol{$key});
+                               } else {
+                                       $$base{$key} = $$ol{$key};
+                               }
+                       }
+               } else {
+                       for my $key (0 .. scalar(@$ol) - 1) {
+                               if (ref($$ol[$key]) and ref($$ol[$key]) eq ref($$base[$key])) {
+                                       merge_perl($$base[$key], $$ol[$key]);
+                               } else {
+                                       $$base[$key] = $$ol[$key];
+                               }
+                       }
+               }
+       } else {
+               $base = $ol;
+       }
+
+       return $base;
+}
+
+sub _check_for_int {
+       my $value = shift;
+       return 0+$value if ($value =~ /^\d{1,10}$/o);
+       return $value;
+}
+
+sub XML2perl {
+       my $node = shift;
+       my %output;
+
+       return undef unless($node);
+
+       for my $attr ( ($node->attributes()) ) {
+               next unless($attr);
+               $output{$attr->nodeName} = _check_for_int($attr->value);
+       }
+
+       my @kids = $node->childNodes;
+       if (@kids == 1 && $kids[0]->nodeType == 3) {
+                       return _check_for_int($kids[0]->textContent);
+       } else {
+               for my $kid ( @kids ) {
+                       next if ($kid->nodeName eq 'comment');
+                       if (exists $output{$kid->nodeName}) {
+                               if (ref $output{$kid->nodeName} ne 'ARRAY') {
+                                       $output{$kid->nodeName} = [$output{$kid->nodeName}, XML2perl($kid)];
+                               } else {
+                                       push @{$output{$kid->nodeName}}, XML2perl($kid);
+                               }
+                               next;
+                       }
+                       $output{$kid->nodeName} = XML2perl($kid);
+               }
+       }
+
+       return \%output;
+}
+
+
+# returns the full config hash for a given server
+sub get_server_config {
+       my( $self, $server ) = @_;
+       my $xpath = "/opensrf/default|/opensrf/hosts/$server";
+       return $self->_get( $xpath );
+}
+
+sub get_default_config {
+       my( $self, $server ) = @_;
+       my $xpath = "/opensrf/default";
+       return $self->_get( $xpath );
+}
+
+sub get_bootstrap_config {
+       my( $self ) = @_;
+       my $xpath = "/opensrf/bootstrap";
+       return $self->_get( $xpath );
+}
+
+sub get_router_config {
+       my( $self, $router ) = @_;
+       my $xpath = "/opensrf/routers/$router";
+       return $self->_get($xpath );
+}
+
+
+
+
+1;
diff --git a/trunk/src/perl/t/00-load.t b/trunk/src/perl/t/00-load.t
new file mode 100644 (file)
index 0000000..c30401f
--- /dev/null
@@ -0,0 +1,9 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'OpenSRF' );
+}
+
+diag( "Testing OpenSRF $OpenSRF::VERSION, Perl $], $^X" );
diff --git a/trunk/src/perl/t/01-Application.t b/trunk/src/perl/t/01-Application.t
new file mode 100644 (file)
index 0000000..009dd60
--- /dev/null
@@ -0,0 +1,11 @@
+#!perl -T
+
+use Test::More tests => 4;
+
+BEGIN {
+       use_ok( 'OpenSRF::Application' );
+}
+
+use_ok( 'OpenSRF::Application::Client' );
+use_ok( 'OpenSRF::Application::Persist' );
+use_ok( 'OpenSRF::Application::Settings' );
diff --git a/trunk/src/perl/t/02-AppSession.t b/trunk/src/perl/t/02-AppSession.t
new file mode 100644 (file)
index 0000000..1ac6673
--- /dev/null
@@ -0,0 +1,7 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'OpenSRF::AppSession' );
+}
diff --git a/trunk/src/perl/t/03-DomainObject.t b/trunk/src/perl/t/03-DomainObject.t
new file mode 100644 (file)
index 0000000..2bd18b8
--- /dev/null
@@ -0,0 +1,7 @@
+#!perl -T
+
+use Test::More tests => 3;
+
+use_ok( 'OpenSRF::DomainObject::oilsMessage' );
+use_ok( 'OpenSRF::DomainObject::oilsMethod' );
+use_ok( 'OpenSRF::DomainObject::oilsResponse' );
diff --git a/trunk/src/perl/t/04-EX.t b/trunk/src/perl/t/04-EX.t
new file mode 100644 (file)
index 0000000..a9378d5
--- /dev/null
@@ -0,0 +1,7 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'OpenSRF::EX' );
+}
diff --git a/trunk/src/perl/t/05-MultiSession.t b/trunk/src/perl/t/05-MultiSession.t
new file mode 100644 (file)
index 0000000..b20e8d6
--- /dev/null
@@ -0,0 +1,7 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'OpenSRF::MultiSession' );
+}
diff --git a/trunk/src/perl/t/06-System.t b/trunk/src/perl/t/06-System.t
new file mode 100644 (file)
index 0000000..9372ff3
--- /dev/null
@@ -0,0 +1,7 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'OpenSRF::System' );
+}
diff --git a/trunk/src/perl/t/07-Transport.t b/trunk/src/perl/t/07-Transport.t
new file mode 100644 (file)
index 0000000..64d4069
--- /dev/null
@@ -0,0 +1,17 @@
+#!perl -T
+
+use Test::More tests => 10;
+
+BEGIN {
+       use_ok( 'OpenSRF::Transport' );
+}
+
+use_ok( 'OpenSRF::Transport::Listener' );
+use_ok( 'OpenSRF::Transport::PeerHandle' );
+use_ok( 'OpenSRF::Transport::SlimJabber' );
+use_ok( 'OpenSRF::Transport::SlimJabber::Client' );
+use_ok( 'OpenSRF::Transport::SlimJabber::Inbound' );
+use_ok( 'OpenSRF::Transport::SlimJabber::MessageWrapper' );
+use_ok( 'OpenSRF::Transport::SlimJabber::PeerConnection' );
+use_ok( 'OpenSRF::Transport::SlimJabber::XMPPMessage' );
+use_ok( 'OpenSRF::Transport::SlimJabber::XMPPReader' );
diff --git a/trunk/src/perl/t/08-UnixServer.t b/trunk/src/perl/t/08-UnixServer.t
new file mode 100644 (file)
index 0000000..9f8996c
--- /dev/null
@@ -0,0 +1,7 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'OpenSRF::UnixServer' );
+}
diff --git a/trunk/src/perl/t/09-Utils.t b/trunk/src/perl/t/09-Utils.t
new file mode 100644 (file)
index 0000000..ddbf43a
--- /dev/null
@@ -0,0 +1,15 @@
+#!perl -T
+
+use Test::More tests => 8;
+
+BEGIN {
+       use_ok( 'OpenSRF::Utils' );
+}
+
+use_ok( 'OpenSRF::Utils::Cache' );
+use_ok( 'OpenSRF::Utils::Config' );
+use_ok( 'OpenSRF::Utils::JSON' );
+use_ok( 'OpenSRF::Utils::Logger' );
+use_ok( 'OpenSRF::Utils::LogServer' );
+use_ok( 'OpenSRF::Utils::SettingsClient' );
+use_ok( 'OpenSRF::Utils::SettingsParser' );
diff --git a/trunk/src/perl/t/pod-coverage.t b/trunk/src/perl/t/pod-coverage.t
new file mode 100644 (file)
index 0000000..5844b85
--- /dev/null
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+# FIXME SKIPPING POD COVERAGE TESTS FOR NOW
+ok(1);exit;
+
+# Ensure a recent version of Test::Pod::Coverage
+my $min_tpc = 1.08;
+eval "use Test::Pod::Coverage $min_tpc";
+plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
+    if $@;
+
+# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
+# but older versions don't recognize some common documentation styles
+my $min_pc = 0.18;
+eval "use Pod::Coverage $min_pc";
+plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
+    if $@;
+
+all_pod_coverage_ok();
diff --git a/trunk/src/perl/t/pod.t b/trunk/src/perl/t/pod.t
new file mode 100644 (file)
index 0000000..ee8b18a
--- /dev/null
@@ -0,0 +1,12 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod
+my $min_tp = 1.22;
+eval "use Test::Pod $min_tp";
+plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
+
+all_pod_files_ok();
diff --git a/trunk/src/ports/strn_compat/Makefile.in b/trunk/src/ports/strn_compat/Makefile.in
new file mode 100644 (file)
index 0000000..1398045
--- /dev/null
@@ -0,0 +1,20 @@
+# OSRF_LOG_PARAMS log all incoming method params at OSRF_INFO log level. 
+# OSRF_STRICT_PARAMS instructs the app handler to return an error if the number of method arguments
+#      provided to any method is not at least as large as the 'argc' setting for the method
+
+CFLAGS +=  -rdynamic -fno-strict-aliasing -fPIC
+
+TARGETS = strndup.o strnlen.o
+HEADERS = strndup.h strnlen.h
+
+all: libfreebsd_str_compat.so $(TARGETS)
+
+libfreebsd_str_compat.so: $(TARGETS)
+       $(CC) -shared -W1 $(TARGETS) -o $@
+
+strndup.o:     strndup.c strndup.h
+strnlen.o:     strnlen.c strnlen.h
+
+clean:
+       /bin/rm -f *o
+
diff --git a/trunk/src/ports/strn_compat/strndup.c b/trunk/src/ports/strn_compat/strndup.c
new file mode 100644 (file)
index 0000000..10b49fd
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2007 Albert Lee <trisk at acm.jhu.edu>.
+ * 
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "strnlen.h"
+
+char *
+strndup(const char *s, size_t n)
+{
+       char *ns;
+
+       n = strnlen(s, n);
+
+       if ((ns = (char *)malloc(n + 1))) {
+               ns[n] = '\0';
+               return memcpy(ns, s, n);
+       }
+
+       return NULL;
+}
diff --git a/trunk/src/ports/strn_compat/strndup.h b/trunk/src/ports/strn_compat/strndup.h
new file mode 100644 (file)
index 0000000..be91f5f
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2007 Albert Lee <trisk at acm.jhu.edu>.
+ * 
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+char   *strndup(const char *s, size_t n);
+
diff --git a/trunk/src/ports/strn_compat/strnlen.c b/trunk/src/ports/strn_compat/strnlen.c
new file mode 100644 (file)
index 0000000..81a6f17
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2007 The Akuma Project
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * $Id$
+ */
+
+/*
+ * sys/types.h is a Single Unix Specification header and defines size_t.
+ */
+
+#include <sys/types.h>
+
+/*
+ * As per the Linux manual page:
+ *
+ * The strnlen() function returns the number of characters in the string
+ * pointed to by s, not including the terminating '\0' character, but at most
+ * maxlen. In doing this, strnlen() looks only at the first maxlen characters
+ * at s and never beyond s+maxlen.
+ *
+ * The strnlen() function returns strlen(s), if that is less than maxlen, or
+ * maxlen if there is no '\0' character among the first maxlen characters
+ * pointed to by s.
+ */
+
+size_t
+strnlen(const char *string, size_t maxlen)
+{
+       int len = 0;
+
+       if (maxlen == 0)
+               return (0);
+
+       while (*string++ && ++len < maxlen)
+               ;
+
+       return (len);
+}
diff --git a/trunk/src/ports/strn_compat/strnlen.h b/trunk/src/ports/strn_compat/strnlen.h
new file mode 100644 (file)
index 0000000..181780a
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2007 The Akuma Project
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * $Id$
+ */
+
+size_t strnlen(const char *, size_t);
+
diff --git a/trunk/src/python/Makefile.am b/trunk/src/python/Makefile.am
new file mode 100644 (file)
index 0000000..6225f27
--- /dev/null
@@ -0,0 +1,22 @@
+# makefile for OpenSRF Python modules and scripts
+
+all-local:
+       @echo $@
+       python @srcdir@/setup.py build
+
+# ------------------------------------------------------------------------------
+# INSTALL
+# ------------------------------------------------------------------------------
+install-data-local:    
+       @echo $@
+       python @srcdir@/setup.py install
+
+distclean-local:
+       rm @builddir@/OpenSRF.egg-info/PKG-INFO
+       rm @builddir@/OpenSRF.egg-info/requires.txt
+       rm @builddir@/OpenSRF.egg-info/top_level.txt
+       rm @builddir@/OpenSRF.egg-info/SOURCES.txt
+       rm @builddir@/OpenSRF.egg-info/dependency_links.txt
+       rm @builddir@/build/scripts-2.5/srfsh.py
+       rm @builddir@/dist/OpenSRF-1.0.0-py2.5.egg
+
diff --git a/trunk/src/python/opensrf.py b/trunk/src/python/opensrf.py
new file mode 100755 (executable)
index 0000000..1204f40
--- /dev/null
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+# -----------------------------------------------------------------------
+# Copyright (C) 2008  Equinox Software, Inc.
+# Bill Erickson <erickson@esilibrary.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA
+# -----------------------------------------------------------------------
+
+import sys, getopt, os, signal
+import osrf.system, osrf.server, osrf.app, osrf.set, osrf.json
+
+def do_help():
+    print '''
+    Manage OpenSRF application processes
+
+    Options:
+        -a <action>
+            start   -- Start a service
+            stop    -- stop a service
+            restart -- restart a service
+
+        -s <service>
+            The service name
+
+        -f <config file>
+            The OpenSRF config file
+
+        -c <config context>
+            The OpenSRF config file context
+
+        -p <PID dir>
+            The location of application PID files.  Default is /tmp
+
+        -d 
+            If set, run in daemon (background) mode.  This creates a PID 
+            file for managing the process.
+
+        -h
+            Prints help message
+    '''
+    sys.exit(0)
+
+
+# Parse the command line options
+ops, args = None, None
+try:
+    ops, args = getopt.getopt(sys.argv[1:], 'a:s:f:c:p:dh')
+except getopt.GetoptError, e:
+    print '* %s' % str(e)
+    do_help()
+
+options = dict(ops)
+
+if '-a' not in options or '-s' not in options or '-f' not in options:
+    do_help()
+
+action = options['-a']
+service = options['-s']
+config_file = options['-f']
+config_ctx = options.get('-c', 'config.opensrf')
+pid_dir = options.get('-p', '/tmp')
+as_daemon = '-d' in options
+pidfile = "%s/osrf_py_%s.pid" % (pid_dir, service)
+
+
+def do_start():
+
+    # connect to the OpenSRF network
+    osrf.system.System.net_connect(
+        config_file = config_file, config_context = config_ctx)
+
+    osrf.set.load(osrf.conf.get('domain'))
+    settings = osrf.json.to_json(osrf.set.get('apps/%s' % service))
+
+    if settings['language'].lower() != 'python':
+        print '%s is not a Python application' % service
+        return
+
+    # XXX load the settings configs...
+    osrf.app.Application.load(service, 'osrf.apps.example') # XXX example only for now
+    osrf.app.Application.register_sysmethods()
+    osrf.app.Application.application.global_init()
+
+    controller = osrf.server.Controller(service)
+    controller.max_requests = 100
+    controller.max_children = 6
+    controller.min_children = 3
+
+    if as_daemon:
+        osrf.system.System.daemonize()
+        file = open(pidfile, 'w')
+        file.write(str(os.getpid()))
+        file.close()
+
+    controller.run()
+
+def do_stop():
+    file = open(pidfile)
+    pid = file.read()
+    file.close()
+    os.kill(int(pid), signal.SIGTERM)
+    os.remove(pidfile)
+
+
+if action == 'start':
+    do_start()
+
+elif action == 'stop':
+    do_stop()
+
+elif action == 'restart':
+    do_stop()
+    do_start()
+
+elif action == 'help':
+    do_help()
diff --git a/trunk/src/python/osrf/__init__.py b/trunk/src/python/osrf/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/src/python/osrf/app.py b/trunk/src/python/osrf/app.py
new file mode 100644 (file)
index 0000000..a073f89
--- /dev/null
@@ -0,0 +1,183 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2008  Equinox Software, Inc.
+# Bill Erickson <erickson@esilibrary.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA  
+# 02110-1301, USA
+# -----------------------------------------------------------------------
+
+import time
+import osrf.log, osrf.ses, osrf.json
+
+
+class Method(object):
+    def __init__(self, **kwargs):
+        self.name = kwargs['api_name']
+        self.handler = kwargs['method']
+        self.stream = kwargs.get('stream', False)
+        self.argc = kwargs.get('argc', 0)
+        self.atomic = kwargs.get('atomic', False)
+
+    def get_func(self):
+        ''' Returns the function handler reference '''
+        return getattr(Application.application, self.handler)
+
+    def get_doc(self):
+        ''' Returns the function documentation '''
+        return self.get_func().func_doc
+        
+
+
+class Application(object):
+    ''' Base class for OpenSRF applications.  Provides static methods
+        for loading and registering applications as well as common 
+        applicatoin methods. '''
+
+    # global application handle
+    application = None
+    name = None
+    methods = {}
+
+    def __init__(self):
+        ''' Sets the application name and loads the application methods '''
+        self.name = None
+
+    def global_init(self):
+        ''' Override this method to run code at application startup '''
+        pass
+
+    def child_init(self):
+        ''' Override this method to run code at child startup.
+            This is useful for initializing database connections or
+            initializing other persistent resources '''
+        pass
+
+    def child_exit(self):
+        ''' Override this method to run code at process exit time.
+            This is useful for cleaning up resources like databaes 
+            handles, etc. '''
+        pass
+
+
+    @staticmethod
+    def load(name, module_name):
+        ''' Loads the provided application module '''
+        Application.name = name
+        try:
+            osrf.log.log_info("Loading application module %s" % module_name)
+            exec('import %s' % module_name)
+        except Exception, e:
+            osrf.log.log_error("Error importing application module %s:%s" % (
+                module_name, unicode(e)))
+
+    @staticmethod
+    def register_app(app):
+        ''' Registers an application for use '''
+        app.name = Application.name
+        Application.application = app
+
+    @staticmethod
+    def register_method(**kwargs):
+        Application.methods[kwargs['api_name']] = Method(**kwargs)
+        if kwargs.get('stream'):
+            kwargs['atomic'] = 1 
+            kwargs['api_name'] +=  '.atomic'
+            Application.methods[kwargs['api_name']] = Method(**kwargs)
+
+
+    @staticmethod
+    def handle_request(session, osrf_msg):
+        ''' Find the handler, construct the server request, then run the method '''
+
+        req_method = osrf_msg.payload()
+        params = req_method.params()
+        method = Application.methods[req_method.method()]
+        handler = method.get_func()
+
+        param_json = osrf.json.to_json(params)
+        param_json = param_json[1:len(param_json)-1]
+
+        osrf.log.log_info("CALL: %s %s %s" % (session.service, method.name, param_json))
+        server_req = osrf.ses.ServerRequest(session, osrf_msg.threadTrace(), method, params)
+
+        result = None
+        try:
+            result = handler(server_req, *params)
+        except Exception, e:
+            osrf.log.log_error("Error running method %s %s %s" % (method.name, param_json, unicode(e)))
+            session.send_status(
+                osrf_msg.threadTrace(),
+                osrf.net_obj.NetworkObject.osrfMethodException({   
+                    'status' : unicode(e),
+                    'statusCode': osrf.const.OSRF_STATUS_INTERNALSERVERERROR
+                })
+            )
+            return
+
+        server_req.respond_complete(result)
+
+    @staticmethod
+    def register_sysmethods():
+        ''' Registers the global system methods '''
+
+        Application.register_method(
+            api_name = 'opensrf.system.time',
+            method = 'sysmethod_time',
+            argc = 0,
+        )
+        
+        Application.register_method(
+            api_name = 'opensrf.system.introspect',
+            method = 'sysmethod_introspect',
+            argc = 0,
+            stream = True
+        )
+
+        Application.register_method(
+            api_name = 'opensrf.system.echo',
+            method = 'sysmethod_echo',
+            argc = 1,
+            stream = True
+        )
+
+    def sysmethod_time(self, request):
+        '''@return type:number The current epoch time '''
+        return time.time()
+
+    def sysmethod_echo(self, request, *args):
+        '''@return type:string The current epoch time '''
+        for a in args:
+            request.respond(a)
+
+    def sysmethod_introspect(self, request, prefix=None):
+        ''' Generates a list of methods with method metadata 
+            @param type:string The limiting method name prefix.  If defined,
+            only methods matching the given prefix will be returned.
+            @return type:array List of method information '''
+
+        for name, method in self.methods.iteritems():
+            if prefix is not None and prefix != name[:len(prefix)]:
+                continue
+
+            request.respond({
+                'api_name' : name,
+                'method' : method.handler,
+                'service' : self.name,
+                'argc' : method.argc,
+                'params' : [], # XXX parse me
+                'desc' : method.get_doc() # XXX parse me
+            })
+
+
diff --git a/trunk/src/python/osrf/apps/__init__.py b/trunk/src/python/osrf/apps/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/trunk/src/python/osrf/apps/example.py b/trunk/src/python/osrf/apps/example.py
new file mode 100644 (file)
index 0000000..f4b6790
--- /dev/null
@@ -0,0 +1,68 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2008  Equinox Software, Inc.
+# Bill Erickson <erickson@esilibrary.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA  
+# 02110-1301, USA
+# -----------------------------------------------------------------------
+import os
+import osrf.log
+from osrf.app import Application
+
+class Example(Application):
+    ''' Example OpenSRF application. '''
+
+    # ---------------------------------------------------------
+    # Register a new method for this application
+    # ---------------------------------------------------------
+    Application.register_method(
+        api_name = 'opensrf.py-example.reverse', # published API name for the method
+        method = 'reverse', # name of def that implements this method
+        argc = 1, # expects a single argument
+        stream = True # returns a stream of results.  can be called atomic-ly
+    )
+
+    # ---------------------------------------------------------
+    # This method implements the API call registered above
+    # ---------------------------------------------------------
+    def reverse(self, request, message=''):
+        ''' Returns the given string in reverse order one character at a time
+            @param type:string Message to reverse 
+            @return type:string The reversed message, one character at a time.  '''
+        idx = len(message) - 1
+        while idx >= 0:
+            request.respond(message[idx])
+            idx -= 1
+
+    # ---------------------------------------------------------
+    # These example methods override methods from 
+    # osrf.app.Application.  They are not required.
+    # ---------------------------------------------------------
+    def global_init(self):
+        osrf.log.log_debug("Running global init handler for %s" % __name__)
+
+    def child_init(self):
+        osrf.log.log_debug("Running child init handler for process %d" % os.getpid())
+
+    def child_exit(self):
+        osrf.log.log_debug("Running child exit handler for process %d" % os.getpid())
+
+
+# ---------------------------------------------------------
+# Now register an instance of this class as an application
+# ---------------------------------------------------------
+Application.register_app(Example())
+
+
diff --git a/trunk/src/python/osrf/cache.py b/trunk/src/python/osrf/cache.py
new file mode 100644 (file)
index 0000000..b5b3e6c
--- /dev/null
@@ -0,0 +1,65 @@
+import memcache
+from osrf.json import to_json, to_object
+import osrf.log
+
+'''
+Abstracted OpenSRF caching interface.
+Requires memcache: ftp://ftp.tummy.com/pub/python-memcached/
+'''
+
+_client = None
+defaultTimeout = 0
+
+class CacheException(Exception):
+    def __init__(self, info):
+        self.info = info
+    def __str__(self):
+        return "%s: %s" % (self.__class__.__name__, self.info)
+
+class CacheClient(object):
+    def __init__(self, servers=None):
+        ''' If no servers are provided, this instance will use 
+            the global memcache connection.
+            servers takes the form ['server:port', 'server2:port2', ...]
+            '''
+        global _client
+        if servers:
+            self.client = memcache.Client(server, debug=1)
+        else:
+            if not _client:
+                raise CacheException(
+                    "not connected to any memcache servers."
+                    "try CacheClient.connect(servers)"
+                )
+            self.client = _client
+
+    def put(self, key, val, timeout=None):
+        global defaultTimeout
+        if timeout is None:
+            timeout = defaultTimeout
+        timeout = int(timeout)
+        json = to_json(val)
+        osrf.log.log_internal("cache: %s => %s" % (str(key), json))
+        return self.client.set(str(key), json, timeout)
+
+    def get(self, key):
+        obj = self.client.get(str(key))
+        osrf.log.log_internal("cache: fetching %s => %s" % (str(key), obj))
+        return to_object(obj or "null")
+
+    def delete(self, key):
+        osrf.log.log_internal("cache: deleting %s" % str(key))
+        self.client.delete(str(key))
+
+    @staticmethod
+    def connect(svrs):
+        global _client
+        osrf.log.log_debug("cache: connecting to servers %s" % str(svrs))
+        _client = memcache.Client(svrs, debug=1)
+
+    @staticmethod
+    def get_client():
+        global _client
+        return _client
+
+
diff --git a/trunk/src/python/osrf/conf.py b/trunk/src/python/osrf/conf.py
new file mode 100644 (file)
index 0000000..6caa293
--- /dev/null
@@ -0,0 +1,72 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# -----------------------------------------------------------------------
+
+
+import osrf.net_obj
+import osrf.ex
+import osrf.xml_obj
+import re
+
+class Config(object):
+    """Loads and parses the bootstrap config file"""
+
+    config = None
+
+    def __init__(self, file, context=None):
+        self.file = file    
+        self.context = context
+        self.data = {}
+
+    #def parseConfig(self,file=None):
+    def parse_config(self):
+        self.data = osrf.xml_obj.xml_file_to_object(self.file)
+        Config.config = self
+    
+    def get_value(self, key, idx=None):
+        if self.context:
+            if re.search('/', key):
+                key = "%s/%s" % (self.context, key)
+            else:
+                key = "%s.%s" % (self.context, key)
+
+        val = osrf.net_obj.find_object_path(self.data, key, idx)
+        if not val:
+            raise osrf.ex.OSRFConfigException("Config value not found: " + key)
+        return val
+
+
+def get(key, idx=None):
+    """Returns a bootstrap config value.
+
+    key -- A string representing the path to the value in the config object
+        e.g.  "domains.domain", "username"
+    idx -- Optional array index if the searched value is an array member
+    """
+    return Config.config.get_value(key, idx)
+                
+
+def get_no_ex(key, idx=None):
+    """ Returns a bootstrap config value without throwing an exception
+        if the item is not found. 
+
+    key -- A string representing the path to the value in the config object
+        e.g.  "domains.domain", "username"
+    idx -- Optional array index if the searched value is an array member
+    """
+    try:
+        return Config.config.get_value(key, idx)
+    except:
+        return None
+
diff --git a/trunk/src/python/osrf/const.py b/trunk/src/python/osrf/const.py
new file mode 100644 (file)
index 0000000..d888b75
--- /dev/null
@@ -0,0 +1,82 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+#
+#
+# Collection of global constants
+# -----------------------------------------------------------------------
+
+# -----------------------------------------------------------------------
+# log levels
+# -----------------------------------------------------------------------
+OSRF_LOG_ACT   = -1
+OSRF_LOG_NONE  = 0
+OSRF_LOG_ERR   = 1
+OSRF_LOG_WARN  = 2
+OSRF_LOG_INFO  = 3
+OSRF_LOG_DEBUG = 4
+OSRF_LOG_INTERNAL = 5
+OSRF_LOG_TYPE_FILE = 1
+OSRF_LOG_TYPE_SYSLOG = 2
+OSRF_LOG_TYPE_STDERR = 3
+
+# -----------------------------------------------------------------------
+# Session states
+# -----------------------------------------------------------------------
+OSRF_APP_SESSION_CONNECTED    = 0
+OSRF_APP_SESSION_CONNECTING   = 1
+OSRF_APP_SESSION_DISCONNECTED = 2
+
+# -----------------------------------------------------------------------
+# OpenSRF message types
+# -----------------------------------------------------------------------
+OSRF_MESSAGE_TYPE_REQUEST = 'REQUEST'
+OSRF_MESSAGE_TYPE_STATUS  = 'STATUS' 
+OSRF_MESSAGE_TYPE_RESULT  = 'RESULT'
+OSRF_MESSAGE_TYPE_CONNECT = 'CONNECT'
+OSRF_MESSAGE_TYPE_DISCONNECT = 'DISCONNECT'
+
+# -----------------------------------------------------------------------
+# OpenSRF message statuses
+# -----------------------------------------------------------------------
+OSRF_STATUS_CONTINUE                 = 100
+OSRF_STATUS_OK                       = 200
+OSRF_STATUS_ACCEPTED                 = 202
+OSRF_STATUS_COMPLETE                 = 205
+OSRF_STATUS_REDIRECTED               = 307
+OSRF_STATUS_BADREQUEST               = 400
+OSRF_STATUS_UNAUTHORIZED             = 401
+OSRF_STATUS_FORBIDDEN                = 403
+OSRF_STATUS_NOTFOUND                 = 404
+OSRF_STATUS_NOTALLOWED               = 405
+OSRF_STATUS_TIMEOUT                  = 408
+OSRF_STATUS_EXPFAILED                = 417
+OSRF_STATUS_INTERNALSERVERERROR      = 500
+OSRF_STATUS_NOTIMPLEMENTED           = 501
+OSRF_STATUS_VERSIONNOTSUPPORTED      = 505
+
+
+# -----------------------------------------------------------------------
+# Some well-known services
+# -----------------------------------------------------------------------
+OSRF_APP_SETTINGS = 'opensrf.settings'
+OSRF_APP_MATH = 'opensrf.math'
+
+
+# where do we find the settings config
+OSRF_METHOD_GET_HOST_CONFIG = 'opensrf.settings.host_config.get'
+
+OSRF_JSON_PAYLOAD_KEY = '__p'
+OSRF_JSON_CLASS_KEY = '__c'
+
+
diff --git a/trunk/src/python/osrf/ex.py b/trunk/src/python/osrf/ex.py
new file mode 100644 (file)
index 0000000..45b3484
--- /dev/null
@@ -0,0 +1,50 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# 
+#
+# This modules define the exception classes.  In general, an 
+# exception is little more than a name.
+# -----------------------------------------------------------------------
+
+class OSRFException(Exception):
+    """Root class for exceptions."""
+    def __init__(self, info=''):
+        self.msg = '%s: %s' % (self.__class__.__name__, info)
+    def __str__(self):
+        return self.msg
+
+
+class NetworkException(OSRFException):
+    def __init__(self):
+        OSRFException.__init__('Error communicating with the OpenSRF network')
+
+class OSRFProtocolException(OSRFException):
+    """Raised when something happens during opensrf network stack processing."""
+    pass
+
+class OSRFServiceException(OSRFException):
+    """Raised when there was an error communicating with a remote service."""
+    pass
+
+class OSRFConfigException(OSRFException):
+    """Invalid config option requested."""
+    pass
+
+class OSRFNetworkObjectException(OSRFException):
+    pass
+    
+class OSRFJSONParseException(OSRFException):
+    """Raised when a JSON parsing error occurs."""
+    pass
+
diff --git a/trunk/src/python/osrf/gateway.py b/trunk/src/python/osrf/gateway.py
new file mode 100644 (file)
index 0000000..11776a4
--- /dev/null
@@ -0,0 +1,210 @@
+from xml.dom import minidom
+from xml.sax import handler, make_parser, saxutils
+from osrf.json import to_object
+from osrf.net_obj import NetworkObject, new_object_from_hint
+import osrf.log
+import urllib, urllib2, sys, re
+
+defaultHost = None
+
+class GatewayRequest:
+    def __init__(self, service, method, params=[]):
+        self.service = service
+        self.method = method
+        self.params = params
+        self.path = 'gateway'
+
+    def setPath(self, path):
+        self.path = path
+
+    def send(self):
+        params = self.buildPOSTParams()
+        request = urllib2.Request(self.buildURL(), data=params)
+        response = None
+        try:
+            response =urllib2.urlopen(request)
+        except urllib2.HTTPError, e:
+            # log this?
+            sys.stderr.write('%s => %s?%s\n' % (unicode(e), self.buildURL(), params))
+            raise e
+            
+        return self.handleResponse(response)
+
+    def buildPOSTParams(self):
+
+        params = urllib.urlencode({   
+            'service': self.service,
+            'method': self.method,
+            'format': self.getFormat(),
+            'input_format': self.getInputFormat()
+        })
+
+        for p in self.params:
+            params += '&param=%s' % urllib.quote(self.encodeParam(p), "'/")
+        return params
+
+    def setDefaultHost(host):
+        global defaultHost
+        defaultHost = host
+    setDefaultHost = staticmethod(setDefaultHost)
+
+    def buildURL(self):
+        return 'http://%s/%s' % (defaultHost, self.path)
+
+class JSONGatewayRequest(GatewayRequest):
+    def __init__(self, service, method, *params):
+        GatewayRequest.__init__(self, service, method, list(params))
+
+    def getFormat(self):
+        return 'json'
+
+    def getInputFormat(self):
+        return self.getFormat()
+
+    def handleResponse(self, response):
+        s = response.read()
+        obj = to_object(s)
+        if obj['status'] != 200:
+            sys.stderr.write('JSON gateway returned status %d:\n%s\n' % (obj['status'], s))
+            return None
+
+        # the gateway wraps responses in an array to handle streaming data
+        # if there is only one item in the array, it (probably) wasn't a streaming request
+        p = obj['payload']
+        if len(p) > 1: return p
+        return p[0]
+
+    def encodeParam(self, param):
+        return osrf.json.to_json(param)
+
+class XMLGatewayRequest(GatewayRequest):
+
+    def __init__(self, service, method, *params):
+        GatewayRequest.__init__(self, service, method, list(params))
+
+    def getFormat(self):
+        return 'xml'
+
+    def getInputFormat(self):
+        return self.getFormat()
+
+    def handleResponse(self, response):
+        handler = XMLGatewayParser()
+        parser = make_parser()
+        parser.setContentHandler(handler)
+        try:
+            parser.parse(response)
+        except Exception, e:
+            osrf.log.log_error('Error parsing gateway XML: %s' % unicode(e))
+            return None
+
+        return handler.getResult()
+
+    def encodeParam(self, param):
+        return osrf.net_obj.to_xml(param);
+
+class XMLGatewayParser(handler.ContentHandler):
+
+    def __init__(self):
+        self.result = None
+        self.objStack = []
+        self.keyStack = []
+        self.posStack = [] # for tracking array-based hinted object indices
+
+        # true if we are parsing an element that may have character data
+        self.charsPending = 0 
+
+    def getResult(self):
+        return self.result
+
+    def __getAttr(self, attrs, name):
+        for (k, v) in attrs.items():
+            if k == name:
+                return v
+        return None
+
+    def startElement(self, name, attrs):
+        
+        if self.charsPending:
+            # we just read a 'string' or 'number' element that resulted
+            # in no text data.  Appaned a None object
+            self.appendChild(None)
+
+        if name == 'null':
+            self.appendChild(None)
+            return
+
+        if name == 'string' or name == 'number':
+            self.charsPending = True
+            return
+
+        if name == 'element': # this is an object item wrapper
+            self.keyStack.append(self.__getAttr(attrs, 'key'))
+            return
+
+        hint = self.__getAttr(attrs, 'class_hint')
+        if hint:
+            obj = new_object_from_hint(hint)
+            self.appendChild(obj)
+            self.objStack.append(obj)
+            if name == 'array':
+                self.posStack.append(0)
+            return
+
+        if name == 'array':
+            obj = []
+            self.appendChild(obj)
+            self.objStack.append(obj)
+            return
+
+        if name == 'object':
+            obj = {}
+            self.appendChild(obj)
+            self.objStack.append(obj)
+            return
+
+        if name == 'boolean':
+            self.appendChild((self.__getAttr(attrs, 'value') == 'true'))
+            return
+
+
+    def appendChild(self, child):
+
+        if self.result == None:
+            self.result = child
+
+        if not self.objStack: return;
+
+        parent = self.objStack[len(self.objStack)-1]
+
+        if isinstance(parent, list):
+            parent.append(child)
+        else:
+            if isinstance(parent, dict):
+                parent[self.keyStack.pop()] = child
+            else:
+                if isinstance(parent, NetworkObject):
+                    key = None
+                    if parent.get_registry().protocol == 'array':
+                        keys = parent.get_registry().keys
+                        i = self.posStack.pop()
+                        key = keys[i]
+                        if i+1 < len(keys):
+                            self.posStack.append(i+1)
+                    else:
+                        key = self.keyStack.pop()
+
+                    parent.set_field(key, child)
+
+    def endElement(self, name):
+        if name == 'array' or name == 'object':
+            self.objStack.pop()
+
+    def characters(self, chars):
+        self.charsPending = False
+        self.appendChild(urllib.unquote_plus(chars))
+
+
+
+    
+
diff --git a/trunk/src/python/osrf/http_translator.py b/trunk/src/python/osrf/http_translator.py
new file mode 100644 (file)
index 0000000..745d0a4
--- /dev/null
@@ -0,0 +1,349 @@
+import sys, os, time, md5, random
+from mod_python import apache, util
+import osrf.system, osrf.cache, osrf.json, osrf.conf, osrf.net, osrf.log
+from osrf.const import OSRF_MESSAGE_TYPE_DISCONNECT, OSRF_MESSAGE_TYPE_CONNECT, \
+    OSRF_STATUS_CONTINUE, OSRF_STATUS_TIMEOUT, OSRF_MESSAGE_TYPE_STATUS, OSRF_MESSAGE_TYPE_REQUEST
+
+
+''' 
+Proof of concept OpenSRF-HTTP multipart streaming gateway.
+
+Example Apache mod_python config:
+
+<Location /osrf-http-translator>
+   SetHandler mod_python
+   PythonPath "['/path/to/osrf-python'] + sys.path"
+   PythonHandler osrf.http_translator
+   PythonOption OSRF_CONFIG /path/to/opensrf_core.xml
+   PythonOption OSRF_CONFIG_CONTEXT config.gateway
+   PythonOption OSRF_CACHE_SERVERS 127.0.0.1:11211
+   # testing only
+   PythonAutoReload On
+</Location>
+'''
+
+OSRF_HTTP_HEADER_TO = 'X-OpenSRF-to'
+OSRF_HTTP_HEADER_XID = 'X-OpenSRF-xid'
+OSRF_HTTP_HEADER_FROM = 'X-OpenSRF-from'
+OSRF_HTTP_HEADER_THREAD = 'X-OpenSRF-thread'
+OSRF_HTTP_HEADER_TIMEOUT = 'X-OpenSRF-timeout'
+OSRF_HTTP_HEADER_SERVICE = 'X-OpenSRF-service'
+OSRF_HTTP_HEADER_MULTIPART = 'X-OpenSRF-multipart'
+MULTIPART_CONTENT_TYPE = 'multipart/x-mixed-replace;boundary="%s"'
+JSON_CONTENT_TYPE = 'text/plain'
+CACHE_TIME = 300
+
+ROUTER_NAME = None
+OSRF_DOMAIN = None
+
+# If DEBUG_WRITE = True, all data sent to the client is also written
+# to stderr (apache error log)
+DEBUG_WRITE = False
+
+def _dbg(msg):
+    ''' testing only '''
+    sys.stderr.write("%s\n\n" % str(msg))
+    sys.stderr.flush()
+
+INIT_COMPLETE = False
+def child_init(req):
+    ''' At time of writing, mod_python doesn't support a child_init handler,
+        so this function is called once per process to initialize 
+        the opensrf connection '''
+
+    global INIT_COMPLETE, ROUTER_NAME, OSRF_DOMAIN
+    if INIT_COMPLETE: 
+        return
+
+    # Apache complains with: UnboundLocalError: local variable 'osrf' referenced before assignment
+    # if the following import line is removed, even though its also at the top of the file...
+    import osrf.system 
+
+    ops = req.get_options()
+    conf = ops['OSRF_CONFIG']
+    ctxt = ops.get('OSRF_CONFIG_CONTEXT') or 'opensrf'
+    osrf.system.System.net_connect(config_file=conf, config_context=ctxt)
+
+    ROUTER_NAME = osrf.conf.get('router_name')
+    OSRF_DOMAIN = osrf.conf.get('domain')
+    INIT_COMPLETE = True
+
+    servers = ops.get('OSRF_CACHE_SERVERS')
+    if servers:
+        servers = servers.split(',')
+    else:
+        # no cache servers configured, see if we can talk to the settings server
+        import osrf.set
+        servers = osrf.set.get('cache.global.servers.server')
+        if not isinstance(servers, list):
+            servers = [servers]
+
+    osrf.cache.CacheClient.connect(servers)
+
+
+def handler(req):
+    ''' Create the translator and tell it to process the request. '''
+    child_init(req)
+
+    # capture the callback handle, clear it, then reset 
+    # it after we've handled the request
+    handle = osrf.net.get_network_handle()
+    callback = handle.receive_callback
+    handle.set_receive_callback(None)
+
+    try:
+        translator = HTTPTranslator(req)
+        status = translator.process()
+        osrf.log.log_debug("translator call resulted in status %d" % int(status))
+        if translator.local_xid:
+            osrf.log.clear_xid()
+    finally:
+        handle.receive_callback = callback
+        
+    return status
+
+class HTTPTranslator(object):
+    def __init__(self, apreq):
+
+        self.apreq = apreq
+
+        if OSRF_HTTP_HEADER_XID in apreq.headers_in:
+            osrf.log.log_debug('read XID from client %s' % apreq.headers_in.get(OSRF_HTTP_HEADER_XID))
+            osrf.log.set_xid(apreq.headers_in.get(OSRF_HTTP_HEADER_XID))
+            self.local_xid = False
+        else:
+            osrf.log.make_xid()
+            osrf.log.log_debug('created new XID %s' % osrf.log.get_xid())
+            self.local_xid = True
+
+        if apreq.header_only: 
+            return
+
+        for k,v in apreq.headers_in.iteritems():
+            osrf.log.log_internal('HEADER: %s = %s' % (k, v))
+
+        try:
+            #post = util.parse_qsl(apreq.read(int(apreq.headers_in['Content-length'])))
+            post = util.parse_qsl(apreq.read())
+            osrf.log.log_debug('post = ' + str(post))
+            self.body = [d for d in post if d[0] == 'osrf-msg'][0][1]
+            osrf.log.log_debug(self.body)
+        except Exception, e: 
+            osrf.log.log_warn("error parsing osrf message: %s" % unicode(e))
+            self.body = None
+            return
+
+        self.messages = []
+        self.complete = False
+        self.handle = osrf.net.get_network_handle()
+
+        self.recipient = apreq.headers_in.get(OSRF_HTTP_HEADER_TO)
+        self.service = apreq.headers_in.get(OSRF_HTTP_HEADER_SERVICE)
+        self.thread = apreq.headers_in.get(OSRF_HTTP_HEADER_THREAD) or \
+            "%s%s" % (os.getpid(), time.time())
+        self.timeout = apreq.headers_in.get(OSRF_HTTP_HEADER_TIMEOUT) or 1200
+        self.multipart = str(
+            apreq.headers_in.get(OSRF_HTTP_HEADER_MULTIPART)).lower() == 'true'
+        self.connect_only = False
+        self.disconnect_only = False
+
+        # generate a random multipart delimiter
+        mpart = md5.new()
+        mpart.update("%f%d%d" % (time.time(), os.getpid(), \
+            random.randint(100, 10000000)))
+        self.delim = mpart.hexdigest()
+        self.remote_host = self.apreq.get_remote_host(apache.REMOTE_NOLOOKUP)
+        self.cache = osrf.cache.CacheClient()
+
+
+
+    def process(self):
+
+        if self.apreq.header_only: 
+            return apache.OK
+        if not self.body:
+            return apache.HTTP_BAD_REQUEST
+        if not self.set_to_addr():
+            return apache.HTTP_BAD_REQUEST
+        if not self.parse_request():
+            return apache.HTTP_BAD_REQUEST
+
+        while self.handle.recv(0):
+            pass # drop stale messages
+
+
+        net_msg = osrf.net.NetworkMessage(
+            recipient=self.recipient, thread=self.thread, body=self.body)
+        self.handle.send(net_msg)
+
+        if self.disconnect_only:
+            osrf.log.log_debug("exiting early on DISCONNECT")
+            return apache.OK
+
+        first_write = True
+        while not self.complete:
+
+            net_msg = None
+            try:
+                net_msg = self.handle.recv(self.timeout)
+            except osrf.net.XMPPNoRecipient:
+                return apache.HTTP_NOT_FOUND 
+
+            if not net_msg: 
+                return apache.GATEWAY_TIME_OUT
+
+            if not self.check_status(net_msg):
+                continue 
+
+            if first_write:
+                self.init_headers(net_msg)
+                first_write = False
+
+            if self.multipart:
+                self.respond_chunk(net_msg)
+                if self.connect_only:
+                    break
+            else:
+                self.messages.append(net_msg.body)
+
+                # condense the sets of arrays into a single array of messages
+                if self.complete or self.connect_only:
+                    json = self.messages.pop(0)
+                    while len(self.messages) > 0:
+                        msg = self.messages.pop(0)
+                        json = "%s,%s" % (json[0:len(json)-1], msg[1:])
+                        
+                    self.write("%s" % json)
+
+
+        return apache.OK
+
+    def parse_request(self):
+        '''
+        If this is solely a DISCONNECT message, we set self.disconnect_only
+        to true
+        @return True if the body parses correctly, False otherwise
+        '''
+        osrf_msgs = osrf.json.to_object(self.body)
+        if not osrf_msgs:
+            return False
+        
+        if len(osrf_msgs) == 1:
+            if osrf_msgs[0].type() == OSRF_MESSAGE_TYPE_CONNECT:
+                self.connect_only = True
+            elif osrf_msgs[0].type() == OSRF_MESSAGE_TYPE_DISCONNECT:
+                self.disconnect_only = True
+
+        for msg in osrf_msgs:
+            if msg.type() == OSRF_MESSAGE_TYPE_REQUEST:
+                method = msg.payload()
+                params = osrf.json.to_json(method.params())
+                if len(params) == 2:
+                    params = ''
+                else:
+                    params = params[1:len(params)-1]
+                    
+                osrf.log.log_activity("[%s] [%s] %s %s %s" % (
+                    self.remote_host,
+                    '', # XXX auth token?
+                    self.service,
+                    method.method(),
+                    params
+                ))
+
+
+        return True
+
+
+    def set_to_addr(self):
+        ''' Determines the TO address.  Returns false if 
+            the address is missing or ambiguous. 
+            Also returns false if an explicit TO is specified and the
+            thread/IP/TO combination is not found in the session cache
+            '''
+        if self.service:
+            if self.recipient:
+                osrf.log.log_warn("specifying both SERVICE and TO is not allowed")
+                return False
+            self.recipient = "%s@%s/%s" % \
+                (ROUTER_NAME, OSRF_DOMAIN, self.service)
+            osrf.log.log_debug("set service to %s" % self.recipient)
+            return True
+        else:
+            if self.recipient:
+                # If the client specifies a specific TO address, verify it's
+                # the same address that was cached with the previous request.  
+                obj = self.cache.get(self.thread)
+                if obj and obj['ip'] == self.remote_host and \
+                    obj['jid'] == self.recipient:
+                    self.service = obj['service']
+                    return True
+        osrf.log.log_warn("client [%s] attempted to send directly "
+            "[%s] without a session" % (self.remote_host, self.recipient))
+        return False
+
+        
+    def init_headers(self, net_msg):
+        osrf.log.log_debug("initializing request headers")
+        self.apreq.headers_out[OSRF_HTTP_HEADER_FROM] = net_msg.sender
+        self.apreq.headers_out[OSRF_HTTP_HEADER_THREAD] = self.thread
+        if self.multipart:
+            self.apreq.content_type = MULTIPART_CONTENT_TYPE % self.delim
+            self.write("--%s\n" % self.delim)
+        else:
+            self.apreq.content_type = JSON_CONTENT_TYPE
+        self.cache.put(self.thread, \
+            {'ip':self.remote_host, 'jid': net_msg.sender, 'service':self.service}, CACHE_TIME)
+
+        osrf.log.log_debug("caching session [%s] for host [%s] and server "
+            " drone [%s]" % (self.thread, self.remote_host, net_msg.sender))
+
+
+
+    def check_status(self, net_msg): 
+        ''' Checks the status of the server response. 
+            If we received a timeout message, we drop it.
+            if it's any other non-continue status, we mark this session as
+            complete and carry on.
+            @return False if there is no data to return to the caller 
+            (dropped message, eg. timeout), True otherwise '''
+
+        osrf_msgs = osrf.json.to_object(net_msg.body)
+        last_msg = osrf_msgs.pop()
+
+        if last_msg.type() == OSRF_MESSAGE_TYPE_STATUS:
+            code = int(last_msg.payload().statusCode())
+            osrf.log.log_debug("got a status message with code %d" % code)
+
+            if code == OSRF_STATUS_TIMEOUT:
+                osrf.log.log_debug("removing cached session [%s] and "
+                    "dropping TIMEOUT message" % net_msg.thread)
+                self.cache.delete(net_msg.thread)
+                return False 
+
+            if code != OSRF_STATUS_CONTINUE:
+                self.complete = True
+
+        return True
+
+
+    def respond_chunk(self, resp):
+        ''' Writes a single multipart-delimited chunk of data '''
+
+        self.write("Content-type: %s\n\n" % JSON_CONTENT_TYPE)
+        self.write("%s\n\n" % resp.body)
+        if self.complete:
+            self.write("--%s--\n" % self.delim)
+        else:
+            self.write("--%s\n" % self.delim)
+        self.apreq.flush()
+
+    def write(self, msg):
+        ''' Writes data to the client stream. '''
+
+        if DEBUG_WRITE:
+            sys.stderr.write(msg)
+            sys.stderr.flush()
+        self.apreq.write(msg)
+            
+
diff --git a/trunk/src/python/osrf/json.py b/trunk/src/python/osrf/json.py
new file mode 100644 (file)
index 0000000..198ffb5
--- /dev/null
@@ -0,0 +1,183 @@
+import simplejson, types 
+from osrf.net_obj import NetworkObject, parse_net_object
+from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
+import osrf.log
+
+try:
+    # if available, use the faster cjson module for encoding/decoding JSON
+    import cjson
+    _use_cjson = True
+except ImportError:
+    _use_cjson = False
+
+_use_cjson = False
+
+class NetworkEncoder(simplejson.JSONEncoder):
+    ''' Encoder used by simplejson '''
+    def default(self, obj):
+
+        if isinstance(obj, NetworkObject):
+            reg = obj.get_registry()
+            data = obj.get_data()
+
+            # re-encode the object as an array if necessary
+            if reg.protocol == 'array':
+                objarray = []
+                for key in reg.keys:
+                    objarray.append(data.get(key)) 
+                data = objarray
+
+            return { 
+                OSRF_JSON_CLASS_KEY: reg.hint,
+                OSRF_JSON_PAYLOAD_KEY: self.default(data)
+            }   
+        return obj
+
+
+def encode_object(obj):
+    ''' Generic opensrf object encoder, used by cjson '''
+
+    if isinstance(obj, dict):
+        newobj = {}
+        for k,v in obj.iteritems():
+            newobj[k] = encode_object(v)
+        return newobj
+
+    elif isinstance(obj, list):
+        return [encode_object(v) for v in obj]
+
+    elif isinstance(obj, NetworkObject):
+        reg = obj.get_registry()
+        data = obj.get_data()
+
+        if reg.protocol == 'array':
+            objarray = []
+            for key in reg.keys:
+                objarray.append(data.get(key)) 
+            data = objarray
+
+        return {
+            OSRF_JSON_CLASS_KEY: reg.hint,
+            OSRF_JSON_PAYLOAD_KEY: encode_object(data)
+        }
+
+    return obj
+        
+
+
+def to_json(obj):
+    """Turns a python object into a wrapped JSON object"""
+    if _use_cjson:
+        return cjson.encode(encode_object(obj))
+    return simplejson.dumps(obj, cls=NetworkEncoder)
+
+
+def to_object(json):
+    """Turns a JSON string into python objects"""
+    if _use_cjson:
+        return parse_net_object(cjson.decode(json))
+    return parse_net_object(simplejson.loads(json))
+
+def parse_json_raw(json):
+    """Parses JSON the old fashioned way."""
+    if _use_cjson:
+        return cjson.decode(json)
+    return simplejson.loads(json)
+
+def to_json_raw(obj):
+    """Stringifies an object as JSON with no additional logic."""
+    if _use_cjson:
+        return cjson.encode(json)
+    return simplejson.dumps(obj)
+
+def __tabs(depth):
+    space = ''
+    for i in range(depth):
+        space += '   '
+    return space
+
+def debug_net_object(obj, depth=1):
+    """Returns a debug string for a given object.
+
+    If it's an NetworkObject and has registered keys, key/value pairs
+    are returned.  Otherwise formatted JSON is returned"""
+
+    debug_str = ''
+    if isinstance(obj, NetworkObject):
+        reg = obj.get_registry()
+        keys = list(reg.keys) # clone it, so sorting won't break the original
+        keys.sort()
+
+        for k in keys:
+
+            key = str(k)
+            while len(key) < 24:
+                key += '.' # pad the names to make the values line up somewhat
+            val = getattr(obj, k)()
+
+            subobj = val and not (isinstance(val, unicode) or isinstance(val, str) or \
+                isinstance(val, int) or isinstance(val, float) or isinstance(val, long))
+
+            debug_str += __tabs(depth) + key + ' = '
+
+            if subobj:
+                debug_str += '\n'
+                val = debug_net_object(val, depth+1)
+
+            debug_str += str(val)
+
+            if not subobj: debug_str += '\n'
+
+    else:
+        osrf.log.log_internal("Pretty-printing NetworkObject")
+        debug_str = pprint(to_json(obj))
+    return debug_str
+
+def pprint(json):
+    """JSON pretty-printer"""
+    r = ''
+    t = 0
+    instring = False
+    inescape = False
+    done = False
+    eatws = False
+
+    for c in json:
+
+        if eatws and not _use_cjson: # simpljson adds a pesky space after array and object items
+            if c == ' ': 
+                continue
+
+        eatws = False
+        done = False
+        if (c == '{' or c == '[') and not instring:
+            t += 1
+            r += c + '\n' + __tabs(t)
+            done = True
+
+        if (c == '}' or c == ']') and not instring:
+            t -= 1
+            r += '\n' + __tabs(t) + c
+            done = True
+
+        if c == ',' and not instring:
+            r += c + '\n' + __tabs(t)
+            done = True
+            eatws = True
+
+        if c == ':' and not instring:
+            eatws = True
+
+        if c == '"' and not inescape:
+            instring = not instring
+
+        if inescape: 
+            inescape = False
+
+        if c == '\\':
+            inescape = True
+
+        if not done:
+            r += c
+
+    return r
diff --git a/trunk/src/python/osrf/log.py b/trunk/src/python/osrf/log.py
new file mode 100644 (file)
index 0000000..a64d8a4
--- /dev/null
@@ -0,0 +1,197 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# -----------------------------------------------------------------------
+
+import traceback, sys, os, re, threading, time
+from osrf.const import OSRF_LOG_DEBUG, OSRF_LOG_ERR, OSRF_LOG_INFO, \
+    OSRF_LOG_INTERNAL, OSRF_LOG_TYPE_FILE, OSRF_LOG_TYPE_STDERR, \
+    OSRF_LOG_TYPE_SYSLOG, OSRF_LOG_WARN, OSRF_LOG_ACT
+LOG_SEMAPHORE = threading.BoundedSemaphore(value=1)
+
+
+LOG_LEVEL = OSRF_LOG_DEBUG
+LOG_TYPE = OSRF_LOG_TYPE_STDERR
+LOG_FILE = None
+FRGX = re.compile('/.*/')
+
+_xid = '' # the current XID
+_xid_pfx = '' # our XID prefix
+_xid_ctr = 0
+_xid_is_client = False # true if we are the request origin
+
+
+def initialize(level, facility=None, logfile=None, is_client=False):
+    """Initialize the logging subsystem."""
+    global LOG_LEVEL, LOG_TYPE, LOG_FILE, _xid_is_client
+
+    _xid_is_client = is_client
+    LOG_LEVEL = level
+
+    if facility: 
+        try:
+            import syslog
+        except ImportError:
+            sys.stderr.write("syslog not found, logging to stderr\n")
+            return
+
+        LOG_TYPE = OSRF_LOG_TYPE_SYSLOG
+        initialize_syslog(facility, level)
+        return
+        
+    if logfile:
+        LOG_TYPE = OSRF_LOG_TYPE_FILE
+        LOG_FILE = logfile
+
+def make_xid():
+    global _xid, _xid_pfx, _xid_is_client, _xid_ctr
+    if _xid_is_client:
+        if not _xid_pfx:
+            _xid_pfx = "%s%s" % (time.time(), os.getpid())
+        _xid = "%s%d" % (_xid_pfx, _xid_ctr)
+        _xid_ctr += 1
+         
+def clear_xid():
+    global _xid
+    _xid =  ''
+
+def set_xid(xid):
+    global _xid
+    _xid = xid
+
+def get_xid():
+    return _xid
+
+# -----------------------------------------------------------------------
+# Define wrapper functions for the log levels
+# -----------------------------------------------------------------------
+def log_internal(debug_str):
+    __log(OSRF_LOG_INTERNAL, debug_str)
+def log_debug(debug_str):
+    __log(OSRF_LOG_DEBUG, debug_str)
+def log_info(debug_str):
+    __log(OSRF_LOG_INFO, debug_str)
+def log_warn(debug_str):
+    __log(OSRF_LOG_WARN, debug_str)
+def log_error(debug_str):
+    __log(OSRF_LOG_ERR, debug_str)
+def log_activity(debug_str):
+    __log(OSRF_LOG_ACT, debug_str)
+
+def __log(level, msg):
+    """Builds the log message and passes the message off to the logger."""
+    global LOG_LEVEL, LOG_TYPE
+
+    try:
+        import syslog
+    except:
+        if level == OSRF_LOG_ERR:
+            sys.stderr.write('ERR ' + msg)
+        return
+        
+    if int(level) > int(LOG_LEVEL): return
+
+    # find the caller info for logging the file and line number
+    tb = traceback.extract_stack(limit=3)
+    tb = tb[0]
+    lvl = 'DEBG'
+
+    if level == OSRF_LOG_INTERNAL:
+        lvl = 'INT '
+    if level == OSRF_LOG_INFO:
+        lvl = 'INFO'
+    if level == OSRF_LOG_WARN:
+        lvl = 'WARN'
+    if level == OSRF_LOG_ERR:
+        lvl = 'ERR '
+    if level == OSRF_LOG_ACT:
+        lvl = 'ACT '
+
+    filename = FRGX.sub('', tb[0])
+    msg = '[%s:%d:%s:%s:%s:%s] %s' % (lvl, os.getpid(), filename, tb[1], threading.currentThread().getName(), _xid, msg)
+
+    if LOG_TYPE == OSRF_LOG_TYPE_SYSLOG:
+        __log_syslog(level, msg)
+    else:
+        if LOG_TYPE == OSRF_LOG_TYPE_FILE:
+            __log_file(msg)
+        else:
+            sys.stderr.write("%s\n" % msg)
+
+    if level == OSRF_LOG_ERR and LOG_TYPE != OSRF_LOG_TYPE_STDERR:
+        sys.stderr.write(msg + '\n')
+
+def __log_syslog(level, msg):
+    ''' Logs the message to syslog '''
+    import syslog
+
+    slvl = syslog.LOG_DEBUG
+    if level == OSRF_LOG_INTERNAL:
+        slvl = syslog.LOG_DEBUG
+    if level == OSRF_LOG_INFO or level == OSRF_LOG_ACT:
+        slvl = syslog.LOG_INFO
+    if level == OSRF_LOG_WARN:
+        slvl = syslog.LOG_WARNING
+    if level == OSRF_LOG_ERR:
+        slvl = syslog.LOG_ERR
+
+    syslog.syslog(slvl, msg)
+
+def __log_file(msg):
+    ''' Logs the message to a file. '''
+
+    global LOG_TYPE
+
+    epoch = time.time()
+    timestamp = time.strftime('%Y-%M-%d %H:%m:%S', time.localtime(epoch))
+    timestamp += '.%s' % str('%0.3f' % epoch)[-3:] # grab the millis
+
+    logfile = None
+    try:
+        logfile = open(LOG_FILE, 'a')
+    except:
+        sys.stderr.write("cannot open log file for writing: %s\n" % LOG_FILE)
+        LOG_TYPE = OSRF_LOG_TYPE_STDERR
+        return
+    try:
+        LOG_SEMAPHORE.acquire()
+        logfile.write("%s %s\n" % (timestamp, msg))
+    finally:
+        LOG_SEMAPHORE.release()
+        
+    logfile.close()
+
+def initialize_syslog(facility, level):
+    """Connect to syslog and set the logmask based on the level provided."""
+
+    import syslog
+    level = int(level)
+
+    if facility == 'local0':
+        facility = syslog.LOG_LOCAL0
+    if facility == 'local1':
+        facility = syslog.LOG_LOCAL1
+    if facility == 'local2':
+        facility = syslog.LOG_LOCAL2
+    if facility == 'local3':
+        facility = syslog.LOG_LOCAL3
+    if facility == 'local4':
+        facility = syslog.LOG_LOCAL4
+    if facility == 'local5':
+        facility = syslog.LOG_LOCAL5
+    if facility == 'local6':
+        facility = syslog.LOG_LOCAL6
+    # add other facility maps if necessary...
+
+    syslog.openlog(sys.argv[0], 0, facility)
+
diff --git a/trunk/src/python/osrf/net.py b/trunk/src/python/osrf/net.py
new file mode 100644 (file)
index 0000000..cad2e67
--- /dev/null
@@ -0,0 +1,250 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# -----------------------------------------------------------------------
+
+
+import os, time, threading
+from pyxmpp.jabber.client import JabberClient
+from pyxmpp.message import Message
+from pyxmpp.jid import JID
+from socket import gethostname
+import libxml2
+import osrf.log, osrf.ex
+
+THREAD_SESSIONS = {}
+
+# - log jabber activity (for future reference)
+#import logging
+#logger=logging.getLogger()
+#logger.addHandler(logging.StreamHandler())
+#logger.addHandler(logging.FileHandler('j.log'))
+#logger.setLevel(logging.DEBUG)
+
+
+
+
+class XMPPNoRecipient(osrf.ex.OSRFException):
+    ''' Raised when a message was sent to a non-existent recipient 
+        The recipient is stored in the 'recipient' field on this object
+    '''
+    def __init__(self, recipient):
+        osrf.ex.OSRFException.__init__(self, 'Error communicating with %s' % recipient)
+        self.recipient = recipient
+
+class XMPPNoConnection(osrf.ex.OSRFException):
+    pass
+
+def set_network_handle(handle):
+    """ Sets the thread-specific network handle"""
+    THREAD_SESSIONS[threading.currentThread().getName()] = handle
+
+def get_network_handle():
+    """ Returns the thread-specific network connection handle."""
+    return THREAD_SESSIONS.get(threading.currentThread().getName())
+
+def clear_network_handle():
+    ''' Disconnects the thread-specific handle and discards it '''
+    handle = THREAD_SESSIONS.get(threading.currentThread().getName())
+    if handle:
+        osrf.log.log_internal("clearing network handle %s" % handle.jid.as_utf8())
+        #handle.disconnect()
+        del THREAD_SESSIONS[threading.currentThread().getName()]
+        return handle
+
+class NetworkMessage(object):
+    """Network message
+
+    attributes:
+
+    sender - message sender
+    recipient - message recipient
+    body - the body of the message
+    thread - the message thread
+    locale - locale of the message
+    osrf_xid - The logging transaction ID
+    """
+
+    def __init__(self, message=None, **args):
+        if message:
+            self.body = message.get_body()
+            self.thread = message.get_thread()
+            self.recipient = message.get_to()
+            self.router_command = None
+            self.router_class = None
+            if message.xmlnode.hasProp('router_from') and \
+                message.xmlnode.prop('router_from') != '':
+                self.sender = message.xmlnode.prop('router_from')
+            else:
+                self.sender = message.get_from().as_utf8()
+            if message.xmlnode.hasProp('osrf_xid'):
+                self.xid = message.xmlnode.prop('osrf_xid')
+            else:
+                self.xid = ''
+        else:
+            self.sender = args.get('sender')
+            self.recipient = args.get('recipient')
+            self.body = args.get('body')
+            self.thread = args.get('thread')
+            self.router_command = args.get('router_command')
+            self.router_class = args.get('router_class')
+            self.xid = osrf.log.get_xid()
+
+    @staticmethod
+    def from_xml(xml):
+        doc = libxml2.parseDoc(xml)
+        msg = Message(doc.getRootElement())
+        return NetworkMessage(msg)
+        
+
+    def make_xmpp_msg(self):
+        ''' Creates a pyxmpp.message.Message and adds custom attributes '''
+
+        msg = Message(None, self.sender, self.recipient, None, None, None, \
+            self.body, self.thread)
+        if self.router_command:
+            msg.xmlnode.newProp('router_command', self.router_command)
+        if self.router_class:
+            msg.xmlnode.newProp('router_class', self.router_class)
+        if self.xid:
+            msg.xmlnode.newProp('osrf_xid', self.xid)
+        return msg
+
+    def to_xml(self):
+        ''' Turns this message into XML '''
+        return self.make_xmpp_msg().serialize()
+        
+
+class Network(JabberClient):
+    def __init__(self, **args):
+        self.isconnected = False
+
+        # Create a unique jabber resource
+        resource = args.get('resource') or 'python_client'
+        resource += '_' + gethostname() + ':' + str(os.getpid()) + '_' + \
+            threading.currentThread().getName().lower()
+        self.jid = JID(args['username'], args['host'], resource)
+
+        osrf.log.log_debug("initializing network with JID %s and host=%s, "
+            "port=%s, username=%s" % (self.jid.as_utf8(), args['host'], \
+            args['port'], args['username']))
+
+        #initialize the superclass
+        JabberClient.__init__(self, self.jid, args['password'], args['host'])
+        self.queue = []
+
+        self.receive_callback = None
+        self.transport_error_msg = None
+
+    def connect(self):
+        JabberClient.connect(self)
+        while not self.isconnected:
+            stream = self.get_stream()
+            act = stream.loop_iter(10)
+            if not act:
+                self.idle()
+
+    def set_receive_callback(self, func):
+        """The callback provided is called when a message is received.
+        
+            The only argument to the function is the received message. """
+        self.receive_callback = func
+
+    def session_started(self):
+        osrf.log.log_info("Successfully connected to the opensrf network")
+        self.authenticated()
+        self.stream.set_message_handler("normal", self.message_received)
+        self.stream.set_message_handler("error", self.error_received)
+        self.isconnected = True
+
+    def send(self, message):
+        """Sends the provided network message."""
+        osrf.log.log_internal("jabber sending to %s: %s" % (message.recipient, message.body))
+        message.sender = self.jid.as_utf8()
+        msg = message.make_xmpp_msg()
+        self.stream.send(msg)
+
+    def error_received(self, stanza):
+        self.transport_error_msg = NetworkMessage(stanza)
+        osrf.log.log_error("XMPP error message received from %s" % self.transport_error_msg.sender)
+    
+    def message_received(self, stanza):
+        """Handler for received messages."""
+        if stanza.get_type()=="headline":
+            return True
+        # check for errors
+        osrf.log.log_internal("jabber received message from %s : %s" 
+            % (stanza.get_from().as_utf8(), stanza.get_body()))
+        self.queue.append(NetworkMessage(stanza))
+        return True
+
+    def stream_closed(self, stream):
+        osrf.log.log_debug("XMPP Stream closing...")
+
+    def stream_error(self, err):
+        osrf.log.log_error("XMPP Stream error: condition: %s %r"
+            % (err.get_condition().name,err.serialize()))
+
+    def disconnected(self):
+        osrf.log.log_internal('XMPP Disconnected')
+
+    def recv(self, timeout=120):
+        """Attempts to receive a message from the network.
+
+        timeout - max number of seconds to wait for a message.  
+        If a message is received in 'timeout' seconds, the message is passed to 
+        the receive_callback is called and True is returned.  Otherwise, false is
+        returned.
+        """
+
+        forever = False
+        if timeout < 0:
+            forever = True
+            timeout = None
+
+        if len(self.queue) == 0:
+            while (forever or timeout >= 0) and len(self.queue) == 0:
+                starttime = time.time()
+
+                stream = self.get_stream()
+                if not stream:
+                   raise XMPPNoConnection('We lost our server connection...') 
+
+                act = stream.loop_iter(timeout)
+                endtime = time.time() - starttime
+
+                if not forever:
+                    timeout -= endtime
+
+                osrf.log.log_internal("exiting stream loop after %s seconds. "
+                    "act=%s, queue size=%d" % (str(endtime), act, len(self.queue)))
+
+                if self.transport_error_msg:
+                    msg = self.transport_error_msg
+                    self.transport_error_msg = None
+                    raise XMPPNoRecipient(msg.sender)
+
+                if not act:
+                    self.idle()
+
+        # if we've acquired a message, handle it
+        msg = None
+        if len(self.queue) > 0:
+            msg = self.queue.pop(0)
+            if self.receive_callback:
+                self.receive_callback(msg)
+
+        return msg
+
+
+
diff --git a/trunk/src/python/osrf/net_obj.py b/trunk/src/python/osrf/net_obj.py
new file mode 100644 (file)
index 0000000..beed013
--- /dev/null
@@ -0,0 +1,290 @@
+from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
+import re
+from xml.sax import saxutils
+
+
+# -----------------------------------------------------------
+# Define the global network-class registry
+# -----------------------------------------------------------
+
+
+class NetworkRegistry(object):
+    ''' Network-serializable objects must be registered.  The class
+        hint maps to a set (ordered in the case of array-base objects)
+        of field names (keys).  
+        '''
+
+    # Global object registry 
+    registry = {}
+
+    def __init__(self, hint, keys, protocol):
+        self.hint = hint
+        self.keys = keys
+        self.protocol = protocol
+        NetworkRegistry.registry[hint] = self
+    
+    @staticmethod
+    def get_registry(hint):
+        return NetworkRegistry.registry.get(hint)
+
+# -----------------------------------------------------------
+# Define the base class for all network-serializable objects
+# -----------------------------------------------------------
+
+class NetworkObject(object):
+    ''' Base class for all network serializable objects '''
+
+    # link to our registry object for this registered class
+    registry = None
+
+    def __init__(self, data=None):
+        ''' If this is an array, we pull data out of the data array
+            (if there is any) and translate that into a hash internally '''
+
+        self._data = data
+        if not data: self._data = {}
+        if isinstance(data, list):
+            self.import_array_data(list)
+
+    def import_array_data(self, data):
+        ''' If an array-based object is created with an array
+            of data, cycle through and load the data '''
+
+        self._data = {}
+        if len(data) == 0:
+            return
+
+        reg = self.get_registry()
+        if reg.protocol == 'array':
+            for entry in range(len(reg.keys)):
+                if len(data) > entry:
+                    break
+                self.set_field(reg.keys[entry], data[entry])
+
+    def get_data(self):
+        ''' Returns the full dataset for this object as a dict '''
+        return self._data
+
+    def set_field(self, field, value):
+        self._data[field] = value
+
+    def get_field(self, field):
+        return self._data.get(field)
+
+    def get_registry(self):
+        ''' Returns the registry object for this registered class '''
+        return self.__class__.registry
+
+    def shallow_clone(self):
+        ''' Makes a shallow copy '''
+        reg = self.get_registry()
+        obj = new_object_from_hint(reg.hint)
+        for field in reg.keys:
+            obj.set_field(field, self.get_field(field))
+        return obj
+            
+
+
+def new_object_from_hint(hint):
+    ''' Given a hint, this will create a new object of that 
+        type and return it.  If this hint is not registered,
+        an object of type NetworkObject.__unknown is returned'''
+    try:
+        obj = None
+        exec('obj = NetworkObject.%s()' % hint)
+        return obj
+    except AttributeError:
+        return NetworkObject.__unknown()
+
+def __make_network_accessor(cls, key):
+    ''' Creates and accessor/mutator method for the given class.  
+        'key' is the name the method will have and represents
+        the field on the object whose data we are accessing ''' 
+    def accessor(self, *args):
+        if len(args) != 0:
+            self.set_field(key, args[0])
+        return self.get_field(key)
+    setattr(cls, key, accessor)
+
+
+def register_hint(hint, keys, type='hash'):
+    ''' Registers a new network-serializable object class.
+
+        'hint' is the class hint
+        'keys' is the list of field names on the object
+            If this is an array-based object, the field names
+            must be sorted to reflect the encoding order of the fields
+        'type' is the wire-protocol of the object.  hash or array.
+        '''
+
+    # register the class with the global registry
+    registry = NetworkRegistry(hint, keys, type)
+
+    # create the new class locally with the given hint name
+    exec('class %s(NetworkObject):\n\tpass' % hint)
+
+    # give the new registered class a local handle
+    cls = None
+    exec('cls = %s' % hint)
+
+    # assign an accessor/mutator for each field on the object
+    for k in keys:
+        __make_network_accessor(cls, k)
+
+    # attach our new class to the NetworkObject 
+    # class so others can access it
+    setattr(NetworkObject, hint , cls)
+    cls.registry = registry
+
+
+
+
+# create a unknown object to handle unregistred types
+register_hint('__unknown', [], 'hash')
+
+# -------------------------------------------------------------------
+# Define the custom object parsing behavior 
+# -------------------------------------------------------------------
+def parse_net_object(obj):
+    
+    try:
+        hint = obj[OSRF_JSON_CLASS_KEY]
+        sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
+        reg = NetworkRegistry.get_registry(hint)
+
+        obj = {}
+
+        if reg.protocol == 'array':
+            for entry in range(len(reg.keys)):
+                if len(sub_object) > entry:
+                    obj[reg.keys[entry]] = parse_net_object(sub_object[entry])
+                else:
+                    obj[reg.keys[entry]] = None
+        else:
+            for key in reg.keys:
+                obj[key] = parse_net_object(sub_object.get(key))
+
+        estr = 'obj = NetworkObject.%s(obj)' % hint
+        try:
+            exec(estr)
+        except:
+            # this object has not been registered, shove it into the default container
+            obj = NetworkObject.__unknown(obj)
+
+        return obj
+
+    except:
+        pass
+
+    # the current object does not have a class hint
+    if isinstance(obj, list):
+        for entry in range(len(obj)):
+            obj[entry] = parse_net_object(obj[entry])
+
+    else:
+        if isinstance(obj, dict):
+            for key, value in obj.iteritems():
+                obj[key] = parse_net_object(value)
+
+    return obj
+
+
+def to_xml(obj):
+    """ Returns the XML representation of an internal object."""
+    chars = []
+    __to_xml(obj, chars)
+    return ''.join(chars)
+
+def __to_xml(obj, chars):
+    """ Turns an internal object into OpenSRF XML """
+
+    if obj is None:
+        chars.append('<null/>')
+        return
+
+    if isinstance(obj, unicode) or isinstance(obj, str):
+        chars.append('<string>%s</string>' % saxutils.escape(obj))
+        return
+
+    if isinstance(obj, int)  or isinstance(obj, long):
+        chars.append('<number>%d</number>' % obj)
+        return
+
+    if isinstance(obj, float):
+        chars.append('<number>%f</number>' % obj)
+        return
+
+    if isinstance(obj, NetworkObject): 
+
+        registry = obj.get_registry()
+        data = obj.get_data()
+        hint = saxutils.escape(registry.hint)
+
+        if registry.protocol == 'array':
+            chars.append("<array class_hint='%s'>" % hint)
+            for key in registry.keys:
+                __to_xml(data.get(key), chars)
+            chars.append('</array>')
+
+        else:
+            if registry.protocol == 'hash':
+                chars.append("<object class_hint='%s'>" % hint)
+                for key, value in data.items():
+                    chars.append("<element key='%s'>" % saxutils.escape(key))
+                    __to_xml(value, chars)
+                    chars.append('</element>')
+                chars.append('</object>')
+                
+
+    if isinstance(obj, list):
+        chars.append('<array>')
+        for entry in obj:
+            __to_xml(entry, chars)
+        chars.append('</array>')
+        return
+
+    if isinstance(obj, dict):
+        chars.append('<object>')
+        for key, value in obj.items():
+            chars.append("<element key='%s'>" % saxutils.escape(key))
+            __to_xml(value, chars)
+            chars.append('</element>')
+        chars.append('</object>')
+        return
+
+    if isinstance(obj, bool):
+        val = 'false'
+        if obj:
+            val = 'true'
+        chars.append("<boolean value='%s'/>" % val)
+        return
+
+def find_object_path(obj, path, idx=None):
+    """Searches an object along the given path for a value to return.
+
+    Path separators can be '/' or '.', '/' is tried first."""
+
+    parts = []
+
+    if re.search('/', path):
+        parts = path.split('/')
+    else:
+        parts = path.split('.')
+
+    for part in parts:
+        try:
+            val = obj[part]
+        except:
+            return None
+        if isinstance(val, str): 
+            return val
+        if isinstance(val, list):
+            if idx != None:
+                return val[idx]
+            return val
+        if isinstance(val, dict):
+            obj = val
+        else:
+            return val
+
+    return obj
diff --git a/trunk/src/python/osrf/server.py b/trunk/src/python/osrf/server.py
new file mode 100644 (file)
index 0000000..10f4d66
--- /dev/null
@@ -0,0 +1,322 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2008  Equinox Software, Inc.
+# Bill Erickson <erickson@esilibrary.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA  
+# 02110-1301, USA
+# -----------------------------------------------------------------------
+
+import os, sys, threading, logging, fcntl, socket, errno, signal, time
+import osrf.log, osrf.conf, osrf.net, osrf.system, osrf.stack, osrf.app
+
+
+# used to define the size of the PID/size leader in 
+# status and data messages passed to and from children
+SIZE_PAD = 12
+
+class Controller(object):
+    ''' 
+        OpenSRF forking request server.  
+    '''
+
+    def __init__(self, service):
+        self.service = service # service name
+        self.application = None # the application we're serving
+        self.max_requests = 0 # max child requests
+        self.max_children = 0 # max num of child processes
+        self.min_childen = 0 # min num of child processes
+        self.num_children = 0 # current num children
+        self.child_idx = 0 # current index into the children array
+        self.children = [] # list of children
+        self.osrf_handle = None # xmpp handle
+        self.routers = [] # list of registered routers
+
+        # Global status socketpair.  All children relay their 
+        # availability info to the parent through this socketpair. 
+        self.read_status, self.write_status = socket.socketpair()
+
+    def load_app(self):
+        settings = osrf.set.get('activeapps.%s' % self.service)
+        
+
+    def cleanup(self):
+        ''' Closes management sockets, kills children, reaps children, exits '''
+
+        osrf.log.log_info("Shutting down...")
+        self.cleanup_routers()
+
+        self.read_status.shutdown(socket.SHUT_RDWR)
+        self.write_status.shutdown(socket.SHUT_RDWR)
+        self.read_status.close()
+        self.write_status.close()
+
+        for child in self.children:
+            child.read_data.shutdown(socket.SHUT_RDWR)
+            child.write_data.shutdown(socket.SHUT_RDWR)
+            child.read_data.close()
+            child.write_data.close()
+
+        os.kill(0, signal.SIGKILL)
+        self.reap_children(True)
+        os._exit(0)
+
+
+    def handle_signals(self):
+        ''' Installs SIGINT and SIGTERM handlers '''
+        def handler(signum, frame):
+            self.cleanup()
+        signal.signal(signal.SIGINT, handler)
+        signal.signal(signal.SIGTERM, handler)
+
+
+    def run(self):
+
+        osrf.net.get_network_handle().disconnect()
+        osrf.net.clear_network_handle()
+        self.spawn_children()
+        self.handle_signals()
+
+        time.sleep(.5) # give children a chance to connect before we start taking data
+        self.osrf_handle = osrf.system.System.net_connect(resource = '%s_listener' % self.service)
+
+        # clear the recv callback so inbound messages do not filter through the opensrf stack
+        self.osrf_handle.receive_callback = None
+
+        # connect to our listening routers
+        self.register_routers()
+
+        try:
+            osrf.log.log_debug("entering main server loop...")
+            while True: # main server loop
+
+                self.reap_children()
+                self.check_status()
+                data = self.osrf_handle.recv(-1).to_xml()
+
+                if self.try_avail_child(data):
+                    continue
+
+                if self.try_new_child(data):
+                    continue
+
+                self.wait_for_child()
+
+        except KeyboardInterrupt:
+            self.cleanup()
+        #except Exception, e: 
+            #osrf.log.log_error("server exiting with exception: %s" % e.message)
+            #self.cleanup()
+                
+
+    def try_avail_child(self, data):
+        ''' Trys to send current request data to an available child process '''
+        ctr = 0
+        while ctr < self.num_children:
+
+            if self.child_idx >= self.num_children:
+                self.child_idx = 0
+            child = self.children[self.child_idx]
+
+            if child.available:
+                osrf.log.log_internal("sending data to available child")
+                self.write_child(child, data)
+                return True
+
+            ctr += 1
+            self.child_idx += 1
+        return False
+
+    def try_new_child(self, data):
+        ''' Tries to spawn a new child to send request data to '''
+        if self.num_children < self.max_children:
+            osrf.log.log_internal("spawning new child to handle data")
+            child = self.spawn_child()
+            self.write_child(child, data)
+            return True
+        return False
+
+    def try_wait_child(self, data):
+        ''' Waits for a child to become available '''
+        osrf.log.log_warn("No children available, waiting...")
+        child = self.check_status(True)
+        self.write_child(child, data)
+
+
+    def write_child(self, child, data):
+        ''' Sends data to the child process '''
+        child.available = False
+        child.write_data.sendall(str(len(data)).rjust(SIZE_PAD) + data)
+        self.child_idx += 1
+
+
+    def check_status(self, block=False):
+        ''' Checks to see if any children have indicated they are done with 
+            their current request.  If block is true, this will wait 
+            indefinitely for a child to be free. '''
+
+        pid = None
+        child = None
+        if block:
+            pid = self.read_status.recv(SIZE_PAD)
+        else:
+            try:
+                self.read_status.setblocking(0)
+                pid = self.read_status.recv(SIZE_PAD)
+            except socket.error, e:
+                if e.args[0] != errno.EAGAIN:
+                    raise e
+            self.read_status.setblocking(1)
+                
+        if pid:
+            pid = int(pid)
+            child = [c for c in self.children if c.pid == pid][0]
+            child.available = True
+
+        return child
+        
+
+    def reap_children(self, done=False):
+        ''' Uses waitpid() to reap the children.  If necessary, new children are spawned '''
+        options = 0
+        if not done: 
+            options = os.WNOHANG 
+
+        while True:
+            try:
+                (pid, status) = os.waitpid(0, options)
+                if pid == 0:
+                    if not done:
+                        self.spawn_children()
+                    return
+                osrf.log.log_debug("reaping child %d" % pid)
+                self.num_children -= 1
+                self.children = [c for c in self.children if c.pid != pid]
+            except OSError:
+                return
+        
+    def spawn_children(self):
+        ''' Launches up to min_children child processes '''
+        while self.num_children < self.min_children:
+            self.spawn_child()
+
+    def spawn_child(self):
+        ''' Spawns a new child process '''
+
+        child = Child(self)
+        child.read_data, child.write_data = socket.socketpair()
+        child.pid = os.fork()
+
+        if child.pid:
+            self.num_children += 1
+            self.children.append(child)
+            osrf.log.log_debug("spawned child %d : %d total" % (child.pid, self.num_children))
+            return child
+        else:
+            child.pid = os.getpid()
+            child.init()
+            child.run()
+            osrf.net.get_network_handle().disconnect()
+            osrf.log.log_internal("child exiting...")
+            os._exit(0)
+
+    def register_routers(self):
+        ''' Registers this application instance with all configured routers '''
+        routers = osrf.conf.get('routers.router')
+
+        if not isinstance(routers, list):
+            routers = [routers]
+
+        for router in routers:
+            if isinstance(router, dict):
+                if not 'services' in router or \
+                        self.service in router['services']['service']:
+                    target = "%s@%s/router" % (router['name'], router['domain'])
+                    self.register_router(target)
+            else:
+                router_name = osrf.conf.get('router_name')
+                target = "%s@%s/router" % (router_name, router)
+                self.register_router(target)
+
+
+    def register_router(self, target):
+        ''' Registers with a single router '''
+        osrf.log.log_info("registering with router %s" % target)
+        self.routers.append(target)
+
+        reg_msg = osrf.net.NetworkMessage(
+            recipient = target,
+            body = 'registering...',
+            router_command = 'register',
+            router_class = self.service
+        )
+
+        self.osrf_handle.send(reg_msg)
+
+    def cleanup_routers(self):
+        ''' Un-registers with all connected routers '''
+        for target in self.routers:
+            osrf.log.log_info("un-registering with router %s" % target)
+            unreg_msg = osrf.net.NetworkMessage(
+                recipient = target,
+                body = 'un-registering...',
+                router_command = 'unregister',
+                router_class = self.service
+            )
+            self.osrf_handle.send(unreg_msg)
+        
+
+class Child(object):
+    ''' Models a single child process '''
+
+    def __init__(self, controller):
+        self.controller = controller # our Controller object
+        self.num_requests = 0 # how many requests we've served so far
+        self.read_data = None # the child reads data from the controller on this socket
+        self.write_data = None # the controller sends data to the child on this socket 
+        self.available = True # true if this child is not currently serving a request
+        self.pid = 0 # my process id
+
+
+    def run(self):
+        ''' Loops, processing data, until max_requests is reached '''
+        while True:
+            try:
+                size = int(self.read_data.recv(SIZE_PAD) or 0)
+                data = self.read_data.recv(size)
+                osrf.log.log_internal("recv'd data " + data)
+                osrf.stack.push(osrf.net.NetworkMessage.from_xml(data))
+                self.num_requests += 1
+                if self.num_requests == self.controller.max_requests:
+                    break
+                self.send_status()
+            except KeyboardInterrupt:
+                pass
+        # run the exit handler
+        osrf.app.Application.application.child_exit()
+
+    def send_status(self):
+        ''' Informs the controller that we are done processing this request '''
+        fcntl.lockf(self.controller.write_status.fileno(), fcntl.LOCK_EX)
+        try:
+            self.controller.write_status.sendall(str(self.pid).rjust(SIZE_PAD))
+        finally:
+            fcntl.lockf(self.controller.write_status.fileno(), fcntl.LOCK_UN)
+
+    def init(self):
+        ''' Connects the opensrf xmpp handle '''
+        osrf.net.clear_network_handle()
+        osrf.system.System.net_connect(resource = '%s_drone' % self.controller.service)
+        osrf.app.Application.application.child_init()
+
diff --git a/trunk/src/python/osrf/ses.py b/trunk/src/python/osrf/ses.py
new file mode 100644 (file)
index 0000000..63c3a35
--- /dev/null
@@ -0,0 +1,480 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# -----------------------------------------------------------------------
+
+import osrf.json, osrf.conf, osrf.log, osrf.net, osrf.net_obj, osrf.const
+from osrf.const import OSRF_APP_SESSION_CONNECTED, \
+    OSRF_APP_SESSION_CONNECTING, OSRF_APP_SESSION_DISCONNECTED, \
+    OSRF_MESSAGE_TYPE_CONNECT, OSRF_MESSAGE_TYPE_DISCONNECT, \
+    OSRF_MESSAGE_TYPE_REQUEST, OSRF_MESSAGE_TYPE_RESULT, OSRF_MESSAGE_TYPE_STATUS
+import osrf.ex
+import random, os, time, threading
+
+
+# -----------------------------------------------------------------------
+# Go ahead and register the common network objects
+# -----------------------------------------------------------------------
+osrf.net_obj.register_hint('osrfMessage', ['threadTrace', 'locale', 'type', 'payload'], 'hash')
+osrf.net_obj.register_hint('osrfMethod', ['method', 'params'], 'hash')
+osrf.net_obj.register_hint('osrfResult', ['status', 'statusCode', 'content'], 'hash')
+osrf.net_obj.register_hint('osrfConnectStatus', ['status', 'statusCode'], 'hash')
+osrf.net_obj.register_hint('osrfMethodException', ['status', 'statusCode'], 'hash')
+
+
+class Session(object):
+    """Abstract session superclass."""
+
+    ''' Global cache of in-service sessions '''
+    session_cache = {}
+
+    def __init__(self):
+        # by default, we're connected to no one
+        self.state = OSRF_APP_SESSION_DISCONNECTED
+        self.remote_id = None
+        self.locale = None
+        self.thread = None
+        self.service = None
+
+    @staticmethod
+    def find_or_create(thread):
+        if thread in Session.session_cache:
+            return Session.session_cache[thread]
+        return ServerSession(thread)
+
+    def set_remote_id(self, remoteid):
+        self.remote_id = remoteid
+        osrf.log.log_internal("Setting request remote ID to %s" % self.remote_id)
+
+    def wait(self, timeout=120):
+        """Wait up to <timeout> seconds for data to arrive on the network"""
+        osrf.log.log_internal("Session.wait(%d)" % timeout)
+        handle = osrf.net.get_network_handle()
+        handle.recv(timeout)
+
+    def send(self, omessages):
+        """Sends an OpenSRF message"""
+        if not isinstance(omessages, list):
+            omessages = [omessages]
+            
+        net_msg = osrf.net.NetworkMessage(
+            recipient      = self.remote_id,
+            body    = osrf.json.to_json(omessages),
+            thread = self.thread,
+            locale = self.locale,
+        )
+
+        handle = osrf.net.get_network_handle()
+        handle.send(net_msg)
+
+    def cleanup(self):
+        """Removes the session from the global session cache."""
+        del Session.session_cache[self.thread]
+
+class ClientSession(Session):
+    """Client session object.  Use this to make server requests."""
+
+    def __init__(self, service, locale='en-US'):
+        
+        # call superclass constructor
+        Session.__init__(self)
+
+        # the service we are sending requests to
+        self.service = service
+
+        # the locale we want requests to be returned in
+        self.locale = locale
+
+        # find the remote service handle <router>@<domain>/<service>
+        domain = osrf.conf.get('domain', 0)
+        router = osrf.conf.get('router_name')
+        self.remote_id = "%s@%s/%s" % (router, domain, service)
+        self.orig_remote_id = self.remote_id
+
+        # generate a random message thread
+        self.thread = "%s%s%s%s" % (os.getpid(), 
+            str(random.randint(100,100000)), str(time.time()),threading.currentThread().getName().lower())
+
+        # how many requests this session has taken part in
+        self.next_id = 0 
+
+        # cache of request objects 
+        self.requests = {}
+
+        # cache this session in the global session cache
+        Session.session_cache[self.thread] = self
+
+    def reset_request_timeout(self, rid):
+        req = self.find_request(rid)
+        if req:
+            req.reset_timeout = True
+            
+
+    def request2(self, method, arr):
+        """Creates a new request and sends the request to the server using a python array as the params."""
+        return self.__request(method, arr)
+
+    def request(self, method, *args):
+        """Creates a new request and sends the request to the server using a variable argument list as params"""
+        arr = list(args)
+        return self.__request(method, arr)
+
+    def __request(self, method, arr):
+        """Builds the request object and sends it."""
+        if self.state != OSRF_APP_SESSION_CONNECTED:
+            self.reset_remote_id()
+
+        osrf.log.make_xid()
+
+        osrf.log.log_debug("Sending request %s -> %s " % (self.service, method))
+        req = ClientRequest(self, self.next_id, method, arr, self.locale)
+        self.requests[str(self.next_id)] = req
+        self.next_id += 1
+        req.send()
+        return req
+
+
+    def connect(self, timeout=10):
+        """Connects to a remote service"""
+
+        if self.state == OSRF_APP_SESSION_CONNECTED:
+            return True
+        self.state = OSRF_APP_SESSION_CONNECTING
+
+        # construct and send a CONNECT message
+        self.send(
+            osrf.net_obj.NetworkObject.osrfMessage( 
+                {   'threadTrace' : 0,
+                    'type' : OSRF_MESSAGE_TYPE_CONNECT
+                } 
+            )
+        )
+
+        while timeout >= 0 and not self.state == OSRF_APP_SESSION_CONNECTED:
+            start = time.time()
+            self.wait(timeout)
+            timeout -= time.time() - start
+        
+        if self.state != OSRF_APP_SESSION_CONNECTED:
+            raise osrf.ex.OSRFServiceException("Unable to connect to " + self.service)
+        
+        return True
+
+    def disconnect(self):
+        """Disconnects from a remote service"""
+
+        if self.state == OSRF_APP_SESSION_DISCONNECTED:
+            return True
+
+        self.send(
+            osrf.net_obj.NetworkObject.osrfMessage( 
+                {   'threadTrace' : 0,
+                    'type' : OSRF_MESSAGE_TYPE_DISCONNECT
+                } 
+            )
+        )
+
+        self.state = OSRF_APP_SESSION_DISCONNECTED
+
+    
+
+    def reset_remote_id(self):
+        """Recovers the original remote id"""
+        self.remote_id = self.orig_remote_id
+        osrf.log.log_internal("Resetting remote ID to %s" % self.remote_id)
+
+    def push_response_queue(self, message):
+        """Pushes the message payload onto the response queue 
+            for the request associated with the message's ID."""
+        osrf.log.log_debug("pushing %s" % message.payload())
+        try:
+            self.find_request(message.threadTrace()).push_response(message.payload())
+        except Exception, e: 
+            osrf.log.log_warn("pushing respond to non-existent request %s : %s" % (message.threadTrace(), e))
+
+    def find_request(self, rid):
+        """Returns the original request matching this message's threadTrace."""
+        try:
+            return self.requests[str(rid)]
+        except KeyError:
+            osrf.log.log_debug('find_request(): non-existent request %s' % str(rid))
+            return None
+
+    @staticmethod
+    def atomic_request(service, method, *args):
+        ses = ClientSession(service)
+        req = ses.request2(method, list(args))
+        resp = req.recv()
+        data = None
+        if resp:
+            data = resp.content()
+        req.cleanup()
+        ses.cleanup()
+        return data
+
+
+
+
+class Request(object):
+    def __init__(self, session, rid, method=None, params=[], locale='en-US'):
+        self.session = session # my session handle
+        self.rid     = rid # my unique request ID
+        self.method = method # method name
+        self.params = params # my method params
+        self.locale = locale
+        self.complete = False # is this request done?
+        self.complete_time =  0 # time at which the request was completed
+
+
+class ClientRequest(Request):
+    """Represents a single OpenSRF request.
+        A request is made and any resulting respones are 
+        collected for the client."""
+
+    def __init__(self, session, rid, method=None, params=[], locale='en-US'):
+        Request.__init__(self, session, rid, method, params, locale)
+        self.queue  = [] # response queue
+        self.reset_timeout = False # resets the recv timeout?
+        self.send_time = 0 # local time the request was put on the wire
+        self.first_response_time = 0 # time it took for our first reponse to be received
+
+    def send(self):
+        """Sends a request message"""
+
+        # construct the method object message with params and method name
+        method = osrf.net_obj.NetworkObject.osrfMethod( {
+            'method' : self.method,
+            'params' : self.params
+        } )
+
+        # construct the osrf message with our method message embedded
+        message = osrf.net_obj.NetworkObject.osrfMessage( {
+            'threadTrace' : self.rid,
+            'type' : OSRF_MESSAGE_TYPE_REQUEST,
+            'payload' : method,
+            'locale' : self.locale
+        } )
+
+        self.send_time = time.time()
+        self.session.send(message)
+
+    def recv(self, timeout=120):
+        """ Waits up to <timeout> seconds for a response to this request.
+        
+            If a message is received in time, the response message is returned.
+            Returns None otherwise."""
+
+        self.session.wait(0)
+
+        orig_timeout = timeout
+        while not self.complete and (timeout >= 0 or orig_timeout < 0) and len(self.queue) == 0:
+
+            s = time.time()
+            self.session.wait(timeout)
+
+            if self.reset_timeout:
+                self.reset_timeout = False
+                timeout = orig_timeout
+
+            elif orig_timeout >= 0:
+                timeout -= time.time() - s
+
+        now = time.time()
+
+        # -----------------------------------------------------------------
+        # log some statistics 
+        if len(self.queue) > 0:
+            if not self.first_response_time:
+                self.first_response_time = now
+                osrf.log.log_debug("time elapsed before first response: %f" \
+                    % (self.first_response_time - self.send_time))
+
+        if self.complete:
+            if not self.complete_time:
+                self.complete_time = now
+                osrf.log.log_debug("time elapsed before complete: %f" \
+                    % (self.complete_time - self.send_time))
+        # -----------------------------------------------------------------
+
+
+        if len(self.queue) > 0:
+            # we have a reponse, return it
+            return self.queue.pop(0)
+
+        return None
+
+    def push_response(self, content):
+        """Pushes a method response onto this requests response queue."""
+        self.queue.append(content)
+
+    def cleanup(self):
+        """Cleans up request data from the cache. 
+
+            Do this when you are done with a request to prevent "leaked" cache memory."""
+        del self.session.requests[str(self.rid)]
+
+    def set_complete(self):
+        """Sets me as complete.  This means the server has sent a 'request complete' message"""
+        self.complete = True
+
+
+class ServerSession(Session):
+    """Implements a server-side session"""
+
+    def __init__(self, thread):
+        Session.__init__(self)
+        self.thread = thread
+
+    def send_status(self, thread_trace, payload):
+        self.send(
+            osrf.net_obj.NetworkObject.osrfMessage( 
+                {   'threadTrace' : thread_trace,
+                    'type' : osrf.const.OSRF_MESSAGE_TYPE_STATUS,
+                    'payload' : payload,
+                    'locale' : self.locale
+                } 
+            )
+        )
+
+    def send_connect_ok(self, thread_trace):
+        status_msg = osrf.net_obj.NetworkObject.osrfConnectStatus({   
+            'status' : 'Connection Successful',
+            'statusCode': osrf.const.OSRF_STATUS_OK
+        })
+        self.send_status(thread_trace, status_msg)
+
+
+class ServerRequest(Request):
+
+    def __init__(self, session, rid, method, params=[]):
+        Request.__init__(self, session, rid, method, params, session.locale)
+        self.response_list = []
+
+    def _build_response_msg(self, data):
+        result = osrf.net_obj.NetworkObject.osrfResult({
+            'content' :  data,
+            'statusCode' : osrf.const.OSRF_STATUS_OK,
+            'status' : 'OK'
+        })
+
+        return osrf.net_obj.NetworkObject.osrfMessage({
+            'threadTrace' : self.rid,
+            'type' : OSRF_MESSAGE_TYPE_RESULT,
+            'payload' : result,
+            'locale' : self.locale
+        })
+
+    def _build_complete_msg(self):
+
+        status = osrf.net_obj.NetworkObject.osrfConnectStatus({   
+            'threadTrace' : self.rid,
+            'status' : 'Request Complete',
+            'statusCode': osrf.const.OSRF_STATUS_COMPLETE
+        })
+
+        return osrf.net_obj.NetworkObject.osrfMessage({
+            'threadTrace' : self.rid,
+            'type' : OSRF_MESSAGE_TYPE_STATUS,
+            'payload' : status,
+            'locale' : self.locale
+        })
+
+    def respond(self, data):
+        ''' For non-atomic calls, this sends a response directly back
+            to the client.  For atomic calls, this pushes the response
+            onto the response list '''
+        osrf.log.log_internal("responding with %s" % str(data))
+        if self.method.atomic:
+            self.response_list.append(data)
+        else:
+            self.session.send(self._build_response_msg(data))
+
+    def respond_complete(self, data):
+        ''' Sends a complete message accompanied by the final result if applicable '''
+
+        if self.complete: 
+            return
+        self.complete = True
+        self.complete_time = time.time()
+
+        if self.method.atomic:
+            if data is not None:
+                self.response_list.append(data) 
+            self.session.send([
+                self._build_response_msg(self.response_list),
+                self._build_complete_msg(),
+            ])
+
+        elif data is not None:
+            self.session.send([
+                self._build_response_msg(data),
+                self._build_complete_msg(),
+            ])
+
+        else:
+            self.session.send(self._build_complete_msg())
+            
+
+class MultiSession(object):
+    ''' Manages multiple requests.  With the current implementation, a 1 second 
+        lag time before the first response is practically guaranteed.  Use 
+        only for long running requests.
+
+        Another approach would be a threaded version, but that would require
+        build-up and breakdown of thread-specific xmpp connections somewhere.
+        conection pooling? 
+    '''
+    class Container(object):
+        def __init__(self, req):
+            self.req = req
+            self.id = None
+
+    def __init__(self):
+        self.complete = False
+        self.reqs = []
+
+    def request(self, service, method, *args):
+        ses = ClientSession(service)
+        cont = MultiSession.Container(ses.request(method, *args))
+        cont.id = len(self.reqs)
+        self.reqs.append(cont)
+
+    def recv(self, timeout=120):
+        ''' Returns a tuple of req_id, response '''
+        duration = 0
+        block_time = 1
+        while True:
+            for i in range(0, len(self.reqs)):
+                cont = self.reqs[i]
+                req = cont.req
+
+                res = req.recv(0)
+                if i == 0 and not res:
+                    res = req.recv(block_time)
+
+                if res: break
+
+            if res: break
+
+            duration += block_time
+            if duration >= timeout:
+                return None
+
+        if req.complete:
+            self.reqs.pop(self.reqs.index(cont))
+
+        if len(self.reqs) == 0:
+            self.complete = True
+
+        return cont.id, res.content()
+
diff --git a/trunk/src/python/osrf/set.py b/trunk/src/python/osrf/set.py
new file mode 100644 (file)
index 0000000..5c7d30b
--- /dev/null
@@ -0,0 +1,39 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# -----------------------------------------------------------------------
+
+from osrf.const import OSRF_APP_SETTINGS, OSRF_METHOD_GET_HOST_CONFIG
+import osrf.ex, osrf.net_obj, osrf.ses
+
+# global settings config object
+__config = None
+
+def get(path, idx=0):
+    global __config
+    val = osrf.net_obj.find_object_path(__config, path, idx)
+    if not val:
+        raise osrf.ex.OSRFConfigException("Config value not found: " + path)
+    return val
+
+
+def load(hostname):
+    global __config
+
+    ses = osrf.ses.ClientSession(OSRF_APP_SETTINGS)
+    req = ses.request(OSRF_METHOD_GET_HOST_CONFIG, hostname)
+    resp = req.recv(timeout=30)
+    __config = resp.content()
+    req.cleanup()
+    ses.cleanup()
+
diff --git a/trunk/src/python/osrf/stack.py b/trunk/src/python/osrf/stack.py
new file mode 100644 (file)
index 0000000..3426ada
--- /dev/null
@@ -0,0 +1,121 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# -----------------------------------------------------------------------
+
+import time
+import osrf.json, osrf.log, osrf.ex, osrf.ses, osrf.const, osrf.app
+
+def push(net_msg):
+
+    ses = osrf.ses.Session.find_or_create(net_msg.thread)
+    ses.set_remote_id(net_msg.sender)
+    if not ses.service:
+        ses.service = osrf.app.Application.name
+
+    omessages = osrf.json.to_object(net_msg.body)
+
+    osrf.log.log_internal("stack.push(): received %d messages" % len(omessages))
+
+    # Pass each bundled opensrf message to the message handler
+    start = time.time()
+    for msg in omessages:
+        handle_message(ses, msg)
+    duration = time.time() - start
+
+    if isinstance(ses, osrf.ses.ServerSession):
+        osrf.log.log_info("Message processing duration %f" % duration)
+
+def handle_message(session, message):
+
+    osrf.log.log_internal("handle_message(): processing message of "
+        "type %s" % message.type())
+
+    if isinstance(session, osrf.ses.ClientSession):
+        handle_client(session, message)
+    else:
+        handle_server(session, message)
+
+
+def handle_client(session, message):
+
+    if message.type() == osrf.const.OSRF_MESSAGE_TYPE_RESULT:
+        session.push_response_queue(message)
+        return
+
+    if message.type() == osrf.const.OSRF_MESSAGE_TYPE_STATUS:
+
+        status_code = int(message.payload().statusCode())
+        status_text = message.payload().status()
+        osrf.log.log_internal("handle_message(): processing STATUS, "
+            "status_code =  %d" % status_code)
+
+        if status_code == osrf.const.OSRF_STATUS_COMPLETE:
+            # The server has informed us that this request is complete
+            req = session.find_request(message.threadTrace())
+            if req: 
+                osrf.log.log_internal("marking request as complete: %d" % req.rid)
+                req.set_complete()
+            return
+
+        if status_code == osrf.const.OSRF_STATUS_OK:
+            # We have connected successfully
+            osrf.log.log_debug("Successfully connected to " + session.service)
+            session.state = OSRF_APP_SESSION_CONNECTED
+            return
+
+        if status_code == osrf.const.OSRF_STATUS_CONTINUE:
+            # server is telling us to reset our wait timeout and keep waiting for a response
+            session.reset_request_timeout(message.threadTrace())
+            return
+
+        if status_code == osrf.const.OSRF_STATUS_TIMEOUT:
+            osrf.log.log_debug("The server did not receive a request from us in time...")
+            session.state = OSRF_APP_SESSION_DISCONNECTED
+            return
+
+        if status_code == osrf.const.OSRF_STATUS_NOTFOUND:
+            osrf.log.log_error("Requested method was not found on the server: %s" % status_text)
+            session.state = OSRF_APP_SESSION_DISCONNECTED
+            raise osrf.ex.OSRFServiceException(status_text)
+
+        if status_code == osrf.const.OSRF_STATUS_INTERNALSERVERERROR:
+            raise osrf.ex.OSRFServiceException("Server error %d : %s" % (status_code, status_text))
+
+        raise osrf.ex.OSRFProtocolException("Unknown message status: %d" % status_code)
+
+
+def handle_server(session, message):
+
+    if message.type() == osrf.const.OSRF_MESSAGE_TYPE_REQUEST:
+        osrf.log.log_debug("server received REQUEST from %s" % session.remote_id)
+        osrf.app.Application.handle_request(session, message)
+        return
+
+    if message.type() == osrf.const.OSRF_MESSAGE_TYPE_CONNECT:
+        osrf.log.log_debug("server received CONNECT from %s" % session.remote_id)
+        session.state == osrf.const.OSRF_APP_SESSION_CONNECTED 
+        session.send_connect_ok(message.threadTrace())
+        return
+
+    if message.type() == osrf.const.OSRF_MESSAGE_TYPE_DISCONNECT:
+        osrf.log.log_debug("server received DISCONNECT from %s" % session.remote_id)
+        session.state = osrf.const.OSRF_APP_SESSION_DISCONNECTED
+        return
+
+    if message.type() == osrf.const.OSRF_MESSAGE_TYPE_STATUS:
+        # Should never get here
+        osrf.log.log_warn("server received STATUS from %s" % session.remote_id)
+        return
+
+
diff --git a/trunk/src/python/osrf/system.py b/trunk/src/python/osrf/system.py
new file mode 100644 (file)
index 0000000..accf45c
--- /dev/null
@@ -0,0 +1,108 @@
+# -----------------------------------------------------------------------
+# Copyright (C) 2007  Georgia Public Library Service
+# Bill Erickson <billserickson@gmail.com>
+# 
+# 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.
+# -----------------------------------------------------------------------
+
+import osrf.conf
+from osrf.net import Network, set_network_handle, get_network_handle
+import osrf.stack, osrf.log, osrf.set, osrf.cache
+import sys, os
+
+class System(object):
+
+    config_file = None
+    config_context = None
+
+    @staticmethod
+    def net_connect(**kwargs):
+        if get_network_handle():
+            ''' This thread already has a handle '''
+            return
+
+        config_file = kwargs.get('config_file') or System.config_file
+        config_context = kwargs.get('config_context') or System.config_context
+
+        # store the last config file info for later
+        System.config_file = config_file
+        System.config_context = config_context
+
+        # parse the config file
+        config_parser = osrf.conf.Config(config_file, config_context)
+        config_parser.parse_config()
+
+        # set up logging
+        osrf.log.initialize(
+            osrf.conf.get('loglevel'), 
+            osrf.conf.get_no_ex('syslog'),
+            osrf.conf.get_no_ex('logfile'),
+            osrf.conf.get_no_ex('client') == 'true')
+
+        # connect to the opensrf network
+        network = Network(
+            host = osrf.conf.get('domain'),
+            port = osrf.conf.get('port'),
+            username = osrf.conf.get('username'), 
+            password = osrf.conf.get('passwd'),
+            resource = kwargs.get('resource'))
+
+        network.set_receive_callback(osrf.stack.push)
+        osrf.net.set_network_handle(network)
+        network.connect()
+
+        return network
+
+
+    @staticmethod
+    def connect(**kwargs):
+        """ Connects to the opensrf network 
+            Options:
+                config_file
+                config_context
+                connect_cache
+                resource
+        """
+
+        network = System.net_connect(**kwargs)
+
+        # load the domain-wide settings file
+        osrf.set.load(osrf.conf.get('domain'))
+
+        if kwargs.get('connect_cache'):
+            System.connect_cache()
+
+        return network
+
+
+    @staticmethod
+    def connect_cache():
+        ''' Initializes the cache connections '''
+        cache_servers = osrf.set.get('cache.global.servers.server')
+        if cache_servers:
+            if not isinstance(cache_servers, list):
+                cache_servers = [cache_servers]
+            if not osrf.cache.CacheClient.get_client():
+                osrf.cache.CacheClient.connect(cache_servers)
+
+    @staticmethod
+    def daemonize():
+        pid = os.fork() 
+        if pid == 0:
+            os.chdir('/')
+            os.setsid()
+            sys.stdin.close()
+            sys.stdout.close()
+            sys.stderr.close()
+        else:
+            os._exit(0)
+
+
diff --git a/trunk/src/python/osrf/xml_obj.py b/trunk/src/python/osrf/xml_obj.py
new file mode 100644 (file)
index 0000000..f023bc3
--- /dev/null
@@ -0,0 +1,162 @@
+import xml.dom.minidom
+import osrf.json
+from xml.sax import handler, make_parser, saxutils
+import urllib, re
+
+def xml_file_to_object(filename):
+    """Turns the contents of an XML file into a Python object"""
+    doc = xml.dom.minidom.parse(filename)
+    obj = xml_node_to_object(doc.documentElement)
+    doc.unlink()
+    return obj
+
+def xml_string_to_object(string):
+    """Turns an XML string into a Python object"""
+    doc = xml.dom.minidom.parseString(string)
+    obj = xml_node_to_object(doc.documentElement)
+    doc.unlink()
+    return obj
+
+def xml_node_to_object(xml_node):
+    """Turns an XML node into a Python object"""
+    obj = {}
+
+    if xml_node.nodeType != xml_node.ELEMENT_NODE:
+        return obj
+
+    done = False
+    node_name = xml_node.nodeName
+
+    for node_child in xml_node.childNodes:
+        if node_child.nodeType == xml_node.ELEMENT_NODE:
+            sub_obj = xml_node_to_object(node_child)
+            __append_child_node(obj, node_name, node_child.nodeName, sub_obj)
+            done = True
+
+    for attr in xml_node.attributes.values():
+        __append_child_node(obj, node_name, attr.name,
+            dict([(attr.name, attr.value)]))
+        
+
+    if not done and len(xml_node.childNodes) > 0:
+        # If the node has no element children, clean up the text 
+        # content and use that as the data
+        text_node = xml_node.childNodes[0] # extract the text node
+        data = unicode(text_node.nodeValue).replace('^\s*','')
+        data = data.replace('\s*$','')
+
+        if node_name in obj:
+            # the current element contains attributes and text
+            obj[node_name]['#text'] = data
+        else:
+            # the current element contains text only
+            obj[node_name] = data
+
+    return obj
+
+
+def __append_child_node(obj, node_name, child_name, sub_obj):
+    """ If a node has element children, create a new sub-object 
+        for this node, attach an array for each type of child
+        and recursively collect the children data into the array(s) """
+
+    if not obj.has_key(node_name):
+        obj[node_name] = {}
+
+    if not obj[node_name].has_key(child_name):
+        # we've encountered 1 sub-node with node_child's name
+        if child_name in sub_obj:
+            obj[node_name][child_name] = sub_obj[child_name]
+        else:
+            obj[node_name][child_name] = None
+
+    else:
+        if isinstance(obj[node_name][child_name], list):
+            # we already have multiple sub-nodes with node_child's name
+            obj[node_name][child_name].append(sub_obj[child_name])
+
+        else:
+            # we already have 1 sub-node with node_child's name, make 
+            # it a list and append the current node
+            val = obj[node_name][child_name]
+            obj[node_name][child_name] = [ val, sub_obj[child_name] ]
+
+
+
+class XMLFlattener(handler.ContentHandler):
+    ''' Turns an XML string into a flattened dictionary of properties.
+
+        Example <doc><a><b>text1</b></a><c>text2</c><c>text3</c></doc> becomes
+        {
+            'doc.a.b' : 'text1',
+            'doc.c' : ['text2', 'text3']
+        }
+    '''
+
+    reg = re.compile('^\s*$')
+    class Handler(handler.ContentHandler):
+        def __init__(self):
+            self.result = {}
+            self.elements = []
+            self.use_json = None
+    
+        def startElement(self, name, attrs):
+            self.elements.append(name)
+
+        def characters(self, chars):
+            text = urllib.unquote_plus(chars)
+            if re.match(XMLFlattener.reg, text):
+                return
+            key = ''
+            for elm in self.elements:
+                key += elm + '.'
+            key = key[:-1]
+            
+            if key in self.result:
+                data = self._decode(self.result[key])
+                if isinstance(data, list):
+                    data.append(text)
+                else:
+                    data = [data, text]
+                self.result[key] = self._encode(data)
+            else:
+                self.result[key] = self._encode(text)
+
+            
+        def endElement(self, name):
+            self.elements.pop()
+
+        def _decode(self, string):
+            if self.use_json:
+                return osrf.json.to_object(string)
+            return string
+
+        def _encode(self, obj):
+            if self.use_json:
+                return osrf.json.to_json(obj)
+            return obj
+            
+
+
+    def __init__(self, xml_str, encode_as_json=False):
+        self.xml_str = xml_str
+        self.use_json = encode_as_json
+
+    def parse(self):
+        ''' Parses the XML string and returns the dict of keys/values '''
+        sax_handler = XMLFlattener.Handler()
+        sax_handler.use_json = self.use_json
+        parser = make_parser()
+        parser.setContentHandler(sax_handler)
+        try:
+            import StringIO
+            parser.parse(StringIO.StringIO(self.xml_str))
+        except Exception, e:
+            osrf.log.log_error('Error parsing XML: %s' % unicode(e))
+            raise e
+
+        return sax_handler.result
+
+
+
+
diff --git a/trunk/src/python/setup.py.in b/trunk/src/python/setup.py.in
new file mode 100644 (file)
index 0000000..1b841ae
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+from setuptools import setup
+
+setup(name='OpenSRF',
+    version='1.0.0',
+    install_requires=[
+        'dnspython', # required by pyxmpp
+       'python-memcached',
+        'pyxmpp>=1.0.0',
+        'simplejson>=1.7.1'
+    ],
+    dependency_links = [
+        "http://pyxmpp.jajcus.net/downloads/",
+        "ftp://ftp.tummy.com/pub/python-memcached/python-memcached-latest.tar.gz"
+    ],
+    description='OpenSRF Python Modules',
+    author='Bill Erickson',
+    author_email='erickson@esilibrary.com',
+    license="GPL",
+    url='http://www.open-ils.org/',
+    packages=['osrf'],
+    scripts=['srfsh.py']
+)
diff --git a/trunk/src/python/srfsh.py b/trunk/src/python/srfsh.py
new file mode 100755 (executable)
index 0000000..74af0aa
--- /dev/null
@@ -0,0 +1,323 @@
+#!/usr/bin/python
+# vim:et:ts=4
+"""
+srfsh.py - provides a basic shell for issuing OpenSRF requests
+
+  help
+    - show this menu
+
+  math_bench <count>
+    - runs <count> opensrf.math requests and reports the average time
+
+  request <service> <method> [<param1>, <param2>, ...]
+    - performs an opensrf request
+
+  set VAR=<value>
+    - sets an environment variable
+
+  Environment variables:
+    SRFSH_OUTPUT = pretty - print pretty JSON and key/value pairs for network objects
+                 = raw - print formatted JSON 
+
+    SRFSH_LOCALE = <locale> - request responses to be returned in locale <locale> if available
+"""
+
+import os, sys, time, readline, atexit, re
+import osrf.json, osrf.system, osrf.ses, osrf.conf, osrf.log, osrf.net
+
+# -------------------------------------------------------------------
+# main listen loop
+# -------------------------------------------------------------------
+def do_loop():
+    while True:
+
+        try:
+            #line = raw_input("srfsh% ")
+            line = raw_input("\033[01;32msrfsh\033[01;34m% \033[00m")
+            if not len(line): 
+                continue
+            if str.lower(line) == 'exit' or str.lower(line) == 'quit': 
+                break
+            parts = str.split(line)
+
+            command = parts[0]
+        
+            if command == 'request':
+                parts.pop(0)
+                handle_request(parts)
+                continue
+
+            if command == 'math_bench':
+                parts.pop(0)
+                handle_math_bench(parts)
+                continue
+
+            if command == 'help':
+                handle_help()
+                continue
+
+            if command == 'set':
+                parts.pop(0)
+                handle_set(parts)
+
+            if command == 'get':
+                parts.pop(0)
+                handle_get(parts)
+
+
+
+        except KeyboardInterrupt:
+            print ""
+
+        except EOFError:
+            print "exiting..."
+            sys.exit(0)
+
+
+# -------------------------------------------------------------------
+# Set env variables to control behavior
+# -------------------------------------------------------------------
+def handle_set(parts):
+    cmd = "".join(parts)
+    pattern = re.compile('(.*)=(.*)').match(cmd)
+    key = pattern.group(1)
+    val = pattern.group(2)
+    set_var(key, val)
+    print "%s = %s" % (key, val)
+
+def handle_get(parts):
+    try:
+        print get_var(parts[0])
+    except:
+        print ""
+
+
+# -------------------------------------------------------------------
+# Prints help info
+# -------------------------------------------------------------------
+def handle_help():
+    print __doc__
+
+# -------------------------------------------------------------------
+# performs an opensrf request
+# -------------------------------------------------------------------
+def handle_request(parts):
+    service = parts.pop(0)
+    method = parts.pop(0)
+    locale = __get_locale()
+    jstr = '[%s]' % "".join(parts)
+    params = None
+
+    try:
+        params = osrf.json.to_object(jstr)
+    except:
+        print "Error parsing JSON: %s" % jstr
+        return
+
+    ses = osrf.ses.ClientSession(service, locale=locale)
+
+    start = time.time()
+
+    req = ses.request2(method, tuple(params))
+
+
+    while True:
+        resp = None
+        try:
+            resp = req.recv(timeout=120)
+        except osrf.net.XMPPNoRecipient:
+            print "Unable to communicate with %s" % service
+            total = 0
+            break
+
+        osrf.log.log_internal("Looping through receive request")
+        if not resp:
+            break
+        total = time.time() - start
+
+        otp = get_var('SRFSH_OUTPUT')
+        if otp == 'pretty':
+            print "\n" + osrf.json.debug_net_object(resp.content())
+        else:
+            print osrf.json.pprint(osrf.json.to_json(resp.content()))
+
+    req.cleanup()
+    ses.cleanup()
+
+    print '-'*60
+    print "Total request time: %f" % total
+    print '-'*60
+
+
+def handle_math_bench(parts):
+
+    count = int(parts.pop(0))
+    ses = osrf.ses.ClientSession('opensrf.math')
+    times = []
+
+    for cnt in range(100):
+        if cnt % 10:
+            sys.stdout.write('.')
+        else:
+            sys.stdout.write( str( cnt / 10 ) )
+    print ""
+
+
+    for cnt in range(count):
+    
+        starttime = time.time()
+        req = ses.request('add', 1, 2)
+        resp = req.recv(timeout=2)
+        endtime = time.time()
+    
+        if resp.content() == 3:
+            sys.stdout.write("+")
+            sys.stdout.flush()
+            times.append( endtime - starttime )
+        else:
+            print "What happened? %s" % str(resp.content())
+    
+        req.cleanup()
+        if not ( (cnt + 1) % 100):
+            print ' [%d]' % (cnt + 1)
+    
+    ses.cleanup()
+    total = 0
+    for cnt in times:
+        total += cnt 
+    print "\naverage time %f" % (total / len(times))
+
+
+
+
+# -------------------------------------------------------------------
+# Defines the tab-completion handling and sets up the readline history 
+# -------------------------------------------------------------------
+def setup_readline():
+    class SrfshCompleter(object):
+        def __init__(self, words):
+            self.words = words
+            self.prefix = None
+    
+        def complete(self, prefix, index):
+            if prefix != self.prefix:
+                # find all words that start with this prefix
+                self.matching_words = [
+                    w for w in self.words if w.startswith(prefix)
+                ]
+                self.prefix = prefix
+                try:
+                    return self.matching_words[index]
+                except IndexError:
+                    return None
+    
+    words = 'request', 'help', 'exit', 'quit', 'opensrf.settings', 'opensrf.math', 'set'
+    completer = SrfshCompleter(words)
+    readline.parse_and_bind("tab: complete")
+    readline.set_completer(completer.complete)
+
+    histfile = os.path.join(get_var('HOME'), ".srfsh_history")
+    try:
+        readline.read_history_file(histfile)
+    except IOError:
+        pass
+    atexit.register(readline.write_history_file, histfile)
+
+def do_connect():
+    file = os.path.join(get_var('HOME'), ".srfsh.xml")
+    print_green("Connecting to opensrf...")
+    osrf.system.System.connect(config_file=file, config_context='srfsh')
+    print_red('OK\n')
+
+def load_plugins():
+    # Load the user defined external plugins
+    # XXX Make this a real module interface, with tab-complete words, commands, etc.
+    try:
+        plugins = osrf.conf.get('plugins')
+
+    except:
+        # XXX standard srfsh.xml does not yet define <plugins> element
+        print_red("No plugins defined in /srfsh/plugins/plugin\n")
+        return
+
+    plugins = osrf.conf.get('plugins.plugin')
+    if not isinstance(plugins, list):
+        plugins = [plugins]
+
+    for module in plugins:
+        name = module['module']
+        init = module['init']
+        print_green("Loading module %s..." % name)
+
+        try:
+            string = 'import %s\n%s.%s()' % (name, name, init)
+            exec(string)
+            print_red('OK\n')
+
+        except Exception, e:
+            sys.stderr.write("\nError importing plugin %s, with init symbol %s: \n%s\n" % (name, init, e))
+
+def set_vars():
+    if not get_var('SRFSH_OUTPUT'):
+        set_var('SRFSH_OUTPUT', 'pretty')
+
+    # XXX Do we need to differ between LANG and LC_MESSAGES?
+    if not get_var('SRFSH_LOCALE'):
+        set_var('SRFSH_LOCALE', get_var('LC_ALL'))
+
+def set_var(key, val):
+    os.environ[key] = val
+
+def get_var(key):
+    return os.environ.get(key, '')
+    
+def __get_locale():
+    """
+    Return the defined locale for this srfsh session.
+
+    A locale in OpenSRF is currently defined as a [a-z]{2}-[A-Z]{2} pattern.
+    This function munges the LC_ALL setting to conform to that pattern; for
+    example, trimming en_CA.UTF-8 to en-CA.
+
+    >>> import srfsh
+    >>> srfsh.set_var('SRFSH_LOCALE', 'zz-ZZ')
+    >>> print __get_locale()
+    zz-ZZ
+    >>> srfsh.set_var('SRFSH_LOCALE', 'en_CA.UTF-8')
+    >>> print __get_locale()
+    en-CA
+    """
+
+    env_locale = get_var('SRFSH_LOCALE')
+    if env_locale:
+        pattern = re.compile(r'^\s*([a-z]+)[^a-zA-Z]([A-Z]+)').search(env_locale)
+        lang = pattern.group(1)
+        region = pattern.group(2)
+        locale = "%s-%s" % (lang, region)
+    else:
+        locale = 'en-US'
+
+    return locale
+    
+def print_green(string):
+    sys.stdout.write("\033[01;32m")
+    sys.stdout.write(string)
+    sys.stdout.write("\033[00m")
+    sys.stdout.flush()
+
+def print_red(string):
+    sys.stdout.write("\033[01;31m")
+    sys.stdout.write(string)
+    sys.stdout.write("\033[00m")
+    sys.stdout.flush()
+
+
+if __name__ == '__main__':
+
+    # Kick it off
+    set_vars()
+    setup_readline()
+    do_connect()
+    load_plugins()
+    do_loop()
+
diff --git a/trunk/src/router/Makefile.am b/trunk/src/router/Makefile.am
new file mode 100644 (file)
index 0000000..d777f3b
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+LDADD = -lxml2 $(DEF_LDLIBS) 
+AM_CFLAGS = $(DEF_CFLAGS) -D_ROUTER -L@top_builddir@/src/libopensrf
+AM_LDFLAGS = $(DEF_LDFLAGS)
+
+bin_PROGRAMS = opensrf_router
+opensrf_router_SOURCES = osrf_router.c osrf_router_main.c osrf_router.h 
+
diff --git a/trunk/src/router/osrf_router.c b/trunk/src/router/osrf_router.c
new file mode 100644 (file)
index 0000000..4a888d9
--- /dev/null
@@ -0,0 +1,855 @@
+#include "osrf_router.h"
+
+/* a class maintains a set of server nodes */
+struct _osrfRouterClassStruct {
+       osrfRouter* router; /* our router handle */
+       osrfHashIterator* itr;
+       osrfHash* nodes;
+       transport_client* connection;
+};
+typedef struct _osrfRouterClassStruct osrfRouterClass;
+
+/* represents a link to a single server's inbound connection */
+struct _osrfRouterNodeStruct {
+       char* remoteId; /* send message to me via this login */
+       int count;                      /* how many message have been sent to this node */
+       transport_message* lastMessage;
+};
+typedef struct _osrfRouterNodeStruct osrfRouterNode;
+
+static osrfRouterClass* osrfRouterAddClass( osrfRouter* router, const char* classname );
+static int osrfRouterClassAddNode( osrfRouterClass* rclass, const char* remoteId );
+static int osrfRouterHandleMessage( osrfRouter* router, transport_message* msg );
+static int osrfRouterClassHandleMessage( osrfRouter* router,
+               osrfRouterClass* rclass, transport_message* msg );
+static int osrfRouterRemoveClass( osrfRouter* router, const char* classname );
+static int osrfRouterClassRemoveNode( osrfRouter* router, const char* classname,
+               const char* remoteId );
+static void osrfRouterClassFree( char* classname, void* rclass );
+static void osrfRouterNodeFree( char* remoteId, void* node );
+static osrfRouterClass* osrfRouterFindClass( osrfRouter* router, const char* classname );
+static osrfRouterNode* osrfRouterClassFindNode( osrfRouterClass* rclass,
+               const char* remoteId );
+static int _osrfRouterFillFDSet( osrfRouter* router, fd_set* set );
+static void osrfRouterHandleIncoming( osrfRouter* router );
+static int osrfRouterClassHandleIncoming( osrfRouter* router,
+               const char* classname,  osrfRouterClass* class );
+static transport_message* osrfRouterClassHandleBounce( osrfRouter* router,
+               const char* classname, osrfRouterClass* rclass, transport_message* msg );
+static int osrfRouterHandleAppRequest( osrfRouter* router, transport_message* msg );
+static int osrfRouterRespondConnect( osrfRouter* router, transport_message* msg,
+               osrfMessage* omsg );
+static int osrfRouterProcessAppRequest( osrfRouter* router, transport_message* msg,
+               osrfMessage* omsg );
+static int osrfRouterHandleAppResponse( osrfRouter* router, 
+               transport_message* msg, osrfMessage* omsg, const jsonObject* response );
+static int osrfRouterHandleMethodNFound( osrfRouter* router, transport_message* msg,
+               osrfMessage* omsg );
+
+#define ROUTER_SOCKFD connection->session->sock_id
+#define ROUTER_REGISTER "register"
+#define ROUTER_UNREGISTER "unregister"
+
+
+#define ROUTER_REQUEST_CLASS_LIST "opensrf.router.info.class.list"
+#define ROUTER_REQUEST_STATS_NODE_FULL "opensrf.router.info.stats.class.node.all"
+#define ROUTER_REQUEST_STATS_CLASS_FULL "opensrf.router.info.stats.class.all"
+#define ROUTER_REQUEST_STATS_CLASS "opensrf.router.info.stats.class"
+#define ROUTER_REQUEST_STATS_CLASS_SUMMARY "opensrf.router.info.stats.class.summary"
+
+osrfRouter* osrfNewRouter( 
+               const char* domain, const char* name, 
+               const char* resource, const char* password, int port,
+               osrfStringArray* trustedClients, osrfStringArray* trustedServers ) {
+
+       if(!( domain && name && resource && password && port && trustedClients && trustedServers )) return NULL;
+
+       osrfRouter* router      = safe_malloc(sizeof(osrfRouter));
+       router->domain                  = strdup(domain);
+       router->name                    = strdup(name);
+       router->password                = strdup(password);
+       router->resource                = strdup(resource);
+       router->port                    = port;
+
+       router->trustedClients = trustedClients;
+       router->trustedServers = trustedServers;
+
+       
+       router->classes = osrfNewHash(); 
+       osrfHashSetCallback(router->classes, &osrfRouterClassFree);
+
+       router->connection = client_init( domain, port, NULL, 0 );
+
+       return router;
+}
+
+
+
+int osrfRouterConnect( osrfRouter* router ) {
+       if(!router) return -1;
+       int ret = client_connect( router->connection, router->name, 
+                       router->password, router->resource, 10, AUTH_DIGEST );
+       if( ret == 0 ) return -1;
+       return 0;
+}
+
+
+void osrfRouterRun( osrfRouter* router ) {
+       if(!(router && router->classes)) return;
+
+       int routerfd = router->ROUTER_SOCKFD;
+       int selectret = 0;
+
+       while(1) {
+
+               fd_set set;
+               int maxfd = _osrfRouterFillFDSet( router, &set );
+               int numhandled = 0;
+
+               if( (selectret = select(maxfd + 1, &set, NULL, NULL, NULL)) < 0 ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "Top level select call failed with errno %d", errno);
+                       continue;
+               }
+
+               /* see if there is a top level router message */
+
+               if( FD_ISSET(routerfd, &set) ) {
+                       osrfLogDebug( OSRF_LOG_MARK, "Top router socket is active: %d", routerfd );
+                       numhandled++;
+                       osrfRouterHandleIncoming( router );
+               }
+
+
+               /* now check each of the connected classes and see if they have data to route */
+               while( numhandled < selectret ) {
+
+                       osrfRouterClass* class;
+                       osrfHashIterator* itr = osrfNewHashIterator(router->classes);
+
+                       while( (class = osrfHashIteratorNext(itr)) ) {
+
+                               const char* classname = osrfHashIteratorKey(itr);
+
+                               if( classname && (class = osrfRouterFindClass( router, classname )) ) {
+
+                                       osrfLogDebug( OSRF_LOG_MARK, "Checking %s for activity...", classname );
+
+                                       int sockfd = class->ROUTER_SOCKFD;
+                                       if(FD_ISSET( sockfd, &set )) {
+                                               osrfLogDebug( OSRF_LOG_MARK, "Socket is active: %d", sockfd );
+                                               numhandled++;
+                                               osrfRouterClassHandleIncoming( router, classname, class );
+                                       }
+                               }
+                       }
+
+                       osrfHashIteratorFree(itr);
+               }
+       }
+}
+
+
+/**
+  Utility method for handling incoming requests to the router
+  and making sure the sender is allowed.
+ */
+static void osrfRouterHandleIncoming( osrfRouter* router ) {
+       if(!router) return;
+
+       transport_message* msg = NULL;
+
+       //if( (msg = client_recv( router->connection, 0 )) ) { 
+       while( (msg = client_recv( router->connection, 0 )) ) { 
+
+               if( msg->sender ) {
+
+                       osrfLogDebug(OSRF_LOG_MARK, 
+                               "osrfRouterHandleIncoming(): investigating message from %s", msg->sender);
+
+                       /* if the sender is not a trusted server, drop the message */
+                       int len = strlen(msg->sender) + 1;
+                       char domain[len];
+                       memset(domain, 0, sizeof(domain));
+                       jid_get_domain( msg->sender, domain, len - 1 );
+
+                       if(osrfStringArrayContains( router->trustedServers, domain)) 
+                               osrfRouterHandleMessage( router, msg );
+                        else 
+                               osrfLogWarning( OSRF_LOG_MARK, "Received message from un-trusted server domain %s", msg->sender);
+               }
+
+               message_free(msg);
+       }
+}
+
+/**
+       Utility method for handling incoming requests to a router class,
+       makes sure sender is a trusted client
+ */
+static int osrfRouterClassHandleIncoming( osrfRouter* router, const char* classname,
+               osrfRouterClass* class ) {
+       if(!(router && class)) return -1;
+
+       transport_message* msg;
+       osrfLogDebug( OSRF_LOG_MARK, "osrfRouterClassHandleIncoming()");
+
+       while( (msg = client_recv( class->connection, 0 )) ) {
+
+      osrfLogSetXid(msg->osrf_xid);
+
+               if( msg->sender ) {
+
+                       osrfLogDebug(OSRF_LOG_MARK, 
+                               "osrfRouterClassHandleIncoming(): investigating message from %s", msg->sender);
+
+                       /* if the client is not from a trusted domain, drop the message */
+                       int len = strlen(msg->sender) + 1;
+                       char domain[len];
+                       memset(domain, 0, sizeof(domain));
+                       jid_get_domain( msg->sender, domain, len - 1 );
+
+                       if(osrfStringArrayContains( router->trustedClients, domain)) {
+
+                               transport_message* bouncedMessage = NULL;
+                               if( msg->is_error )  {
+
+                                       /* handle bounced message */
+                                       if( !(bouncedMessage = osrfRouterClassHandleBounce( router, classname, class, msg )) ) 
+                                               return -1; /* we have no one to send the requested message to */
+
+                                       message_free( msg );
+                                       msg = bouncedMessage;
+                               }
+                               osrfRouterClassHandleMessage( router, class, msg );
+
+                       } else {
+                               osrfLogWarning( OSRF_LOG_MARK, "Received client message from untrusted client domain %s", domain );
+                       }
+               }
+
+      osrfLogClearXid();
+               message_free( msg );
+       }
+
+       return 0;
+}
+
+
+
+
+/**
+  Handles top level router messages
+  @return 0 on success
+ */
+static int osrfRouterHandleMessage( osrfRouter* router, transport_message* msg ) {
+       if(!(router && msg)) return -1;
+
+       if( !msg->router_command || !strcmp(msg->router_command,"")) 
+               return osrfRouterHandleAppRequest( router, msg ); /* assume it's an app session level request */
+
+       if(!msg->router_class) return -1;
+
+       osrfRouterClass* class = NULL;
+       if(!strcmp(msg->router_command, ROUTER_REGISTER)) {
+               class = osrfRouterFindClass( router, msg->router_class );
+
+               osrfLogInfo( OSRF_LOG_MARK, "Registering class %s", msg->router_class );
+
+               if(!class) class = osrfRouterAddClass( router, msg->router_class );
+
+               if(class) { 
+
+                       if( osrfRouterClassFindNode( class, msg->sender ) )
+                               return 0;
+                       else 
+                               osrfRouterClassAddNode( class, msg->sender );
+
+               } 
+
+       } else if( !strcmp( msg->router_command, ROUTER_UNREGISTER ) ) {
+
+               if( msg->router_class && strcmp( msg->router_class, "") ) {
+                       osrfLogInfo( OSRF_LOG_MARK, "Unregistering router class %s", msg->router_class );
+                       osrfRouterClassRemoveNode( router, msg->router_class, msg->sender );
+               }
+       }
+
+       return 0;
+}
+
+
+
+/**
+  Allocates and adds a new router class handler to the router's list of handlers.
+  Also connects the class handler to the network at <routername>@domain/<classname>
+  @param router The current router instance
+  @param classname The name of the class this node handles.
+  @return 0 on success, -1 on connection error.
+ */
+static osrfRouterClass* osrfRouterAddClass( osrfRouter* router, const char* classname ) {
+       if(!(router && router->classes && classname)) return NULL;
+
+       osrfRouterClass* class = safe_malloc(sizeof(osrfRouterClass));
+       class->nodes = osrfNewHash();
+       class->itr = osrfNewHashIterator(class->nodes);
+       osrfHashSetCallback(class->nodes, &osrfRouterNodeFree);
+       class->router   = router;
+
+       class->connection = client_init( router->domain, router->port, NULL, 0 );
+
+       if(!client_connect( class->connection, router->name,
+                       router->password, classname, 10, AUTH_DIGEST ) ) {
+                               // We cast away the constness of classname.  Though ugly, this
+                               // cast is benign because osrfRouterClassFree doesn't actually
+                               // write through the pointer.  We can't readily change its
+                               // signature because it is used for a function pointer, and
+                               // we would have to change other signatures the same way.
+                               osrfRouterClassFree( (char *) classname, class );
+               return NULL;
+       }
+       
+       osrfHashSet( router->classes, class, classname );
+       return class;
+}
+
+
+/**
+  Adds a new server node to the given class.
+  @param rclass The Router class to add the node to
+  @param remoteId The remote login of this node
+  @return 0 on success, -1 on generic error
+ */
+static int osrfRouterClassAddNode( osrfRouterClass* rclass, const char* remoteId ) {
+       if(!(rclass && rclass->nodes && remoteId)) return -1;
+
+       osrfLogInfo( OSRF_LOG_MARK, "Adding router node for remote id %s", remoteId );
+
+       osrfRouterNode* node = safe_malloc(sizeof(osrfRouterNode));
+       node->count = 0;
+       node->lastMessage = NULL;
+       node->remoteId = strdup(remoteId);
+
+       osrfHashSet( rclass->nodes, node, remoteId );
+       return 0;
+}
+
+/* copy off the lastMessage, remove the offending node, send error if it's tht last node 
+       ? return NULL if it's the last node ?
+ */
+
+/* handles case where router node is not longer reachable.  copies over the
+       data from the last sent message and returns a newly crafted suitable for treating
+       as a newly inconing message.  Removes the dead node and If there are no more
+       nodes to send the new message to, returns NULL.
+       */
+static transport_message* osrfRouterClassHandleBounce( osrfRouter* router,
+               const char* classname, osrfRouterClass* rclass, transport_message* msg ) {
+
+       osrfLogDebug( OSRF_LOG_MARK, "osrfRouterClassHandleBounce()");
+
+       osrfLogInfo( OSRF_LOG_MARK, "Received network layer error message from %s", msg->sender );
+       osrfRouterNode* node = osrfRouterClassFindNode( rclass, msg->sender );
+       transport_message* lastSent = NULL;
+
+       if( node && osrfHashGetCount(rclass->nodes) == 1 ) { /* the last node is dead */
+
+               if( node->lastMessage ) {
+                       osrfLogWarning( OSRF_LOG_MARK, "We lost the last node in the class, responding with error and removing...");
+       
+                       transport_message* error = message_init( 
+                               node->lastMessage->body, node->lastMessage->subject, 
+                               node->lastMessage->thread, node->lastMessage->router_from, node->lastMessage->recipient );
+         message_set_osrf_xid(error, node->lastMessage->osrf_xid);
+                       set_msg_error( error, "cancel", 501 );
+       
+                       /* send the error message back to the original sender */
+                       client_send_message( rclass->connection, error );
+                       message_free( error );
+               }
+       
+               return NULL;
+       
+       } else { 
+
+               if( node ) {
+                       if( node->lastMessage ) {
+                               osrfLogDebug( OSRF_LOG_MARK, "Cloning lastMessage so next node can send it");
+                               lastSent = message_init( node->lastMessage->body,
+                                       node->lastMessage->subject, node->lastMessage->thread, "", node->lastMessage->router_from );
+                               message_set_router_info( lastSent, node->lastMessage->router_from, NULL, NULL, NULL, 0 );
+            message_set_osrf_xid( lastSent, node->lastMessage->osrf_xid );
+                       }
+               } else {
+
+                       osrfLogInfo(OSRF_LOG_MARK, "network error occurred after we removed the class.. ignoring");
+                       return NULL;
+               }
+       }
+
+       /* remove the dead node */
+       osrfRouterClassRemoveNode( router, classname, msg->sender);
+       return lastSent;
+}
+
+
+/**
+  Handles class level requests
+  If we get a regular message, we send it to the next node in the list of nodes
+  if we get an error, it's a bounce back from a previous attempt.  We take the
+  body and thread from the last sent on the node that had the bounced message
+  and propogate them on to the new message being sent
+  @return 0 on success
+ */
+static int osrfRouterClassHandleMessage( 
+               osrfRouter* router, osrfRouterClass* rclass, transport_message* msg ) {
+       if(!(router && rclass && msg)) return -1;
+
+       osrfLogDebug( OSRF_LOG_MARK, "osrfRouterClassHandleMessage()");
+
+       osrfRouterNode* node = osrfHashIteratorNext( rclass->itr );
+       if(!node) {
+               osrfHashIteratorReset(rclass->itr);
+               node = osrfHashIteratorNext( rclass->itr );
+       }
+
+       if(node) {
+
+               transport_message* new_msg= message_init(       msg->body, 
+                               msg->subject, msg->thread, node->remoteId, msg->sender );
+               message_set_router_info( new_msg, msg->sender, NULL, NULL, NULL, 0 );
+      message_set_osrf_xid( new_msg, msg->osrf_xid );
+
+               osrfLogInfo( OSRF_LOG_MARK,  "Routing message:\nfrom: [%s]\nto: [%s]", 
+                               new_msg->router_from, new_msg->recipient );
+
+               message_free( node->lastMessage );
+               node->lastMessage = new_msg;
+
+               if ( client_send_message( rclass->connection, new_msg ) == 0 ) 
+                       node->count++;
+
+               else {
+                       message_prepare_xml(new_msg);
+                       osrfLogWarning( OSRF_LOG_MARK, "Error sending message from %s to %s\n%s", 
+                                       new_msg->sender, new_msg->recipient, new_msg->msg_xml );
+               }
+
+       } 
+
+       return 0;
+}
+
+
+/**
+  Removes a given class from the router, freeing as it goes
+ */
+static int osrfRouterRemoveClass( osrfRouter* router, const char* classname ) {
+       if(!(router && router->classes && classname)) return -1;
+       osrfLogInfo( OSRF_LOG_MARK, "Removing router class %s", classname );
+       osrfHashRemove( router->classes, classname );
+       return 0;
+}
+
+
+/**
+  Removes the given node from the class.  Also, if this is that last node in the set,
+  removes the class from the router 
+  @return 0 on successful removal with no class removal
+  @return 1 on successful remove with class removal
+  @return -1 error on removal
+ */
+static int osrfRouterClassRemoveNode( 
+               osrfRouter* router, const char* classname, const char* remoteId ) {
+
+       if(!(router && router->classes && classname && remoteId)) return 0;
+
+       osrfLogInfo( OSRF_LOG_MARK, "Removing router node %s", remoteId );
+
+       osrfRouterClass* class = osrfRouterFindClass( router, classname );
+
+       if( class ) {
+
+               osrfHashRemove( class->nodes, remoteId );
+               if( osrfHashGetCount(class->nodes) == 0 ) {
+                       osrfRouterRemoveClass( router, classname );
+                       return 1;
+               }
+
+               return 0;
+       }
+
+       return -1;
+}
+
+
+/**
+  Frees a router class object
+  Takes a void* since it is freed by the hash code
+ */
+static void osrfRouterClassFree( char* classname, void* c ) {
+       if(!(classname && c)) return;
+       osrfRouterClass* rclass = (osrfRouterClass*) c;
+       client_disconnect( rclass->connection );        
+       client_free( rclass->connection );      
+
+       osrfHashIteratorReset( rclass->itr );
+       osrfRouterNode* node;
+
+       while( (node = osrfHashIteratorNext(rclass->itr)) ) 
+               osrfRouterClassRemoveNode( rclass->router, classname, node->remoteId );
+
+   osrfHashIteratorFree(rclass->itr);
+   osrfHashFree(rclass->nodes);
+
+       free(rclass);
+}
+
+
+/**
+  Frees a router node object 
+  Takes a void* since it is freed by the list code
+ */
+static void osrfRouterNodeFree( char* remoteId, void* n ) {
+       if(!n) return;
+       osrfRouterNode* node = (osrfRouterNode*) n;
+       free(node->remoteId);
+       message_free(node->lastMessage);
+       free(node);
+}
+
+
+void osrfRouterFree( osrfRouter* router ) {
+       if(!router) return;
+
+       osrfHashFree(router->classes);
+       free(router->domain);           
+       free(router->name);
+       free(router->resource);
+       free(router->password);
+
+       osrfStringArrayFree( router->trustedClients );
+       osrfStringArrayFree( router->trustedServers );
+
+       client_free( router->connection );
+       free(router);
+}
+
+
+
+/**
+  Finds the class associated with the given class name in the list of classes
+ */
+static osrfRouterClass* osrfRouterFindClass( osrfRouter* router, const char* classname ) {
+       if(!( router && router->classes && classname )) return NULL;
+       return (osrfRouterClass*) osrfHashGet( router->classes, classname );
+}
+
+
+/**
+  Finds the router node within this class with the given remote id 
+ */
+static osrfRouterNode* osrfRouterClassFindNode( osrfRouterClass* rclass,
+               const char* remoteId ) {
+       if(!(rclass && remoteId))  return NULL;
+       return (osrfRouterNode*) osrfHashGet( rclass->nodes, remoteId );
+}
+
+
+/**
+  Clears and populates the provided fd_set* with file descriptors
+  from the router's top level connection as well as each of the
+  router class connections
+  @return The largest file descriptor found in the filling process
+ */
+static int _osrfRouterFillFDSet( osrfRouter* router, fd_set* set ) {
+       if(!(router && router->classes && set)) return -1;
+
+       FD_ZERO(set);
+       int maxfd = router->ROUTER_SOCKFD;
+       FD_SET(maxfd, set);
+
+       int sockid;
+
+       osrfRouterClass* class = NULL;
+       osrfHashIterator* itr = osrfNewHashIterator(router->classes);
+
+       while( (class = osrfHashIteratorNext(itr)) ) {
+               const char* classname = osrfHashIteratorKey(itr);
+
+               if( classname && (class = osrfRouterFindClass( router, classname )) ) {
+                       sockid = class->ROUTER_SOCKFD;
+       
+                       if( osrfUtilsCheckFileDescriptor( sockid ) ) {
+
+                               osrfLogWarning(OSRF_LOG_MARK, 
+                                       "Removing router class '%s' because of a bad top-level file descriptor [%d]", classname, sockid);
+                               osrfRouterRemoveClass( router, classname );
+       
+                       } else {
+                               if( sockid > maxfd ) maxfd = sockid;
+                               FD_SET(sockid, set);
+                       }
+               }
+       }
+
+       osrfHashIteratorFree(itr);
+       return maxfd;
+}
+
+/**
+  handles messages that don't have a 'router_command' set.  They are assumed to
+  be app request messages 
+ */
+static int osrfRouterHandleAppRequest( osrfRouter* router, transport_message* msg ) {
+
+       int T = 32;
+       osrfMessage* arr[T];
+       memset(arr, 0, sizeof(arr));
+
+       int num_msgs = osrf_message_deserialize( msg->body, arr, T );
+       osrfMessage* omsg = NULL;
+
+       int i;
+       for( i = 0; i != num_msgs; i++ ) {
+
+               if( !(omsg = arr[i]) ) continue;
+
+               switch( omsg->m_type ) {
+
+                       case CONNECT:
+                               osrfRouterRespondConnect( router, msg, omsg );
+                               break;
+
+                       case REQUEST:
+                               osrfRouterProcessAppRequest( router, msg, omsg );
+                               break;
+
+                       default: break;
+               }
+
+               osrfMessageFree( omsg );
+       }
+
+       return 0;
+}
+
+static int osrfRouterRespondConnect( osrfRouter* router, transport_message* msg,
+               osrfMessage* omsg ) {
+       if(!(router && msg && omsg)) return -1;
+
+       osrfMessage* success = osrf_message_init( STATUS, omsg->thread_trace, omsg->protocol );
+
+       osrfLogDebug( OSRF_LOG_MARK, "router received a CONNECT message from %s", msg->sender );
+
+       osrf_message_set_status_info( 
+               success, "osrfConnectStatus", "Connection Successful", OSRF_STATUS_OK );
+
+       char* data      = osrf_message_serialize(success);
+
+       transport_message* return_m = message_init( 
+               data, "", msg->thread, msg->sender, "" );
+
+       client_send_message(router->connection, return_m);
+
+       free(data);
+       osrfMessageFree(success);
+       message_free(return_m);
+
+       return 0;
+}
+
+
+
+static int osrfRouterProcessAppRequest( osrfRouter* router, transport_message* msg,
+               osrfMessage* omsg ) {
+
+       if(!(router && msg && omsg && omsg->method_name)) return -1;
+
+       osrfLogInfo( OSRF_LOG_MARK, "Router received app request: %s", omsg->method_name );
+
+       jsonObject* jresponse = NULL;
+       if(!strcmp( omsg->method_name, ROUTER_REQUEST_CLASS_LIST )) {
+
+               int i;
+               jresponse = jsonNewObjectType(JSON_ARRAY);
+
+               osrfStringArray* keys = osrfHashKeys( router->classes );
+               for( i = 0; i != keys->size; i++ )
+                       jsonObjectPush( jresponse, jsonNewObject(osrfStringArrayGetString( keys, i )) );
+               osrfStringArrayFree(keys);
+
+
+       } else if(!strcmp( omsg->method_name, ROUTER_REQUEST_STATS_CLASS_SUMMARY )) {
+
+               osrfRouterClass* class;
+               osrfRouterNode* node;
+               int count = 0;
+
+               char* classname = jsonObjectToSimpleString( jsonObjectGetIndex( omsg->_params, 0 ) );
+
+               if (!classname)
+                       return -1;
+
+               class = osrfHashGet(router->classes, classname);
+               free(classname);
+
+               osrfHashIterator* node_itr = osrfNewHashIterator(class->nodes);
+               while( (node = osrfHashIteratorNext(node_itr)) ) {
+                       count += node->count;
+                       //jsonObjectSetKey( class_res, node->remoteId, jsonNewNumberObject( (double) node->count ) );
+               }
+               osrfHashIteratorFree(node_itr);
+
+               jresponse = jsonNewNumberObject( (double) count );
+
+       } else if(!strcmp( omsg->method_name, ROUTER_REQUEST_STATS_CLASS )) {
+
+               osrfRouterClass* class;
+               osrfRouterNode* node;
+
+               char* classname = jsonObjectToSimpleString( jsonObjectGetIndex( omsg->_params, 0 ) );
+
+               if (!classname)
+                       return -1;
+
+               jresponse = jsonNewObjectType(JSON_HASH);
+               class = osrfHashGet(router->classes, classname);
+               free(classname);
+
+               osrfHashIterator* node_itr = osrfNewHashIterator(class->nodes);
+               while( (node = osrfHashIteratorNext(node_itr)) ) {
+                       jsonObjectSetKey( jresponse, node->remoteId, jsonNewNumberObject( (double) node->count ) );
+               }
+               osrfHashIteratorFree(node_itr);
+
+       } else if(!strcmp( omsg->method_name, ROUTER_REQUEST_STATS_CLASS_FULL )) {
+
+               osrfRouterClass* class;
+               osrfRouterNode* node;
+               jresponse = jsonNewObjectType(JSON_HASH);
+
+               osrfHashIterator* class_itr = osrfNewHashIterator(router->classes);
+               while( (class = osrfHashIteratorNext(class_itr)) ) {
+
+                       jsonObject* class_res = jsonNewObjectType(JSON_HASH);
+                       const char* classname = osrfHashIteratorKey(class_itr);
+
+                       osrfHashIterator* node_itr = osrfNewHashIterator(class->nodes);
+                       while( (node = osrfHashIteratorNext(node_itr)) ) {
+                               jsonObjectSetKey( class_res, node->remoteId, jsonNewNumberObject( (double) node->count ) );
+                       }
+                       osrfHashIteratorFree(node_itr);
+
+                       jsonObjectSetKey( jresponse, classname, class_res );
+               }
+
+               osrfHashIteratorFree(class_itr);
+
+       } else if(!strcmp( omsg->method_name, ROUTER_REQUEST_STATS_NODE_FULL )) {
+
+               osrfRouterClass* class;
+               osrfRouterNode* node;
+               int count;
+               jresponse = jsonNewObjectType(JSON_HASH);
+
+               osrfHashIterator* class_itr = osrfNewHashIterator(router->classes);
+               while( (class = osrfHashIteratorNext(class_itr)) ) {
+
+                       count = 0;
+                       const char* classname = osrfHashIteratorKey(class_itr);
+
+                       osrfHashIterator* node_itr = osrfNewHashIterator(class->nodes);
+                       while( (node = osrfHashIteratorNext(node_itr)) ) {
+                               count += node->count;
+                       }
+                       osrfHashIteratorFree(node_itr);
+
+                       jsonObjectSetKey( jresponse, classname, jsonNewNumberObject( (double) count ) );
+               }
+
+               osrfHashIteratorFree(class_itr);
+
+       } else {
+
+               return osrfRouterHandleMethodNFound( router, msg, omsg );
+       }
+
+
+       osrfRouterHandleAppResponse( router, msg, omsg, jresponse );
+       jsonObjectFree(jresponse); 
+
+       return 0;
+
+}
+
+
+
+static int osrfRouterHandleMethodNFound( 
+               osrfRouter* router, transport_message* msg, osrfMessage* omsg ) {
+
+       osrfMessage* err = osrf_message_init( STATUS, omsg->thread_trace, 1);
+               osrf_message_set_status_info( err, 
+                               "osrfMethodException", "Router method not found", OSRF_STATUS_NOTFOUND );
+
+               char* data =  osrf_message_serialize(err);
+
+               transport_message* tresponse = message_init(
+                               data, "", msg->thread, msg->sender, msg->recipient );
+
+               client_send_message(router->connection, tresponse );
+
+               free(data);
+               osrfMessageFree( err );
+               message_free(tresponse);
+               return 0;
+}
+
+
+
+static int osrfRouterHandleAppResponse( osrfRouter* router, 
+       transport_message* msg, osrfMessage* omsg, const jsonObject* response ) {
+
+       if( response ) { /* send the response message */
+
+               osrfMessage* oresponse = osrf_message_init(
+                               RESULT, omsg->thread_trace, omsg->protocol );
+       
+               char* json = jsonObjectToJSON(response);
+               osrf_message_set_result_content( oresponse, json);
+       
+               char* data =  osrf_message_serialize(oresponse);
+               osrfLogDebug( OSRF_LOG_MARK,  "Responding to client app request with data: \n%s\n", data );
+
+               transport_message* tresponse = message_init(
+                               data, "", msg->thread, msg->sender, msg->recipient );
+       
+               client_send_message(router->connection, tresponse );
+
+               osrfMessageFree(oresponse); 
+               message_free(tresponse);
+               free(json);
+               free(data);
+       }
+
+
+       /* now send the 'request complete' message */
+       osrfMessage* status = osrf_message_init( STATUS, omsg->thread_trace, 1);
+       osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete", OSRF_STATUS_COMPLETE );
+
+       char* statusdata = osrf_message_serialize(status);
+
+       transport_message* sresponse = message_init(
+                       statusdata, "", msg->thread, msg->sender, msg->recipient );
+       client_send_message(router->connection, sresponse );
+
+
+       free(statusdata);
+       osrfMessageFree(status);
+       message_free(sresponse);
+
+       return 0;
+}
+
+
+
+
diff --git a/trunk/src/router/osrf_router.h b/trunk/src/router/osrf_router.h
new file mode 100644 (file)
index 0000000..8d5e21d
--- /dev/null
@@ -0,0 +1,83 @@
+#ifndef OSRF_ROUTER_H
+#define OSRF_ROUTER_H
+
+#include <sys/select.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "opensrf/utils.h"
+#include "opensrf/log.h"
+#include "opensrf/osrf_list.h"
+#include "opensrf/osrf_hash.h"
+
+#include "opensrf/string_array.h"
+#include "opensrf/transport_client.h"
+#include "opensrf/transport_message.h"
+
+#include "opensrf/osrf_message.h"
+
+
+
+/* a router maintains a list of server classes */
+struct _osrfRouterStruct {
+
+       osrfHash* classes;      /* our list of server classes */
+       char* domain;                   /* our login domain */
+       char* name;
+       char* resource;
+       char* password;
+       int port;
+
+       osrfStringArray* trustedClients;
+       osrfStringArray* trustedServers;
+
+       transport_client* connection;
+};
+
+typedef struct _osrfRouterStruct osrfRouter;
+
+/**
+  Allocates a new router.  
+  @param domain The jabber domain to connect to
+  @param name The login name for the router
+  @param resource The login resource for the router
+  @param password The login password for the new router
+  @param port The port to connect to the jabber server on
+  @param trustedClients The array of client domains that we allow to send requests through us
+  @param trustedServers The array of server domains that we allow to register, etc. with ust.
+  @return The allocated router or NULL on memory error
+  */
+osrfRouter* osrfNewRouter( const char* domain, const char* name, const char* resource, 
+       const char* password, int port, osrfStringArray* trustedClients,
+       osrfStringArray* trustedServers );
+
+/**
+  Connects the given router to the network
+  */
+int osrfRouterConnect( osrfRouter* router );
+
+/**
+  Waits for incoming data to route
+  If this function returns, then the router's connection to the jabber server
+  has failed.
+  */
+void osrfRouterRun( osrfRouter* router );
+
+
+/**
+  Frees a router
+  */
+void osrfRouterFree( osrfRouter* router );
+
+/**
+  Handles connects, disconnects, etc.
+  */
+//int osrfRouterHandeStatusMessage( osrfRouter* router, transport_message* msg );
+
+/**
+  Handles REQUEST messages 
+  */
+//int osrfRouterHandleRequestMessage( osrfRouter* router, transport_message* msg );
+
+#endif
+
diff --git a/trunk/src/router/osrf_router_main.c b/trunk/src/router/osrf_router_main.c
new file mode 100644 (file)
index 0000000..30e4975
--- /dev/null
@@ -0,0 +1,166 @@
+#include "osrf_router.h"
+#include <opensrf/osrfConfig.h>
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+#include <opensrf/osrf_json.h>
+#include <signal.h>
+
+static osrfRouter* router = NULL;
+
+void routerSignalHandler( int signo ) {
+       osrfLogWarning( OSRF_LOG_MARK, "Received signal [%d], cleaning up...", signo );
+
+    /* for now, just forcibly exit.  This is not a friendly way to clean up, but
+     * there is a bug in osrfRouterFree() (in particular with cleaning up sockets),
+     * that can cause the router process to stick around.  If we do this, we 
+     * are guaranteed to exit.  
+     */
+    _exit(0);
+
+       osrfConfigCleanup();
+       osrfRouterFree(router);
+       router = NULL;
+
+       // Exit by re-raising the signal so that the parent
+       // process can detect it
+       
+       signal( signo, SIG_DFL );
+       raise( signo );
+}
+
+static int setupRouter(jsonObject* configChunk);
+
+
+int main( int argc, char* argv[] ) {
+
+       if( argc < 3 ) {
+               osrfLogError( OSRF_LOG_MARK,  "Usage: %s <path_to_config_file> <config_context>", argv[0] );
+               exit(0);
+       }
+
+       char* config = strdup( argv[1] );
+       char* context = strdup( argv[2] );
+       init_proc_title( argc, argv );
+       set_proc_title( "OpenSRF Router" );
+
+       osrfConfig* cfg = osrfConfigInit(config, context);
+       osrfConfigSetDefaultConfig(cfg);
+    jsonObject* configInfo = osrfConfigGetValueObject(NULL, "/router");
+
+    int i;
+    for(i = 0; i < configInfo->size; i++) {
+        jsonObject* configChunk = jsonObjectGetIndex(configInfo, i);
+        if(fork() == 0) /* create a new child to run this router instance */
+            setupRouter(configChunk);
+    }
+
+       free(config);
+       free(context);
+    return EXIT_SUCCESS;
+}
+
+int setupRouter(jsonObject* configChunk) {
+
+    if(!jsonObjectGetKey(configChunk, "transport"))
+        return 0; /* these are not the configs you're looking for */
+
+       char* server = jsonObjectGetString(jsonObjectFindPath(configChunk, "/transport/server"));
+       char* port = jsonObjectGetString(jsonObjectFindPath(configChunk, "/transport/port"));
+       char* username = jsonObjectGetString(jsonObjectFindPath(configChunk, "/transport/username"));
+       char* password = jsonObjectGetString(jsonObjectFindPath(configChunk, "/transport/password"));
+       char* resource = jsonObjectGetString(jsonObjectFindPath(configChunk, "/transport/resource"));
+
+       char* level = jsonObjectGetString(jsonObjectFindPath(configChunk, "/loglevel"));
+       char* log_file = jsonObjectGetString(jsonObjectFindPath(configChunk, "/logfile"));
+       char* facility = jsonObjectGetString(jsonObjectFindPath(configChunk, "/syslog"));
+
+       int llevel = 1;
+       if(level) llevel = atoi(level);
+
+       if(!log_file) { fprintf(stderr, "Log file name not specified\n"); return -1; }
+
+       if(!strcmp(log_file, "syslog")) {
+               osrfLogInit( OSRF_LOG_TYPE_SYSLOG, "router", llevel );
+               osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
+
+       } else {
+               osrfLogInit( OSRF_LOG_TYPE_FILE, "router", llevel );
+               osrfLogSetFile( log_file );
+       }
+
+       free(facility);
+       free(level);
+       free(log_file);
+
+       osrfLogInfo(  OSRF_LOG_MARK, "Router connecting as: server: %s port: %s "
+                       "user: %s resource: %s", server, port, username, resource );
+
+       int iport = 0;
+       if(port)        iport = atoi( port );
+
+       osrfStringArray* tclients = osrfNewStringArray(4);
+       osrfStringArray* tservers = osrfNewStringArray(4);
+
+    jsonObject* tclientsList = jsonObjectFindPath(configChunk, "/trusted_domains/client");
+    jsonObject* tserversList = jsonObjectFindPath(configChunk, "/trusted_domains/server");
+
+       int i;
+
+    if(tserversList->type == JSON_ARRAY) {
+           for( i = 0; i != tserversList->size; i++ ) {
+            char* serverDomain = jsonObjectGetString(jsonObjectGetIndex(tserversList, i));
+                   osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted server: %s", serverDomain);
+            osrfStringArrayAdd(tservers, serverDomain);
+        }
+    } else {
+        char* serverDomain = jsonObjectGetString(tserversList);
+        osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted server: %s", serverDomain);
+        osrfStringArrayAdd(tservers, serverDomain);
+    }
+
+    if(tclientsList->type == JSON_ARRAY) {
+           for( i = 0; i != tclientsList->size; i++ ) {
+            char* clientDomain = jsonObjectGetString(jsonObjectGetIndex(tclientsList, i));
+                   osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted client: %s", clientDomain);
+            osrfStringArrayAdd(tclients, clientDomain);
+        }
+    } else {
+        char* clientDomain = jsonObjectGetString(tclientsList);
+        osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted client: %s", clientDomain);
+        osrfStringArrayAdd(tclients, clientDomain);
+    }
+
+
+       if( tclients->size == 0 || tservers->size == 0 ) {
+               osrfLogError( OSRF_LOG_MARK, "We need trusted servers and trusted client to run the router...");
+               osrfStringArrayFree( tservers );
+               osrfStringArrayFree( tclients );
+               return -1;
+       }
+
+       router = osrfNewRouter( server,
+                       username, resource, password, iport, tclients, tservers );
+       
+       signal(SIGHUP,routerSignalHandler);
+       signal(SIGINT,routerSignalHandler);
+       signal(SIGTERM,routerSignalHandler);
+
+       if( (osrfRouterConnect(router)) != 0 ) {
+               fprintf(stderr, "Unable to connect router to jabber server %s... exiting", server );
+               osrfRouterFree(router);
+               return -1;
+       }
+
+       daemonize();
+       osrfRouterRun( router );
+
+       // Shouldn't get here, since osrfRouterRun()
+       // should go into an infinite loop
+
+       osrfRouterFree(router);
+       router = NULL;
+       
+       return -1;
+}
+
+
diff --git a/trunk/src/srfsh/Makefile.am b/trunk/src/srfsh/Makefile.am
new file mode 100644 (file)
index 0000000..5c9f451
--- /dev/null
@@ -0,0 +1,20 @@
+# Copyright (C) 2008 Equinox Software, Inc.
+# Kevin Beswick <kevinbeswick00@gmail.com>
+#
+# 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.
+
+
+LDADD = -lreadline -lxml2 -lncurses $(DEF_LDLIBS) 
+AM_CFLAGS = $(DEF_CFLAGS) -DEXEC_DEFAULT -L@top_builddir@/src/libopensrf
+AM_LDFLAGS = $(DEF_LDFLAGS)
+
+bin_PROGRAMS = srfsh
+srfsh_SOURCES = srfsh.c
diff --git a/trunk/src/srfsh/srfsh.c b/trunk/src/srfsh/srfsh.c
new file mode 100644 (file)
index 0000000..1d4129c
--- /dev/null
@@ -0,0 +1,917 @@
+#include <opensrf/transport_client.h>
+#include <opensrf/osrf_message.h>
+#include <opensrf/osrf_app_session.h>
+#include <time.h>
+#include <ctype.h>
+#include <sys/timeb.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <opensrf/utils.h>
+#include <opensrf/log.h>
+
+#include <signal.h>
+
+#include <stdio.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#define SRFSH_PORT 5222
+#define COMMAND_BUFSIZE 4096
+
+
+/* shell prompt */
+static const char* prompt = "srfsh# ";
+
+static char* history_file = NULL;
+
+//static int child_dead = 0;
+
+static char* login_session = NULL;
+
+/* true if we're pretty printing json results */
+static int pretty_print = 1;
+/* true if we're bypassing 'less' */
+static int raw_print = 0;
+
+/* our jabber connection */
+static transport_client* client = NULL; 
+
+/* the last result we received */
+static osrf_message* last_result = NULL;
+
+/* functions */
+static int parse_request( char* request );
+
+/* handles router requests */
+static int handle_router( char* words[] );
+
+/* utility method for print time data */
+static int handle_time( char* words[] ); 
+
+/* handles app level requests */
+static int handle_request( char* words[], int relay );
+static int handle_set( char* words[]);
+static int handle_print( char* words[]);
+static int send_request( char* server, 
+                                 char* method, growing_buffer* buffer, int relay );
+static int parse_error( char* words[] );
+static int router_query_servers( const char* server );
+static int print_help( void );
+
+//static int srfsh_client_connect();
+//static char* tabs(int count);
+//static void sig_child_handler( int s );
+//static void sig_int_handler( int s );
+
+static int load_history( void );
+static int handle_math( char* words[] );
+static int do_math( int count, int style );
+static int handle_introspect(char* words[]);
+static int handle_login( char* words[]);
+
+static int recv_timeout = 120;
+static int is_from_script = 0;
+
+int main( int argc, char* argv[] ) {
+
+       /* --------------------------------------------- */
+       /* see if they have a .srfsh.xml in their home directory */
+       char* home = getenv("HOME");
+       int l = strlen(home) + 36;
+       char fbuf[l];
+       snprintf(fbuf, sizeof(fbuf), "%s/.srfsh.xml", home);
+       
+       if(!access(fbuf, R_OK)) {
+               if( ! osrf_system_bootstrap_client(fbuf, "srfsh") ) {
+                       fprintf(stderr,"Unable to bootstrap client for requests\n");
+                       osrfLogError( OSRF_LOG_MARK,  "Unable to bootstrap client for requests");
+                       return -1;
+               }
+
+       } else {
+               fprintf(stderr,"No Config file found at %s\n", fbuf ); 
+               return -1;
+       }
+
+       if(argc > 1) {
+               /* for now.. the first arg is used as a script file for processing */
+               int f;
+               if( (f = open(argv[1], O_RDONLY)) == -1 ) {
+                       osrfLogError( OSRF_LOG_MARK, "Unable to open file %s for reading, exiting...", argv[1]);
+                       return -1;
+               }
+
+               if(dup2(f, STDIN_FILENO) == -1) {
+                       osrfLogError( OSRF_LOG_MARK, "Unable to duplicate STDIN, exiting...");
+                       return -1;
+               }
+
+               close(f);
+               is_from_script = 1;
+       }
+               
+       /* --------------------------------------------- */
+       load_history();
+
+
+       client = osrfSystemGetTransportClient();
+
+       /* main process loop */
+       int newline_needed = 1;  /* used as boolean */
+       char* request;
+       while((request=readline(prompt))) {
+
+               // Find first non-whitespace character
+               
+               char * cmd = request;
+               while( isspace( (unsigned char) *cmd ) )
+                       ++cmd;
+
+               // ignore comments and empty lines
+
+               if( '\0' == *cmd || '#' == *cmd )
+                       continue;
+
+               // Remove trailing whitespace.  We know at this point that
+               // there is at least one non-whitespace character somewhere,
+               // or we would have already skipped this line.  Hence we
+               // needn't check to make sure that we don't back up past
+               // the beginning.
+
+               {
+                       // The curly braces limit the scope of the end variable
+                       
+                       char * end = cmd + strlen(cmd) - 1;
+                       while( isspace( (unsigned char) *end ) )
+                               --end;
+                       end[1] = '\0';
+               }
+
+               if( !strcasecmp(cmd, "exit") || !strcasecmp(cmd, "quit"))
+               {
+                       newline_needed = 0;
+                       break; 
+               }
+               
+               char* req_copy = strdup(cmd);
+
+               parse_request( req_copy ); 
+               if( request && *cmd ) {
+                       add_history(request);
+               }
+
+               free(request);
+               free(req_copy);
+
+               fflush(stderr);
+               fflush(stdout);
+       }
+
+       if( newline_needed ) {
+               
+               // We left the readline loop after seeing an EOF, not after
+               // seeing "quit" or "exit".  So we issue a newline in order
+               // to avoid leaving a dangling prompt.
+
+               putchar( '\n' );
+       }
+
+       if(history_file != NULL )
+               write_history(history_file);
+
+       free(request);
+       free(login_session);
+
+       osrf_system_shutdown();
+       return 0;
+}
+
+static int load_history( void ) {
+
+       char* home = getenv("HOME");
+       int l = strlen(home) + 24;
+       char fbuf[l];
+       snprintf(fbuf, sizeof(fbuf), "%s/.srfsh_history", home);
+       history_file = strdup(fbuf);
+
+       if(!access(history_file, W_OK | R_OK )) {
+               history_length = 5000;
+               read_history(history_file);
+       }
+       return 1;
+}
+
+
+static int parse_error( char* words[] ) {
+
+       if( ! words )
+               return 0;
+
+       growing_buffer * gbuf = buffer_init( 64 );
+       buffer_add( gbuf, *words );
+       while( *++words ) {
+               buffer_add( gbuf, " " );
+               buffer_add( gbuf, *words );
+       }
+       fprintf( stderr, "???: %s\n", gbuf->buf );
+       buffer_free( gbuf );
+       
+       return 0;
+
+}
+
+
+static int parse_request( char* request ) {
+
+       if( request == NULL )
+               return 0;
+
+       char* original_request = strdup( request );
+       char* words[COMMAND_BUFSIZE]; 
+       
+       int ret_val = 0;
+       int i = 0;
+
+
+       char* req = request;
+       char* cur_tok = strtok( req, " " );
+
+       if( cur_tok == NULL )
+       {
+               free( original_request );
+               return 0;
+       }
+
+       /* Load an array with pointers to    */
+       /* the tokens as defined by strtok() */
+       
+       while(cur_tok != NULL) {
+               if( i < COMMAND_BUFSIZE - 1 ) {
+                       words[i++] = cur_tok;
+                       cur_tok = strtok( NULL, " " );
+               } else {
+                       fprintf( stderr, "Too many tokens in command\n" );
+                       free( original_request );
+                       return 1;
+               }
+       }
+
+       words[i] = NULL;
+       
+       /* pass off to the top level command */
+       if( !strcmp(words[0],"router") ) 
+               ret_val = handle_router( words );
+
+       else if( !strcmp(words[0],"time") ) 
+               ret_val = handle_time( words );
+
+       else if (!strcmp(words[0],"request"))
+               ret_val = handle_request( words, 0 );
+
+       else if (!strcmp(words[0],"relay"))
+               ret_val = handle_request( words, 1 );
+
+       else if (!strcmp(words[0],"help"))
+               ret_val = print_help();
+
+       else if (!strcmp(words[0],"set"))
+               ret_val = handle_set(words);
+
+       else if (!strcmp(words[0],"print"))
+               ret_val = handle_print(words);
+
+       else if (!strcmp(words[0],"math_bench"))
+               ret_val = handle_math(words);
+
+       else if (!strcmp(words[0],"introspect"))
+               ret_val = handle_introspect(words);
+
+       else if (!strcmp(words[0],"login"))
+               ret_val = handle_login(words);
+
+       else if (words[0][0] == '!') {
+               system( original_request + 1 );
+               ret_val = 1;
+       }
+       
+       free( original_request );
+       
+       if(!ret_val)
+               return parse_error( words );
+       else
+               return 1;
+}
+
+
+static int handle_introspect(char* words[]) {
+
+       if( ! words[1] )
+               return 0;
+
+       fprintf(stderr, "--> %s\n", words[1]);
+
+       // Build a command in a suitably-sized
+       // buffer and then parse it
+       
+       size_t len;
+       if( words[2] ) {
+               static const char text[] = "request %s opensrf.system.method %s";
+               len = sizeof( text ) + strlen( words[1] ) + strlen( words[2] );
+               char buf[len];
+               snprintf( buf, sizeof(buf), text, words[1], words[2] );
+               return parse_request( buf );
+
+       } else {
+               static const char text[] = "request %s opensrf.system.method.all";
+               len = sizeof( text ) + strlen( words[1] );
+               char buf[len];
+               snprintf( buf, sizeof(buf), text, words[1] );
+               return parse_request( buf );
+
+       }
+}
+
+
+static int handle_login( char* words[]) {
+
+       if( words[1] && words[2]) {
+
+               char* username          = words[1];
+               char* password          = words[2];
+               char* type                      = words[3];
+               char* orgloc            = words[4];
+               char* workstation       = words[5];
+               int orgloci = (orgloc) ? atoi(orgloc) : 0;
+               if(!type) type = "opac";
+
+               char login_text[] = "request open-ils.auth open-ils.auth.authenticate.init \"%s\"";
+               size_t len = sizeof( login_text ) + strlen(username) + 1;
+
+               char buf[len];
+               snprintf( buf, sizeof(buf), login_text, username );
+               parse_request(buf);
+
+               const char* hash;
+               if(last_result && last_result->_result_content) {
+                       jsonObject* r = last_result->_result_content;
+                       hash = jsonObjectGetString(r);
+               } else return 0;
+
+
+               char* pass_buf = md5sum(password);
+
+               size_t both_len = strlen( hash ) + strlen( pass_buf ) + 1;
+               char both_buf[both_len];
+               snprintf(both_buf, sizeof(both_buf), "%s%s", hash, pass_buf);
+
+               char* mess_buf = md5sum(both_buf);
+
+               growing_buffer* argbuf = buffer_init(64);
+               buffer_fadd(argbuf, 
+                               "request open-ils.auth open-ils.auth.authenticate.complete "
+                               "{ \"username\" : \"%s\", \"password\" : \"%s\"", username, mess_buf );
+
+               if(type) buffer_fadd( argbuf, ", \"type\" : \"%s\"", type );
+               if(orgloci) buffer_fadd( argbuf, ", \"org\" : %d", orgloci );
+               if(workstation) buffer_fadd( argbuf, ", \"workstation\" : \"%s\"", workstation);
+               buffer_add(argbuf, "}");
+
+               free(pass_buf);
+               free(mess_buf);
+
+               parse_request( argbuf->buf );
+               buffer_free(argbuf);
+
+               if( login_session != NULL )
+                       free( login_session );
+
+               const jsonObject* x = last_result->_result_content;
+               double authtime = 0;
+               if(x) {
+                       const char* authtoken = jsonObjectGetString(
+                                       jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtoken"));
+                       authtime  = jsonObjectGetNumber(
+                                       jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtime"));
+
+                       if(authtoken)
+                               login_session = strdup(authtoken);
+                       else
+                               login_session = NULL;
+               }
+               else login_session = NULL;
+
+               printf("Login Session: %s.  Session timeout: %f\n",
+                          (login_session ? login_session : "(none)"), authtime );
+               
+               return 1;
+
+       }
+
+       return 0;
+}
+
+static int handle_set( char* words[]) {
+
+       char* variable;
+       if( (variable=words[1]) ) {
+
+               char* val;
+               if( (val=words[2]) ) {
+
+                       if(!strcmp(variable,"pretty_print")) {
+                               if(!strcmp(val,"true")) {
+                                       pretty_print = 1;
+                                       printf("pretty_print = true\n");
+                                       return 1;
+                               } 
+                               if(!strcmp(val,"false")) {
+                                       pretty_print = 0;
+                                       printf("pretty_print = false\n");
+                                       return 1;
+                               } 
+                       }
+
+                       if(!strcmp(variable,"raw_print")) {
+                               if(!strcmp(val,"true")) {
+                                       raw_print = 1;
+                                       printf("raw_print = true\n");
+                                       return 1;
+                               } 
+                               if(!strcmp(val,"false")) {
+                                       raw_print = 0;
+                                       printf("raw_print = false\n");
+                                       return 1;
+                               } 
+                       }
+
+               }
+       }
+
+       return 0;
+}
+
+
+static int handle_print( char* words[]) {
+
+       char* variable;
+       if( (variable=words[1]) ) {
+               if(!strcmp(variable,"pretty_print")) {
+                       if(pretty_print) {
+                               printf("pretty_print = true\n");
+                               return 1;
+                       } else {
+                               printf("pretty_print = false\n");
+                               return 1;
+                       }
+               }
+
+               if(!strcmp(variable,"raw_print")) {
+                       if(raw_print) {
+                               printf("raw_print = true\n");
+                               return 1;
+                       } else {
+                               printf("raw_print = false\n");
+                               return 1;
+                       }
+               }
+
+               if(!strcmp(variable,"login")) {
+                       printf("login session = %s\n",
+                                  login_session ? login_session : "(none)" );
+                       return 1;
+               }
+
+       }
+       return 0;
+}
+
+static int handle_router( char* words[] ) {
+
+       if(!client)
+               return 1;
+
+       int i;
+
+       if( words[1] ) { 
+               if( !strcmp(words[1],"query") ) {
+                       
+                       if( words[2] && !strcmp(words[2],"servers") ) {
+                               for(i=3; i < COMMAND_BUFSIZE - 3 && words[i]; i++ ) {   
+                                       router_query_servers( words[i] );
+                               }
+                               return 1;
+                       }
+                       return 0;
+               }
+               return 0;
+       }
+       return 0;
+}
+
+
+
+static int handle_request( char* words[], int relay ) {
+
+       if(!client)
+               return 1;
+
+       if(words[1]) {
+               char* server = words[1];
+               char* method = words[2];
+               int i;
+               growing_buffer* buffer = NULL;
+               if(!relay) {
+                       buffer = buffer_init(128);
+                       buffer_add(buffer, "[");
+                       for(i = 3; words[i] != NULL; i++ ) {
+                               /* removes trailing semicolon if user accidentally enters it */
+                               if( words[i][strlen(words[i])-1] == ';' )
+                                       words[i][strlen(words[i])-1] = '\0';
+                               buffer_add( buffer, words[i] );
+                               buffer_add(buffer, " ");
+                       }
+                       buffer_add(buffer, "]");
+               }
+
+               int rc = send_request( server, method, buffer, relay );
+               buffer_free( buffer );
+               return rc;
+       } 
+
+       return 0;
+}
+
+int send_request( char* server, 
+               char* method, growing_buffer* buffer, int relay ) {
+       if( server == NULL || method == NULL )
+               return 0;
+
+       jsonObject* params = NULL;
+       if( !relay ) {
+               if( buffer != NULL && buffer->n_used > 0 ) 
+                       params = jsonParseString(buffer->buf);
+       } else {
+               if(!last_result || ! last_result->_result_content) { 
+                       printf("We're not going to call 'relay' with no result params\n");
+                       return 1;
+               }
+               else {
+                       params = jsonNewObject(NULL);
+                       jsonObjectPush(params, last_result->_result_content );
+               }
+       }
+
+
+       if(buffer->n_used > 0 && params == NULL) {
+               fprintf(stderr, "JSON error detected, not executing\n");
+               jsonObjectFree(params);
+               return 1;
+       }
+
+       osrfAppSession* session = osrfAppSessionClientInit(server);
+
+       if(!osrf_app_session_connect(session)) {
+               fprintf(stderr, "Unable to communicate with service %s\n", server);
+               osrfLogWarning( OSRF_LOG_MARK,  "Unable to connect to remote service %s\n", server );
+               jsonObjectFree(params);
+               return 1;
+       }
+
+       double start = get_timestamp_millis();
+
+       int req_id = osrfAppSessionMakeRequest( session, params, method, 1, NULL );
+       jsonObjectFree(params);
+
+       osrf_message* omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
+
+       if(!omsg) 
+               printf("\nReceived no data from server\n");
+       
+       
+       signal(SIGPIPE, SIG_IGN);
+
+       FILE* less; 
+       if(!is_from_script) less = popen( "less -EX", "w");
+       else less = stdout;
+
+       if( less == NULL ) { less = stdout; }
+
+       growing_buffer* resp_buffer = buffer_init(4096);
+
+       while(omsg) {
+
+               if(raw_print) {
+
+                       if(omsg->_result_content) {
+       
+                               osrfMessageFree(last_result);
+                               last_result = omsg;
+       
+                               char* content;
+       
+                               if( pretty_print ) {
+                                       char* j = jsonObjectToJSON(omsg->_result_content);
+                                       //content = json_printer(j); 
+                                       content = jsonFormatString(j);
+                                       free(j);
+                               } else {
+                                       const char * temp_content = jsonObjectGetString(omsg->_result_content);
+                                       if( ! temp_content )
+                                               temp_content = "[null]";
+                                       content = strdup( temp_content );
+                               }
+                               
+                               printf( "\nReceived Data: %s\n", content ); 
+                               free(content);
+       
+                       } else {
+
+                               char code[16];
+                               snprintf( code, sizeof(code), "%d", omsg->status_code );
+                               buffer_add( resp_buffer, code );
+
+                               printf( "\nReceived Exception:\nName: %s\nStatus: %s\nStatus: %s\n", 
+                                               omsg->status_name, omsg->status_text, code );
+
+                               fflush(stdout);
+                       }
+
+               } else {
+
+                       if(omsg->_result_content) {
+       
+                               osrfMessageFree(last_result);
+                               last_result = omsg;
+       
+                               char* content;
+       
+                               if( pretty_print && omsg->_result_content ) {
+                                       char* j = jsonObjectToJSON(omsg->_result_content);
+                                       //content = json_printer(j); 
+                                       content = jsonFormatString(j);
+                                       free(j);
+                               } else {
+                                       const char * temp_content = jsonObjectGetString(omsg->_result_content);
+                                       if( temp_content )
+                                               content = strdup( temp_content );
+                                       else
+                                               content = NULL;
+                               }
+
+                               buffer_add( resp_buffer, "\nReceived Data: " ); 
+                               buffer_add( resp_buffer, content );
+                               buffer_add( resp_buffer, "\n" );
+                               free(content);
+       
+                       } else {
+       
+                               buffer_add( resp_buffer, "\nReceived Exception:\nName: " );
+                               buffer_add( resp_buffer, omsg->status_name );
+                               buffer_add( resp_buffer, "\nStatus: " );
+                               buffer_add( resp_buffer, omsg->status_text );
+                               buffer_add( resp_buffer, "\nStatus: " );
+                               char code[16];
+                               snprintf( code, sizeof(code), "%d", omsg->status_code );
+                               buffer_add( resp_buffer, code );
+                       }
+               }
+
+
+               omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
+
+       }
+
+       double end = get_timestamp_millis();
+
+       fputs( resp_buffer->buf, less );
+       buffer_free( resp_buffer );
+       fputs("\n------------------------------------\n", less);
+       if( osrf_app_session_request_complete( session, req_id ))
+               fputs("Request Completed Successfully\n", less);
+
+
+       fprintf(less, "Request Time in seconds: %.6f\n", end - start );
+       fputs("------------------------------------\n", less);
+
+       pclose(less); 
+
+       osrf_app_session_request_finish( session, req_id );
+       osrf_app_session_disconnect( session );
+       osrfAppSessionFree( session );
+
+
+       return 1;
+
+
+}
+
+static int handle_time( char* words[] ) {
+       if(!words[1]) {
+               printf("%f\n", get_timestamp_millis());
+    } else {
+        time_t epoch = (time_t) atoi(words[1]);
+               printf("%s", ctime(&epoch));
+    }
+       return 1;
+}
+
+               
+
+static int router_query_servers( const char* router_server ) {
+
+       if( ! router_server || strlen(router_server) == 0 ) 
+               return 0;
+
+       const static char router_text[] = "router@%s/router";
+       size_t len = sizeof( router_text ) + strlen( router_server ) + 1;
+       char rbuf[len];
+       snprintf(rbuf, sizeof(rbuf), router_text, router_server );
+               
+       transport_message* send = 
+               message_init( "servers", NULL, NULL, rbuf, NULL );
+       message_set_router_info( send, NULL, NULL, NULL, "query", 0 );
+
+       client_send_message( client, send );
+       message_free( send );
+
+       transport_message* recv = client_recv( client, -1 );
+       if( recv == NULL ) {
+               fprintf(stderr, "NULL message received from router\n");
+               return 1;
+       }
+       
+       printf( 
+                       "---------------------------------------------------------------------------------\n"
+                       "Received from 'server' query on %s\n"
+                       "---------------------------------------------------------------------------------\n"
+                       "original reg time | latest reg time | last used time | class | server\n"
+                       "---------------------------------------------------------------------------------\n"
+                       "%s"
+                       "---------------------------------------------------------------------------------\n"
+                       , router_server, recv->body );
+
+       message_free( recv );
+       
+       return 1;
+}
+
+static int print_help( void ) {
+
+       fputs(
+                       "---------------------------------------------------------------------------------\n"
+                       "Commands:\n"
+                       "---------------------------------------------------------------------------------\n"
+                       "help                   - Display this message\n"
+                       "!<command> [args]      - Forks and runs the given command in the shell\n"
+               /*
+                       "time                   - Prints the current time\n"
+                       "time <timestamp>       - Formats seconds since epoch into readable format\n"
+               */
+                       "set <variable> <value> - set a srfsh variable (e.g. set pretty_print true )\n"
+                       "print <variable>       - Displays the value of a srfsh variable\n"
+                       "---------------------------------------------------------------------------------\n"
+
+                       "router query servers <server1 [, server2, ...]>\n"
+                       "       - Returns stats on connected services\n"
+                       "\n"
+                       "\n"
+                       "request <service> <method> [ <json formatted string of params> ]\n"
+                       "       - Anything passed in will be wrapped in a json array,\n"
+                       "               so add commas if there is more than one param\n"
+                       "\n"
+                       "\n"
+                       "relay <service> <method>\n"
+                       "       - Performs the requested query using the last received result as the param\n"
+                       "\n"
+                       "\n"
+                       "math_bench <num_batches> [0|1|2]\n"
+                       "       - 0 means don't reconnect, 1 means reconnect after each batch of 4, and\n"
+                       "                2 means reconnect after every request\n"
+                       "\n"
+                       "introspect <service>\n"
+                       "       - prints the API for the service\n"
+                       "\n"
+                       "\n"
+                       "---------------------------------------------------------------------------------\n"
+                       " Commands for Open-ILS\n"
+                       "---------------------------------------------------------------------------------\n"
+                       "login <username> <password> [type] [org_unit] [workstation]\n"
+                       "       - Logs into the 'server' and displays the session id\n"
+                       "       - To view the session id later, enter: print login\n"
+                       "---------------------------------------------------------------------------------\n"
+                       "\n"
+                       "\n"
+                       "Note: long output is piped through 'less'. To search in 'less', type: /<search>\n"
+                       "---------------------------------------------------------------------------------\n"
+                       "\n",
+                       stdout );
+
+       return 1;
+}
+
+
+/*
+static char* tabs(int count) {
+       growing_buffer* buf = buffer_init(24);
+       int i;
+       for(i=0;i!=count;i++)
+               buffer_add(buf, "  ");
+
+       char* final = buffer_data( buf );
+       buffer_free( buf );
+       return final;
+}
+*/
+
+
+static int handle_math( char* words[] ) {
+       if( words[1] )
+               return do_math( atoi(words[1]), 0 );
+       return 0;
+}
+
+
+static int do_math( int count, int style ) {
+
+       osrfAppSession* session = osrfAppSessionClientInit( "opensrf.math" );
+       osrf_app_session_connect(session);
+
+       jsonObject* params = jsonParseString("[]");
+       jsonObjectPush(params,jsonNewObject("1"));
+       jsonObjectPush(params,jsonNewObject("2"));
+
+       char* methods[] = { "add", "sub", "mult", "div" };
+       char* answers[] = { "3", "-1", "2", "0.5" };
+
+       float times[ count * 4 ];
+       memset(times, 0, sizeof(times));
+
+       int k;
+       for(k=0;k!=100;k++) {
+               if(!(k%10)) 
+                       fprintf(stderr,"|");
+               else
+                       fprintf(stderr,".");
+       }
+
+       fprintf(stderr,"\n\n");
+
+       int running = 0;
+       int i;
+       for(i=0; i!= count; i++) {
+
+               int j;
+               for(j=0; j != 4; j++) {
+
+                       ++running;
+
+                       double start = get_timestamp_millis();
+                       int req_id = osrfAppSessionMakeRequest( session, params, methods[j], 1, NULL );
+                       osrf_message* omsg = osrfAppSessionRequestRecv( session, req_id, 5 );
+                       double end = get_timestamp_millis();
+
+                       times[(4*i) + j] = end - start;
+
+                       if(omsg) {
+       
+                               if(omsg->_result_content) {
+                                       char* jsn = jsonObjectToJSON(omsg->_result_content);
+                                       if(!strcmp(jsn, answers[j]))
+                                               fprintf(stderr, "+");
+                                       else
+                                               fprintf(stderr, "\n![%s] - should be %s\n", jsn, answers[j] );
+                                       free(jsn);
+                               }
+
+
+                               osrfMessageFree(omsg);
+               
+                       } else { fprintf( stderr, "\nempty message for tt: %d\n", req_id ); }
+
+                       osrf_app_session_request_finish( session, req_id );
+
+                       if(style == 2)
+                               osrf_app_session_disconnect( session );
+
+                       if(!(running%100))
+                               fprintf(stderr,"\n");
+               }
+
+               if(style==1)
+                       osrf_app_session_disconnect( session );
+       }
+
+       osrfAppSessionFree( session );
+       jsonObjectFree(params);
+
+       int c;
+       float total = 0;
+       for(c=0; c!= count*4; c++) 
+               total += times[c];
+
+       float avg = total / (count*4); 
+       fprintf(stderr, "\n      Average round trip time: %f\n", avg );
+
+       return 1;
+}